PY-17657 If docstring format contains both tags and sections, tags are stronger heuristic
[idea/community.git] / python / testSrc / com / jetbrains / python / PySectionBasedDocStringTest.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python;
17
18 import com.intellij.psi.PsiElement;
19 import com.intellij.psi.search.PsiElementProcessor;
20 import com.intellij.psi.util.PsiTreeUtil;
21 import com.jetbrains.python.documentation.docstrings.*;
22 import com.jetbrains.python.documentation.docstrings.SectionBasedDocString.Section;
23 import com.jetbrains.python.documentation.docstrings.SectionBasedDocString.SectionField;
24 import com.jetbrains.python.fixtures.PyTestCase;
25 import com.jetbrains.python.psi.PyStringLiteralExpression;
26 import com.jetbrains.python.toolbox.Substring;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import java.util.List;
31
32 /**
33  * @author Mikhail Golubev
34  */
35 public class PySectionBasedDocStringTest extends PyTestCase {
36
37   public void testSimpleGoogleDocString() {
38     checkSimpleDocstringStructure(findAndParseGoogleStyleDocString());
39   }
40
41   public void testSimpleNumpyDocstring() {
42     checkSimpleDocstringStructure(findAndParseNumpyStyleDocString());
43   }
44
45   private static void checkSimpleDocstringStructure(@NotNull SectionBasedDocString docString) {
46     assertEquals("Summary", docString.getSummary());
47     final List<Section> sections = docString.getSections();
48     assertSize(3, sections);
49
50     assertEquals("parameters", sections.get(0).getNormalizedTitle());
51     final List<SectionField> paramFields = sections.get(0).getFields();
52     assertSize(2, paramFields);
53     final SectionField param1 = paramFields.get(0);
54     assertEquals("x", param1.getName());
55     assertEquals("int", param1.getType());
56     assertEquals("first parameter", param1.getDescription());
57
58     final SectionField param2 = paramFields.get(1);
59     assertEquals("y", param2.getName());
60     assertEmpty(param2.getType());
61     assertEquals("second parameter\n" +
62                  "with longer description", param2.getDescription());
63
64     assertEquals("raises", sections.get(1).getNormalizedTitle());
65     final List<SectionField> exceptionFields = sections.get(1).getFields();
66     assertSize(1, exceptionFields);
67     final SectionField exception1 = exceptionFields.get(0);
68     assertEmpty(exception1.getName());
69     assertEquals("Exception", exception1.getType());
70     assertEquals("if anything bad happens", exception1.getDescription());
71
72     assertEquals("returns", sections.get(2).getNormalizedTitle());
73     final List<SectionField> returnFields = sections.get(2).getFields();
74     assertSize(1, returnFields);
75     final SectionField return1 = returnFields.get(0);
76     assertEmpty(return1.getName());
77     assertEquals("None", return1.getType());
78     assertEquals("always", return1.getDescription());
79   }
80
81   @NotNull
82   private Substring findAndParseDocString() {
83     myFixture.configureByFile(getTestName(true) + ".py");
84     final String docStringText = findFirstDocString();
85
86     assertNotNull(docStringText);
87     return new Substring(docStringText);
88   }
89
90   @NotNull
91   private GoogleCodeStyleDocString findAndParseGoogleStyleDocString() {
92     return new GoogleCodeStyleDocString(findAndParseDocString());
93   }
94
95   @NotNull
96   private NumpyDocString findAndParseNumpyStyleDocString() {
97     return new NumpyDocString(findAndParseDocString());
98   }
99
100   @Nullable
101   private String findFirstDocString() {
102     final PsiElementProcessor.FindElement<PsiElement> processor = new PsiElementProcessor.FindElement<PsiElement>() {
103       @Override
104       public boolean execute(@NotNull PsiElement element) {
105         if (element instanceof PyStringLiteralExpression && element.getFirstChild().getNode().getElementType() == PyTokenTypes.DOCSTRING) {
106           return setFound(element);
107         }
108         return true;
109       }
110     };
111     PsiTreeUtil.processElements(myFixture.getFile(), processor);
112     if (!processor.isFound()) {
113       return null;
114     }
115     final PsiElement foundElement = processor.getFoundElement();
116     assertNotNull(foundElement);
117     return ((PyStringLiteralExpression)foundElement).getStringValue();
118   }
119
120   public void testSectionStartAfterQuotes() {
121     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
122     assertEmpty(docString.getSummary());
123
124     assertSize(2, docString.getSections());
125
126     final Section examplesSection = docString.getSections().get(0);
127     assertEquals("examples", examplesSection.getNormalizedTitle());
128     assertSize(1, examplesSection.getFields());
129     final SectionField example1 = examplesSection.getFields().get(0);
130     assertEmpty(example1.getName());
131     assertEmpty(example1.getType());
132     assertEquals("Useless call\n" +
133                  "func() == func()", example1.getDescription());
134
135     final Section notesSection = docString.getSections().get(1);
136     assertEquals("notes", notesSection.getNormalizedTitle());
137     assertSize(1, notesSection.getFields());
138     final SectionField note1 = notesSection.getFields().get(0);
139     assertEmpty(note1.getName());
140     assertEmpty(note1.getType());
141     assertEquals("some\n" +
142                  "notes", note1.getDescription());
143   }
144
145   public void testTypeReferences() {
146     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
147     assertEmpty(docString.getSummary());
148     assertSize(2, docString.getSections());
149
150     final Section paramSection = docString.getSections().get(0);
151     assertSize(1, paramSection.getFields());
152     final SectionField param1 = paramSection.getFields().get(0);
153     assertEquals("a1", param1.getName());
154     assertEquals(":class:`MyClass`", param1.getType());
155     assertEquals("used to call :def:`my_function` and access :attr:`my_attr`", param1.getDescription());
156
157     final Section raisesSection = docString.getSections().get(1);
158     assertSize(1, raisesSection.getFields());
159     final SectionField exception1 = raisesSection.getFields().get(0);
160     assertEmpty(exception1.getName());
161     assertEquals(":class:`MyException`", exception1.getType());
162     assertEquals("thrown in case of any error", exception1.getDescription());
163   }
164
165   public void testNestedIndentation() {
166     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
167     assertSize(1, docString.getSections());
168     final Section section1 = docString.getSections().get(0);
169     assertEquals("parameters", section1.getNormalizedTitle());
170     assertSize(1, section1.getFields());
171     final SectionField param1 = section1.getFields().get(0);
172     assertEquals("x", param1.getName());
173     assertEquals("int", param1.getType());
174     assertEquals("first line of the description\n" +
175                  "second line\n" +
176                  "  third line\n" +
177                  "\n" +
178                  "Example::\n" +
179                  "\n" +
180                  "    assert func(42) is None", param1.getDescription());
181   }
182
183   public void testMultilineSummary() {
184     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
185     assertEquals("First line\n" +
186                  "Second line\n" +
187                  "Third line", docString.getSummary());
188   }
189
190   public void testNamedReturnsAndYields() {
191     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
192     assertEmpty(docString.getSummary());
193     assertSize(2, docString.getSections());
194
195     final Section returnSection = docString.getSections().get(0);
196     assertSize(2, returnSection.getFields());
197
198     final SectionField return1 = returnSection.getFields().get(0);
199     assertEquals("status_code", return1.getName());
200     assertEquals("int", return1.getType());
201     assertEquals("HTTP status code", return1.getDescription());
202
203     final SectionField return2 = returnSection.getFields().get(1);
204     assertEquals("template", return2.getName());
205     assertEquals("str", return2.getType());
206     assertEquals("path to template in template roots", return2.getDescription());
207
208     final Section yieldSection = docString.getSections().get(1);
209     assertSize(1, yieldSection.getFields());
210     final SectionField yield1 = yieldSection.getFields().get(0);
211     assertEquals("progress", yield1.getName());
212     assertEquals("float", yield1.getType());
213     assertEquals("floating point value in range [0, 1) indicating progress\n" +
214                  "of the task", yield1.getDescription());
215   }
216
217   public void testNumpySignature() {
218     final NumpyDocString docString = findAndParseNumpyStyleDocString();
219     assertEquals("a.diagonal(offset=0, axis1=0, axis2=1)", docString.getSignature());
220     assertEquals("Return specified diagonals.", docString.getSummary());
221   }
222
223   public void testNumpySectionBlockBreaksOnDoubleEmptyLine() {
224     final NumpyDocString docString = findAndParseNumpyStyleDocString();
225     assertSize(1, docString.getSections());
226     final Section paramSection = docString.getSections().get(0);
227     assertEquals("parameters", paramSection.getNormalizedTitle());
228     assertSize(1, paramSection.getFields());
229     final SectionField param1 = paramSection.getFields().get(0);
230     assertEquals("x", param1.getName());
231     assertEmpty(param1.getType());
232     assertEquals("First line\n" +
233                  "Second line\n" +
234                  "\n" +
235                  "Line after single break", param1.getDescription());
236   }
237
238   public void testGoogleEmptyParamTypeInParenthesis() {
239     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
240     assertSize(1, docString.getSections());
241     final Section paramSection = docString.getSections().get(0);
242     assertEquals("parameters", paramSection.getNormalizedTitle());
243     assertSize(1, paramSection.getFields());
244     final SectionField param1 = paramSection.getFields().get(0);
245     assertEquals("x", param1.getName());
246     assertEmpty(param1.getDescription());
247     assertEmpty(param1.getType());
248     assertNotNull(param1.getTypeAsSubstring());
249     assertEquals(26, param1.getTypeAsSubstring().getStartOffset());
250     assertEquals(26, param1.getTypeAsSubstring().getEndOffset());
251   }
252
253   public void testGoogleReturnTypeNoDescription() {
254     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
255     assertSize(1, docString.getSections());
256     final Section returnSection = docString.getSections().get(0);
257     assertEquals("returns", returnSection.getNormalizedTitle());
258     assertSize(1, returnSection.getFields());
259     final SectionField return1 = returnSection.getFields().get(0);
260     assertEmpty(return1.getName());
261     assertEmpty(return1.getDescription());
262     assertEquals("object", return1.getType());
263     assertNotNull(return1.getTypeAsSubstring());
264     assertEquals(20, return1.getTypeAsSubstring().getStartOffset());
265     assertEquals(26, return1.getTypeAsSubstring().getEndOffset());
266   }
267
268   public void testGoogleNoEmptyLineAfterSummary() {
269     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
270     assertEquals("Summary.", docString.getSummary());
271     assertSize(1, docString.getSections());
272     assertSize(1, docString.getSections().get(0).getFields());
273   }
274
275   public void testGoogleParametersSectionWithoutSummary() {
276     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
277     assertEmpty(docString.getSummary());
278     assertSize(1, docString.getSections());
279     final Section paramSection = docString.getSections().get(0);
280     assertEquals("parameters", paramSection.getNormalizedTitle());
281     assertSize(1, paramSection.getFields());
282   }
283
284   public void testGoogleKeywordArgumentsSection() {
285     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
286     assertEmpty(docString.getSummary());
287     assertSize(1, docString.getSections());
288     assertEquals("keyword arguments", docString.getSections().get(0).getNormalizedTitle());
289   }
290
291   // PY-16766
292   public void testGoogleDocStringContentDetection() {
293     assertTrue(DocStringUtil.isLikeGoogleDocString(
294       "\n" +
295       "    My Section:\n" +
296       "        some user defined section\n" +
297       "    \n" +
298       "    Parameters:\n" +
299       "        param1: \n" +
300       "\n" +
301       "    Returns:\n"));
302   }
303
304   public void testNumpyEmptySectionIndent() {
305     final NumpyDocString docString = findAndParseNumpyStyleDocString();
306     assertSize(3, docString.getSections());
307     final Section paramSection = docString.getSections().get(0);
308     assertEquals("parameters", paramSection.getNormalizedTitle());
309     assertSize(2, paramSection.getFields());
310     final Section exampleSection = docString.getSections().get(1);
311     assertSize(1, exampleSection.getFields());
312     assertEquals("First sentence.\n" +
313                  "Second sentence.", exampleSection.getFields().get(0).getDescription());
314     final Section returnSection = docString.getSections().get(2);
315     assertSize(1, returnSection.getFields());
316     assertEquals("Something", returnSection.getFields().get(0).getType());
317   }
318
319   public void testGoogleParamNamedLikeSection() {
320     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
321     assertSize(1, docString.getSections());
322     final Section paramSection = docString.getSections().get(0);
323     assertSize(2, paramSection.getFields());
324     assertEquals("x", paramSection.getFields().get(0).getName());
325     assertEquals("args", paramSection.getFields().get(1).getName());
326   }
327
328   public void testGoogleNoColonAfterParameter() {
329     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
330     assertSize(1, docString.getSections());
331     final Section paramSection = docString.getSections().get(0);
332     assertSize(2, paramSection.getFields());
333     final SectionField x = paramSection.getFields().get(0);
334     assertEquals("x", x.getName());
335     assertEmpty(x.getType());
336     assertEmpty(x.getDescription());
337
338     final SectionField y = paramSection.getFields().get(1);
339     assertEquals("y", y.getName());
340     assertEquals("int", y.getType());
341     assertEmpty(y.getDescription());
342   }
343
344   public void testNumpyMultipleReturns() {
345     final NumpyDocString docString = findAndParseNumpyStyleDocString();
346     assertSize(1, docString.getSections());
347     final Section returnSection = docString.getSections().get(0);
348     assertSize(2, returnSection.getFields());
349   }
350
351   // PY-16908
352   public void testNumpyCombinedParamDeclarations() {
353     final NumpyDocString docString = findAndParseNumpyStyleDocString();
354     assertSize(1, docString.getSections());
355     final Section paramSection = docString.getSections().get(0);
356     assertSize(1, paramSection.getFields());
357     final SectionField firstField = paramSection.getFields().get(0);
358     assertSameElements(firstField.getNames(), "x", "y", "args", "kwargs");
359     assertEquals(firstField.getType(), "Any");
360     assertEquals(firstField.getDescription(), "description");
361   }
362
363   // PY-16991
364   public void testGoogleMandatoryIndentationInsideSection() {
365     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
366     assertSize(3, docString.getSections());
367     assertEmpty(docString.getSections().get(0).getFields());
368     assertSize(1, docString.getSections().get(1).getFields());
369     final Section thirdSection = docString.getSections().get(2);
370     assertSize(1, thirdSection.getFields());
371     final SectionField firstExample = thirdSection.getFields().get(0);
372     assertEmpty(firstExample.getName());
373     assertEmpty(firstExample.getType());
374     assertEquals("first line\n" +
375                  "second line", firstExample.getDescription());
376   }
377
378   // PY-17002
379   public void testGoogleNoClosingParenthesisAfterParamType() {
380     final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
381     assertSize(1, docString.getSections());
382     final List<SectionField> params = docString.getSections().get(0).getFields();
383     assertSize(2, params);
384     assertEquals("Foo", params.get(0).getType());
385     assertEquals("Bar", params.get(1).getType());
386   }
387
388   // PY-17657, PY-16303
389   public void testNotGoogleFormatIfDocstringContainTags() {
390     assertEquals(DocStringFormat.REST, DocStringUtil.guessDocStringFormat("\"\"\"\n" +
391                                                                           ":type sub_field: FieldDescriptor | () -> FieldDescriptor\n" +
392                                                                           ":param sub_field: The type of field in this collection\n" +
393                                                                           "    Tip: You can pass a ValueObject class here to ...\n" +
394                                                                           "    Example:\n" +
395                                                                           "        addresses = field.Collection(AddressObject)\n" +
396                                                                           "\"\"\""));
397     
398     assertEquals(DocStringFormat.REST, DocStringUtil.guessDocStringFormat("\"\"\"\n" +
399                                                                           "Args:\n" +
400                                                                           "    :param Tuple[int, int] name: Some description\n" +
401                                                                           "\"\"\""));
402     
403   }
404
405   @Override
406   protected String getTestDataPath() {
407     return super.getTestDataPath() + "/docstrings";
408   }
409 }