diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index 67c98ef58943..830fccd6eaf6 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -31,7 +31,7 @@ import java.util.regex.Pattern; * @see Google Python Style: Docstrings */ public class GoogleCodeStyleDocString extends SectionBasedDocString { - public static final Pattern SECTION_HEADER_RE = Pattern.compile("\\s*(\\w+):\\s*", Pattern.MULTILINE); + public static final Pattern SECTION_HEADER_RE = Pattern.compile("\\s*([\\w\\s]+):\\s*", Pattern.MULTILINE); private static final Pattern FIELD_NAME_AND_TYPE_RE = Pattern.compile("\\s*(.+?)\\s*\\(\\s*(.*?)\\s*\\)\\s*"); public GoogleCodeStyleDocString(@NotNull Substring text) { diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 23b204a71d59..a166191d2cc0 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -89,7 +89,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen private static final ImmutableSet SECTIONS_WITH_NAME = ImmutableSet.of(METHODS_SECTION); @Nullable - protected static String normalizeSectionTitle(@NotNull @NonNls String title) { + public static String getNormalizedSectionTitle(@NotNull @NonNls String title) { return SECTION_ALIASES.get(title.toLowerCase()); } @@ -148,7 +148,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen if (pair.getFirst() == null) { return Pair.create(null, sectionStartLine); } - final String normalized = normalizeSectionTitle(pair.getFirst().toString()); + final String normalized = getNormalizedSectionTitle(pair.getFirst().toString()); if (normalized == null) { return Pair.create(null, sectionStartLine); } @@ -497,15 +497,20 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen } @NotNull - private List
getSectionsWithNormalizedTitle(@NotNull final String title) { + public List
getSectionsWithNormalizedTitle(@NotNull final String title) { return ContainerUtil.mapNotNull(mySections, new Function() { @Override public Section fun(Section section) { - return section.getNormalizedTitle().equals(title) ? section : null; + return section.getNormalizedTitle().equals(getNormalizedSectionTitle(title)) ? section : null; } }); } + @Nullable + public Section getFirstSectionWithNormalizedTitle(@NotNull String title) { + return ContainerUtil.getFirstItem(getSectionsWithNormalizedTitle(title)); + } + @Nullable @Override public String getAttributeDescription() { @@ -534,7 +539,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen @NotNull public String getNormalizedTitle() { //noinspection ConstantConditions - return normalizeSectionTitle(getTitle()); + return getNormalizedSectionTitle(getTitle()); } @NotNull diff --git a/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java b/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java index b21e4c9aeb7c..a08fccd8f62b 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java @@ -34,14 +34,24 @@ public abstract class DocStringBuilder { @NotNull public This addLine(@NotNull String line) { - myLines.add(line); + return addLine(line, myLines.size()); + } + + @NotNull + public This addLine(@NotNull String line, int index) { + myLines.add(index, line); //noinspection unchecked return (This)this; } @NotNull public This addEmptyLine() { - return addLine(""); + return addLine("", myLines.size()); + } + + @NotNull + public This addEmptyLine(int index) { + return addLine("", index); } @NotNull diff --git a/python/src/com/jetbrains/python/documentation/docstrings/GoogleCodeStyleDocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/GoogleCodeStyleDocStringUpdater.java index 9db6b61cb7f6..5e590552f6be 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/GoogleCodeStyleDocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/GoogleCodeStyleDocStringUpdater.java @@ -28,7 +28,7 @@ public class GoogleCodeStyleDocStringUpdater extends SectionBasedDocStringUpdate } @Override - void updateParamDeclarationWithType(@NotNull Substring nameSubstring, @NotNull String type) { + protected void updateParamDeclarationWithType(@NotNull Substring nameSubstring, @NotNull String type) { insert(nameSubstring.getEndOffset(), " (" + type + ")"); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/NumpyDocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/NumpyDocStringUpdater.java index 065ba0cee9d2..32d2047d6e2b 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/NumpyDocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/NumpyDocStringUpdater.java @@ -30,12 +30,12 @@ public class NumpyDocStringUpdater extends SectionBasedDocStringUpdater { } @Override - void updateParamDeclarationWithType(@NotNull Substring nameSubstring, @NotNull String type) { + protected void updateParamDeclarationWithType(@NotNull Substring nameSubstring, @NotNull String type) { insert(nameSubstring.getEndOffset(), " : " + type); } @Override - protected int getSectionLastTitleLine(@NotNull Section section) { + protected int getSectionTitleLastLine(@NotNull Section section) { return getSectionStartLine(section) + 1; } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/SectionBasedDocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/SectionBasedDocStringUpdater.java index d24f08c39d77..472efa2f2425 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/SectionBasedDocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/SectionBasedDocStringUpdater.java @@ -15,7 +15,9 @@ */ package com.jetbrains.python.documentation.docstrings; +import com.google.common.collect.ImmutableList; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.documentation.SectionBasedDocString; @@ -27,12 +29,23 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @author Mikhail Golubev */ public abstract class SectionBasedDocStringUpdater extends DocStringUpdater { + private static final ImmutableList CANONICAL_SECTION_ORDER = ImmutableList.of( + SectionBasedDocString.PARAMETERS_SECTION, + SectionBasedDocString.KEYWORD_ARGUMENTS_SECTION, + SectionBasedDocString.OTHER_PARAMETERS_SECTION, + SectionBasedDocString.YIELDS_SECTION, + SectionBasedDocString.RETURNS_SECTION, + SectionBasedDocString.RAISES_SECTION + ); + private final List myAddParameterRequests = new ArrayList(); public SectionBasedDocStringUpdater(@NotNull SectionBasedDocString docString, @NotNull String minContentIndent) { @@ -66,18 +79,15 @@ public abstract class SectionBasedDocStringUpdater extends DocStringUpdater params, @NotNull String sectionIndent, @NotNull String indent) { + return addParametersInBlock(builder, params, sectionIndent).buildContent(indent, true); + } + + private static SectionBasedDocStringBuilder addParametersInBlock(@NotNull SectionBasedDocStringBuilder builder, + @NotNull List params, + @NotNull String sectionIndent) { builder.withSectionIndent(sectionIndent); for (AddParameter param : params) { builder.addParameter(param.name, param.type, ""); } - return builder.buildContent(indent, true); + return builder; + } + + private void insertNewSection(@NotNull SectionBasedDocStringBuilder builder, @NotNull String sectionTitle) { + final Pair pos = findPreferredSectionLine(sectionTitle); + if (pos.getSecond()) { + builder.addEmptyLine(0); + insertAfterLine(pos.getFirst(), builder.buildContent(getExpectedSectionIndent(), true)); + } + else { + builder.addEmptyLine(); + insertBeforeLine(pos.getFirst(), builder.buildContent(getExpectedSectionIndent(), true)); + } + } + + /** + * @return pair (lineNum, insertAfter), i.e. first item is line number, + * second item is true if new section should be inserted after this line and false otherwise + */ + private Pair findPreferredSectionLine(@NotNull String sectionTitle) { + final String normalized = SectionBasedDocString.getNormalizedSectionTitle(sectionTitle); + final int index = CANONICAL_SECTION_ORDER.indexOf(normalized); + if (index < 0) { + return Pair.create(findLastNonEmptyLine(), true); + } + final Map namedSections = new HashMap(); + for (Section section : myOriginalDocString.getSections()) { + final String normalizedTitle = section.getNormalizedTitle(); + // leave only first occurrences + if (!namedSections.containsKey(normalizedTitle)) { + namedSections.put(normalizedTitle, section); + } + } + for (int i = index - 1; i >= 0; i--) { + final Section previous = namedSections.get(CANONICAL_SECTION_ORDER.get(i)); + if (previous != null) { + return Pair.create(getSectionEndLine(previous), true); + } + } + for (int i = index + 1; i < CANONICAL_SECTION_ORDER.size(); i++) { + final Section next = namedSections.get(CANONICAL_SECTION_ORDER.get(i)); + if (next != null) { + return Pair.create(getSectionStartLine(next), false); + } + } + return Pair.create(findLastNonEmptyLine(), true); } + protected abstract void updateParamDeclarationWithType(@NotNull Substring nameSubstring, @NotNull String type); + + protected abstract SectionBasedDocStringBuilder createBuilder(); + + @Nullable private Substring findParamNameSubstring(@NotNull final String name) { return ContainerUtil.find(myOriginalDocString.getParameterSubstrings(), new Condition() { @Override @@ -170,14 +234,10 @@ public abstract class SectionBasedDocStringUpdater extends DocStringUpdater fields = section.getFields(); + //noinspection ConstantConditions + return fields.isEmpty() ? getSectionTitleLastLine(section) : getFieldEndLine(ContainerUtil.getLastItem(fields)); + } + protected int getFieldStartLine(@NotNull SectionField field) { return chooseFirstNotNull(field.getNameAsSubstring(), field.getTypeAsSubstring(), @@ -261,7 +327,7 @@ public abstract class SectionBasedDocStringUpdater extends DocStringUpdaterf(**kwargs): + """ + Keyword arguments: + foo: bar + """ \ No newline at end of file diff --git a/python/testData/intentions/beforeGoogleReturnSectionAfterYields.py b/python/testData/intentions/beforeGoogleReturnSectionAfterYields.py new file mode 100644 index 000000000000..c60604039fdc --- /dev/null +++ b/python/testData/intentions/beforeGoogleReturnSectionAfterYields.py @@ -0,0 +1,10 @@ +def f(): + """ + Yields: + int: meaning of life, universe and everything + + Example: + print(next(f)) + """ + yield 42 + return \ No newline at end of file diff --git a/python/testData/intentions/beforeGoogleReturnSectionBeforeRaises.py b/python/testData/intentions/beforeGoogleReturnSectionBeforeRaises.py new file mode 100644 index 000000000000..9bf63751327e --- /dev/null +++ b/python/testData/intentions/beforeGoogleReturnSectionBeforeRaises.py @@ -0,0 +1,7 @@ +def f(): + """ + Raises: + RuntimeException + """ + raise RuntimeException + \ No newline at end of file diff --git a/python/testData/intentions/beforeParamSectionBeforeKeywords.py b/python/testData/intentions/beforeParamSectionBeforeKeywords.py new file mode 100644 index 000000000000..3f03c8d0548e --- /dev/null +++ b/python/testData/intentions/beforeParamSectionBeforeKeywords.py @@ -0,0 +1,9 @@ +def f(x): + """ + + Keyword arguments: + + Returns: + None + """ + \ 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 f9cdd2f305db..5555ba69c7ed 100644 --- a/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java +++ b/python/testSrc/com/jetbrains/python/PySectionBasedDocStringTest.java @@ -283,6 +283,13 @@ public class PySectionBasedDocStringTest extends PyTestCase { assertSize(1, paramSection.getFields()); } + public void testGoogleKeywordArgumentsSection() { + final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString(); + assertEmpty(docString.getSummary()); + assertSize(1, docString.getSections()); + assertEquals("keyword arguments", docString.getSections().get(0).getNormalizedTitle()); + } + @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 e0898ab983d0..1d2b0fdcfc5f 100644 --- a/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/intentions/PyIntentionTest.java @@ -499,6 +499,26 @@ public class PyIntentionTest extends PyTestCase { doDocReturnTypeTest(DocStringFormat.GOOGLE); } + // PY-16758 + public void testGoogleReturnSectionAfterKeywords() { + doDocReturnTypeTest(DocStringFormat.GOOGLE); + } + + // PY-16758 + public void testGoogleReturnSectionAfterYields() { + doDocReturnTypeTest(DocStringFormat.GOOGLE); + } + + // PY-16758 + public void testGoogleReturnSectionBeforeRaises() { + doDocReturnTypeTest(DocStringFormat.GOOGLE); + } + + // PY-16758 + public void testParamSectionBeforeKeywords() { + doDocAddMissingParamsTest(DocStringFormat.GOOGLE); + } + // PY-9795 public void testGoogleDocStubWithTypes() { final PyCodeInsightSettings codeInsightSettings = PyCodeInsightSettings.getInstance();