PY-9795 Parse multiline summary until first empty line of the end of docstring

This commit is contained in:
Mikhail Golubev
2015-09-02 14:34:14 +03:00
parent d9b09576d2
commit 757860a8a5
4 changed files with 123 additions and 26 deletions
@@ -0,0 +1,46 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.documentation;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
/**
* @author Mikhail Golubev
*/
public class NumpyDocString extends SectionBasedDocString {
public NumpyDocString(@NotNull String text) {
super(text);
}
@NotNull
@Override
protected Pair<SectionField, Integer> parseFieldWithType(int lineNum, int sectionIndent) {
return null;
}
@NotNull
@Override
protected Pair<SectionField, Integer> parseFieldWithNameAndOptionalType(int lineNum, int sectionIndent) {
return null;
}
@NotNull
@Override
protected Pair<String, Integer> parseSectionHeader(int lineNum) {
return null;
}
}
@@ -38,6 +38,7 @@ import java.util.*;
* @see <a href="http://sphinxcontrib-napoleon.readthedocs.org/en/latest/index.html">Napoleon</a>
*/
public abstract class SectionBasedDocString implements StructuredDocString {
/**
* Frequently used section types
*/
@@ -79,31 +80,53 @@ public abstract class SectionBasedDocString implements StructuredDocString {
protected final List<Substring> myLines;
private final List<Substring> myOtherContent = new ArrayList<Substring>();
private final Substring mySummary;
private final List<Section> mySections = new ArrayList<Section>();
private final String mySummary;
private final List<Substring> myOtherContent = new ArrayList<Substring>();
protected SectionBasedDocString(@NotNull String text) {
myLines = new Substring(text).splitLines();
String summary = "";
int lineNum = 0;
List<Substring> summary = Collections.emptyList();
final int startLine = parseHeader(0);
int lineNum = skipEmptyLines(startLine);
while (lineNum < myLines.size()) {
Pair<Section, Integer> result = parseSection(lineNum);
if (result.getFirst() != null) {
mySections.add(result.getFirst());
lineNum = result.getSecond();
final Pair<Section, Integer> parsedSection = parseSection(lineNum);
if (parsedSection.getFirst() != null) {
mySections.add(parsedSection.getFirst());
lineNum = parsedSection.getSecond();
}
else if (lineNum == startLine) {
final Pair<List<Substring>, Integer> parsedSummary = parseSummary(lineNum);
summary = parsedSummary.getFirst();
lineNum = parsedSummary.getSecond();
}
else {
if (lineNum == 0 && isEmptyOrDoesNotExist(lineNum + 1)) {
summary = getLine(0).trim().toString();
}
else {
myOtherContent.add(getLine(lineNum));
}
myOtherContent.add(getLine(lineNum));
lineNum++;
}
lineNum = skipEmptyLines(lineNum);
}
mySummary = summary;
//noinspection ConstantConditions
mySummary = summary.isEmpty() ? null : mergeSubstrings(summary.get(0), summary.get(summary.size() - 1)).trim();
}
@NotNull
private Pair<List<Substring>, Integer> parseSummary(int lineNum) {
final List<Substring> result = new ArrayList<Substring>();
while (!isEmptyOrDoesNotExist(lineNum)) {
result.add(getLine(lineNum));
lineNum++;
}
return Pair.create(result, lineNum);
}
/**
* Used to parse e.g. optional function signature at the beginning of NumPy-style docstring
*
* @return first line from which to star parsing remaining sections
*/
protected int parseHeader(int startLine) {
return startLine;
}
@NotNull
@@ -172,7 +195,7 @@ public abstract class SectionBasedDocString implements StructuredDocString {
}
private boolean isEmptyOrDoesNotExist(int lineNum) {
return lineNum >= myLines.size() - 1 || isEmpty(lineNum);
return lineNum < 0 || lineNum >= myLines.size() || isEmpty(lineNum);
}
private boolean isEmpty(int lineNum) {
@@ -268,8 +291,13 @@ public abstract class SectionBasedDocString implements StructuredDocString {
}
@NotNull
protected Substring getLine(int indent) {
return myLines.get(indent);
protected Substring getLine(int lineNum) {
return myLines.get(lineNum);
}
@Nullable
protected Substring getLineOrNull(int lineNum) {
return lineNum >= 0 && lineNum < myLines.size() ? myLines.get(lineNum) : null;
}
@VisibleForTesting
@@ -285,7 +313,7 @@ public abstract class SectionBasedDocString implements StructuredDocString {
@Override
public String getSummary() {
return mySummary.toString();
return mySummary != null ? mySummary.concatTrimmedLines("\n") : "";
}
@Override
@@ -407,6 +435,7 @@ public abstract class SectionBasedDocString implements StructuredDocString {
return result;
}
@Nullable
private SectionField getFirstFieldForKeywordArgument(@NotNull final String name) {
return ContainerUtil.find(getKeywordArgumentFields(), new Condition<SectionField>() {
@Override
@@ -0,0 +1,4 @@
def func():
"""First line
Second line
Third line"""
@@ -19,6 +19,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.documentation.GoogleCodeStyleDocString;
import com.jetbrains.python.documentation.NumpyDocString;
import com.jetbrains.python.documentation.SectionBasedDocString.Section;
import com.jetbrains.python.documentation.SectionBasedDocString.SectionField;
import com.jetbrains.python.fixtures.PyTestCase;
@@ -31,10 +32,10 @@ import java.util.List;
/**
* @author Mikhail Golubev
*/
public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
public class PySectionBasedDocStringTest extends PyTestCase {
public void testSimpleFunctionDocString() {
final GoogleCodeStyleDocString docString = findAndParseDocString();
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertEquals("Summary", docString.getSummary());
final List<Section> sections = docString.getSections();
assertSize(3, sections);
@@ -71,12 +72,22 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
}
@NotNull
private GoogleCodeStyleDocString findAndParseDocString() {
private String findAndParseDocString() {
myFixture.configureByFile(getTestName(true) + ".py");
final String docStringText = findFirstDocString();
assertNotNull(docStringText);
return new GoogleCodeStyleDocString(docStringText);
return docStringText;
}
@NotNull
private GoogleCodeStyleDocString findAndParseGoogleStyleDocString() {
return new GoogleCodeStyleDocString(findAndParseDocString());
}
@NotNull
private NumpyDocString findAndParseNumpyStyleDocString() {
return new NumpyDocString(findAndParseDocString());
}
@Nullable
@@ -100,7 +111,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
}
public void testSectionStartAfterQuotes() {
final GoogleCodeStyleDocString docString = findAndParseDocString();
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertEmpty(docString.getSummary());
assertSize(2, docString.getSections());
@@ -125,7 +136,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
}
public void testTypeReferences() {
final GoogleCodeStyleDocString docString = findAndParseDocString();
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertEmpty(docString.getSummary());
assertSize(2, docString.getSections());
@@ -145,7 +156,7 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
}
public void testNestedIndentation() {
final GoogleCodeStyleDocString docString = findAndParseDocString();
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertSize(1, docString.getSections());
final Section section1 = docString.getSections().get(0);
assertEquals("parameters", section1.getTitle());
@@ -162,6 +173,13 @@ public class PyGoogleCodeStyleDocStringTest extends PyTestCase {
" assert func(42) is None", param1.getDescription());
}
public void testMultilineSummary() {
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertEquals("First line\n" +
"Second line\n" +
"Third line", docString.getSummary());
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/docstrings";