LAB-31: extracted optimize imports quick fix

GitOrigin-RevId: 0011495a43d4afe392c907fad710390a66953bef
This commit is contained in:
Petr Golubev
2020-12-03 20:18:26 +00:00
committed by intellij-monorepo-bot
parent 9b4183a786
commit 210fd9eb45
11 changed files with 155 additions and 127 deletions
@@ -1,6 +1,4 @@
### Quick fixes ###
QFIX.optimize.imports=Optimize imports
QFIX.add.import.add.import=Add "''{0}''"
#PyImplementMethodsQuickFix
@@ -329,6 +329,7 @@ QFIX.create.class=Create class
QFIX.create.class.0=Create class ''{0}''
QFIX.create.class.in.module=Create class ''{0}'' in module {1}
QFIX.optimize.imports=Optimize imports
QFIX.unresolved.reference=Replace ''{0}'' with ''{1}.{0}''
QFIX.NAME.unresolved.reference.add.param=Create parameter for reference
@@ -12,7 +12,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.psi.PyFile;
import org.jetbrains.annotations.NotNull;
@@ -30,7 +30,7 @@ public class OptimizeImportsQuickFix implements LocalQuickFix, IntentionAction,
@Override
@NotNull
public String getFamilyName() {
return PyBundle.message("QFIX.optimize.imports");
return PyPsiBundle.message("QFIX.optimize.imports");
}
@Override
@@ -21,8 +21,8 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority;
import com.jetbrains.python.formatter.PyCodeStyleSettings;
import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesVisitor;
import com.jetbrains.python.inspections.unresolvedReference.SimplePyUnresolvedReferencesInspection;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import one.util.streamex.StreamEx;
@@ -56,10 +56,8 @@ public class PyImportOptimizer implements ImportOptimizer {
@NotNull
public Runnable processFile(@NotNull final PsiFile file) {
final LocalInspectionToolSession session = new LocalInspectionToolSession(file, 0, file.getTextLength());
final PyUnresolvedReferencesInspection.Visitor visitor = new PyUnresolvedReferencesInspection.Visitor(null,
session,
Collections.emptyList());
session.putUserData(PyUnresolvedReferencesVisitor.INSPECTION, new PyUnresolvedReferencesInspection());
final PyUnresolvedReferencesVisitor visitor = new SimplePyUnresolvedReferencesInspection.Visitor(null, session);
session.putUserData(PyUnresolvedReferencesVisitor.INSPECTION, new SimplePyUnresolvedReferencesInspection());
file.accept(new PyRecursiveElementVisitor() {
@Override
public void visitElement(@NotNull PsiElement node) {
@@ -0,0 +1,49 @@
package com.jetbrains.python.inspections.unresolvedReference;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.inspections.PyInspection;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
public abstract class PyUnresolvedReferencesInspectionBase extends PyInspection {
private static final Key<PyUnresolvedReferencesVisitor> KEY = Key.create("PyUnresolvedReferencesInspection.Visitor");
@Pattern(VALID_ID_PATTERN)
@Override
public @NotNull String getID() {
return "PyUnresolvedReferences";
}
@NotNull
@Override
public final PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
final boolean isOnTheFly,
@NotNull final LocalInspectionToolSession session) {
final PyUnresolvedReferencesVisitor visitor = createVisitor(holder, session);
// buildVisitor() will be called on injected files in the same session - don't overwrite if we already have one
final PyUnresolvedReferencesVisitor existingVisitor = session.getUserData(KEY);
if (existingVisitor == null) {
session.putUserData(KEY, visitor);
}
session.putUserData(PyUnresolvedReferencesVisitor.INSPECTION, this);
return visitor;
}
@Override
public final void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder holder) {
final PyUnresolvedReferencesVisitor visitor = session.getUserData(KEY);
assert visitor != null;
if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) {
visitor.highlightUnusedImports();
}
visitor.highlightImportsInsideGuards();
session.putUserData(KEY, null);
}
protected abstract PyUnresolvedReferencesVisitor createVisitor(@NotNull ProblemsHolder holder,
@NotNull LocalInspectionToolSession session);
}
@@ -3,6 +3,7 @@ package com.jetbrains.python.inspections.unresolvedReference;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.controlflow.ControlFlow;
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
import com.intellij.codeInsight.controlflow.Instruction;
@@ -30,6 +31,7 @@ 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.codeInsight.imports.OptimizeImportsQuickFix;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.documentation.docstrings.DocStringParameterReference;
import com.jetbrains.python.documentation.docstrings.DocStringTypeReference;
@@ -65,6 +67,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
private final Set<PyImportedNameDefiner> myImportsInsideGuard = Collections.synchronizedSet(new HashSet<>());
private final Set<PyImportedNameDefiner> myUsedImports = Collections.synchronizedSet(new HashSet<>());
private final ImmutableSet<String> myIgnoredIdentifiers;
private volatile Boolean myIsEnabled = null;
public static final Key<PyInspection> INSPECTION = Key.create("PyUnresolvedReferencesVisitor.inspection");
@@ -511,7 +514,54 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
return false;
}
abstract boolean isEnabled(@NotNull PsiElement anchor);
private boolean isEnabled(@NotNull PsiElement anchor) {
if (myIsEnabled == null) {
Boolean overridden = overriddenUnresolvedReferenceInspection(anchor.getContainingFile());
myIsEnabled = Objects.requireNonNullElse(overridden, true);
}
return myIsEnabled;
}
private static @Nullable Boolean overriddenUnresolvedReferenceInspection(@NotNull PsiFile file) {
return PyInspectionExtension.EP_NAME.getExtensionList().stream()
.map(e -> e.overrideUnresolvedReferenceInspection(file))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
public void highlightUnusedImports() {
final List<PyInspectionExtension> extensions = PyInspectionExtension.EP_NAME.getExtensionList();
final List<PsiElement> unused = collectUnusedImportElements();
for (PsiElement element : unused) {
if (extensions.stream().anyMatch(extension -> extension.ignoreUnused(element, myTypeEvalContext))) {
continue;
}
if (element.getTextLength() > 0) {
OptimizeImportsQuickFix fix = new OptimizeImportsQuickFix();
registerProblem(element, PyPsiBundle.message("INSP.unused.import.statement"), ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, fix);
}
}
}
public void highlightImportsInsideGuards() {
HashSet<PyImportedNameDefiner> usedImportsInsideImportGuards = Sets.newHashSet(getImportsInsideGuard());
usedImportsInsideImportGuards.retainAll(getUsedImports());
for (PyImportedNameDefiner definer : usedImportsInsideImportGuards) {
PyImportElement importElement = PyUtil.as(definer, PyImportElement.class);
if (importElement == null) {
continue;
}
final PyTargetExpression asElement = importElement.getAsNameElement();
final PyElement toHighlight = asElement != null ? asElement : importElement.getImportReferenceExpression();
registerProblem(toHighlight,
PyPsiBundle.message("INSP.try.except.import.error",
importElement.getVisibleName()),
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
public void optimizeImports() {
final List<PsiElement> elementsToDelete = collectUnusedImportElements();
@@ -4,12 +4,10 @@ import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiReference;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix;
import com.jetbrains.python.codeInsight.imports.PythonImportUtils;
import com.jetbrains.python.inspections.PyInspection;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
import org.jetbrains.annotations.NotNull;
@@ -19,34 +17,24 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SimplePyUnresolvedReferencesInspection extends PyInspection {
@NotNull
public class SimplePyUnresolvedReferencesInspection extends PyUnresolvedReferencesInspectionBase {
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
Visitor visitor = new Visitor(holder, session);
session.putUserData(PyUnresolvedReferencesVisitor.INSPECTION, this);
return visitor;
protected PyUnresolvedReferencesVisitor createVisitor(@NotNull ProblemsHolder holder,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
@Nullable
@Override
public String getStaticDescription() {
return "";
return null;
}
public static class Visitor extends PyUnresolvedReferencesVisitor {
public Visitor(@Nullable ProblemsHolder holder,
@NotNull LocalInspectionToolSession session) {
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
super(holder, session, Collections.emptyList());
}
@Override
boolean isEnabled(@NotNull PsiElement anchor) {
return true;
}
@Override
Iterable<LocalQuickFix> getAutoImportFixes(PyElement node, PsiReference reference, PsiElement element) {
List<LocalQuickFix> fixes = new ArrayList<>();
@@ -552,6 +552,7 @@
<packageManagerProvider implementation="com.jetbrains.python.sdk.pipenv.PyPipenvPackageManagerProvider"/>
<inspectionExtension implementation="com.jetbrains.python.sdk.configuration.PyInterpreterInspectionSuppressor"/>
<inspectionExtension implementation="com.jetbrains.python.inspections.PyUnresolvedReferenceDefaultInspectionExtension" order="last"/>
</extensions>
<actions>
@@ -0,0 +1,31 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.inspections;
import com.intellij.psi.PsiFile;
import com.intellij.util.PlatformUtils;
import com.jetbrains.python.PythonRuntimeService;
import com.jetbrains.python.sdk.PythonSdkUtil;
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
import org.jetbrains.annotations.NotNull;
/**
* The default behavior of enabling "unresolved reference" inspection that can be overridden.
* Must be registered last.
* Keeping it here instead of the inspection class itself is necessary for decoupling intellij.python.psi.impl.
*/
public class PyUnresolvedReferenceDefaultInspectionExtension extends PyInspectionExtension {
@Override
public Boolean overrideUnresolvedReferenceInspection(@NotNull PsiFile file) {
boolean result;
if (PySkeletonRefresher.isGeneratingSkeletons()) {
result = false;
}
else if (PlatformUtils.isPyCharm()) {
result = PythonSdkUtil.findPythonSdk(file) != null || PythonRuntimeService.getInstance().isInScratchFile(file);
}
else {
result = true;
}
return result;
}
}
@@ -1,8 +1,10 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.inspections.unresolvedReference;
import com.google.common.collect.Sets;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.InspectionProfile;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.ListEditForm;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.module.Module;
@@ -11,24 +13,18 @@ import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.util.Key;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.PlatformUtils;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.PyPsiPackageUtil;
import com.jetbrains.python.PythonRuntimeService;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.imports.AutoImportHintAction;
import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix;
import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix;
import com.jetbrains.python.codeInsight.imports.PythonImportUtils;
import com.jetbrains.python.inspections.PyInspection;
import com.jetbrains.python.inspections.PyInspectionExtension;
import com.jetbrains.python.inspections.PyPackageRequirementsInspection;
import com.jetbrains.python.inspections.PyUnresolvedReferenceQuickFixProvider;
import com.jetbrains.python.inspections.quickfix.AddIgnoredIdentifierQuickFix;
@@ -40,20 +36,20 @@ import com.jetbrains.python.packaging.PyRequirementsKt;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.references.PyImportReference;
import com.jetbrains.python.sdk.PythonSdkUtil;
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Marks references that fail to resolve. Also tracks unused imports and provides "optimize imports" support.
* User: dcheryasov
*/
public class PyUnresolvedReferencesInspection extends PyInspection {
private static final Key<Visitor> KEY = Key.create("PyUnresolvedReferencesInspection.Visitor");
public class PyUnresolvedReferencesInspection extends PyUnresolvedReferencesInspectionBase {
public static final Key<PyUnresolvedReferencesInspection> SHORT_NAME_KEY =
Key.create(PyUnresolvedReferencesInspection.class.getSimpleName());
@@ -64,30 +60,10 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
return (PyUnresolvedReferencesInspection)inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element);
}
@Override
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
final boolean isOnTheFly,
@NotNull final LocalInspectionToolSession session) {
final Visitor visitor = new Visitor(holder, session, ignoredIdentifiers);
// buildVisitor() will be called on injected files in the same session - don't overwrite if we already have one
final Visitor existingVisitor = session.getUserData(KEY);
if (existingVisitor == null) {
session.putUserData(KEY, visitor);
}
session.putUserData(PyUnresolvedReferencesVisitor.INSPECTION, this);
return visitor;
}
@Override
public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder holder) {
final Visitor visitor = session.getUserData(KEY);
assert visitor != null;
if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) {
visitor.highlightUnusedImports();
}
visitor.highlightImportsInsideGuards();
session.putUserData(KEY, null);
protected PyUnresolvedReferencesVisitor createVisitor(@NotNull ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session, ignoredIdentifiers);
}
@Override
@@ -98,74 +74,10 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
}
public static class Visitor extends PyUnresolvedReferencesVisitor {
private volatile Boolean myIsEnabled = null;
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session, List<String> ignoredIdentifiers) {
super(holder, session, ignoredIdentifiers);
}
@Override
public boolean isEnabled(@NotNull PsiElement anchor) {
if (myIsEnabled == null) {
final boolean isPyCharm = PlatformUtils.isPyCharm();
Boolean overridden = overriddenUnresolvedReferenceInspection(anchor.getContainingFile());
if (overridden != null) {
myIsEnabled = overridden;
}
else if (PySkeletonRefresher.isGeneratingSkeletons()) {
myIsEnabled = false;
}
else if (isPyCharm) {
myIsEnabled = PythonSdkUtil.findPythonSdk(anchor) != null || PythonRuntimeService.getInstance().isInScratchFile(anchor);
}
else {
myIsEnabled = true;
}
}
return myIsEnabled;
}
private static @Nullable Boolean overriddenUnresolvedReferenceInspection(@NotNull PsiFile file) {
return PyInspectionExtension.EP_NAME.getExtensionList().stream()
.map(e -> e.overrideUnresolvedReferenceInspection(file))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
public void highlightUnusedImports() {
final List<PyInspectionExtension> extensions = PyInspectionExtension.EP_NAME.getExtensionList();
final List<PsiElement> unused = collectUnusedImportElements();
for (PsiElement element : unused) {
if (extensions.stream().anyMatch(extension -> extension.ignoreUnused(element, myTypeEvalContext))) {
continue;
}
if (element.getTextLength() > 0) {
OptimizeImportsQuickFix fix = new OptimizeImportsQuickFix();
registerProblem(element, PyPsiBundle.message("INSP.unused.import.statement"), ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, fix);
}
}
}
public void highlightImportsInsideGuards() {
HashSet<PyImportedNameDefiner> usedImportsInsideImportGuards = Sets.newHashSet(getImportsInsideGuard());
usedImportsInsideImportGuards.retainAll(getUsedImports());
for (PyImportedNameDefiner definer : usedImportsInsideImportGuards) {
PyImportElement importElement = PyUtil.as(definer, PyImportElement.class);
if (importElement == null) {
continue;
}
final PyTargetExpression asElement = importElement.getAsNameElement();
final PyElement toHighlight = asElement != null ? asElement : importElement.getImportReferenceExpression();
registerProblem(toHighlight,
PyPsiBundle.message("INSP.try.except.import.error",
importElement.getVisibleName()),
ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
@Override
public Iterable<LocalQuickFix> getInstallPackageQuickFixes(@NotNull PyElement node,
@NotNull PsiReference reference,