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