PY-16870 Complete parameter in Google docstring when there is no colon after them yet

This commit is contained in:
Mikhail Golubev
2015-09-15 11:18:23 +03:00
parent ec3c403fbf
commit 4a73b1fa66
11 changed files with 67 additions and 19 deletions
@@ -58,6 +58,7 @@ public class PyDocstringCompletionContributor extends CompletionContributor {
final PsiElement element = parameters.getOriginalPosition();
if (element == null) return;
final PsiFile file = element.getContainingFile();
// Parameter references are filled with DocStringParameterReference#getVariants
if (file.findReferenceAt(parameters.getOffset()) != null) return;
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class);
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
@@ -19,10 +19,8 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyDocStringOwner;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.StructuredDocString;
@@ -44,8 +42,7 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull ProcessingContext context) {
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class);
if (docStringOwner != null && element == docStringOwner.getDocStringExpression()) {
if (element == DocStringUtil.getParentDefinitionDocString(element)) {
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
final List<TextRange> ranges = expr.getStringValueTextRanges();
@@ -42,6 +42,9 @@ public class DocStringSectionHeaderCompletionContributor extends CompletionContr
final PsiElement stringNode = parameters.getOriginalPosition();
assert stringNode != null;
final int offset = parameters.getOffset();
if (file.findReferenceAt(offset) != null) {
return;
}
final DocStringFormat format = DocStringUtil.getConfiguredDocStringFormat(file);
if (!(format == DocStringFormat.GOOGLE || format == DocStringFormat.NUMPY)) {
return;
@@ -78,12 +78,11 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString {
boolean mayHaveType,
boolean preferType) {
final Substring line = getLine(lineNum);
Substring name, type = null, description;
Substring name, type = null;
// Napoleon requires that each parameter line contains a colon - we don't because
// we need to parse and complete parameter names before colon is typed
final List<Substring> colonSeparatedParts = splitByFirstColon(line);
assert colonSeparatedParts.size() <= 2;
if (colonSeparatedParts.size() < 2) {
return Pair.create(null, lineNum);
}
final Substring textBeforeColon = colonSeparatedParts.get(0);
name = textBeforeColon.trim();
if (mayHaveType) {
@@ -104,16 +103,20 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString {
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<List<Substring>, Integer> pair = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum));
final List<Substring> nestedBlock = pair.getFirst();
if (!nestedBlock.isEmpty()) {
//noinspection ConstantConditions
description = description.union(ContainerUtil.getLastItem(nestedBlock));
final Pair<List<Substring>, Integer> pair;
if (colonSeparatedParts.size() == 2) {
Substring description = colonSeparatedParts.get(1);
// parse line with indentation at least one space greater than indentation of the field
pair = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum));
final List<Substring> nestedBlock = pair.getFirst();
if (!nestedBlock.isEmpty()) {
//noinspection ConstantConditions
description = description.union(ContainerUtil.getLastItem(nestedBlock));
}
description = description.trim();
return Pair.create(new SectionField(name, type, description), pair.getSecond());
}
description = description.trim();
return Pair.create(new SectionField(name, type, description), pair.getSecond());
return Pair.create(new SectionField(name, type, null), lineNum + 1);
}
@@ -0,0 +1,5 @@
def f(param1, param2):
"""
Args:
par<caret>
"""
@@ -0,0 +1,5 @@
def f(param1, param2):
"""
Args:
par<caret>
"""
@@ -0,0 +1,6 @@
def f(x, y):
"""
Args:
x
y (int)
"""
@@ -1,7 +1,7 @@
def f():
"""
Returns:
object:
object:
Raises:
RuntimeException
@@ -2,7 +2,7 @@ def f(x):
"""
Args:
x:
x:
Keyword arguments:
@@ -328,6 +328,22 @@ public class PySectionBasedDocStringTest extends PyTestCase {
assertEquals("args", paramSection.getFields().get(1).getName());
}
public void testGoogleNoColonAfterParameter() {
final GoogleCodeStyleDocString docString = findAndParseGoogleStyleDocString();
assertSize(1, docString.getSections());
final Section paramSection = docString.getSections().get(0);
assertSize(2, paramSection.getFields());
final SectionField x = paramSection.getFields().get(0);
assertEquals("x", x.getName());
assertEmpty(x.getType());
assertEmpty(x.getDescription());
final SectionField y = paramSection.getFields().get(1);
assertEquals("y", y.getName());
assertEquals("int", y.getType());
assertEmpty(y.getDescription());
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/docstrings";
@@ -426,6 +426,18 @@ public class PythonCompletionTest extends PyTestCase {
});
}
// PY-16870
public void testParamNameInGoogleDocstring() {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
final List<String> variants = doTestByFile();
assertNotNull(variants);
assertSameElements(variants, "param1", "param2");
}
});
}
public void testPep328Completion() { // PY-3409
myFixture.copyDirectoryToProject("pep328", "pep328");
myFixture.configureByFile("pep328/package/subpackage1/moduleX.py");