mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-25263 Attribute initialization via property set implemented
This commit is contained in:
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMoveAttributeToInitQuickFix;
|
||||
import com.jetbrains.python.psi.Property;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
@@ -29,10 +30,12 @@ import com.jetbrains.python.psi.PyTargetExpression;
|
||||
import com.jetbrains.python.psi.impl.PyClassImpl;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.testing.PythonUnitTestUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -40,8 +43,7 @@ import java.util.Map;
|
||||
/**
|
||||
* User: ktisha
|
||||
*
|
||||
* Inspection to detect situations, where instance attribute
|
||||
* defined outside __init__ function
|
||||
* Inspection to detect situations, where instance attribute is defined outside __init__ function.
|
||||
*/
|
||||
public class PyAttributeOutsideInitInspection extends PyInspection {
|
||||
@Nls
|
||||
@@ -74,46 +76,84 @@ public class PyAttributeOutsideInitInspection extends PyInspection {
|
||||
if (!isApplicable(containingClass, myTypeEvalContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final PyFunction.Modifier modifier = node.getModifier();
|
||||
if (modifier != null) return;
|
||||
final List<PyTargetExpression> classAttributes = containingClass.getClassAttributes();
|
||||
|
||||
Map<String, PyTargetExpression> attributesInInit = new HashMap<>();
|
||||
for (PyTargetExpression classAttr : classAttributes) {
|
||||
attributesInInit.put(classAttr.getName(), classAttr);
|
||||
if (node.getModifier() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<PyTargetExpression> classAttributes = containingClass.getClassAttributes();
|
||||
final Map<String, Property> properties = containingClass.getProperties();
|
||||
final Map<String, PyTargetExpression> attributesInInit = new HashMap<>();
|
||||
|
||||
StreamEx.of(classAttributes)
|
||||
.filter(attribute -> !properties.containsKey(attribute.getName()))
|
||||
.forEach(attribute -> attributesInInit.put(attribute.getName(), attribute));
|
||||
|
||||
final PyFunction initMethod = containingClass.findMethodByName(PyNames.INIT, false, null);
|
||||
if (initMethod != null) {
|
||||
PyClassImpl.collectInstanceAttributes(initMethod, attributesInInit);
|
||||
}
|
||||
for (PyClass superClass : containingClass.getAncestorClasses(myTypeEvalContext)) {
|
||||
final PyFunction superInit = superClass.findMethodByName(PyNames.INIT, false, null);
|
||||
if (superInit != null)
|
||||
if (superInit != null) {
|
||||
PyClassImpl.collectInstanceAttributes(superInit, attributesInInit);
|
||||
}
|
||||
|
||||
for (PyTargetExpression classAttr : superClass.getClassAttributes()) {
|
||||
attributesInInit.put(classAttr.getName(), classAttr);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, PyTargetExpression> attributes = new HashMap<>();
|
||||
final Map<String, PyTargetExpression> attributes = new HashMap<>();
|
||||
PyClassImpl.collectInstanceAttributes(node, attributes);
|
||||
|
||||
for (Map.Entry<String, PyTargetExpression> attribute : attributes.entrySet()) {
|
||||
String attributeName = attribute.getKey();
|
||||
for (PyTargetExpression attribute : attributes.values()) {
|
||||
final String attributeName = attribute.getName();
|
||||
if (attributeName == null) continue;
|
||||
final Property property = containingClass.findProperty(attributeName, true, null);
|
||||
if (!attributesInInit.containsKey(attributeName) && property == null) {
|
||||
registerProblem(attribute.getValue(), PyBundle.message("INSP.attribute.$0.outside.init", attributeName),
|
||||
if (!attributesInInit.containsKey(attributeName) &&
|
||||
!isDefinedByProperty(attribute, properties.values(), attributesInInit)) {
|
||||
registerProblem(attribute, PyBundle.message("INSP.attribute.$0.outside.init", attributeName),
|
||||
new PyMoveAttributeToInitQuickFix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isApplicable(@NotNull PyClass containingClass, @NotNull TypeEvalContext context) {
|
||||
return !PythonUnitTestUtil.isTestClass(containingClass, ThreeState.UNSURE, context) && !containingClass.isSubclass("django.db.models.base.Model", context);
|
||||
private static boolean isDefinedByProperty(@NotNull PyTargetExpression attribute,
|
||||
@NotNull Collection<Property> properties,
|
||||
@NotNull Map<String, PyTargetExpression> attributesInInit) {
|
||||
return StreamEx.of(properties)
|
||||
.filter(it -> isSetBy(attribute, it))
|
||||
.anyMatch(it -> attributesInInit.containsKey(it.getName()));
|
||||
}
|
||||
|
||||
private static boolean isApplicable(@NotNull PyClass containingClass, @NotNull TypeEvalContext context) {
|
||||
return !PythonUnitTestUtil.isTestClass(containingClass, ThreeState.UNSURE, context) &&
|
||||
!containingClass.isSubclass("django.db.models.base.Model", context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Collection<PyTargetExpression> getSetterTargetExpressions(@NotNull Property property) {
|
||||
if (!property.getSetter().isDefined() || property.getSetter().value() == null) {
|
||||
return null;
|
||||
}
|
||||
final PyFunction setter = property.getSetter().value().asMethod();
|
||||
if (setter == null) {
|
||||
return null;
|
||||
}
|
||||
return ControlFlowCache.getScope(setter).getTargetExpressions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@code property} sets the {@code attribute} provided.
|
||||
*/
|
||||
private static boolean isSetBy(@NotNull PyTargetExpression attribute, @NotNull Property property) {
|
||||
final Collection<PyTargetExpression> propertyTargetExpressions = getSetterTargetExpressions(property);
|
||||
return propertyTargetExpressions != null &&
|
||||
attribute.getName() != null &&
|
||||
StreamEx.of(propertyTargetExpressions)
|
||||
.map(targetExpression -> targetExpression.getName())
|
||||
.nonNull()
|
||||
.anyMatch(name -> name.equals(attribute.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
class C(object):
|
||||
def __init__(self, value):
|
||||
self.x = value
|
||||
|
||||
def getx(self):
|
||||
return self._x
|
||||
|
||||
def setx(self, value):
|
||||
self._x = value # False positive for self._x
|
||||
|
||||
x = property(getx, setx, doc="The 'x' property.")
|
||||
@@ -0,0 +1,11 @@
|
||||
class C(object):
|
||||
def __init__(self):
|
||||
self.x = None
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
return self._x
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
self._x = value # False positive for self._x
|
||||
@@ -0,0 +1,11 @@
|
||||
class C(object):
|
||||
def __init__(self, value):
|
||||
pass
|
||||
|
||||
def getx(self):
|
||||
return self._x
|
||||
|
||||
def setx(self, value):
|
||||
<weak_warning descr="Instance attribute _x defined outside __init__">self._x</weak_warning> = value
|
||||
|
||||
x = property(getx, setx, doc="The 'x' property.")
|
||||
+15
@@ -70,6 +70,21 @@ public class PyAttributeOutsideInitInspectionTest extends PyInspectionTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25263
|
||||
public void testProperty() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25263
|
||||
public void testPropertyAnnotation() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-25263
|
||||
public void testPropertyNotSetInInit() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user