mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-4330 PY-77891 Extract PyUnusedImportInspection from PyUnresolvedReferencesInspection
Make the inspection for unused imports runForWholeFile=true to stop its blinking. GitOrigin-RevId: 967e95edc229d021420b176f56a5d4b0fb44c3d4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e93d6305de
commit
2faeaa0da8
@@ -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"/>
|
||||
<localInspection language="Python" shortName="PyUnusedImportsInspection"
|
||||
suppressId="PyUnusedImports"
|
||||
bundle="messages.PyPsiBundle" key="INSP.NAME.unused.imports" groupKey="INSP.GROUP.python" enabledByDefault="true"
|
||||
level="WARNING" runForWholeFile="true"
|
||||
implementationClass="com.jetbrains.python.inspections.PyUnusedImportsInspection"/>
|
||||
<localInspection language="Python" shortName="PyTestParametrizedInspection" suppressId="PyTestParametrized"
|
||||
bundle="messages.PyPsiBundle" key="INSP.NAME.pytest-parametrized" groupKey="INSP.GROUP.python"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports unused import statements in Python code.
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- tooltip end -->
|
||||
<p><b>Example:</b></p>
|
||||
|
||||
<pre><code>
|
||||
import os # Unused import
|
||||
import sys # Used import
|
||||
from math import pi # Unused import
|
||||
|
||||
print(sys.version)
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
The inspection provides a quick fix to optimize imports, which removes all unused import statements.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
|
||||
+5
-6
@@ -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() {
|
||||
|
||||
+437
@@ -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<RuntimeException?>(
|
||||
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<PyImportedNameDefiner>()
|
||||
private val myImportsInsideGuard = mutableSetOf<PyImportedNameDefiner>()
|
||||
private val myUsedImports = mutableSetOf<PyImportedNameDefiner>()
|
||||
private val myUnresolvedImports = mutableSetOf<PyImportedNameDefiner>()
|
||||
private val myVersion: Version = Version(languageLevel.majorVersion, languageLevel.minorVersion, 0)
|
||||
|
||||
override fun visitPyImportElement(node: PyImportElement) {
|
||||
super.visitPyImportElement(node)
|
||||
val fromImport = PsiTreeUtil.getParentOfType<PyFromImportStatement?>(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<PyImportElement?>(node, PyImportElement::class.java)
|
||||
if (importElement != null) {
|
||||
val visibleName = importElement.getVisibleName()
|
||||
val owner = ScopeUtil.getScopeOwner(importElement)
|
||||
if (visibleName != null && owner != null) {
|
||||
val allWrites: MutableCollection<PsiElement?> = ScopeUtil.getElementsOfAccessType(visibleName, owner, ReadWriteInstruction.ACCESS.WRITE)
|
||||
val hasWriteInsideGuard = ContainerUtil.exists<PsiElement?>(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<Boolean>(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<PyImportedNameDefiner> {
|
||||
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?> = PyInspectionExtension.EP_NAME.extensionList
|
||||
val unused: List<PsiElement> = collectUnusedImportElements()
|
||||
for (element in unused) {
|
||||
if (ContainerUtil.exists<PyInspectionExtension?>(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<PyImportedNameDefiner?> = Sets.newHashSet(getImportsInsideGuard())
|
||||
usedImportsInsideImportGuards.retainAll(myUsedImports)
|
||||
|
||||
for (definer in usedImportsInsideImportGuards) {
|
||||
val importElement = PyUtil.`as`<PyImportElement?>(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<PsiElement> {
|
||||
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<PsiElement> = ArrayList<PsiElement>()
|
||||
|
||||
val unusedImports: MutableSet<PyImportedNameDefiner> = 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<PyImportedNameDefiner?> = HashSet<PyImportedNameDefiner?>()
|
||||
for (unusedImport in unusedImports) {
|
||||
if (ContainerUtil.exists<PyInspectionExtension>(PyInspectionExtension.EP_NAME.extensionList, Condition { o: PyInspectionExtension? -> o!!.ignoreUnusedImports(unusedImport) })) {
|
||||
unusedImportToSkip.add(unusedImport)
|
||||
}
|
||||
}
|
||||
|
||||
unusedImports.removeAll(unusedImportToSkip)
|
||||
|
||||
val usedImportNames: MutableSet<String?> = HashSet<String?>()
|
||||
for (usedImport in myUsedImports) {
|
||||
for (e in usedImport.iterateNames()) {
|
||||
usedImportNames.add(e.getName())
|
||||
}
|
||||
}
|
||||
|
||||
val unusedStatements: MutableSet<PyImportStatementBase?> = HashSet<PyImportStatementBase?>()
|
||||
var packageQName: QualifiedName? = null
|
||||
var dunderAll: MutableList<String?>? = 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<PyTryExceptStatement?>(importStatement, PyTryExceptStatement::class.java) != null) {
|
||||
continue
|
||||
}
|
||||
// Don't report conditional imports as unused
|
||||
if (PsiTreeUtil.getParentOfType<PyIfStatement?>(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<PyImportedNameDefiner>): Boolean {
|
||||
val elements = importStatement.getImportElements()
|
||||
for (element in elements) {
|
||||
if (!unusedImports.contains(element)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun optimizeImports() {
|
||||
val elementsToDelete: List<PsiElement> = collectUnusedImportElements()
|
||||
for (element in elementsToDelete) {
|
||||
PyPsiUtils.assertValid(element)
|
||||
element.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY: Key<Visitor> = Key.create<Visitor>("PyUnusedImportsInspection.Visitor")
|
||||
}
|
||||
}
|
||||
+2
-22
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-10
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+9
-322
@@ -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<PyImportedNameDefiner> myAllImports = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<PyImportedNameDefiner> myImportsInsideGuard = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<PyImportedNameDefiner> myUsedImports = Collections.synchronizedSet(new HashSet<>());
|
||||
private final Set<PyImportedNameDefiner> myUnresolvedImports = Collections.synchronizedSet(new HashSet<>());
|
||||
private final ImmutableSet<String> myIgnoredIdentifiers;
|
||||
private final PyInspection myInspection;
|
||||
private final Version myVersion;
|
||||
private volatile Boolean myIsEnabled = null;
|
||||
protected final List<PyPackageInstallAllProblemInfo> myUnresolvedRefs = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
protected PyUnresolvedReferencesVisitor(@Nullable ProblemsHolder holder,
|
||||
@NotNull List<String> 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<Pair<PsiElement, TextRange>> files = InjectedLanguageManager.getInstance(node.getProject()).getInjectedPsiFiles(node);
|
||||
if (files != null) {
|
||||
for (Pair<PsiElement, TextRange> 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<PsiElement> 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<PyInspectionExtension> extensions = PyInspectionExtension.EP_NAME.getExtensionList();
|
||||
final List<PsiElement> 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<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();
|
||||
for (PsiElement element : elementsToDelete) {
|
||||
PyPsiUtils.assertValid(element);
|
||||
element.delete();
|
||||
}
|
||||
}
|
||||
|
||||
protected List<PsiElement> 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<PsiElement> result = new ArrayList<>();
|
||||
|
||||
Set<PyImportedNameDefiner> unusedImports = new HashSet<>(getAllImports());
|
||||
unusedImports.removeAll(getUsedImports());
|
||||
unusedImports.removeAll(myUnresolvedImports);
|
||||
|
||||
// Remove those unsed, that are reported to be skipped by extension points
|
||||
final Set<PyImportedNameDefiner> 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<String> usedImportNames = new HashSet<>();
|
||||
for (PyImportedNameDefiner usedImport : getUsedImports()) {
|
||||
for (PyElement e : usedImport.iterateNames()) {
|
||||
usedImportNames.add(e.getName());
|
||||
}
|
||||
}
|
||||
|
||||
Set<PyImportStatementBase> unusedStatements = new HashSet<>();
|
||||
QualifiedName packageQName = null;
|
||||
List<String> 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<PyImportedNameDefiner> 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<PyImportedNameDefiner> getImportsInsideGuard() {
|
||||
return Collections.unmodifiableCollection(myImportsInsideGuard);
|
||||
}
|
||||
|
||||
Collection<PyImportedNameDefiner> getAllImports() {
|
||||
return Collections.unmodifiableCollection(myAllImports);
|
||||
}
|
||||
|
||||
Collection<PyImportedNameDefiner> 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<Boolean> 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();
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -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<String>,
|
||||
inspection: PyInspection,
|
||||
context: TypeEvalContext,
|
||||
languageLevel: LanguageLevel,
|
||||
) : PyUnresolvedReferencesVisitor(holder, ignoredIdentifiers, inspection, context, languageLevel) {
|
||||
) : PyUnresolvedReferencesVisitor(holder, ignoredIdentifiers, context, languageLevel) {
|
||||
|
||||
override fun getInstallPackageQuickFixes(
|
||||
node: PyElement,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#noinspection PyUnresolvedReferences
|
||||
#noinspection PyUnusedImports
|
||||
import sys
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#noinspection PyUnresolvedReferences
|
||||
#noinspection PyUnusedImports
|
||||
import sys
|
||||
|
||||
+6
@@ -932,4 +932,10 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase {
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
return PyUnresolvedReferencesInspection.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureInspection() {
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class, PyUnusedImportsInspection.class);
|
||||
myFixture.checkHighlighting(isWarning(), isInfo(), isWeakWarning());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class PyiInspectionsTest extends PyTestCase {
|
||||
}
|
||||
|
||||
public void testPyiUnusedImports() {
|
||||
doPyiTest(PyUnresolvedReferencesInspection.class);
|
||||
doPyiTest(PyUnusedImportsInspection.class);
|
||||
}
|
||||
|
||||
public void testPyiRelativeImports() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user