diff --git a/python/src/com/jetbrains/python/documentation/DocStringLineParser.java b/python/src/com/jetbrains/python/documentation/DocStringLineParser.java index c05c0394d8ea..21ea76a26b4d 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringLineParser.java +++ b/python/src/com/jetbrains/python/documentation/DocStringLineParser.java @@ -82,11 +82,14 @@ public abstract class DocStringLineParser { * it means that no block with requested indentation exist. Block can contain intermediate empty lines or lines that consists * solely of spaces: their indentation is ignored, but it always ends with non-empty line. Non-empty lines in a block must * have indentation greater than indentThreshold and for them {@link #isBlockEnd(int)} must return false. + * + * @param indentThreshold indentation size of non-empty line that will break block once reached, i.e. minimum possible indentation + * inside a block is {@code indentThreshold + 1} */ - public int parseIndentedBlock(int startLine, int indentThreshold) { + public int consumeIndentedBlock(int startLine, int indentThreshold) { int blockEnd = startLine - 1; int lineNum = startLine; - while (!(lineNum >= getLineCount() || isBlockEnd(lineNum))) { + while (lineNum < getLineCount() && !isBlockEnd(lineNum)) { if (!isEmpty(lineNum)) { if (getLineIndentSize(lineNum) > indentThreshold) { blockEnd = lineNum; @@ -100,7 +103,7 @@ public abstract class DocStringLineParser { return blockEnd + 1; } - public int skipEmptyLines(int lineNum) { + public int consumeEmptyLines(int lineNum) { while (lineNum < getLineCount() && isEmpty(lineNum)) { lineNum++; } diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index 61f3a1a0184a..03fc40001614 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -17,6 +17,7 @@ package com.jetbrains.python.documentation; import com.intellij.openapi.util.Pair; import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.python.PyNames; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -98,9 +99,12 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString { type = name; name = null; } + if (name != null ? !PyNames.isIdentifierString(name.toString()) : type.isEmpty()) { + return Pair.create(null, lineNum); + } description = colonSeparatedParts.get(1); // parse line with indentation at least one space greater than indentation of the field - final Pair, Integer> pair = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum), sectionIndent); + final Pair, Integer> pair = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum)); final List nestedBlock = pair.getFirst(); if (!nestedBlock.isEmpty()) { //noinspection ConstantConditions diff --git a/python/src/com/jetbrains/python/documentation/NumpyDocString.java b/python/src/com/jetbrains/python/documentation/NumpyDocString.java index 31767b4f0d88..ae8ae037894f 100644 --- a/python/src/com/jetbrains/python/documentation/NumpyDocString.java +++ b/python/src/com/jetbrains/python/documentation/NumpyDocString.java @@ -16,6 +16,7 @@ package com.jetbrains.python.documentation; import com.intellij.openapi.util.Pair; +import com.jetbrains.python.PyNames; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -39,7 +40,7 @@ public class NumpyDocString extends SectionBasedDocString { @Override protected int parseHeader(int startLine) { - final int nextNonEmptyLineNum = skipEmptyLines(startLine); + final int nextNonEmptyLineNum = consumeEmptyLines(startLine); final Substring line = getLineOrNull(nextNonEmptyLineNum); if (line != null && SIGNATURE.matcher(line).matches()) { mySignature = line.trim(); @@ -82,7 +83,10 @@ public class NumpyDocString extends SectionBasedDocString { type = name; name = null; } - final Pair, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum), sectionIndent); + if (name != null? !PyNames.isIdentifierString(name.toString()) : type.isEmpty()) { + return Pair.create(null, lineNum); + } + final Pair, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum)); final List descriptionLines = parsedDescription.getFirst(); if (!descriptionLines.isEmpty()) { description = descriptionLines.get(0).union(descriptionLines.get(descriptionLines.size() - 1)); diff --git a/python/src/com/jetbrains/python/documentation/PlainDocString.java b/python/src/com/jetbrains/python/documentation/PlainDocString.java index 5b094addca4b..c88c061e65f8 100644 --- a/python/src/com/jetbrains/python/documentation/PlainDocString.java +++ b/python/src/com/jetbrains/python/documentation/PlainDocString.java @@ -36,7 +36,7 @@ public class PlainDocString extends DocStringLineParser implements StructuredDoc super(content); if (!isEmpty(0) && isEmptyOrDoesNotExist(1)) { mySummary = getLine(0).trim().toString(); - final int next = skipEmptyLines(1); + final int next = consumeEmptyLines(1); if (next != 1) { final String remaining = getLine(next).union(getLine(getLineCount() - 1)).toString(); myDescription = PyIndentUtil.removeCommonIndent(remaining, false); diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 15373ff51dfb..98a2d7e092fa 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -100,7 +100,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen protected SectionBasedDocString(@NotNull Substring text) { super(text); List summary = Collections.emptyList(); - int startLine = skipEmptyLines(parseHeader(0)); + int startLine = consumeEmptyLines(parseHeader(0)); int lineNum = startLine; while (lineNum < getLineCount()) { final Pair parsedSection = parseSection(lineNum); @@ -117,7 +117,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen myOtherContent.add(getLine(lineNum)); lineNum++; } - lineNum = skipEmptyLines(lineNum); + lineNum = consumeEmptyLines(lineNum); } //noinspection ConstantConditions mySummary = summary.isEmpty() ? null : summary.get(0).union(summary.get(summary.size() - 1)).trim(); @@ -144,29 +144,32 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen @NotNull protected Pair parseSection(int sectionStartLine) { - final Pair pair = parseSectionHeader(sectionStartLine); - if (pair.getFirst() == null) { + final Pair parsedHeader = parseSectionHeader(sectionStartLine); + if (parsedHeader.getFirst() == null) { return Pair.create(null, sectionStartLine); } - final String normalized = getNormalizedSectionTitle(pair.getFirst().toString()); + final String normalized = getNormalizedSectionTitle(parsedHeader.getFirst().toString()); if (normalized == null) { return Pair.create(null, sectionStartLine); } - int lineNum = skipEmptyLines(pair.getSecond()); final List fields = new ArrayList(); final int sectionIndent = getLineIndentSize(sectionStartLine); + int lineNum = consumeEmptyLines(parsedHeader.getSecond()); while (!isSectionBreak(lineNum, sectionIndent)) { + final Pair parsedField = parseSectionField(lineNum, normalized, sectionIndent); if (!isEmpty(lineNum)) { - final Pair result = parseSectionField(lineNum, normalized, sectionIndent); - if (result.getFirst() != null) { - fields.add(result.getFirst()); - lineNum = result.getSecond(); + if (parsedField.getFirst() != null) { + fields.add(parsedField.getFirst()); + lineNum = parsedField.getSecond(); continue; } + else { + myOtherContent.add(getLine(lineNum)); + } } lineNum++; } - return Pair.create(new Section(pair.getFirst(), fields), lineNum); + return Pair.create(new Section(parsedHeader.getFirst(), fields), lineNum); } @NotNull @@ -193,7 +196,8 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen @NotNull protected Pair parseGenericField(int lineNum, int sectionIndent) { - final Pair, Integer> pair = parseIndentedBlock(lineNum, sectionIndent, sectionIndent); + // We want to let section content has the same indent as section header, in particular for Numpy + final Pair, Integer> pair = parseIndentedBlock(lineNum, Math.max(sectionIndent - 1, 0)); final Substring firstLine = ContainerUtil.getFirstItem(pair.getFirst()); final Substring lastLine = ContainerUtil.getLastItem(pair.getFirst()); if (firstLine != null && lastLine != null) { @@ -212,8 +216,9 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen protected boolean isSectionBreak(int lineNum, int curSectionIndent) { return lineNum >= getLineCount() || - (!isEmpty(lineNum) && getLineIndentSize(lineNum) <= curSectionIndent) || - isBlockEnd(lineNum); + // note that field may have the same indent as its containing section + (!isEmpty(lineNum) && getLineIndentSize(lineNum) < curSectionIndent) || + isSectionStart(lineNum); } /** @@ -223,8 +228,8 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen * @param blockIndent indentation threshold, block ends with a line that has greater indentation */ @NotNull - protected Pair, Integer> parseIndentedBlock(int lineNum, int blockIndent, int sectionIndent) { - final int blockEnd = parseIndentedBlock(lineNum, blockIndent); + protected Pair, Integer> parseIndentedBlock(int lineNum, int blockIndent) { + final int blockEnd = consumeIndentedBlock(lineNum, blockIndent); return Pair.create(myLines.subList(lineNum, blockEnd), blockEnd); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java index 931e63906558..1c5775a99b98 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java @@ -79,7 +79,7 @@ public abstract class DocStringUpdater { } private int skipEmptyLines(int startLine) { - return Math.min(myOriginalDocString.skipEmptyLines(startLine), myOriginalDocString.getLineCount() - 1); + return Math.min(myOriginalDocString.consumeEmptyLines(startLine), myOriginalDocString.getLineCount() - 1); } protected final void removeLine(int line) { diff --git a/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java index da5366ef1e65..eda1b91b6d4a 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java @@ -65,7 +65,7 @@ public class TagBasedDocStringUpdater extends DocStringUpdater