PY-16906 Resolve references to global variables in module-level docstrings

Because DocStringParameterReference doesn't implement
PsiPolyVariantReference I had to adjust helper methods like
PyResolveTestCase#findReferenceByMarker and
PyMultiFileResolveTestCase#doResolve in resolve
tests to handle normal PsiReference reference under cursor.
This commit is contained in:
Mikhail Golubev
2015-11-10 17:17:56 +03:00
parent c7c77f57f2
commit bee4d5b022
8 changed files with 108 additions and 52 deletions

View File

@@ -49,7 +49,7 @@ public class DocStringParameterReference extends PsiReferenceBase<PyStringLitera
myType = refType;
}
public enum ReferenceType {PARAMETER, PARAMETER_TYPE, KEYWORD, VARIABLE, CLASS_VARIABLE, INSTANCE_VARIABLE}
public enum ReferenceType {PARAMETER, PARAMETER_TYPE, KEYWORD, VARIABLE, CLASS_VARIABLE, INSTANCE_VARIABLE, GLOBAL_VARIABLE}
@Override
public PsiElement resolve() {
@@ -78,6 +78,19 @@ public class DocStringParameterReference extends PsiReferenceBase<PyStringLitera
return resolveInstanceVariable((PyClass)owner);
}
}
if (owner instanceof PyFile && myType == ReferenceType.GLOBAL_VARIABLE) {
return resolveGlobalVariable(((PyFile)owner));
}
return null;
}
@Nullable
private PsiElement resolveGlobalVariable(@NotNull PyFile owner) {
for (PyTargetExpression assignment : owner.getTopLevelAttributes()) {
if (getCanonicalText().equals(assignment.getName())) {
return assignment;
}
}
return null;
}

View File

