From de8cc97b6709f386677d3df78e68db74c08b0631 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 6 Sep 2013 17:20:29 +0400 Subject: [PATCH] Split shadowing inspection into built-in and names from outer scopes shadowing The reason is to have compatible shared inspections profile files for various versions of the IDE. --- .../PyShadowingBuiltinsInspection.html | 5 + .../PyShadowingNamesInspection.html | 2 +- python/src/META-INF/python-plugin-core.xml | 3 +- .../PyShadowingBuiltinsInspection.java | 144 ++++++++++++++++++ .../PyShadowingNamesInspection.java | 139 ++++------------- .../PyShadowingBuiltinsInspection/test.py | 50 ++++++ .../PyShadowingNamesInspection/test.py | 54 +------ .../com/jetbrains/python/PyQuickFixTest.java | 2 +- .../python/PythonInspectionsTest.java | 4 + 9 files changed, 234 insertions(+), 169 deletions(-) create mode 100644 python/resources/inspectionDescriptions/PyShadowingBuiltinsInspection.html create mode 100644 python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java create mode 100644 python/testData/inspections/PyShadowingBuiltinsInspection/test.py diff --git a/python/resources/inspectionDescriptions/PyShadowingBuiltinsInspection.html b/python/resources/inspectionDescriptions/PyShadowingBuiltinsInspection.html new file mode 100644 index 000000000000..f25c27847c4f --- /dev/null +++ b/python/resources/inspectionDescriptions/PyShadowingBuiltinsInspection.html @@ -0,0 +1,5 @@ + + +This inspection detects shadowing built-in names, such as 'len' or 'list'. + + \ No newline at end of file diff --git a/python/resources/inspectionDescriptions/PyShadowingNamesInspection.html b/python/resources/inspectionDescriptions/PyShadowingNamesInspection.html index 3f32f07ca884..dabfaf0b94a1 100644 --- a/python/resources/inspectionDescriptions/PyShadowingNamesInspection.html +++ b/python/resources/inspectionDescriptions/PyShadowingNamesInspection.html @@ -1,5 +1,5 @@ -This inspection detects shadowing names defined in outer scopes, including built-in names. +This inspection detects shadowing names defined in outer scopes. \ No newline at end of file diff --git a/python/src/META-INF/python-plugin-core.xml b/python/src/META-INF/python-plugin-core.xml index 12f60d8d6362..8858ffbac17e 100644 --- a/python/src/META-INF/python-plugin-core.xml +++ b/python/src/META-INF/python-plugin-core.xml @@ -323,7 +323,8 @@ - + + diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java new file mode 100644 index 000000000000..94b06b54e474 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java @@ -0,0 +1,144 @@ +package com.jetbrains.python.inspections; + +import com.google.common.collect.ImmutableSet; +import com.intellij.codeInsight.intention.LowPriorityAction; +import com.intellij.codeInspection.*; +import com.intellij.codeInspection.ui.ListEditForm; +import com.intellij.openapi.project.Project; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiNameIdentifierOwner; +import com.intellij.util.Consumer; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Warns about shadowing built-in names. + * + * @author vlan + */ +public class PyShadowingBuiltinsInspection extends PyInspection { + // Persistent settings + public List ignoredNames = new ArrayList(); + + @NotNull + @Override + public String getDisplayName() { + return "Shadowing built-ins"; + } + + @Override + public JComponent createOptionsPanel() { + final ListEditForm form = new ListEditForm("Ignore built-ins", ignoredNames); + return form.getContentPanel(); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + return new Visitor(holder, session, ignoredNames); + } + + private static class Visitor extends PyInspectionVisitor { + private final Set myIgnoredNames; + + public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session, @NotNull Collection ignoredNames) { + super(holder, session); + myIgnoredNames = ImmutableSet.copyOf(ignoredNames); + } + + @Override + public void visitPyClass(@NotNull PyClass node) { + processElement(node); + } + + @Override + public void visitPyFunction(@NotNull PyFunction node) { + processElement(node); + } + + @Override + public void visitPyNamedParameter(@NotNull PyNamedParameter node) { + processElement(node); + } + + @Override + public void visitPyTargetExpression(@NotNull PyTargetExpression node) { + if (node.getQualifier() == null) { + processElement(node); + } + } + + private void processElement(@NotNull PsiNameIdentifierOwner element) { + final ScopeOwner owner = ScopeUtil.getScopeOwner(element); + if (owner instanceof PyClass) { + return; + } + final String name = element.getName(); + if (name != null && !myIgnoredNames.contains(name)) { + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element); + final PsiElement builtin = builtinCache.getByName(name); + if (builtin != null && !PyUtil.inSameFile(builtin, element)) { + final PsiElement identifier = element.getNameIdentifier(); + final PsiElement problemElement = identifier != null ? identifier : element; + registerProblem(problemElement, String.format("Shadows built-in name '%s'", name), + ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix(), new PyIgnoreBuiltinQuickFix(name)); + } + } + } + + private static class PyIgnoreBuiltinQuickFix implements LocalQuickFix, LowPriorityAction { + @NotNull private final String myName; + + private PyIgnoreBuiltinQuickFix(@NotNull String name) { + myName = name; + } + + @NotNull + @Override + public String getName() { + return getFamilyName() + " \"" + myName + "\""; + } + + @NotNull + @Override + public String getFamilyName() { + return "Ignore shadowed built-in name"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (element != null) { + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + profile.modifyProfile(new Consumer() { + @Override + public void consume(ModifiableModel model) { + final String toolName = PyShadowingBuiltinsInspection.class.getSimpleName(); + final PyShadowingBuiltinsInspection inspection = (PyShadowingBuiltinsInspection)model.getUnwrappedTool(toolName, element); + if (inspection != null) { + if (!inspection.ignoredNames.contains(myName)) { + inspection.ignoredNames.add(myName); + } + } + } + }); + } + } + } + } +} + diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java index c19945da06df..aad248db7c95 100644 --- a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java @@ -1,52 +1,32 @@ package com.jetbrains.python.inspections; -import com.google.common.collect.ImmutableSet; -import com.intellij.codeInsight.intention.LowPriorityAction; -import com.intellij.codeInspection.*; -import com.intellij.codeInspection.ui.ListEditForm; -import com.intellij.openapi.project.Project; -import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiNameIdentifierOwner; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Consumer; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.ResolveProcessor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; - /** * Warns about shadowing names defined in outer scopes. * * @author vlan */ public class PyShadowingNamesInspection extends PyInspection { - // Persistent settings - public List ignoredNames = new ArrayList(); - @NotNull @Override public String getDisplayName() { - return "Shadowing names"; - } - - @Override - public JComponent createOptionsPanel() { - final ListEditForm form = new ListEditForm("Ignore built-ins", ignoredNames); - return form.getContentPanel(); + return "Shadowing names from outer scopes"; } @NotNull @@ -54,15 +34,12 @@ public class PyShadowingNamesInspection extends PyInspection { public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { - return new Visitor(holder, session, ignoredNames); + return new Visitor(holder, session); } private static class Visitor extends PyInspectionVisitor { - private final Set myIgnoredNames; - - public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session, @NotNull Collection ignoredNames) { + public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { super(holder, session); - myIgnoredNames = ImmutableSet.copyOf(ignoredNames); } @Override @@ -92,99 +69,35 @@ public class PyShadowingNamesInspection extends PyInspection { private void processElement(@NotNull PsiNameIdentifierOwner element) { final ScopeOwner owner = ScopeUtil.getScopeOwner(element); - // Class-level names are rarely accessed inside at the class level, usually they are accessed as attributes if (owner instanceof PyClass) { return; } final String name = element.getName(); - if (name != null && !myIgnoredNames.contains(name)) { + if (name != null) { final PsiElement identifier = element.getNameIdentifier(); final PsiElement problemElement = identifier != null ? identifier : element; - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element); - final PsiElement builtin = builtinCache.getByName(name); - if (builtin != null) { - processBuiltin(element, name, problemElement, builtin); + if ("_".equals(name)) { + return; } - else { - processOuterScope(element, name, problemElement); - } - } - } - - private void processBuiltin(@NotNull PsiNameIdentifierOwner element, - @NotNull String name, - @NotNull PsiElement problemElement, - @NotNull PsiElement builtin) { - if (!PyUtil.inSameFile(builtin, element)) { - registerProblem(problemElement, String.format("Shadows built-in name '%s'", name), - ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix(), - new PyIgnoreBuiltinQuickFix(name)); - } - } - - private void processOuterScope(@NotNull PsiNameIdentifierOwner element, @NotNull String name, @NotNull PsiElement problemElement) { - if ("_".equals(name)) { - return; - } - final ScopeOwner owner = ScopeUtil.getScopeOwner(element); - if (owner != null) { - final ScopeOwner nextOwner = ScopeUtil.getScopeOwner(owner); - if (nextOwner != null) { - final ResolveProcessor processor = new ResolveProcessor(name); - PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null); - final PsiElement resolved = processor.getResult(); - if (resolved != null) { - final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(resolved, PyComprehensionElement.class); - if (comprehension != null && PyUtil.isOwnScopeComprehension(comprehension)) { - return; - } - final Scope scope = ControlFlowCache.getScope(owner); - if (scope.isGlobal(name) || scope.isNonlocal(name)) { - return; - } - registerProblem(problemElement, String.format("Shadows name '%s' from outer scope", name), - ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix()); - } - } - } - } - - private static class PyIgnoreBuiltinQuickFix implements LocalQuickFix, LowPriorityAction { - @NotNull private final String myName; - - private PyIgnoreBuiltinQuickFix(@NotNull String name) { - myName = name; - } - - @NotNull - @Override - public String getName() { - return getFamilyName() + " \"" + myName + "\""; - } - - @NotNull - @Override - public String getFamilyName() { - return "Ignore shadowed built-in name"; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiElement element = descriptor.getPsiElement(); - if (element != null) { - final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); - profile.modifyProfile(new Consumer() { - @Override - public void consume(ModifiableModel model) { - final String toolName = PyShadowingNamesInspection.class.getSimpleName(); - final PyShadowingNamesInspection inspection = (PyShadowingNamesInspection)model.getUnwrappedTool(toolName, element); - if (inspection != null) { - if (!inspection.ignoredNames.contains(myName)) { - inspection.ignoredNames.add(myName); - } + if (owner != null) { + final ScopeOwner nextOwner = ScopeUtil.getScopeOwner(owner); + if (nextOwner != null) { + final ResolveProcessor processor = new ResolveProcessor(name); + PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null); + final PsiElement resolved = processor.getResult(); + if (resolved != null) { + final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(resolved, PyComprehensionElement.class); + if (comprehension != null && PyUtil.isOwnScopeComprehension(comprehension)) { + return; } + final Scope scope = ControlFlowCache.getScope(owner); + if (scope.isGlobal(name) || scope.isNonlocal(name)) { + return; + } + registerProblem(problemElement, String.format("Shadows name '%s' from outer scope", name), + ProblemHighlightType.WEAK_WARNING, null, new PyRenameElementQuickFix()); } - }); + } } } } diff --git a/python/testData/inspections/PyShadowingBuiltinsInspection/test.py b/python/testData/inspections/PyShadowingBuiltinsInspection/test.py new file mode 100644 index 000000000000..4951bf1fdf50 --- /dev/null +++ b/python/testData/inspections/PyShadowingBuiltinsInspection/test.py @@ -0,0 +1,50 @@ +def test_import_builtin_names(): + import float + from foo import float + from bar import baz as float + + +def test_builtin_function_parameters(): + def test1(x, _, len, file=None): + pass + + +def test_builtin_function_name(): + def list(): + pass + + +def test_builtin_assignment_targets(): + foo = 2 + list = [] + for int in range(10): + print(int) + range = [] + list, _ = (1, 2) + return [int for int in range(10)] + + +def test_builtin_class_name(): + class list(object): + pass + + +def test_builtin_method_name(): + class C: + def list(self): + pass + + +# PY-8646 +def test_builtin_qualified_name(): + test1.range = float() + + class C: + def foo(self): + self.list = [] + + +# PY-10164 +def test_builtin_class_attribute(): + class C: + id = 1 diff --git a/python/testData/inspections/PyShadowingNamesInspection/test.py b/python/testData/inspections/PyShadowingNamesInspection/test.py index 98eec4846722..b87b8b27bae3 100644 --- a/python/testData/inspections/PyShadowingNamesInspection/test.py +++ b/python/testData/inspections/PyShadowingNamesInspection/test.py @@ -1,58 +1,6 @@ global_foo = 1 -def test_import_builtin_names(): - import float - from foo import float - from bar import baz as float - - -def test_builtin_function_parameters(): - def test1(x, _, len, file=None): - pass - - -def test_builtin_function_name(): - def list(): - pass - - -def test_builtin_assignment_targets(): - foo = 2 - list = [] - for int in range(10): - print(int) - range = [] - list, _ = (1, 2) - return [int for int in range(10)] - - -def test_builtin_class_name(): - class list(object): - pass - - -def test_builtin_method_name(): - class C: - def list(self): - pass - - -# PY-8646 -def test_builtin_qualified_name(): - test1.range = float() - - class C: - def foo(self): - self.list = [] - - -# PY-10164 -def test_builtin_class_attribute(): - class C: - id = 1 - - def test_outer_function(): foo = 1 def bar(): @@ -91,7 +39,7 @@ def test_outer_global(): global_foo = 2 -def test_comprehensions(): +def test_outer_comprehensions(): print(x for x in range(10)) print([y for y in range(10)]) def f(x, y): diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index a1b195d9b8df..d75aa2b88956 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -445,7 +445,7 @@ public class PyQuickFixTest extends PyTestCase { public void testRenameFunctionShadowingBuiltins() { final String fileName = "RenameFunctionShadowingBuiltins.py"; myFixture.configureByFile(fileName); - myFixture.enableInspections(PyShadowingNamesInspection.class); + myFixture.enableInspections(PyShadowingBuiltinsInspection.class); myFixture.checkHighlighting(true, false, true); final IntentionAction intentionAction = myFixture.getAvailableIntention("Rename element"); assertNotNull(intentionAction); diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 183bad8ed613..06ce32a3e896 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -295,6 +295,10 @@ public class PythonInspectionsTest extends PyTestCase { } // PY-5807 + public void testPyShadowingBuiltinsInspection() { + doHighlightingTest(PyShadowingBuiltinsInspection.class); + } + public void testPyShadowingNamesInspection() { doHighlightingTest(PyShadowingNamesInspection.class); }