PY-16760, PY-28549, PY-35743, PY-55609, PY-46654: Correct resolve of class attributes in docstrings

Previously in docstrings we incorrectly recognized references in 'Attributes' section only as instance attribute references. It led to false positive 'Unresolved reference' on class attributes references in docstrings and wrong resolve when using class and instance attributes with the same names. Now we use ReferenceType.VARIABLE to identify both class and instance attribute references in 'Attributes' section and then resolve them with priority of instance attributes. Also fixed wrong resolve of attributes references to constructor parameters.

GitOrigin-RevId: ea10bfb092472c0ab14b77f06efd93093cfcd684
This commit is contained in:
Irina.Fediaeva
2022-08-25 21:11:57 +00:00
committed by intellij-monorepo-bot
parent ce84c71a5c
commit cce0ef17d5
32 changed files with 460 additions and 20 deletions
@@ -1540,4 +1540,122 @@ public abstract class PyCommonResolveTest extends PyCommonResolveTestCase {
assertEquals("ModuleType", target.getContainingClass().getName());
assertEquals("types.pyi", target.getContainingFile().getName());
}
// PY-16760
public void testGoogleDocstringAttributeNameResolvesToClassAttribute() {
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-16760
public void testGoogleDocstringAttributeNameResolvesToInstanceAttributeOverClassAttribute() {
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "attr1");
assertTrue(PyUtil.isInstanceAttribute(definition));
});
}
// PY-16760
public void testGoogleDocstringAttributeNameResolvesToInstanceAttributeOverInitParameter() {
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-16760
public void testNumpyDocstringAttributeNameResolvesToClassAttribute() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-28549
public void testGoogleDocstringAttributeNameResolvesToDataclassClassAttribute() {
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-28549
public void testNumpyDocstringAttributeNameResolvesToDataclassClassAttribute() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-28549
public void testNumpyDocstringParameterNameResolvesToDataclassClassAttributeWithoutInit() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-28549
public void testNumpyDocstringParameterNameUnresolvedWithInit() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertUnresolved());
}
// PY-28549
public void testNumpyDocstringParameterNameResolvesToDataclassInitParameterOverClassAttribute() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyNamedParameter.class, "attr1"));
}
// PY-35743
public void testGoogleDocstringAttributeNameResolvesToNamedTupleClassAttribute() {
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-35743
public void testNumpyDocstringAttributeNameResolvesToNamedTupleClassAttribute() {
runWithDocStringFormat(DocStringFormat.NUMPY, () -> assertResolvesTo(PyTargetExpression.class, "attr1"));
}
// PY-55609
public void testRestDocstringVarNameResolvesToInstanceAttributeOverInitParameter() {
runWithDocStringFormat(DocStringFormat.REST, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "var1");
assertTrue(PyUtil.isInstanceAttribute(definition));
});
}
// PY-55609
public void testRestDocstringVarNameResolvesToInstanceAttributeOverClassAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "var1");
assertTrue(PyUtil.isInstanceAttribute(definition));
});
}
// PY-55609
public void testRestDocstringIvarNameResolvesToInstanceAttributeOverInitParameter() {
runWithDocStringFormat(DocStringFormat.REST, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "var1");
assertTrue(PyUtil.isInstanceAttribute(definition));
});
}
// PY-55609
public void testRestDocstringCvarNameResolvesToClassAttributeOverInitParameter() {
runWithDocStringFormat(DocStringFormat.REST, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "var1");
assertTrue(PyUtil.isClassAttribute(definition));
});
}
// PY-55609
public void testRestDocstringCvarNameResolvesToClassAttributeOverInstanceAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> {
PyTargetExpression definition = assertResolvesTo(PyTargetExpression.class, "var1");
assertTrue(PyUtil.isClassAttribute(definition));
});
}
// PY-55609
public void testRestDocstringVarNameResolvesToClassAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> assertResolvesTo(PyTargetExpression.class, "var1"));
}
// PY-55609
public void testRestDocstringTypeOwnerNameResolvesToInitParameterOverClassAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> assertResolvesTo(PyNamedParameter.class, "p"));
}
// PY-55609
public void testRestDocstringTypeOwnerNameResolvesToInitParameterOverInstanceAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> assertResolvesTo(PyNamedParameter.class, "p"));
}
// PY-46654
public void testRestDocstringIvarNameResolvesToDataClassAttribute() {
runWithDocStringFormat(DocStringFormat.REST, () -> assertResolvesTo(PyTargetExpression.class, "var1"));
}
}
@@ -38,26 +38,23 @@ public class DocStringParameterReference extends PsiReferenceBase<PyStringLitera
return resolveParameter((PyFunction)owner);
}
if (owner instanceof PyClass) {
final PyFunction init = ((PyClass)owner).findMethodByName(PyNames.INIT, false, null);
if (init != null) {
PyElement element = resolveParameter(init);
if (element == null && (myType.equals(ReferenceType.CLASS_VARIABLE) || myType.equals(ReferenceType.PARAMETER_TYPE))) {
element = resolveClassVariable((PyClass)owner);
}
if (element == null && (myType.equals(ReferenceType.INSTANCE_VARIABLE) || myType.equals(ReferenceType.PARAMETER_TYPE))) {
element = resolveInstanceVariable((PyClass)owner);
}
return element;
PyClass pyClass = (PyClass)owner;
final PyFunction init = pyClass.findMethodByName(PyNames.INIT, false, null);
if (myType == ReferenceType.PARAMETER) {
return init != null ? resolveParameter(init) : resolveClassVariable(pyClass);
}
else {
PyElement element = null;
if (myType.equals(ReferenceType.CLASS_VARIABLE) || myType.equals(ReferenceType.PARAMETER_TYPE)) {
element = resolveClassVariable((PyClass)owner);
if (myType == ReferenceType.INSTANCE_VARIABLE || myType == ReferenceType.VARIABLE || myType == ReferenceType.PARAMETER_TYPE) {
if (myType == ReferenceType.PARAMETER_TYPE && init != null) {
PyNamedParameter parameter = resolveParameter(init);
if (parameter != null) {
return parameter;
}
}
if (element == null && (myType.equals(ReferenceType.INSTANCE_VARIABLE) || myType.equals(ReferenceType.PARAMETER_TYPE))) {
element = resolveInstanceVariable((PyClass)owner);
}
return element;
PyElement instanceAttr = resolveInstanceVariable(pyClass);
return instanceAttr != null ? instanceAttr : resolveClassVariable(pyClass);
}
if (myType == ReferenceType.CLASS_VARIABLE) {
return resolveClassVariable(pyClass);
}
}
if (owner instanceof PyFile && myType == ReferenceType.GLOBAL_VARIABLE) {
@@ -87,8 +87,8 @@ public class DocStringReferenceProvider extends PsiReferenceProvider {
final SectionBasedDocString sectioned = (SectionBasedDocString)docString;
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.getAttributeFields(),
PyUtil.isTopLevel(element) ? ReferenceType.GLOBAL_VARIABLE : ReferenceType.VARIABLE));
result.addAll(referencesFromFields(expr, offset, sectioned.getReturnFields(), null));
}
return result.toArray(PsiReference.EMPTY_ARRAY);
@@ -0,0 +1,7 @@
class MyClass:
"""Class description
Attributes:
at<caret>tr1: attr1 description
"""
attr1 = 1
@@ -0,0 +1,7 @@
class MyClass:
"""Class description
Attributes:
bar: attr1 description
"""
bar = 1
@@ -0,0 +1,10 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
at<caret>tr1: attr1 description
"""
attr1 = 1
@@ -0,0 +1,10 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
bar: attr1 description
"""
bar = 1
@@ -0,0 +1,12 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
at<caret>tr1: attribute description
"""
attr1 = 3
@@ -0,0 +1,12 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
bar: attribute description
"""
bar = 3
@@ -0,0 +1,15 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
at<caret>tr1: attribute description
"""
attr1 = 3
def __init__(self, attr1):
pass
@@ -0,0 +1,15 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
bar: attribute description
"""
attr1 = 3
def __init__(self, bar):
pass
@@ -0,0 +1,8 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1
@@ -0,0 +1,11 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1
@@ -0,0 +1,11 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1
def __init__(self):
self.attr1 = 0
@@ -0,0 +1,10 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
def __init__(self, attr1):
self.attr1 = attr1
@@ -0,0 +1,10 @@
from typing import NamedTuple
class SomeObject(NamedTuple):
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1
@@ -0,0 +1,10 @@
class MyClass:
"""Class description
Attributes
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
@@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
@@ -0,0 +1,12 @@
from typing import NamedTuple
class SomeObject(NamedTuple):
"""Class description
Attributes
---------
attr1:
<ref>
attr1 description
"""
attr1 = 1
@@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Parameters
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
@@ -0,0 +1,16 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Parameters
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
def __init__(self, attr1):
pass
@@ -0,0 +1,16 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Parameters
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
def __init__(self):
pass
@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:cvar var1: var1 description
<ref>
"""
var1 = 1
def __init__(self, var1):
pass
@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:cvar var1: var1 description
<ref>
"""
var1 = 1
def __init__(self):
self.var1 = 0
@@ -0,0 +1,9 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
:ivar var1: description
<ref>
"""
var1 = 0
@@ -0,0 +1,9 @@
class MyClass:
"""Class description
:ivar var1: var1 description
<ref>
"""
def __init__(self, var1):
self.var1 = var1
@@ -0,0 +1,10 @@
class MyClass:
"""
:param p: description
:type p: int
<ref>
"""
p = 0
def __init__(self, p):
pass
@@ -0,0 +1,9 @@
class MyClass:
"""
:param p: description
:type p: int
<ref>
"""
def __init__(self, p):
self.p = p
@@ -0,0 +1,8 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
var1 = 1
@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
var1 = 1
def __init__(self):
self.var1 = 0
@@ -0,0 +1,9 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
def __init__(self, var1):
self.var1 = var1
@@ -224,6 +224,26 @@ public class PyRenameTest extends PyTestCase {
renameWithDocStringFormat(DocStringFormat.NUMPY, "bar");
}
// PY-16760
public void testGoogleDocstringAttributeRenamesWithClassAttribute() {
renameWithDocStringFormat(DocStringFormat.GOOGLE, "bar");
}
// PY-28549
public void testGoogleDocstringAttributeRenamesWithDataclassClassAttribute() {
renameWithDocStringFormat(DocStringFormat.GOOGLE, "bar");
}
// PY-28549
public void testGoogleDocstringDataClassParameterRenamesWithClassAttribute() {
renameWithDocStringFormat(DocStringFormat.GOOGLE, "bar");
}
// PY-28549
public void testGoogleDocstringDataClassParameterRenamesWithInitParameterOverClassAttribute() {
renameWithDocStringFormat(DocStringFormat.GOOGLE, "bar");
}
// PY-2748
public void testFormatStringDictLiteral() {
doUnsupportedOperationTest();