@@ -21,8 +21,10 @@ import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.documentation.docstrings.DocStringParameterReference.ReferenceType;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.StructuredDocString;
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
import com.jetbrains.python.psi.types.PyType;
@@ -59,29 +61,30 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
final TagBasedDocString taggedDocString = (TagBasedDocString)docString;
result.addAll(referencesFromNames(expr, offset, docString,
taggedDocString.getTagArguments(TagBasedDocString.PARAM_TAGS),
DocStringParameterReference.ReferenceType.PARAMETER));
ReferenceType.PARAMETER));
result.addAll(referencesFromNames(expr, offset, docString,
taggedDocString.getTagArguments(TagBasedDocString.PARAM_TYPE_TAGS),
DocStringParameterReference.ReferenceType.PARAMETER_TYPE));
ReferenceType.PARAMETER_TYPE));
result.addAll(referencesFromNames(expr, offset, docString,
docString.getKeywordArgumentSubstrings(), DocStringParameterReference.ReferenceType.KEYWORD));
docString.getKeywordArgumentSubstrings(), ReferenceType.KEYWORD));
result.addAll(referencesFromNames(expr, offset, docString,
taggedDocString.getTagArguments("var"),
DocStringParameterReference.ReferenceType.VARIABLE));
ReferenceType.VARIABLE));
result.addAll(referencesFromNames(expr, offset, docString,
taggedDocString.getTagArguments("cvar"),
DocStringParameterReference.ReferenceType.CLASS_VARIABLE));
ReferenceType.CLASS_VARIABLE));
result.addAll(referencesFromNames(expr, offset, docString,
taggedDocString.getTagArguments("ivar"),
DocStringParameterReference.ReferenceType.INSTANCE_VARIABLE));
ReferenceType.INSTANCE_VARIABLE));
result.addAll(returnTypes(element, docString, offset));
}
else if (docString instanceof SectionBasedDocString) {
final SectionBasedDocString sectioned = (SectionBasedDocString)docString;
result.addAll(referencesFromFields(expr, offset, sectioned.getParameterFields(), DocStringParameterReference.ReferenceType.PARAMETER));
result.addAll(referencesFromFields(expr, offset, sectioned.getKeywordArgumentFields(), DocStringParameterReference.ReferenceType.KEYWORD));
result.addAll(referencesFromFields(expr, offset, sectioned.getAttributeFields(), DocStringParameterReference.ReferenceType.INSTANCE_VARIABLE));
result.addAll(referencesFromFields(expr, offset, sectioned.getParameterFields(), ReferenceType.PARAMETER));
result.addAll(referencesFromFields(expr, offset, sectioned.getKeywordArgumentFields(), ReferenceType.KEYWORD));
result.addAll(referencesFromFields(expr, offset, sectioned.getAttributeFields(),
PyUtil.isTopLevel(element) ? ReferenceType.GLOBAL_VARIABLE : ReferenceType.INSTANCE_VARIABLE));
result.addAll(referencesFromFields(expr, offset, sectioned.getReturnFields(), null));
}
return result.toArray(new PsiReference[result.size()]);
@@ -105,7 +108,7 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
int offset,
@NotNull StructuredDocString docString,
@NotNull List<Substring> paramNames,
@NotNull DocStringParameterReference.ReferenceType refType) {
@NotNull ReferenceType refType) {
List<PsiReference> result = new ArrayList<PsiReference>();
for (Substring name : paramNames) {
final String s = name.toString();
@@ -113,7 +116,7 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
final TextRange range = name.getTextRange().shiftRight(offset);
result.add(new DocStringParameterReference(element, range, refType));
}
if (refType.equals(DocStringParameterReference.ReferenceType.PARAMETER_TYPE)) {
if (refType.equals(ReferenceType.PARAMETER_TYPE)) {
final Substring type = docString.getParamTypeSubstring(s);
if (type != null) {
result.addAll(parseTypeReferences(element, type, offset));
@@ -127,7 +130,7 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
private static List<PsiReference> referencesFromFields(@NotNull PyStringLiteralExpression element,
int offset,
@NotNull List<SectionBasedDocString.SectionField> fields,
@Nullable DocStringParameterReference.ReferenceType nameRefType) {
@Nullable ReferenceType nameRefType) {
final List<PsiReference> result = new ArrayList<PsiReference>();
for (SectionBasedDocString.SectionField field : fields) {
for (Substring nameSub: field.getNamesAsSubstrings()) {

View File

@@ -0,0 +1,10 @@
"""Attributes:
module_level_variable1 (int): Module level variables may be documented in
<ref>
either the ``Attributes`` section of the module docstring, or in an
inline docstring immediately following the variable.
"""
module_level_variable1 = 12345

View File

@@ -16,7 +16,7 @@
package com.jetbrains.python;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority;
import com.jetbrains.python.fixtures.PyResolveTestCase;
@@ -164,7 +164,7 @@ public class PyAddImportTest extends PyTestCase {
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), new Runnable() {
@Override
public void run() {
final PsiPolyVariantReference reference = PyResolveTestCase.findReferenceByMarker(myFixture.getFile());
final PsiReference reference = PyResolveTestCase.findReferenceByMarker(myFixture.getFile());
if (qualifier != null) {
AddImportHelper.addLocalFromImportStatement(reference.getElement(), qualifier, name);
}

View File

@@ -22,6 +22,8 @@ import com.jetbrains.python.fixtures.PyMultiFileResolveTestCase;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.*;
import java.util.List;
/**
* @author yole
*/
@@ -41,23 +43,23 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
}
public void testFromImport() {
ResolveResult[] results = doMultiResolve();
assertTrue(results.length == 2); // func and import stmt
PsiElement func_elt = results[0].getElement();
assertTrue("is PyFunction?", func_elt instanceof PyFunction);
assertEquals("named 'func'?", "func", ((PyFunction)func_elt).getName());
PsiElement import_elt = results[1].getElement();
assertTrue("is import?", import_elt instanceof PyImportElement);
List<PsiElement> results = doMultiResolve();
assertSize(2, results); // func and import stmt
PsiElement funcElt = results.get(0);
assertTrue("is PyFunction?", funcElt instanceof PyFunction);
assertEquals("named 'func'?", "func", ((PyFunction)funcElt).getName());
PsiElement importElt = results.get(1);
assertTrue("is import?", importElt instanceof PyImportElement);
}
public void testFromImportStar() {
ResolveResult[] results = doMultiResolve();
assertTrue(results.length == 2); // func and import-* stmt
PsiElement func_elt = results[0].getElement();
assertTrue("is PyFunction?", func_elt instanceof PyFunction);
assertEquals("named 'func'?", "func", ((PyFunction)func_elt).getName());
PsiElement import_elt = results[1].getElement();
assertTrue("is import?", import_elt instanceof PyStarImportElement);
List<PsiElement> results = doMultiResolve();
assertSize(2, results); // func and import-* stmt
PsiElement funcElt = results.get(0);
assertTrue("is PyFunction?", funcElt instanceof PyFunction);
assertEquals("named 'func'?", "func", ((PyFunction)funcElt).getName());
PsiElement importElt = results.get(1);
assertTrue("is import?", importElt instanceof PyStarImportElement);
}
public void testFromPackageImport() {
@@ -101,9 +103,9 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
}
public void testTransitiveImport() {
ResolveResult[] results = doMultiResolve();
assertTrue(results.length == 2); // func and import stmt
PsiElement elt = results[0].getElement();
List<PsiElement> results = doMultiResolve();
assertSize(2, results); // func and import stmt
PsiElement elt = results.get(0);
assertTrue("is target?", elt instanceof PyTargetExpression);
}
@@ -116,13 +118,13 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
}
public void testResolveInPkg() {
ResolveResult[] results = doMultiResolve();
assertTrue(results.length == 2); // func and import stmt
PsiElement func_elt = results[0].getElement();
assertTrue("is PyFunction?", func_elt instanceof PyFunction);
assertEquals("named 'token'?", "token", ((PyFunction)func_elt).getName());
PsiElement import_elt = results[1].getElement();
assertTrue("is import?", import_elt instanceof PyImportElement);
List<PsiElement> results = doMultiResolve();
assertSize(2, results); // func and import stmt
final PsiElement funcElt = results.get(0);
assertTrue("is PyFunction?", funcElt instanceof PyFunction);
assertEquals("named 'token'?", "token", ((PyFunction)funcElt).getName());
PsiElement importElt = results.get(1);
assertTrue("is import?", importElt instanceof PyImportElement);
}
public void testCircularImport() {

View File

@@ -528,6 +528,16 @@ public class PyResolveTest extends PyResolveTestCase {
});
}
// PY-16906
public void testGoogleDocstringModuleAttribute() {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
assertResolvesTo(PyTargetExpression.class, "module_level_variable1");
}
});
}
// PY-7541
public void testLoopToUpperReassignment() {
final PsiReference ref = findReferenceByMarker();

View File

@@ -20,9 +20,15 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonTestUtil;
import junit.framework.Assert;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
/**
* @author yole
@@ -36,7 +42,7 @@ public abstract class PyMultiFileResolveTestCase extends PyResolveTestCase {
}
protected PsiElement doResolve(PsiFile psiFile) {
final PsiPolyVariantReference ref = PyResolveTestCase.findReferenceByMarker(psiFile);
final PsiReference ref = PyResolveTestCase.findReferenceByMarker(psiFile);
final PsiManagerImpl psiManager = (PsiManagerImpl)myFixture.getPsiManager();
psiManager.setAssertOnFileLoadingFilter(new VirtualFileFilter() {
@Override
@@ -45,12 +51,16 @@ public abstract class PyMultiFileResolveTestCase extends PyResolveTestCase {
return fileType == PythonFileType.INSTANCE;
}
}, myTestRootDisposable);
final ResolveResult[] resolveResults = ref.multiResolve(false);
psiManager.setAssertOnFileLoadingFilter(VirtualFileFilter.NONE, myTestRootDisposable);
if (resolveResults.length == 0) {
return null;
final PsiElement result;
if (ref instanceof PsiPolyVariantReference) {
final ResolveResult[] resolveResults = ((PsiPolyVariantReference)ref).multiResolve(false);
result = resolveResults.length == 0 || !resolveResults[0].isValidResult() ? null : resolveResults[0].getElement();
}
return resolveResults[0].isValidResult() ? resolveResults[0].getElement() : null;
else {
result = ref.resolve();
}
psiManager.setAssertOnFileLoadingFilter(VirtualFileFilter.NONE, myTestRootDisposable);
return result;
}
@@ -79,9 +89,18 @@ public abstract class PyMultiFileResolveTestCase extends PyResolveTestCase {
return doResolve(prepareFile());
}
protected ResolveResult[] doMultiResolve() {
PsiFile psiFile = prepareFile();
final PsiPolyVariantReference ref = PyResolveTestCase.findReferenceByMarker(psiFile);
return ref.multiResolve(false);
@NotNull
protected List<PsiElement> doMultiResolve() {
final PsiFile psiFile = prepareFile();
final PsiReference ref = PyResolveTestCase.findReferenceByMarker(psiFile);
if (ref instanceof PsiPolyVariantReference) {
return ContainerUtil.map(((PsiPolyVariantReference)ref).multiResolve(false), new Function<ResolveResult, PsiElement>() {
@Override
public PsiElement fun(ResolveResult result) {
return result.getElement();
}
});
}
return Collections.singletonList(ref.resolve());
}
}

View File

@@ -131,9 +131,8 @@ public abstract class PyResolveTestCase extends PyTestCase {
}
@NotNull
public static PsiPolyVariantReference findReferenceByMarker(PsiFile psiFile) {
int offset = findMarkerOffset(psiFile);
final PsiPolyVariantReference ref = (PsiPolyVariantReference)psiFile.findReferenceAt(offset);
public static PsiReference findReferenceByMarker(PsiFile psiFile) {
final PsiReference ref = psiFile.findReferenceAt(findMarkerOffset(psiFile));
assertNotNull("<ref> in test file not found", ref);
return ref;
}