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();