diff --git a/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java b/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java index 8ff9787296b5..336a54a919e2 100644 --- a/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java +++ b/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java @@ -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)); } } diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java index 9ec0e61c4bd7..b3fc5cc6132e 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java @@ -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)) : ""; - if (!isReturn) { - docstringGenerator.withParamTypedByName(name, typeFromSignature); + final String name = isReturn ? "" : StringUtil.notNullize(problemElement.getName()); + final String type; + if (signature != null) { + type = StringUtil.notNullize(signature.getArgTypeQualifiedName(name), "object"); } else { - docstringGenerator.withReturnValue(typeFromSignature); + type = "object"; + } + if (!isReturn) { + docstringGenerator.withParamTypedByName(name, type); + } + else { + docstringGenerator.withReturnValue(type); } docstringGenerator.buildAndInsert(); diff --git a/python/src/com/jetbrains/python/documentation/DocStringUtil.java b/python/src/com/jetbrains/python/documentation/DocStringUtil.java index 75e9cb43d752..724d9f8df197 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringUtil.java +++ b/python/src/com/jetbrains/python/documentation/DocStringUtil.java @@ -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 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; } diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index e09bb2b54f55..b4c1ab518d18 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -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 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); } diff --git a/python/src/com/jetbrains/python/documentation/NumpyDocString.java b/python/src/com/jetbrains/python/documentation/NumpyDocString.java index e90aafcf2455..7c9237dfb8f8 100644 --- a/python/src/com/jetbrains/python/documentation/NumpyDocString.java +++ b/python/src/com/jetbrains/python/documentation/NumpyDocString.java @@ -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 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); } diff --git a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java index 18c862423e55..75454fbca394 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java @@ -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 index 000000000000..8b2649de19b0 --- /dev/null +++ b/python/testData/docstrings/googleEmptyParamTypeInParenthesis.py @@ -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 index 000000000000..2687035d752b --- /dev/null +++ b/python/testData/docstrings/googleReturnTypeNoDescription.py @@ -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 index 000000000000..572bb00b507f --- /dev/null +++ b/python/testData/intentions/afterParamTypeInNewGoogleDocString.py @@ -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 index 000000000000..8e79b996c815 --- /dev/null +++ b/python/testData/intentions/afterParamTypeInNewNumpyDocString.py @@ -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 index 000000000000..cccff17f2f14 --- /dev/null +++ b/python/testData/intentions/afterReturnTypeInNewGoogleDocString.py @@ -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 index 000000000000..69e6777e3c17 --- /dev/null +++ b/python/testData/intentions/afterReturnTypeInNewNumpyDocString.py @@ -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 index 000000000000..c753db31d237 --- /dev/null +++ b/python/testData/intentions/beforeParamTypeInNewGoogleDocString.py @@ -0,0 +1,2 @@ +def f(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 index 000000000000..c753db31d237 --- /dev/null +++ b/python/testData/intentions/beforeParamTypeInNewNumpyDocString.py @@ -0,0 +1,2 @@ +def f(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 index 000000000000..db739be8b11f --- /dev/null +++ b/python/testData/intentions/beforeReturnTypeInNewGoogleDocString.py @@ -0,0 +1,2 @@ +def 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 index 000000000000..db739be8b11f --- /dev/null +++ b/python/testData/intentions/beforeReturnTypeInNewNumpyDocString.py @@ -0,0 +1,2 @@ +def f(x): + return 42 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java b/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java index c2b731a54423..7e1e07730abd 100644 --- a/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java +++ b/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java @@ -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"; diff --git a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java index 4dea3c9bf31c..4e5b69d7c20c 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java @@ -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")); + } + }); + } }