From 879c23d443c9efdc94559464f19dbd375ea5dfa2 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Mon, 9 Jun 2014 22:16:27 +0400 Subject: [PATCH] PY-13210 Unused star import in Django settings.py --- .../jython/PyJythonHighlightingTest.java | 2 +- .../python/psi/PyImportedNameDefiner.java | 2 +- .../python/psi/PyStarImportElement.java | 2 +- python/src/META-INF/python-core.xml | 3 +- .../imports/PyImportOptimizer.java | 2 +- .../doctest/PyDocstringVisitorFilter.java | 1 + .../AddIgnoredIdentifierQuickFix.java | 2 +- .../PyMakeFunctionFromMethodQuickFix.java | 2 +- .../PyUnresolvedReferenceSkipperExtPoint.java | 25 ++++++++++ .../PyUnresolvedReferencesInspection.java | 50 +++++++++++++++---- .../unresolvedReference/package-info.java | 7 +++ .../com/jetbrains/python/PyQuickFixTest.java | 1 + .../python/PySuppressInspectionsTest.java | 2 +- ...Py3UnresolvedReferencesInspectionTest.java | 1 + .../PyUnresolvedReferencesInspectionTest.java | 1 + .../inspections/PyUnusedImportTest.java | 1 + .../quickFixes/AddFieldQuickFixTest.java | 2 +- .../quickFixes/AddMethodQuickFixTest.java | 2 +- 18 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferenceSkipperExtPoint.java rename python/src/com/jetbrains/python/inspections/{ => unresolvedReference}/PyUnresolvedReferencesInspection.java (96%) create mode 100644 python/src/com/jetbrains/python/inspections/unresolvedReference/package-info.java diff --git a/python/pluginTestSrc/com/jetbrains/jython/PyJythonHighlightingTest.java b/python/pluginTestSrc/com/jetbrains/jython/PyJythonHighlightingTest.java index 40330bea6d65..4ddef37bca86 100644 --- a/python/pluginTestSrc/com/jetbrains/jython/PyJythonHighlightingTest.java +++ b/python/pluginTestSrc/com/jetbrains/jython/PyJythonHighlightingTest.java @@ -18,7 +18,7 @@ package com.jetbrains.jython; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import com.jetbrains.python.PythonTestUtil; import com.jetbrains.python.inspections.PyCallingNonCallableInspection; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; /** * @author yole diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyImportedNameDefiner.java b/python/psi-api/src/com/jetbrains/python/psi/PyImportedNameDefiner.java index 6d5626a789d1..8758b851383e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyImportedNameDefiner.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyImportedNameDefiner.java @@ -20,5 +20,5 @@ package com.jetbrains.python.psi; * * @author vlan */ -public interface PyImportedNameDefiner extends NameDefiner { +public interface PyImportedNameDefiner extends NameDefiner, PyElement { } diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyStarImportElement.java b/python/psi-api/src/com/jetbrains/python/psi/PyStarImportElement.java index 0fda47caa719..fc69a0ffbda0 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyStarImportElement.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyStarImportElement.java @@ -20,5 +20,5 @@ package com.jetbrains.python.psi; * User: dcheryasov * Date: Jul 28, 2008 */ -public interface PyStarImportElement extends PyElement, PyImportedNameDefiner { +public interface PyStarImportElement extends PyImportedNameDefiner { } diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index dd9334163819..411fa6cede4d 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -270,7 +270,7 @@ - + @@ -532,6 +532,7 @@ + diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 0ebcaca758f0..5d24e3b89332 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -21,7 +21,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileSystemItem; import com.jetbrains.python.formatter.PyBlock; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java index 4ea4f88a40f0..e79d52cb1685 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java @@ -18,6 +18,7 @@ package com.jetbrains.python.documentation.doctest; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.psi.PsiFile; import com.jetbrains.python.inspections.*; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.validation.DocStringAnnotator; import com.jetbrains.python.validation.HighlightingAnnotator; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java index 23e0b2ed6c30..22c1a262da5a 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java @@ -21,7 +21,7 @@ import com.intellij.openapi.project.Project; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiElement; import com.intellij.util.Consumer; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.intellij.psi.util.QualifiedName; import org.jetbrains.annotations.NotNull; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java index e578bdddfbc8..4c5a7140c677 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java @@ -28,7 +28,7 @@ import com.intellij.usageView.UsageInfo; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.imports.AddImportHelper; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.*; import com.jetbrains.python.refactoring.PyRefactoringUtil; import org.jetbrains.annotations.NonNls; diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferenceSkipperExtPoint.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferenceSkipperExtPoint.java new file mode 100644 index 000000000000..d64d9a9652c9 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferenceSkipperExtPoint.java @@ -0,0 +1,25 @@ +package com.jetbrains.python.inspections.unresolvedReference; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.jetbrains.python.psi.PyImportedNameDefiner; +import org.jetbrains.annotations.NotNull; + +/** + * Inject this point to ask "unused reference" inspection to skip some unused references. + * For example in Django you may import "I18N" to your "settings.py". It is not used in "settings.py", but used by Django + * and should not be marked as "unused". + * + * @author Ilya.Kazakevich + */ +public interface PyUnresolvedReferenceSkipperExtPoint { + @NotNull + ExtensionPointName EP_NAME = ExtensionPointName.create("Pythonid.unresolvedReferenceSkipper"); + + /** + * Checks if some unused import should be skipped + * + * @param importNameDefiner unused import + * @return true if should be skipped + */ + boolean unusedImportShouldBeSkipped(@NotNull PyImportedNameDefiner importNameDefiner); +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java similarity index 96% rename from python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java rename to python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index d416f1b29aa6..fbe26803b39f 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.inspections; +package com.jetbrains.python.inspections.unresolvedReference; import com.google.common.collect.ImmutableSet; import com.intellij.codeInspection.*; @@ -45,6 +45,7 @@ import com.jetbrains.python.codeInsight.imports.PythonReferenceImporter; import com.jetbrains.python.console.PydevConsoleRunner; import com.jetbrains.python.documentation.DocStringParameterReference; import com.jetbrains.python.documentation.DocStringTypeReference; +import com.jetbrains.python.inspections.*; import com.jetbrains.python.inspections.quickfix.*; import com.jetbrains.python.packaging.PyPIPackageUtil; import com.jetbrains.python.packaging.PyPackageManager; @@ -68,6 +69,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; +import java.util.HashSet; import static com.jetbrains.python.inspections.quickfix.AddIgnoredIdentifierQuickFix.END_WILDCARD; @@ -78,7 +80,8 @@ import static com.jetbrains.python.inspections.quickfix.AddIgnoredIdentifierQuic */ public class PyUnresolvedReferencesInspection extends PyInspection { private static Key KEY = Key.create("PyUnresolvedReferencesInspection.Visitor"); - public static final Key SHORT_NAME_KEY = Key.create(PyUnresolvedReferencesInspection.class.getSimpleName()); + public static final Key SHORT_NAME_KEY = + Key.create(PyUnresolvedReferencesInspection.class.getSimpleName()); public JDOMExternalizableStringList ignoredIdentifiers = new JDOMExternalizableStringList(); @@ -125,7 +128,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { public static class Visitor extends PyInspectionVisitor { private Set myUsedImports = Collections.synchronizedSet(new HashSet()); - private Set myAllImports = Collections.synchronizedSet(new HashSet()); + private Set myAllImports = Collections.synchronizedSet(new HashSet()); private final ImmutableSet myIgnoredIdentifiers; private volatile Boolean myIsEnabled = null; @@ -285,10 +288,10 @@ public class PyUnresolvedReferencesInspection extends PyInspection { private void processInjection(@Nullable PsiLanguageInjectionHost node) { if (node == null) return; - final List> + final List> files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node); if (files != null) { - for (Pair pair : files) { + for (Pair pair : files) { new PyRecursiveElementVisitor() { @Override public void visitPyElement(PyElement element) { @@ -478,7 +481,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { addAddSelfFix(node, expr, actions); PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class); if (callExpression != null && (!(callExpression.getCallee() instanceof PyQualifiedExpression) || - ((PyQualifiedExpression)callExpression.getCallee()).getQualifier() == null)) { + ((PyQualifiedExpression)callExpression.getCallee()).getQualifier() == null)) { actions.add(new UnresolvedRefCreateFunctionQuickFix(callExpression, expr)); } PyFunction parentFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); @@ -780,8 +783,9 @@ public class PyUnresolvedReferencesInspection extends PyInspection { if (decoratorList != null) { for (PyDecorator decorator : decoratorList.getDecorators()) { final PyExpression callee = decorator.getCallee(); - if (callee != null && PyNames.CLASSMETHOD.equals(callee.getText())) + if (callee != null && PyNames.CLASSMETHOD.equals(callee.getText())) { isClassMethod = true; + } } } for (PyTargetExpression target : containedClass.getInstanceAttributes()) { @@ -926,8 +930,20 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } List result = new ArrayList(); - Set unusedImports = new HashSet(myAllImports); + Set unusedImports = new HashSet(myAllImports); unusedImports.removeAll(myUsedImports); + + // Remove those unsed, that are reported to be skipped by extension points + final Set unusedImportToSkip = new HashSet(); + for (final PyImportedNameDefiner unusedImport : unusedImports) { + if (importShouldBeSkippedByExtPoint(unusedImport)) { // Pass to extension points + unusedImportToSkip.add(unusedImport); + } + } + + unusedImports.removeAll(unusedImportToSkip); + + Set usedImportNames = new HashSet(); for (PsiElement usedImport : myUsedImports) { if (usedImport instanceof NameDefiner) { @@ -942,7 +958,8 @@ public class PyUnresolvedReferencesInspection extends PyInspection { QualifiedName packageQName = null; List dunderAll = null; - for (NameDefiner unusedImport : unusedImports) { + // TODO: Use strategies instead of pack of "continue" + for (PyImportedNameDefiner unusedImport : unusedImports) { if (packageQName == null) { final PsiFile file = unusedImport.getContainingFile(); if (file instanceof PyFile) { @@ -1014,7 +1031,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { return result; } - private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { + private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { final PyImportElement[] elements = importStatement.getImportElements(); for (PyImportElement element : elements) { if (!unusedImports.contains(element)) { @@ -1034,4 +1051,17 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } } + /** + * Checks if one or more extension points ask unused import to be skipped + * @param importNameDefiner unused import + * @return true of one or more asks + */ + private static boolean importShouldBeSkippedByExtPoint(@NotNull final PyImportedNameDefiner importNameDefiner) { + for (final PyUnresolvedReferenceSkipperExtPoint skipper : PyUnresolvedReferenceSkipperExtPoint.EP_NAME.getExtensions()) { + if (skipper.unusedImportShouldBeSkipped(importNameDefiner)) { + return true; + } + } + return false; + } } diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/package-info.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/package-info.java new file mode 100644 index 000000000000..512a8cabed90 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/package-info.java @@ -0,0 +1,7 @@ +/** + * + * Inspection that reports unresolved and unused references. + * You can inject logic to mark some unused imports as used. See extension points in this package. + * @author Ilya.Kazakevich + */ +package com.jetbrains.python.inspections.unresolvedReference; \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index fde0c2e6c008..c36372d9ba64 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -23,6 +23,7 @@ import com.jetbrains.python.documentation.DocStringFormat; import com.jetbrains.python.documentation.PyDocumentationSettings; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.inspections.*; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.LanguageLevel; import org.jetbrains.annotations.NonNls; diff --git a/python/testSrc/com/jetbrains/python/PySuppressInspectionsTest.java b/python/testSrc/com/jetbrains/python/PySuppressInspectionsTest.java index 9c238be12265..1eab5f2ed627 100644 --- a/python/testSrc/com/jetbrains/python/PySuppressInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PySuppressInspectionsTest.java @@ -18,7 +18,7 @@ package com.jetbrains.python; import com.intellij.codeInsight.intention.IntentionAction; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.inspections.PyInspection; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.inspections.PyUnusedLocalInspection; import java.util.List; diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index ee37eabc41ee..fe019c6abc84 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -20,6 +20,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.LightProjectDescriptor; import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 546795e4fe8f..21417363faa6 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -16,6 +16,7 @@ package com.jetbrains.python.inspections; import com.jetbrains.python.fixtures.PyInspectionTestCase; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.LanguageLevel; import org.jetbrains.annotations.NotNull; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java index e02afa1a55d3..018d75e8fbde 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java @@ -16,6 +16,7 @@ package com.jetbrains.python.inspections; import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import org.jetbrains.annotations.NotNull; /** diff --git a/python/testSrc/com/jetbrains/python/quickFixes/AddFieldQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/AddFieldQuickFixTest.java index c93553d2c3a4..21ada79a4a34 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/AddFieldQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/AddFieldQuickFixTest.java @@ -18,7 +18,7 @@ package com.jetbrains.python.quickFixes; import com.intellij.testFramework.TestDataPath; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyQuickFixTestCase; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.inspections.PyUnusedLocalInspection; /** diff --git a/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java index bde22a966fd1..6bc35712b82c 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/AddMethodQuickFixTest.java @@ -18,7 +18,7 @@ package com.jetbrains.python.quickFixes; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyQuickFixTestCase; import com.jetbrains.python.inspections.PyClassHasNoInitInspection; -import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; /** * User: ktisha