diff --git a/python/resources/inspectionDescriptions/PyRedeclarationInspection.html b/python/resources/inspectionDescriptions/PyRedeclarationInspection.html index 0175bc40b6a6..7ef9296a01d2 100644 --- a/python/resources/inspectionDescriptions/PyRedeclarationInspection.html +++ b/python/resources/inspectionDescriptions/PyRedeclarationInspection.html @@ -5,6 +5,5 @@ This inspection detects redeclarations of names without being used in between, l x = 2 It applies to function and class declarations, and assignments.
-Currently it works in a very limited way and is off by default. diff --git a/python/src/META-INF/python-plugin-core.xml b/python/src/META-INF/python-plugin-core.xml index 681304de2d11..85f2357002d1 100644 --- a/python/src/META-INF/python-plugin-core.xml +++ b/python/src/META-INF/python-plugin-core.xml @@ -260,7 +260,7 @@ - + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 454067904cdc..6d9d5854c1c0 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -280,7 +280,7 @@ INSP.decorator.receives.unexpected.builtin=This decorator will not receive a cal # PyRedeclarationInspection INSP.NAME.redeclaration=Names redeclared without usage -INSP.shadows.same.named.$0.above=Shadows same-named {0} above +INSP.shadows.same.named.$0.above=Shadows a {0} with the same name defined above # PyUnresolvedReferencesInspection INSP.NAME.unresolved.refs=Unresolved references diff --git a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java index e566674c8e54..77df179260a8 100644 --- a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java @@ -1,27 +1,30 @@ package com.jetbrains.python.inspections; +import com.intellij.codeInsight.controlflow.ControlFlowUtil; +import com.intellij.codeInsight.controlflow.Instruction; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiNameIdentifierOwner; +import com.intellij.util.Function; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.PyTargetExpression; -import com.jetbrains.python.psi.resolve.PyResolveUtil; -import com.jetbrains.python.psi.resolve.ResolveProcessor; +import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; +import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Annotates declarations that unconditionally ovverride other without these being used. - * E.g.: x = 1; x = 2 - * User: dcheryasov - * Date: Nov 14, 2008 + * Annotates declarations that unconditionally override others without these being used. + * + * @author dcheryasov + * @author vlan + * + * TODO: Add a rename quick-fix */ public class PyRedeclarationInspection extends PyInspection { @Nls @@ -30,10 +33,6 @@ public class PyRedeclarationInspection extends PyInspection { return PyBundle.message("INSP.NAME.redeclaration"); } - @Override - public boolean isEnabledByDefault() { - return false; // too immature yet - } @NotNull @Override @@ -43,54 +42,83 @@ public class PyRedeclarationInspection extends PyInspection { return new Visitor(holder, session); } - public static class Visitor extends PyInspectionVisitor { + private static class Visitor extends PyInspectionVisitor { public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { super(holder, session); } - // TODO: This function is a shame; replace with a proper interface. - private static String _getKind(PsiElement elt) { - if (elt instanceof PyFunction) return PyBundle.message("GNAME.function"); - if (elt instanceof PyClass) return PyBundle.message("GNAME.class"); - if (elt instanceof PyTargetExpression) return PyBundle.message("GNAME.var"); - return PyBundle.message("GNAME.item"); - } - - private void _checkAbove(PyElement node) { - String name = node.getName(); - if (name != null) { - ResolveProcessor proc = new ResolveProcessor(node.getName()); - PyResolveUtil.treeCrawlUp(proc, node); - PsiElement found = proc.getResult(); - // TODO: check if the redefined name is used somehow - if (found != null && ! (found instanceof PyTargetExpression)) { - final ASTNode identifier = node.getNode().findChildByType(PyTokenTypes.IDENTIFIER); - if (identifier != null) { - registerProblem( - identifier.getPsi(), - PyBundle.message("INSP.shadows.same.named.$0.above", _getKind(found)) - ); - } - //registerProblem(prev.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), "Overridden by same-named " + kind + " below"); - } - } - } - - // TODO: add a rename quickfix - @Override public void visitPyFunction(final PyFunction node) { - _checkAbove(node); + if (!isDecorated(node)) { + processElement(node); + } } @Override public void visitPyTargetExpression(final PyTargetExpression node) { - _checkAbove(node); + final ScopeOwner owner = ScopeUtil.getScopeOwner(node); + if (owner instanceof PyFile || owner instanceof PyClass) { + processElement(node); + } } @Override public void visitPyClass(final PyClass node) { - _checkAbove(node); + if (!isDecorated(node)) { + processElement(node); + } + } + + private static boolean isDecorated(@NotNull PyDecoratable node) { + boolean isDecorated = false; + final PyDecoratorList decoratorList = node.getDecoratorList(); + if (decoratorList != null) { + final PyDecorator[] decorators = decoratorList.getDecorators(); + if (decorators.length > 0) { + isDecorated = true; + } + } + return isDecorated; + } + + private void processElement(@NotNull final PsiNameIdentifierOwner element) { + final String name = element.getName(); + final ScopeOwner owner = ScopeUtil.getScopeOwner(element); + if (owner != null && name != null) { + final Instruction[] instructions = ControlFlowCache.getControlFlow(owner).getInstructions(); + final int startInstruction = ControlFlowUtil.findInstructionNumberByElement(instructions, element); + ControlFlowUtil.iteratePrev(startInstruction, instructions, new Function() { + @Override + public ControlFlowUtil.Operation fun(Instruction instruction) { + if (instruction instanceof ReadWriteInstruction && instruction.num() != startInstruction) { + final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)instruction; + if (name.equals(rwInstruction.getName())) { + if (rwInstruction.getAccess().isWriteAccess()) { + final PsiElement shadowed = rwInstruction.getElement(); + final PsiElement identifier = element.getNameIdentifier(); + registerProblem(identifier != null ? identifier : element, + PyBundle.message("INSP.shadows.same.named.$0.above", getKind(shadowed))); + } + return ControlFlowUtil.Operation.BREAK; + } + } + return ControlFlowUtil.Operation.NEXT; + } + }); + } + } + } + + @NotNull + private static String getKind(@Nullable PsiElement element) { + if (element instanceof PyFunction) { + return PyBundle.message("GNAME.function"); + } + else if (element instanceof PyClass) { + return PyBundle.message("GNAME.class"); + } + else { + return PyBundle.message("GNAME.var"); } } } diff --git a/python/testData/inspections/PyRedeclarationInspection/expected.xml b/python/testData/inspections/PyRedeclarationInspection/expected.xml index 3adaf3cadd08..538b4a439a17 100644 --- a/python/testData/inspections/PyRedeclarationInspection/expected.xml +++ b/python/testData/inspections/PyRedeclarationInspection/expected.xml @@ -3,23 +3,21 @@ dupes.py 2 - Shadows same-named class above + Shadows a class with the same name defined above dupes.py 5 - Shadows same-named function above + Shadows a function with the same name defined above dupes.py 8 - Shadows same-named function above + Shadows a function with the same name defined above -