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-07-27 00:12:26 +03:00
committed by intellij-monorepo-bot
parent ce84c71a5c
commit cce0ef17d5
32 changed files with 460 additions and 20 deletions

View File

@@ -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"));
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -0,0 +1,7 @@
class MyClass:
"""Class description
Attributes:
at<caret>tr1: attr1 description
"""
attr1 = 1

View File

@@ -0,0 +1,7 @@
class MyClass:
"""Class description
Attributes:
bar: attr1 description
"""
bar = 1

View File

@@ -0,0 +1,10 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
at<caret>tr1: attr1 description
"""
attr1 = 1

View File

@@ -0,0 +1,10 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
bar: attr1 description
"""
bar = 1

View File

@@ -0,0 +1,12 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
at<caret>tr1: attribute description
"""
attr1 = 3

View File

@@ -0,0 +1,12 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
bar: attribute description
"""
bar = 3

View File

@@ -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

View File

@@ -0,0 +1,15 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
Class description
Parameters:
bar: attribute description
"""
attr1 = 3
def __init__(self, bar):
pass

View File

@@ -0,0 +1,8 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1

View File

@@ -0,0 +1,11 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1

View File

@@ -0,0 +1,11 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1
def __init__(self):
self.attr1 = 0

View File

@@ -0,0 +1,10 @@
class MyClass:
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
def __init__(self, attr1):
self.attr1 = attr1

View File

@@ -0,0 +1,10 @@
from typing import NamedTuple
class SomeObject(NamedTuple):
"""Class description
Attributes:
attr1: attr1 description
<ref>
"""
attr1 = 1

View File

@@ -0,0 +1,10 @@
class MyClass:
"""Class description
Attributes
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1

View File

@@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Attributes
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1

View File

@@ -0,0 +1,12 @@
from typing import NamedTuple
class SomeObject(NamedTuple):
"""Class description
Attributes
---------
attr1:
<ref>
attr1 description
"""
attr1 = 1

View File

@@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Parameters
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1

View File

@@ -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

View File

@@ -0,0 +1,16 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""Class description
Parameters
----------
attr1:
<ref>
attr1 description
"""
attr1 = 1
def __init__(self):
pass

View File

@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:cvar var1: var1 description
<ref>
"""
var1 = 1
def __init__(self, var1):
pass

View File

@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:cvar var1: var1 description
<ref>
"""
var1 = 1
def __init__(self):
self.var1 = 0

View File

@@ -0,0 +1,9 @@
from dataclasses import dataclass
@dataclass
class MyClass:
"""
:ivar var1: description
<ref>
"""
var1 = 0

View File

@@ -0,0 +1,9 @@
class MyClass:
"""Class description
:ivar var1: var1 description
<ref>
"""
def __init__(self, var1):
self.var1 = var1

View File

@@ -0,0 +1,10 @@
class MyClass:
"""
:param p: description
:type p: int
<ref>
"""
p = 0
def __init__(self, p):
pass

View File

@@ -0,0 +1,9 @@
class MyClass:
"""
:param p: description
:type p: int
<ref>
"""
def __init__(self, p):
self.p = p

View File

@@ -0,0 +1,8 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
var1 = 1

View File

@@ -0,0 +1,11 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
var1 = 1
def __init__(self):
self.var1 = 0

View File

@@ -0,0 +1,9 @@
class MyClass:
"""Class description
:var var1: var1 description
<ref>
"""
def __init__(self, var1):
self.var1 = var1

View File

@@ -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();