From 2faeaa0da839573ff0d8bb8ee043a49cc1b0bbe3 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 11 Apr 2025 13:03:45 +0300 Subject: [PATCH] PY-4330 PY-77891 Extract PyUnusedImportInspection from PyUnresolvedReferencesInspection Make the inspection for unused imports runForWholeFile=true to stop its blinking. GitOrigin-RevId: 967e95edc229d021420b176f56a5d4b0fb44c3d4 --- .../intellij.python.community.impl.xml | 5 + .../PyUnusedImportsInspection.html | 30 ++ .../resources/messages/PyPsiBundle.properties | 3 + .../imports/PyImportOptimizer.java | 11 +- .../inspections/PyUnusedImportsInspection.kt | 437 ++++++++++++++++++ .../PyMakeFunctionFromMethodQuickFix.java | 24 +- .../PyUnresolvedReferencesInspectionBase.java | 13 +- .../PyUnresolvedReferencesVisitor.java | 331 +------------ ...implePyUnresolvedReferencesInspection.java | 2 +- .../PyUnresolvedReferencesInspection.kt | 5 +- .../caseInsensitiveOrderOfImports.after.py | 4 +- .../caseInsensitiveOrderOfImports.py | 4 +- .../optimizeImports/suppressed.after.py | 2 +- python/testData/optimizeImports/suppressed.py | 2 +- .../PyUnresolvedReferencesInspectionTest.java | 6 + .../inspections/PyUnusedImportTest.java | 2 +- .../python/pyi/PyiInspectionsTest.java | 2 +- .../quickFixes/PyAddImportQuickFixTest.java | 3 +- 18 files changed, 512 insertions(+), 374 deletions(-) create mode 100644 python/python-psi-impl/resources/inspectionDescriptions/PyUnusedImportsInspection.html create mode 100644 python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnusedImportsInspection.kt diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index 3f8571568609..3ed53127729a 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -215,6 +215,11 @@ bundle="messages.PyPsiBundle" key="INSP.NAME.unresolved.refs" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection"/> + + +Reports unused import statements in Python code. + +

+ This inspection detects import statements that are not used in the code and can be safely removed. + Removing unused imports helps to keep the code clean and reduces the risk of name conflicts. +

+ + +

Example:

+ +

+import os           # Unused import
+import sys         # Used import
+from math import pi # Unused import
+
+print(sys.version)
+
+ +

+ The inspection provides a quick fix to optimize imports, which removes all unused import statements. +

+ +

+ Note that some imports might be used indirectly (e.g., for side effects) and should not be removed. + You can suppress this inspection for specific imports if they are needed for side effects. +

