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