From 847800c572a6ffb7e0416c274edb819a03bd6d16 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Fri, 30 Apr 2010 20:47:22 +0400 Subject: [PATCH] initial implementation of 'optimize imports' for Python --- python/src/META-INF/python-plugin-common.xml | 2 + .../python/codeInsight/PyImportOptimizer.java | 34 +++++++++++++++ .../inspections/PyInspectionVisitor.java | 22 ++++++---- .../PyUnresolvedReferencesInspection.java | 41 +++++++++++++++---- .../python/psi/PyElementVisitor.java | 4 ++ .../psi/impl/PyFromImportStatementImpl.java | 6 +++ .../psi/impl/PyImportStatementImpl.java | 6 +++ .../jetbrains/python/psi/impl/PyPsiUtils.java | 14 +++++++ .../psi/impl/PyStarImportElementImpl.java | 5 +++ .../optimizeImports/importStar.after.py | 2 + python/testData/optimizeImports/importStar.py | 3 ++ .../importStarOneOfMultiple.after.py | 2 + .../importStarOneOfMultiple.py | 2 + .../optimizeImports/oneOfMultiple.after.py | 2 + .../testData/optimizeImports/oneOfMultiple.py | 2 + .../testData/optimizeImports/simple.after.py | 0 python/testData/optimizeImports/simple.py | 1 + .../python/PyOptimizeImportsTest.java | 32 +++++++++++++++ .../jetbrains/python/PythonAllTestsSuite.java | 3 +- 19 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 python/src/com/jetbrains/python/codeInsight/PyImportOptimizer.java create mode 100644 python/testData/optimizeImports/importStar.after.py create mode 100644 python/testData/optimizeImports/importStar.py create mode 100644 python/testData/optimizeImports/importStarOneOfMultiple.after.py create mode 100644 python/testData/optimizeImports/importStarOneOfMultiple.py create mode 100644 python/testData/optimizeImports/oneOfMultiple.after.py create mode 100644 python/testData/optimizeImports/oneOfMultiple.py create mode 100644 python/testData/optimizeImports/simple.after.py create mode 100644 python/testData/optimizeImports/simple.py create mode 100644 python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index c77ed8efc2d9..d4257936a1f4 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -163,6 +163,8 @@ + + diff --git a/python/src/com/jetbrains/python/codeInsight/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/PyImportOptimizer.java new file mode 100644 index 000000000000..10ddb456e02b --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/PyImportOptimizer.java @@ -0,0 +1,34 @@ +package com.jetbrains.python.codeInsight; + +import com.intellij.lang.ImportOptimizer; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyRecursiveElementVisitor; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class PyImportOptimizer implements ImportOptimizer { + public boolean supports(PsiFile file) { + return true; + } + + @NotNull + public Runnable processFile(PsiFile file) { + final PyUnresolvedReferencesInspection.Visitor visitor = new PyUnresolvedReferencesInspection.Visitor(null); + file.accept(new PyRecursiveElementVisitor() { + @Override + public void visitPyElement(PyElement node) { + super.visitPyElement(node); + node.accept(visitor); + } + }); + return new Runnable() { + public void run() { + visitor.optimizeImports(); + } + }; + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java index a8f8471826c4..895d3fa8021c 100644 --- a/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java +++ b/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java @@ -13,13 +13,13 @@ import org.jetbrains.annotations.Nullable; * A copy of Ruby's visitor helper. */ public class PyInspectionVisitor extends PyElementVisitor { - private final ProblemsHolder myHolder; + @Nullable private final ProblemsHolder myHolder; - public PyInspectionVisitor(final ProblemsHolder holder) { + public PyInspectionVisitor(@Nullable final ProblemsHolder holder) { myHolder = holder; } - public ProblemsHolder getHolder() { + protected ProblemsHolder getHolder() { return myHolder; } @@ -28,7 +28,9 @@ public class PyInspectionVisitor extends PyElementVisitor { if (element == null || element.getTextLength() == 0){ return; } - myHolder.registerProblem(element, message); + if (myHolder != null) { + myHolder.registerProblem(element, message); + } } protected final void registerProblem(@Nullable final PsiElement element, @@ -37,7 +39,9 @@ public class PyInspectionVisitor extends PyElementVisitor { if (element == null || element.getTextLength() == 0){ return; } + if (myHolder != null) { myHolder.registerProblem(element, message, quickFix); + } } protected final void registerProblem(final PsiElement element, @@ -47,7 +51,9 @@ public class PyInspectionVisitor extends PyElementVisitor { if (element == null || element.getTextLength() == 0){ return; } - myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(element, message, type, action, myHolder.isOnTheFly())); + if (myHolder != null) { + myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(element, message, type, action, myHolder.isOnTheFly())); + } } /** @@ -61,7 +67,9 @@ public class PyInspectionVisitor extends PyElementVisitor { final HintAction hintAction, final LocalQuickFix... fixes) { - myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(psiElement, descriptionTemplate, highlightType, hintAction, - myHolder.isOnTheFly(), fixes)); + if (myHolder != null) { + myHolder.registerProblem(myHolder.getManager().createProblemDescriptor(psiElement, descriptionTemplate, highlightType, hintAction, + myHolder.isOnTheFly(), fixes)); + } } } diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 717924391055..2589472a933e 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -35,7 +35,7 @@ import org.jetbrains.annotations.NotNull; import java.util.*; /** - * Marks references that fail to resolve. + * Marks references that fail to resolve. Also tracks unused imports and provides "optimize imports" support. * User: dcheryasov * Date: Nov 15, 2008 */ @@ -99,7 +99,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { 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()); public Visitor(final ProblemsHolder holder) { super(holder); @@ -277,6 +277,12 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { myAllImports.add(node); } + @Override + public void visitPyStarImportElement(PyStarImportElement node) { + super.visitPyStarImportElement(node); + myAllImports.add(node); + } + @Override public void visitPyElement(final PyElement node) { super.visitPyElement(node); @@ -442,32 +448,49 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { } public void highlightUnusedImports() { - myAllImports.removeAll(myUsedImports); - final NameDefiner[] unusedImports = myAllImports.toArray(new NameDefiner[myAllImports.size()]); + final List unused = collectUnusedImportElements(); + for (PsiElement element : unused) { + registerProblem(element, "Unused import statement", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null); + } + } + + private List collectUnusedImportElements() { + List result = new ArrayList(); + + Set unusedImports = new HashSet(myAllImports); + unusedImports.removeAll(myUsedImports); Set unusedStatements = new HashSet(); for (NameDefiner unusedImport : unusedImports) { PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class); if (importStatement != null && !unusedStatements.contains(importStatement)) { - if (areAllImportsUnused(importStatement)) { + if (unusedImport instanceof PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) { unusedStatements.add(importStatement); - registerProblem(importStatement, "Unused import statement", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null); + result.add(importStatement); } else { - registerProblem(unusedImport, "Unused import statement", ProblemHighlightType.LIKE_UNUSED_SYMBOL, null); + result.add(unusedImport); } } } + return result; } - private boolean areAllImportsUnused(PyImportStatementBase importStatement) { + private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { final PyImportElement[] elements = importStatement.getImportElements(); for (PyImportElement element : elements) { - if (!myAllImports.contains(element)) { + if (!unusedImports.contains(element)) { return false; } } return true; } + + public void optimizeImports() { + final List elementsToDelete = collectUnusedImportElements(); + for (PsiElement element : elementsToDelete) { + element.delete(); + } + } } } diff --git a/python/src/com/jetbrains/python/psi/PyElementVisitor.java b/python/src/com/jetbrains/python/psi/PyElementVisitor.java index d51a69d151b0..4e5b39503abc 100644 --- a/python/src/com/jetbrains/python/psi/PyElementVisitor.java +++ b/python/src/com/jetbrains/python/psi/PyElementVisitor.java @@ -217,4 +217,8 @@ public class PyElementVisitor extends PsiElementVisitor { public void visitPyImportElement(PyImportElement node) { visitPyElement(node); } + + public void visitPyStarImportElement(PyStarImportElement node) { + visitPyElement(node); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java index accafa14afa8..caa2d5b52fa2 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFromImportStatementImpl.java @@ -139,4 +139,10 @@ public class PyFromImportStatementImpl extends PyBaseElementImpl