+ + \ No newline at end of file diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index 7c7a1b551809..abce1ac2880c 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -927,6 +927,9 @@ INSP.unresolved.refs.ignore.references.label=Ignored references: unresolved.docstring.param.reference=Function ''{0}'' does not have a parameter ''{1}'' unresolved.import.reference=No module named ''{0}'' +# PyUnusedImportInspection +INSP.NAME.unused.imports=Unused imports + # PyRedundantParenthesesInspection INSP.NAME.redundant.parentheses=Redundant parentheses INSP.redundant.parens.ignore.empty.lists.of.base.classes=Ignore empty lists of base classes diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index bba9b8a25e81..64a97f98d403 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -23,8 +23,7 @@ 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.PyUnresolvedReferencesVisitor; -import com.jetbrains.python.inspections.unresolvedReference.SimplePyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.PyUnusedImportsInspection; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; @@ -61,7 +60,7 @@ public final class PyImportOptimizer implements ImportOptimizer { return EmptyRunnable.INSTANCE; } - PyUnresolvedReferencesVisitor visitor = prepare(file); + PyUnusedImportsInspection.Visitor visitor = prepare(file); return () -> { LOG.debug(String.format("----------------- OPTIMIZE IMPORTS STARTED (%s) -----------------", file.getVirtualFile())); visitor.optimizeImports(); @@ -72,14 +71,14 @@ public final class PyImportOptimizer implements ImportOptimizer { }; } - private PyUnresolvedReferencesVisitor prepare(@NotNull PsiFile file) { + private PyUnusedImportsInspection.Visitor prepare(@NotNull PsiFile file) { final PsiFile contextFile = FileContextUtil.getContextFile(file); final PsiFile rfile = ObjectUtils.chooseNotNull(contextFile, file); TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), rfile); - SimplePyUnresolvedReferencesInspection inspection = new SimplePyUnresolvedReferencesInspection(); - final PyUnresolvedReferencesVisitor visitor = new SimplePyUnresolvedReferencesInspection.Visitor( + PyUnusedImportsInspection inspection = new PyUnusedImportsInspection(); + PyUnusedImportsInspection.Visitor visitor = new PyUnusedImportsInspection.Visitor( null, inspection, context, PythonLanguageLevelPusher.getLanguageLevelForFile(file) ); file.accept(new PyRecursiveElementVisitor() { diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnusedImportsInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnusedImportsInspection.kt new file mode 100644 index 000000000000..bb4fbba59a0a --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyUnusedImportsInspection.kt @@ -0,0 +1,437 @@ +package com.jetbrains.python.inspections + +import com.google.common.collect.Sets +import com.intellij.codeInsight.controlflow.ControlFlowUtil +import com.intellij.codeInsight.controlflow.Instruction +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.util.Condition +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.Ref +import com.intellij.openapi.util.Version +import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.QualifiedName +import com.intellij.util.Processor +import com.intellij.util.ThrowableRunnable +import com.intellij.util.containers.ContainerUtil +import com.jetbrains.python.PyPsiBundle +import com.jetbrains.python.PythonRuntimeService +import com.jetbrains.python.codeInsight.PyCodeInsightSettings +import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache +import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil +import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix +import com.jetbrains.python.inspections.PyInspectionVisitor.getContext +import com.jetbrains.python.psi.* +import com.jetbrains.python.psi.impl.PyBuiltinCache +import com.jetbrains.python.psi.impl.PyPsiUtils +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher +import com.jetbrains.python.psi.impl.stubs.evaluateVersionsForElement +import com.jetbrains.python.psi.resolve.ImportedResolveResult +import com.jetbrains.python.psi.resolve.PyResolveContext +import com.jetbrains.python.psi.resolve.QualifiedNameFinder +import com.jetbrains.python.psi.types.TypeEvalContext +import java.util.* + + +class PyUnusedImportsInspection : PyInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + val visitor = Visitor(holder = holder, + myInspection = this, + typeEvalContext = getContext(session), + languageLevel = PythonLanguageLevelPusher.getLanguageLevelForFile(session.file)) + session.putUserData(KEY, visitor) + return visitor + } + + override fun inspectionFinished(session: LocalInspectionToolSession, holder: ProblemsHolder) { + val visitor = session.getUserData(KEY) + checkNotNull(visitor) + ReadAction.run( + ThrowableRunnable { + if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) { + visitor.highlightUnusedImports() + } + visitor.highlightImportsInsideGuards() + } + ) + session.putUserData(KEY, null) + } + + class Visitor( + holder: ProblemsHolder?, + private val myInspection: PyInspection, + typeEvalContext: TypeEvalContext, + languageLevel: LanguageLevel, + ) : + PyInspectionVisitor(holder, typeEvalContext) { + + private val myAllImports = mutableSetOf() + private val myImportsInsideGuard = mutableSetOf() + private val myUsedImports = mutableSetOf() + private val myUnresolvedImports = mutableSetOf() + private val myVersion: Version = Version(languageLevel.majorVersion, languageLevel.minorVersion, 0) + + override fun visitPyImportElement(node: PyImportElement) { + super.visitPyImportElement(node) + val fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement::class.java) + if (fromImport == null || !fromImport.isFromFuture()) { + myAllImports.add(node) + } + } + + override fun visitPyStarImportElement(node: PyStarImportElement) { + super.visitPyStarImportElement(node) + myAllImports.add(node) + } + + override fun visitComment(comment: PsiComment) { + super.visitComment(comment) + if (comment is PsiLanguageInjectionHost) { + processInjection(comment as PsiLanguageInjectionHost) + } + } + + override fun visitPyElement(node: PyElement) { + super.visitPyElement(node) + if (node is PyReferenceOwner) { + val resolveContext = PyResolveContext.defaultContext(myTypeEvalContext) + processReference(node, node.getReference(resolveContext)) + } + else { + for (reference in node.getReferences()) { + processReference(node, reference) + } + } + } + + private fun processReference(node: PyElement, reference: PsiReference?) { + if (reference == null || reference.isSoft()) { + return + } + val guard = getImportErrorGuard(node) + if (guard != null) { + processReferenceInImportGuard(node, guard) + return + } + var target: PsiElement? = null + val unresolved: Boolean + if (reference is PsiPolyVariantReference) { + val resolveResults = reference.multiResolve(false) + unresolved = (resolveResults.size == 0) + for (resolveResult in resolveResults) { + if (target == null && resolveResult.isValidResult()) { + target = resolveResult.getElement() + } + if (resolveResult is ImportedResolveResult) { + val definer = resolveResult.getDefiner() + if (definer != null) { + myUsedImports.add(definer) + } + } + } + } + else { + target = reference.resolve() + unresolved = (target == null) + } + if (unresolved) { + val ignoreUnresolved = ignoreUnresolved(node, reference) || !evaluateVersionsForElement(node).contains(myVersion) + if (!ignoreUnresolved) { + val severity = if (reference is PsiReferenceEx) + reference.getUnresolvedHighlightSeverity(myTypeEvalContext) + else + HighlightSeverity.ERROR + if (severity == null) return + } + // don't highlight unresolved imports as unused + val importElement = node.getParent() as? PyImportElement + if (importElement != null) { + myUnresolvedImports.add(importElement) + } + } + } + + private fun ignoreUnresolved(node: PyElement, reference: PsiReference): Boolean { + var ignoreUnresolved = false + for (extension in PyInspectionExtension.EP_NAME.extensionList) { + if (extension.ignoreUnresolvedReference(node, reference, myTypeEvalContext)) { + ignoreUnresolved = true + break + } + } + return ignoreUnresolved + } + + private fun processReferenceInImportGuard(node: PyElement, guard: PyExceptPart) { + val importElement = PsiTreeUtil.getParentOfType(node, PyImportElement::class.java) + if (importElement != null) { + val visibleName = importElement.getVisibleName() + val owner = ScopeUtil.getScopeOwner(importElement) + if (visibleName != null && owner != null) { + val allWrites: MutableCollection = ScopeUtil.getElementsOfAccessType(visibleName, owner, ReadWriteInstruction.ACCESS.WRITE) + val hasWriteInsideGuard = ContainerUtil.exists(allWrites, Condition { e: PsiElement? -> PsiTreeUtil.isAncestor(guard, e!!, false) }) + if (!hasWriteInsideGuard && !shouldSkipMissingWriteInsideGuard(guard, visibleName)) { + myImportsInsideGuard.add(importElement) + } + } + } + } + + private fun shouldSkipMissingWriteInsideGuard(guard: PyExceptPart, name: String): Boolean { + return isDefinedInParentScope(name, guard) || PyBuiltinCache.getInstance(guard).getByName(name) != null || + controlFlowAlwaysTerminatesInsideGuard(guard) + } + + private fun isDefinedInParentScope(name: String, anchor: PsiElement): Boolean { + return ScopeUtil.getDeclarationScopeOwner(ScopeUtil.getScopeOwner(anchor), name) != null + } + + private fun controlFlowAlwaysTerminatesInsideGuard(guard: PyExceptPart): Boolean { + val owner = ScopeUtil.getScopeOwner(guard) + if (owner == null) return false + val flow = ControlFlowCache.getControlFlow(owner) + val instructions = flow.getInstructions() + val start = ControlFlowUtil.findInstructionNumberByElement(instructions, guard.exceptClass) + if (start <= 0) return false + val canEscapeGuard = Ref.create(false) + // TODO can we replace that with return ControlFlowUtil.process? + ControlFlowUtil.process(instructions, start, Processor { instruction: Instruction -> + val e = instruction.getElement() + if (e != null && !PsiTreeUtil.isAncestor(guard, e, true)) { + canEscapeGuard.set(true) + return@Processor false + } + return@Processor true + }) + return !canEscapeGuard.get() + } + + fun getImportsInsideGuard(): MutableCollection { + return Collections.unmodifiableCollection(myImportsInsideGuard) + } + + private fun getImportErrorGuard(node: PyElement?): PyExceptPart? { + val importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase::class.java) + if (importStatement != null) { + val tryPart = PsiTreeUtil.getParentOfType(node, PyTryPart::class.java) + if (tryPart != null) { + val tryExceptStatement = PsiTreeUtil.getParentOfType(tryPart, PyTryExceptStatement::class.java) + if (tryExceptStatement != null) { + for (exceptPart in tryExceptStatement.getExceptParts()) { + val expr = exceptPart.getExceptClass() + if (expr != null && "ImportError" == expr.getName()) { + return exceptPart + } + } + } + } + } + return null + } + + private fun processInjection(node: PsiLanguageInjectionHost?) { + if (node == null) return + val files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node) + if (files != null) { + for (pair in files) { + object : PyRecursiveElementVisitor() { + override fun visitPyElement(element: PyElement) { + super.visitPyElement(element) + if (element is PyReferenceOwner) { + val resolveContext = PyResolveContext.defaultContext(myTypeEvalContext) + val reference = element.getReference(resolveContext) + markTargetImportsAsUsed(reference) + } + } + }.visitElement(pair.getFirst()!!) + } + } + } + + private fun markTargetImportsAsUsed(reference: PsiPolyVariantReference) { + val resolveResults = reference.multiResolve(false) + for (resolveResult in resolveResults) { + if (resolveResult is ImportedResolveResult) { + val definer = resolveResult.getDefiner() + if (definer != null) { + myUsedImports.add(definer) + } + } + } + } + + fun highlightUnusedImports() { + val extensions: List = PyInspectionExtension.EP_NAME.extensionList + val unused: List = collectUnusedImportElements() + for (element in unused) { + if (ContainerUtil.exists(extensions, Condition { extension: PyInspectionExtension? -> extension!!.ignoreUnused(element, myTypeEvalContext) })) { + continue + } + if (!evaluateVersionsForElement(element).contains(myVersion)) { + continue + } + if (element.getTextLength() > 0) { + val fix = OptimizeImportsQuickFix() + registerProblem(element, PyPsiBundle.message("INSP.unused.import.statement"), ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, fix) + } + } + } + + fun highlightImportsInsideGuards() { + val usedImportsInsideImportGuards: HashSet = Sets.newHashSet(getImportsInsideGuard()) + usedImportsInsideImportGuards.retainAll(myUsedImports) + + for (definer in usedImportsInsideImportGuards) { + val importElement = PyUtil.`as`(definer, PyImportElement::class.java) + if (importElement == null) { + continue + } + val asElement = importElement.getAsNameElement() + val toHighlight: PyElement? = if (asElement != null) asElement else importElement.getImportReferenceExpression() + registerProblem(toHighlight, + PyPsiBundle.message("INSP.try.except.import.error", + importElement.getVisibleName()), + ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + } + } + + fun collectUnusedImportElements(): List { + if (myAllImports.isEmpty()) { + return emptyList() + } + // PY-1315 Unused imports inspection shouldn't work in python REPL console + val first: PyImportedNameDefiner = myAllImports.first() + if (first.getContainingFile() is PyExpressionCodeFragment || PythonRuntimeService.getInstance().isInPydevConsole(first)) { + return emptyList() + } + val result: MutableList = ArrayList() + + val unusedImports: MutableSet = HashSet(myAllImports) + unusedImports.removeAll(myUsedImports) + // TODO revise that + unusedImports.removeAll(myUnresolvedImports) + + // Remove those unsed, that are reported to be skipped by extension points + val unusedImportToSkip: MutableSet = HashSet() + for (unusedImport in unusedImports) { + if (ContainerUtil.exists(PyInspectionExtension.EP_NAME.extensionList, Condition { o: PyInspectionExtension? -> o!!.ignoreUnusedImports(unusedImport) })) { + unusedImportToSkip.add(unusedImport) + } + } + + unusedImports.removeAll(unusedImportToSkip) + + val usedImportNames: MutableSet = HashSet() + for (usedImport in myUsedImports) { + for (e in usedImport.iterateNames()) { + usedImportNames.add(e.getName()) + } + } + + val unusedStatements: MutableSet = HashSet() + var packageQName: QualifiedName? = null + var dunderAll: MutableList? = null + + // TODO: Use strategies instead of pack of "continue" + iterUnused@ for (unusedImport in unusedImports) { + if (packageQName == null) { + val file = unusedImport.getContainingFile() + if (file is PyFile) { + dunderAll = file.getDunderAll() + } + if (file != null && PyUtil.isPackage(file)) { + packageQName = QualifiedNameFinder.findShortestImportableQName(file) + } + } + val importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase::class.java) + if (importStatement != null && !unusedStatements.contains(importStatement) && !myUsedImports.contains(unusedImport)) { + val inspection: PyInspection = checkNotNull(myInspection) + if (inspection.isSuppressedFor(importStatement)) { + continue + } + // don't remove as unused imports in try/except statements + if (PsiTreeUtil.getParentOfType(importStatement, PyTryExceptStatement::class.java) != null) { + continue + } + // Don't report conditional imports as unused + if (PsiTreeUtil.getParentOfType(unusedImport, PyIfStatement::class.java) != null) { + for (e in unusedImport.iterateNames()) { + if (usedImportNames.contains(e.getName())) { + continue@iterUnused + } + } + } + val importedElement: PsiFileSystemItem? + if (unusedImport is PyImportElement) { + val element = unusedImport.resolve() + if (element == null) { + if (unusedImport.getImportedQName() != null) { + //Mark import as unused even if it can't be resolved + if (areAllImportsUnused(importStatement, unusedImports)) { + result.add(importStatement) + } + else { + result.add(unusedImport) + } + } + continue + } + if (dunderAll != null && dunderAll.contains(unusedImport.getVisibleName())) { + continue + } + importedElement = element.getContainingFile() + } + else { + assert(importStatement is PyFromImportStatement) + importedElement = (importStatement as PyFromImportStatement).resolveImportSource() + if (importedElement == null) { + continue + } + } + if (packageQName != null && importedElement != null) { + val importedQName = QualifiedNameFinder.findShortestImportableQName(importedElement) + if (importedQName != null && importedQName.matchesPrefix(packageQName)) { + continue + } + } + if (unusedImport is PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) { + unusedStatements.add(importStatement) + result.add(importStatement) + } + else { + result.add(unusedImport) + } + } + } + return result + } + + private fun areAllImportsUnused(importStatement: PyImportStatementBase, unusedImports: MutableSet): Boolean { + val elements = importStatement.getImportElements() + for (element in elements) { + if (!unusedImports.contains(element)) { + return false + } + } + return true + } + + fun optimizeImports() { + val elementsToDelete: List = collectUnusedImportElements() + for (element in elementsToDelete) { + PyPsiUtils.assertValid(element) + element.delete() + } + } + } + + companion object { + private val KEY: Key = Key.create("PyUnusedImportsInspection.Visitor") + } +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java index 3a8f0a15014f..d324ebbb70c5 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/quickfix/PyMakeFunctionFromMethodQuickFix.java @@ -9,19 +9,14 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.PsiReference; -import com.intellij.psi.impl.source.resolve.FileContextUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.usageView.UsageInfo; -import com.intellij.util.ObjectUtils; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyPsiBundle; import com.jetbrains.python.codeInsight.PyPsiIndexUtil; import com.jetbrains.python.codeInsight.imports.AddImportHelper; -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.PythonLanguageLevelPusher; -import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,22 +103,7 @@ public class PyMakeFunctionFromMethodQuickFix implements LocalQuickFix { private static void removeFormerImport(final @NotNull PsiFile usageFile, boolean addImport) { if (usageFile instanceof PyFile && addImport) { - final PsiFile contextFile = FileContextUtil.getContextFile(usageFile); - final PsiFile file = ObjectUtils.chooseNotNull(contextFile, usageFile); - TypeEvalContext context = TypeEvalContext.codeAnalysis(file.getProject(), file); - - final PyUnresolvedReferencesVisitor visitor = new SimplePyUnresolvedReferencesInspection.Visitor( - null, new SimplePyUnresolvedReferencesInspection(), context, PythonLanguageLevelPusher.getLanguageLevelForFile(usageFile) - ); - usageFile.accept(new PyRecursiveElementVisitor() { - @Override - public void visitPyElement(@NotNull PyElement node) { - super.visitPyElement(node); - node.accept(visitor); - } - }); - - visitor.optimizeImports(); + PyClassRefactoringUtil.optimizeImports(usageFile); } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspectionBase.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspectionBase.java index fb3e606428f5..32fc9ee191b9 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspectionBase.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspectionBase.java @@ -5,7 +5,6 @@ import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.application.ReadAction; 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; @@ -36,15 +35,9 @@ public abstract class PyUnresolvedReferencesInspectionBase extends PyInspection public final void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder holder) { final PyUnresolvedReferencesVisitor visitor = session.getUserData(KEY); assert visitor != null; - ReadAction.run( - () -> { - visitor.addInstallAllImports(); - if (PyCodeInsightSettings.getInstance().HIGHLIGHT_UNUSED_IMPORTS) { - visitor.highlightUnusedImports(); - } - visitor.highlightImportsInsideGuards(); - } - ); + ReadAction.run(() -> { + visitor.addInstallAllImports(); + }); session.putUserData(KEY, null); } diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java index 152da932b554..bd54475243e8 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesVisitor.java @@ -3,17 +3,15 @@ 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; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.lang.injection.InjectedLanguageManager; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.Version; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -24,29 +22,24 @@ import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyCustomType; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyPsiBundle; -import com.jetbrains.python.PythonRuntimeService; import com.jetbrains.python.codeInsight.PyCustomMember; import com.jetbrains.python.codeInsight.PySubstitutionChunkReference; -import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.PyDataFlow; -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; -import com.jetbrains.python.inspections.PyInspection; import com.jetbrains.python.inspections.PyInspectionExtension; import com.jetbrains.python.inspections.PyInspectionVisitor; import com.jetbrains.python.inspections.quickfix.*; import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.impl.PyCallExpressionNavigator; +import com.jetbrains.python.psi.impl.PyImportStatementNavigator; +import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.references.PyFromImportNameReference; import com.jetbrains.python.psi.impl.references.PyImportReference; import com.jetbrains.python.psi.impl.references.PyOperatorReference; import com.jetbrains.python.psi.impl.references.hasattr.PyHasAttrHelper; -import com.jetbrains.python.psi.resolve.ImportedResolveResult; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.*; @@ -61,24 +54,17 @@ import static com.jetbrains.python.PyNames.END_WILDCARD; import static com.jetbrains.python.psi.impl.stubs.PyVersionSpecificStubBaseKt.evaluateVersionsForElement; public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor { - private final Set myAllImports = Collections.synchronizedSet(new HashSet<>()); - private final Set myImportsInsideGuard = Collections.synchronizedSet(new HashSet<>()); - private final Set myUsedImports = Collections.synchronizedSet(new HashSet<>()); - private final Set myUnresolvedImports = Collections.synchronizedSet(new HashSet<>()); private final ImmutableSet myIgnoredIdentifiers; - private final PyInspection myInspection; private final Version myVersion; private volatile Boolean myIsEnabled = null; protected final List myUnresolvedRefs = Collections.synchronizedList(new ArrayList<>()); protected PyUnresolvedReferencesVisitor(@Nullable ProblemsHolder holder, @NotNull List ignoredIdentifiers, - @NotNull PyInspection inspection, @NotNull TypeEvalContext context, @NotNull LanguageLevel languageLevel) { super(holder, context); myIgnoredIdentifiers = ImmutableSet.copyOf(ignoredIdentifiers); - myInspection = inspection; myVersion = new Version(languageLevel.getMajorVersion(), languageLevel.getMinorVersion(), 0); } @@ -100,93 +86,26 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor } } - @Override - public void visitPyImportElement(@NotNull PyImportElement node) { - super.visitPyImportElement(node); - final PyFromImportStatement fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class); - if (isEnabled(node) && (fromImport == null || !fromImport.isFromFuture())) { - myAllImports.add(node); - } - } - - @Override - public void visitPyStarImportElement(@NotNull PyStarImportElement node) { - super.visitPyStarImportElement(node); - if (isEnabled(node)) { - myAllImports.add(node); - } - } - - @Override - public void visitComment(@NotNull PsiComment comment) { - super.visitComment(comment); - if (comment instanceof PsiLanguageInjectionHost) { - processInjection((PsiLanguageInjectionHost)comment); - } - } - @Override public void visitPyElement(final @NotNull PyElement node) { super.visitPyElement(node); - final PsiFile file = node.getContainingFile(); - final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(node.getProject()); - if (injectedLanguageManager.isInjectedFragment(file)) { - final PsiLanguageInjectionHost host = injectedLanguageManager.getInjectionHost(node); - processInjection(host); - } - if (node instanceof PyReferenceOwner) { + if (node instanceof PyReferenceOwner referenceOwner) { final PyResolveContext resolveContext = PyResolveContext.defaultContext(myTypeEvalContext); - processReference(node, ((PyReferenceOwner)node).getReference(resolveContext)); + processReference(node, referenceOwner.getReference(resolveContext)); } else { - if (node instanceof PsiLanguageInjectionHost) { - processInjection((PsiLanguageInjectionHost)node); - } for (final PsiReference reference : node.getReferences()) { processReference(node, reference); } } } - private void processInjection(@Nullable PsiLanguageInjectionHost node) { - if (node == null) return; - final List> files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node); - if (files != null) { - for (Pair pair : files) { - new PyRecursiveElementVisitor() { - @Override - public void visitPyElement(@NotNull PyElement element) { - super.visitPyElement(element); - if (element instanceof PyReferenceOwner) { - final PyResolveContext resolveContext = PyResolveContext.defaultContext(myTypeEvalContext); - final PsiPolyVariantReference reference = ((PyReferenceOwner)element).getReference(resolveContext); - markTargetImportsAsUsed(reference); - } - } - }.visitElement(pair.getFirst()); - } - } - } - - private void markTargetImportsAsUsed(@NotNull PsiPolyVariantReference reference) { - final ResolveResult[] resolveResults = reference.multiResolve(false); - for (ResolveResult resolveResult : resolveResults) { - if (resolveResult instanceof ImportedResolveResult) { - final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); - if (definer != null) { - myUsedImports.add(definer); - } - } - } - } - private void processReference(@NotNull PyElement node, @Nullable PsiReference reference) { if (!isEnabled(node) || reference == null || reference.isSoft()) { return; } final PyExceptPart guard = getImportErrorGuard(node); if (guard != null) { - processReferenceInImportGuard(node, guard); return; } if (node instanceof PyQualifiedExpression qExpr) { @@ -205,12 +124,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor if (target == null && resolveResult.isValidResult()) { target = resolveResult.getElement(); } - if (resolveResult instanceof ImportedResolveResult) { - final PyImportedNameDefiner definer = ((ImportedResolveResult)resolveResult).getDefiner(); - if (definer != null) { - myUsedImports.add(definer); - } - } } } else { @@ -226,10 +139,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor if (severity == null) return; registerUnresolvedReferenceProblem(node, reference, severity); } - // don't highlight unresolved imports as unused - if (node.getParent() instanceof PyImportElement importElement) { - myUnresolvedImports.add(importElement); - } } else if (reference instanceof PyImportReference && target == reference.getElement().getContainingFile() && @@ -238,21 +147,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor } } - private void processReferenceInImportGuard(@NotNull PyElement node, @NotNull PyExceptPart guard) { - final PyImportElement importElement = PsiTreeUtil.getParentOfType(node, PyImportElement.class); - if (importElement != null) { - final String visibleName = importElement.getVisibleName(); - final ScopeOwner owner = ScopeUtil.getScopeOwner(importElement); - if (visibleName != null && owner != null) { - final Collection allWrites = ScopeUtil.getElementsOfAccessType(visibleName, owner, ReadWriteInstruction.ACCESS.WRITE); - final boolean hasWriteInsideGuard = ContainerUtil.exists(allWrites, e -> PsiTreeUtil.isAncestor(guard, e, false)); - if (!hasWriteInsideGuard && !shouldSkipMissingWriteInsideGuard(guard, visibleName)) { - myImportsInsideGuard.add(importElement); - } - } - } - } - private void registerUnresolvedReferenceProblem(@NotNull PyElement node, final @NotNull PsiReference reference, @NotNull HighlightSeverity severity) { if (reference instanceof DocStringTypeReference) { @@ -550,172 +444,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor } } - public void highlightUnusedImports() { - final List extensions = PyInspectionExtension.EP_NAME.getExtensionList(); - final List unused = collectUnusedImportElements(); - for (PsiElement element : unused) { - if (ContainerUtil.exists(extensions, extension -> extension.ignoreUnused(element, myTypeEvalContext))) { - continue; - } - if (!evaluateVersionsForElement(element).contains(myVersion)) { - 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 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 elementsToDelete = collectUnusedImportElements(); - for (PsiElement element : elementsToDelete) { - PyPsiUtils.assertValid(element); - element.delete(); - } - } - - protected List collectUnusedImportElements() { - if (getAllImports().isEmpty()) { - return Collections.emptyList(); - } - // PY-1315 Unused imports inspection shouldn't work in python REPL console - final PyImportedNameDefiner first = getAllImports().iterator().next(); - if (first.getContainingFile() instanceof PyExpressionCodeFragment || PythonRuntimeService.getInstance().isInPydevConsole(first)) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - - Set unusedImports = new HashSet<>(getAllImports()); - unusedImports.removeAll(getUsedImports()); - unusedImports.removeAll(myUnresolvedImports); - - // Remove those unsed, that are reported to be skipped by extension points - final Set unusedImportToSkip = new HashSet<>(); - for (final PyImportedNameDefiner unusedImport : unusedImports) { - if (ContainerUtil.exists(PyInspectionExtension.EP_NAME.getExtensionList(), o -> o.ignoreUnusedImports(unusedImport))) { - unusedImportToSkip.add(unusedImport); - } - } - - unusedImports.removeAll(unusedImportToSkip); - - Set usedImportNames = new HashSet<>(); - for (PyImportedNameDefiner usedImport : getUsedImports()) { - for (PyElement e : usedImport.iterateNames()) { - usedImportNames.add(e.getName()); - } - } - - Set unusedStatements = new HashSet<>(); - QualifiedName packageQName = null; - List dunderAll = null; - - // TODO: Use strategies instead of pack of "continue" - iterUnused: - for (PyImportedNameDefiner unusedImport : unusedImports) { - if (packageQName == null) { - final PsiFile file = unusedImport.getContainingFile(); - if (file instanceof PyFile) { - dunderAll = ((PyFile)file).getDunderAll(); - } - if (file != null && PyUtil.isPackage(file)) { - packageQName = QualifiedNameFinder.findShortestImportableQName(file); - } - } - PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class); - if (importStatement != null && !unusedStatements.contains(importStatement) && !getUsedImports().contains(unusedImport)) { - PyInspection inspection = myInspection; - assert inspection != null; - if (inspection.isSuppressedFor(importStatement)) { - continue; - } - // don't remove as unused imports in try/except statements - if (PsiTreeUtil.getParentOfType(importStatement, PyTryExceptStatement.class) != null) { - continue; - } - // Don't report conditional imports as unused - if (PsiTreeUtil.getParentOfType(unusedImport, PyIfStatement.class) != null) { - for (PyElement e : unusedImport.iterateNames()) { - if (usedImportNames.contains(e.getName())) { - continue iterUnused; - } - } - } - PsiFileSystemItem importedElement; - if (unusedImport instanceof PyImportElement importElement) { - final PsiElement element = importElement.resolve(); - if (element == null) { - if (importElement.getImportedQName() != null) { - //Mark import as unused even if it can't be resolved - if (areAllImportsUnused(importStatement, unusedImports)) { - result.add(importStatement); - } - else { - result.add(importElement); - } - } - continue; - } - if (dunderAll != null && dunderAll.contains(importElement.getVisibleName())) { - continue; - } - importedElement = element.getContainingFile(); - } - else { - assert importStatement instanceof PyFromImportStatement; - importedElement = ((PyFromImportStatement)importStatement).resolveImportSource(); - if (importedElement == null) { - continue; - } - } - if (packageQName != null && importedElement != null) { - final QualifiedName importedQName = QualifiedNameFinder.findShortestImportableQName(importedElement); - if (importedQName != null && importedQName.matchesPrefix(packageQName)) { - continue; - } - } - if (unusedImport instanceof PyStarImportElement || areAllImportsUnused(importStatement, unusedImports)) { - unusedStatements.add(importStatement); - result.add(importStatement); - } - else { - result.add(unusedImport); - } - } - } - return result; - } - - private static boolean areAllImportsUnused(PyImportStatementBase importStatement, Set unusedImports) { - final PyImportElement[] elements = importStatement.getImportElements(); - for (PyImportElement element : elements) { - if (!unusedImports.contains(element)) { - return false; - } - } - return true; - } - private boolean ignoreUnresolved(@NotNull PyElement node, @NotNull PsiReference reference) { boolean ignoreUnresolved = false; for (PyInspectionExtension extension : PyInspectionExtension.EP_NAME.getExtensionList()) { @@ -727,18 +455,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor return ignoreUnresolved; } - Collection getImportsInsideGuard() { - return Collections.unmodifiableCollection(myImportsInsideGuard); - } - - Collection getAllImports() { - return Collections.unmodifiableCollection(myAllImports); - } - - Collection getUsedImports() { - return Collections.unmodifiableCollection(myUsedImports); - } - private static @Nullable PyExceptPart getImportErrorGuard(PyElement node) { final PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(node, PyImportStatementBase.class); if (importStatement != null) { @@ -762,35 +478,6 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor return PyImportStatementNavigator.getImportStatementByElement(node) == null && target.getName().equals(PyNames.INIT_DOT_PY); } - private static boolean shouldSkipMissingWriteInsideGuard(@NotNull PyExceptPart guard, @NotNull String name) { - return isDefinedInParentScope(name, guard) || - PyBuiltinCache.getInstance(guard).getByName(name) != null || - controlFlowAlwaysTerminatesInsideGuard(guard); - } - - private static boolean isDefinedInParentScope(@NotNull String name, @NotNull PsiElement anchor) { - return ScopeUtil.getDeclarationScopeOwner(ScopeUtil.getScopeOwner(anchor), name) != null; - } - - private static boolean controlFlowAlwaysTerminatesInsideGuard(@NotNull PyExceptPart guard) { - final ScopeOwner owner = ScopeUtil.getScopeOwner(guard); - if (owner == null) return false; - final ControlFlow flow = ControlFlowCache.getControlFlow(owner); - final Instruction[] instructions = flow.getInstructions(); - final int start = ControlFlowUtil.findInstructionNumberByElement(instructions, guard.getExceptClass()); - if (start <= 0) return false; - final Ref canEscapeGuard = Ref.create(false); - ControlFlowUtil.process(instructions, start, instruction -> { - final PsiElement e = instruction.getElement(); - if (e != null && !PsiTreeUtil.isAncestor(guard, e, true)) { - canEscapeGuard.set(true); - return false; - } - return true; - }); - return !canEscapeGuard.get(); - } - private static @Nullable PyExpression getReferenceQualifier(@NotNull PsiReference reference) { final PsiElement element = reference.getElement(); diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/SimplePyUnresolvedReferencesInspection.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/SimplePyUnresolvedReferencesInspection.java index e2f8a323883c..a57165e95d1f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/SimplePyUnresolvedReferencesInspection.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/unresolvedReference/SimplePyUnresolvedReferencesInspection.java @@ -30,7 +30,7 @@ public class SimplePyUnresolvedReferencesInspection extends PyUnresolvedReferenc @NotNull PyInspection inspection, @NotNull TypeEvalContext context, @NotNull LanguageLevel languageLevel) { - super(holder, Collections.emptyList(), inspection, context, languageLevel); + super(holder, Collections.emptyList(), context, languageLevel); } } } diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.kt b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.kt index 9a31c75de79b..f3ec45908df2 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.kt +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.kt @@ -26,7 +26,6 @@ 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.PythonImportUtils -import com.jetbrains.python.inspections.PyInspection import com.jetbrains.python.inspections.PyInspectionVisitor import com.jetbrains.python.inspections.PyUnresolvedReferenceQuickFixProvider import com.jetbrains.python.inspections.quickfix.* @@ -50,7 +49,6 @@ class PyUnresolvedReferencesInspection : PyUnresolvedReferencesInspectionBase() override fun createVisitor(holder: ProblemsHolder, session: LocalInspectionToolSession): PyUnresolvedReferencesVisitor = Visitor(holder, ignoredIdentifiers, - this, PyInspectionVisitor.getContext(session), PythonLanguageLevelPusher.getLanguageLevelForFile(session.file)) @@ -61,10 +59,9 @@ class PyUnresolvedReferencesInspection : PyUnresolvedReferencesInspectionBase() private class Visitor( holder: ProblemsHolder, ignoredIdentifiers: List, - inspection: PyInspection, context: TypeEvalContext, languageLevel: LanguageLevel, - ) : PyUnresolvedReferencesVisitor(holder, ignoredIdentifiers, inspection, context, languageLevel) { + ) : PyUnresolvedReferencesVisitor(holder, ignoredIdentifiers, context, languageLevel) { override fun getInstallPackageQuickFixes( node: PyElement, diff --git a/python/testData/optimizeImports/caseInsensitiveOrderOfImports.after.py b/python/testData/optimizeImports/caseInsensitiveOrderOfImports.after.py index fe543d935828..bda6e3e735a3 100644 --- a/python/testData/optimizeImports/caseInsensitiveOrderOfImports.after.py +++ b/python/testData/optimizeImports/caseInsensitiveOrderOfImports.after.py @@ -1,8 +1,8 @@ import mod_a import mod_Z -# noinspection PyUnresolvedReferences +# noinspection PyUnusedImports import pkg.pkg_a -# noinspection PyUnresolvedReferences +# noinspection PyUnusedImports import pkg.pkg_Z from mod import var_a from mod import var_Z diff --git a/python/testData/optimizeImports/caseInsensitiveOrderOfImports.py b/python/testData/optimizeImports/caseInsensitiveOrderOfImports.py index fe543d935828..bda6e3e735a3 100644 --- a/python/testData/optimizeImports/caseInsensitiveOrderOfImports.py +++ b/python/testData/optimizeImports/caseInsensitiveOrderOfImports.py @@ -1,8 +1,8 @@ import mod_a import mod_Z -# noinspection PyUnresolvedReferences +# noinspection PyUnusedImports import pkg.pkg_a -# noinspection PyUnresolvedReferences +# noinspection PyUnusedImports import pkg.pkg_Z from mod import var_a from mod import var_Z diff --git a/python/testData/optimizeImports/suppressed.after.py b/python/testData/optimizeImports/suppressed.after.py index 764573a240fa..d574d4697fb7 100644 --- a/python/testData/optimizeImports/suppressed.after.py +++ b/python/testData/optimizeImports/suppressed.after.py @@ -1,2 +1,2 @@ -#noinspection PyUnresolvedReferences +#noinspection PyUnusedImports import sys diff --git a/python/testData/optimizeImports/suppressed.py b/python/testData/optimizeImports/suppressed.py index 764573a240fa..d574d4697fb7 100644 --- a/python/testData/optimizeImports/suppressed.py +++ b/python/testData/optimizeImports/suppressed.py @@ -1,2 +1,2 @@ -#noinspection PyUnresolvedReferences +#noinspection PyUnusedImports import sys diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index f1a2efd32432..764e4f3a76ae 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -932,4 +932,10 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { protected Class getInspectionClass() { return PyUnresolvedReferencesInspection.class; } + + @Override + protected void configureInspection() { + myFixture.enableInspections(PyUnresolvedReferencesInspection.class, PyUnusedImportsInspection.class); + myFixture.checkHighlighting(isWarning(), isInfo(), isWeakWarning()); + } } \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java index c4a50e5c0383..e2556839d935 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnusedImportTest.java @@ -48,7 +48,7 @@ public class PyUnusedImportTest extends PyTestCase { private void doTest(@NotNull String filename) { myFixture.copyDirectoryToProject("inspections/unusedImport/" + getTestName(true), ""); - myFixture.enableInspections(PyUnresolvedReferencesInspection.class); + myFixture.enableInspections(PyUnusedImportsInspection.class, PyUnresolvedReferencesInspection.class); myFixture.testHighlighting(true, false, false, filename); } } diff --git a/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java b/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java index abb46ddd311e..3148232275b8 100644 --- a/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/pyi/PyiInspectionsTest.java @@ -134,7 +134,7 @@ public class PyiInspectionsTest extends PyTestCase { } public void testPyiUnusedImports() { - doPyiTest(PyUnresolvedReferencesInspection.class); + doPyiTest(PyUnusedImportsInspection.class); } public void testPyiRelativeImports() { diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java index d4fd4726cbf9..c7873cac1991 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java @@ -30,6 +30,7 @@ import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix; import com.jetbrains.python.codeInsight.imports.ImportCandidateHolder; import com.jetbrains.python.codeInsight.imports.PythonImportUtils; import com.jetbrains.python.formatter.PyCodeStyleSettings; +import com.jetbrains.python.inspections.PyUnusedImportsInspection; import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyReferenceExpression; @@ -460,7 +461,7 @@ public class PyAddImportQuickFixTest extends PyQuickFixTestCase { private void configureMultiFileProject() { myFixture.copyDirectoryToProject(getTestName(true), ""); - myFixture.enableInspections(PyUnresolvedReferencesInspection.class); + myFixture.enableInspections(PyUnresolvedReferencesInspection.class, PyUnusedImportsInspection.class); myFixture.configureByFile("main.py"); myFixture.checkHighlighting(true, false, false); }