PY-9795, PY-4717 Intention "Specify type ..." works for missing docstrings in Google...
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Mon, 24 Aug 2015 16:56:13 +0000 (19:56 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 2 Sep 2015 11:35:12 +0000 (14:35 +0300)
18 files changed:
python/psi-api/src/com/jetbrains/python/toolbox/Substring.java
python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java
python/src/com/jetbrains/python/documentation/DocStringUtil.java
python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java
python/src/com/jetbrains/python/documentation/NumpyDocString.java
python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java
python/testData/docstrings/googleEmptyParamTypeInParenthesis.py [new file with mode: 0644]
python/testData/docstrings/googleReturnTypeNoDescription.py [new file with mode: 0644]
python/testData/intentions/afterParamTypeInNewGoogleDocString.py [new file with mode: 0644]
python/testData/intentions/afterParamTypeInNewNumpyDocString.py [new file with mode: 0644]
python/testData/intentions/afterReturnTypeInNewGoogleDocString.py [new file with mode: 0644]
python/testData/intentions/afterReturnTypeInNewNumpyDocString.py [new file with mode: 0644]
python/testData/intentions/beforeParamTypeInNewGoogleDocString.py [new file with mode: 0644]
python/testData/intentions/beforeParamTypeInNewNumpyDocString.py [new file with mode: 0644]
python/testData/intentions/beforeReturnTypeInNewGoogleDocString.py [new file with mode: 0644]
python/testData/intentions/beforeReturnTypeInNewNumpyDocString.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java
python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java

index 8ff9787296b5d6a2e613a7981d9e3f57748ff292..336a54a919e2f540e38097eb995886844c8f059c 100644 (file)
@@ -115,7 +115,7 @@ public class Substring implements CharSequence {
         start = m.end();
       }
       while (end < myEndOffset && m.find() && splitCount < maxSplits);
-      if (start < myEndOffset) {
+      if (start <= myEndOffset) {
         result.add(createAnotherSubstring(start, myEndOffset));
       }
     }
index 9ec0e61c4bd76ed8f42d8ab8031ad8456a9282d2..b3fc5cc6132e550c52e745032a40ee7e814d4cab 100644 (file)
@@ -80,13 +80,19 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention {
     final PyDocstringGenerator docstringGenerator = new PyDocstringGenerator(pyFunction);
     docstringGenerator.addFirstEmptyLine();
     final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction);
-    String name = isReturn ? "" : StringUtil.notNullize(problemElement.getName());
-    final String typeFromSignature = signature != null ? StringUtil.notNullize(signature.getArgTypeQualifiedName(name)) : "";
+    final String name = isReturn ? "" : StringUtil.notNullize(problemElement.getName());
+    final String type;
+    if (signature != null) {
+      type = StringUtil.notNullize(signature.getArgTypeQualifiedName(name), "object");
+    }
+    else {
+      type = "object";
+    }
     if (!isReturn) {
-      docstringGenerator.withParamTypedByName(name, typeFromSignature);
+      docstringGenerator.withParamTypedByName(name, type);
     }
     else {
-      docstringGenerator.withReturnValue(typeFromSignature);
+      docstringGenerator.withReturnValue(type);
     }
 
     docstringGenerator.buildAndInsert();
index 75e9cb43d75256d3b7054f654477ea29199ced00..724d9f8df197e03f746b597fb462cd8d45895875 100644 (file)
@@ -72,9 +72,9 @@ public class DocStringUtil {
   }
 
 
-    @NotNull
+  @NotNull
   public static StructuredDocString parseDocString(@NotNull DocStringFormat format,
-                                            @NotNull PyStringLiteralExpression literalExpression) {
+                                                   @NotNull PyStringLiteralExpression literalExpression) {
     return parseDocString(format, literalExpression.getStringNodes().get(0));
   }
 
@@ -150,6 +150,7 @@ public class DocStringUtil {
 
   /**
    * Looks for a doc string under given parent.
+   *
    * @param parent where to look. For classes and functions, this would be PyStatementList, for modules, PyFile.
    * @return the defining expression, or null.
    */
@@ -248,7 +249,8 @@ public class DocStringUtil {
     final PyDocumentationSettings settings = PyDocumentationSettings.getInstance(module);
     if (settings.isPlain(file)) {
       final List<String> values = DocStringFormat.ALL_NAMES_BUT_PLAIN;
-      final int i = Messages.showChooseDialog("Docstring format:", "Select Docstring Type", ArrayUtil.toStringArray(values), values.get(0), null);
+      final int i =
+        Messages.showChooseDialog("Docstring format:", "Select Docstring Type", ArrayUtil.toStringArray(values), values.get(0), null);
       if (i < 0) {
         return false;
       }
index e09bb2b54f55d389095aa9339292b5e4567b54c0..b4c1ab518d18f7163cde266702c1e6f0d3dbcbdd 100644 (file)
@@ -18,6 +18,7 @@ package com.jetbrains.python.documentation;
 import com.intellij.openapi.util.Pair;
 import com.intellij.util.containers.ContainerUtil;
 import com.jetbrains.python.toolbox.Substring;
+import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
@@ -31,7 +32,7 @@ import java.util.regex.Pattern;
  */
 public class GoogleCodeStyleDocString extends SectionBasedDocString {
   public static final Pattern SECTION_HEADER_RE = Pattern.compile("\\s*(\\w+):\\s*", Pattern.MULTILINE);
-  private static final Pattern FIELD_NAME_AND_TYPE_RE = Pattern.compile("\\s*(.+?)\\s*\\(\\s*(.+?)\\s*\\)\\s*");
+  private static final Pattern FIELD_NAME_AND_TYPE_RE = Pattern.compile("\\s*(.+?)\\s*\\(\\s*(.*?)\\s*\\)\\s*");
 
   public GoogleCodeStyleDocString(@NotNull Substring text) {
     super(text);
@@ -115,7 +116,10 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString {
   protected Pair<String, Integer> parseSectionHeader(int lineNum) {
     final Matcher matcher = SECTION_HEADER_RE.matcher(getLine(lineNum));
     if (matcher.matches()) {
-      return Pair.create(matcher.group(1).trim(), lineNum + 1);
+      @NonNls final String title = matcher.group(1).trim();
+      if (SECTION_NAMES.contains(title.toLowerCase())) {
+        return Pair.create(title, lineNum + 1);
+      }
     }
     return Pair.create(null, lineNum);
   }
index e90aafcf2455726b1fb93d4adc6ee51ba8aee8e6..7c9237dfb8f858b614807b32ec04a941266b57cc 100644 (file)
@@ -17,6 +17,7 @@ package com.jetbrains.python.documentation;
 
 import com.intellij.openapi.util.Pair;
 import com.jetbrains.python.toolbox.Substring;
+import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
@@ -50,9 +51,12 @@ public class NumpyDocString extends SectionBasedDocString {
   @NotNull
   @Override
   protected Pair<String, Integer> parseSectionHeader(int lineNum) {
-    final Substring nextLine = getLineOrNull(lineNum + 1);
-    if (nextLine != null && SECTION_HEADER.matcher(nextLine).matches()) {
-      return Pair.create(getLine(lineNum).trim().toString(), lineNum + 2);
+    @NonNls final String title = getLine(lineNum).trim().toString();
+    if (SECTION_NAMES.contains(title.toLowerCase())) {
+      final Substring nextLine = getLineOrNull(lineNum + 1);
+      if (nextLine != null && SECTION_HEADER.matcher(nextLine).matches()) {
+        return Pair.create(getLine(lineNum).trim().toString(), lineNum + 2);
+      }
     }
     return Pair.create(null, lineNum);
   }
index 18c862423e5508092546ff86b8c5412958a5424c..75454fbca39424036a37b9f209f280bb50246b7c 100644 (file)
@@ -216,24 +216,23 @@ public class PyDocstringGenerator {
     }
 
     final DocstringParam paramToEdit = getParamToEdit();
-    final String paramName = paramToEdit.getName();
-    final String stringContent = docStringExpression.getStringValue();
-    final StructuredDocString parsed = DocStringUtil.parse(stringContent);
-    if (parsed == null) {
+    final DocStringFormat format = getDocStringFormat();
+    if (format == DocStringFormat.PLAIN) {
       return;
     }
-    Substring substring;
+    final StructuredDocString parsed = DocStringUtil.parseDocString(format, docStringExpression);
+    final Substring substring;
     if (paramToEdit.isReturnValue()) {
       substring = parsed.getReturnTypeSubstring();
     }
     else {
+      final String paramName = paramToEdit.getName();
       substring = parsed.getParamTypeSubstring(paramName);
     }
     if (substring == null) {
       return;
     }
-    builder.replaceRange(substring.getTextRange().shiftRight(docStringExpression.getStringValueTextRange().getStartOffset()),
-                         getDefaultType(getParamToEdit()));
+    builder.replaceRange(substring.getTextRange(), getDefaultType(getParamToEdit()));
     Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate();
     final VirtualFile virtualFile = myDocStringOwner.getContainingFile().getVirtualFile();
     if (virtualFile == null) return;
diff --git a/python/testData/docstrings/googleEmptyParamTypeInParenthesis.py b/python/testData/docstrings/googleEmptyParamTypeInParenthesis.py
new file mode 100644 (file)
index 0000000..8b2649d
--- /dev/null
@@ -0,0 +1,5 @@
+def f(x):
+    """
+    Parameters:
+      x ():
+    """
\ No newline at end of file
diff --git a/python/testData/docstrings/googleReturnTypeNoDescription.py b/python/testData/docstrings/googleReturnTypeNoDescription.py
new file mode 100644 (file)
index 0000000..2687035
--- /dev/null
@@ -0,0 +1,5 @@
+def f(x):
+    """
+    Returns:
+      object:
+    """
\ No newline at end of file
diff --git a/python/testData/intentions/afterParamTypeInNewGoogleDocString.py b/python/testData/intentions/afterParamTypeInNewGoogleDocString.py
new file mode 100644 (file)
index 0000000..572bb00
--- /dev/null
@@ -0,0 +1,7 @@
+def f(x):
+    """
+
+    Parameters:
+        x (object): 
+    """
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/afterParamTypeInNewNumpyDocString.py b/python/testData/intentions/afterParamTypeInNewNumpyDocString.py
new file mode 100644 (file)
index 0000000..8e79b99
--- /dev/null
@@ -0,0 +1,8 @@
+def f(x):
+    """
+
+    Parameters
+    ----------
+        x : object
+    """
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/afterReturnTypeInNewGoogleDocString.py b/python/testData/intentions/afterReturnTypeInNewGoogleDocString.py
new file mode 100644 (file)
index 0000000..cccff17
--- /dev/null
@@ -0,0 +1,7 @@
+def f(x):
+    """
+
+    Returns:
+        object: 
+    """
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/afterReturnTypeInNewNumpyDocString.py b/python/testData/intentions/afterReturnTypeInNewNumpyDocString.py
new file mode 100644 (file)
index 0000000..69e6777
--- /dev/null
@@ -0,0 +1,8 @@
+def f(x):
+    """
+
+    Returns
+    -------
+        object
+    """
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/beforeParamTypeInNewGoogleDocString.py b/python/testData/intentions/beforeParamTypeInNewGoogleDocString.py
new file mode 100644 (file)
index 0000000..c753db3
--- /dev/null
@@ -0,0 +1,2 @@
+def f(<caret>x):
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/beforeParamTypeInNewNumpyDocString.py b/python/testData/intentions/beforeParamTypeInNewNumpyDocString.py
new file mode 100644 (file)
index 0000000..c753db3
--- /dev/null
@@ -0,0 +1,2 @@
+def f(<caret>x):
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/beforeReturnTypeInNewGoogleDocString.py b/python/testData/intentions/beforeReturnTypeInNewGoogleDocString.py
new file mode 100644 (file)
index 0000000..db739be
--- /dev/null
@@ -0,0 +1,2 @@
+def <caret>f(x):
+    return 42
\ No newline at end of file
diff --git a/python/testData/intentions/beforeReturnTypeInNewNumpyDocString.py b/python/testData/intentions/beforeReturnTypeInNewNumpyDocString.py
new file mode 100644 (file)
index 0000000..db739be
--- /dev/null
@@ -0,0 +1,2 @@
+def <caret>f(x):
+    return 42
\ No newline at end of file
index c2b731a54423718bda9612224f087bac2bd3b7bc..7e1e07730abda9d84c10acc9aed903f39d52cc08 100644 (file)
@@ -237,6 +237,36 @@ public class PySectionBasedDocStringTest extends PyTestCase {
                  "Line after single break", param1.getDescription());
   }
 
+  public void testGoogleEmptyParamTypeInParenthesis() {
+    final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
+    assertSize(1, docString.getSections());
+    final Section paramSection = docString.getSections().get(0);
+    assertEquals("parameters", paramSection.getTitle());
+    assertSize(1, paramSection.getFields());
+    final SectionField param1 = paramSection.getFields().get(0);
+    assertEquals("x", param1.getName());
+    assertEmpty(param1.getDescription());
+    assertEmpty(param1.getType());
+    assertNotNull(param1.getTypeAsSubstring());
+    assertEquals(26, param1.getTypeAsSubstring().getStartOffset());
+    assertEquals(26, param1.getTypeAsSubstring().getEndOffset());
+  }
+
+  public void testGoogleReturnTypeNoDescription() {
+    final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
+    assertSize(1, docString.getSections());
+    final Section returnSection = docString.getSections().get(0);
+    assertEquals("returns", returnSection.getTitle());
+    assertSize(1, returnSection.getFields());
+    final SectionField return1 = returnSection.getFields().get(0);
+    assertEmpty(return1.getName());
+    assertEmpty(return1.getDescription());
+    assertEquals("object", return1.getType());
+    assertNotNull(return1.getTypeAsSubstring());
+    assertEquals(20, return1.getTypeAsSubstring().getStartOffset());
+    assertEquals(26, return1.getTypeAsSubstring().getEndOffset());
+  }
+
   @Override
   protected String getTestDataPath() {
     return super.getTestDataPath() + "/docstrings";
index 4dea3c9bf31cce0bc8c554f0582842fe07277f95..4e5b69d7c20c89eee2d8d72c5e24d93a3aad284c 100644 (file)
@@ -24,6 +24,7 @@ import com.jetbrains.python.documentation.PyDocumentationSettings;
 import com.jetbrains.python.fixtures.PyTestCase;
 import com.jetbrains.python.psi.LanguageLevel;
 import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
@@ -287,28 +288,28 @@ public class  PyIntentionTest extends PyTestCase {
 
   public void testTypeInDocstring() {
     getCommonCodeStyleSettings().getIndentOptions().INDENT_SIZE = 2;
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring3() {
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring4() {
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstringParameterInCallable() {
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring5() {
     getCommonCodeStyleSettings().getIndentOptions().INDENT_SIZE = 2;
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstringAtTheEndOfFunction() {
-    doDocReturnTypeTest();
+    doDocReturnTypeTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring6() {         //PY-7973
@@ -316,31 +317,31 @@ public class  PyIntentionTest extends PyTestCase {
   }
 
   public void testTypeInDocstring7() {         //PY-8930
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   // PY-16456
   public void testTypeInDocStringDifferentIndentationSize() {
-    doDocReferenceTest();
+    doDocReferenceTest(DocStringFormat.REST);
   }
 
   // PY-16456
   public void testReturnTypeInDocStringDifferentIndentationSize() {
-    doDocReturnTypeTest();
+    doDocReturnTypeTest(DocStringFormat.REST);
   }
 
   public void testReturnTypeInDocstring() {
-    doDocReturnTypeTest();
+    doDocReturnTypeTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring1() {
     getCommonCodeStyleSettings().getIndentOptions().INDENT_SIZE = 2;
-    doDocReturnTypeTest();
+    doDocReturnTypeTest(DocStringFormat.REST);
   }
 
   public void testTypeInDocstring2() {
     getCommonCodeStyleSettings().getIndentOptions().INDENT_SIZE = 2;
-    doDocReturnTypeTest();
+    doDocReturnTypeTest(DocStringFormat.REST);
   }
 
   public void testTypeInPy3Annotation() {      //PY-7045
@@ -402,18 +403,18 @@ public class  PyIntentionTest extends PyTestCase {
   }
 
   public void testDocStub() {
-    doDocStubTest();
+    doDocStubTest(DocStringFormat.REST);
   }
 
   public void testOneLineDocStub() {
-    doDocStubTest();
+    doDocStubTest(DocStringFormat.REST);
   }
 
   public void testDocStubKeywordOnly() {
     getCommonCodeStyleSettings().getIndentOptions().INDENT_SIZE = 2;
     runWithLanguageLevel(LanguageLevel.PYTHON27, new Runnable() {
       public void run() {
-        doDocStubTest();
+        doDocStubTest(DocStringFormat.REST);
       }
     });
   }
@@ -438,6 +439,16 @@ public class  PyIntentionTest extends PyTestCase {
     doDocStubTest(DocStringFormat.GOOGLE);
   }
 
+  // PY-9795
+  public void testReturnTypeInNewGoogleDocString() {
+    doDocReturnTypeTest(DocStringFormat.GOOGLE);
+  }
+
+  // PY-9795
+  public void testParamTypeInNewGoogleDocString() {
+    doDocReferenceTest(DocStringFormat.GOOGLE);
+  }
+
   // PY-9795
   public void testGoogleDocStubWithTypes() {
     final PyCodeInsightSettings codeInsightSettings = PyCodeInsightSettings.getInstance();
@@ -468,6 +479,16 @@ public class  PyIntentionTest extends PyTestCase {
       codeInsightSettings.INSERT_TYPE_DOCSTUB = oldInsertTypeDocStub;
     }
   }
+  
+  // PY-4717
+  public void testReturnTypeInNewNumpyDocString() {
+    doDocReturnTypeTest(DocStringFormat.NUMPY);
+  }
+
+  // PY-4717
+  public void testParamTypeInNewNumpyDocString() {
+    doDocReferenceTest(DocStringFormat.NUMPY);
+  }
 
   // PY-7383
   public void testYieldFrom() {
@@ -482,32 +503,43 @@ public class  PyIntentionTest extends PyTestCase {
     doTest(PyBundle.message("INTN.convert.static.method.to.function"));
   }
 
-  private void doDocStubTest(@Nullable DocStringFormat format) {
+  private void doWithDocStringFormat(@NotNull DocStringFormat format, @NotNull Runnable runnable) {
     final PyDocumentationSettings settings = PyDocumentationSettings.getInstance(myFixture.getModule());
     final DocStringFormat oldFormat = settings.getFormat();
-    if (format != null) {
-      settings.setFormat(format);
-    }
+    settings.setFormat(format);
     try {
-      CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
-      codeInsightSettings.JAVADOC_STUB_ON_ENTER = true;
-      doTest(PyBundle.message("INTN.doc.string.stub"), true);
+      runnable.run();
     }
     finally {
       settings.setFormat(oldFormat);
     }
   }
 
-  private void doDocStubTest() {
-    doDocStubTest(null);
+  private void doDocStubTest(@NotNull DocStringFormat format) {
+    doWithDocStringFormat(format, new Runnable() {
+      @Override
+      public void run() {
+        CodeInsightSettings.getInstance().JAVADOC_STUB_ON_ENTER = true;
+        doTest(PyBundle.message("INTN.doc.string.stub"), true);
+      }
+    });
   }
 
-  private void doDocReferenceTest() {
-    doTest(PyBundle.message("INTN.specify.type"));
+  private void doDocReferenceTest(@NotNull DocStringFormat format) {
+    doWithDocStringFormat(format, new Runnable() {
+      public void run() {
+        doTest(PyBundle.message("INTN.specify.type"));
+      }
+    });
   }
 
-  private void doDocReturnTypeTest() {
-    doTest(PyBundle.message("INTN.specify.return.type"));
+  private void doDocReturnTypeTest(@NotNull DocStringFormat format) {
+    doWithDocStringFormat(format, new Runnable() {
+        public void run() {
+          doTest(PyBundle.message("INTN.specify.return.type"));
+        }
+      });
+
   }
 
 }