mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-20805 PY-8219 Detect usages of local variables inside f-strings
Additionally, as a positive side-effect, "Unused local" inspection no longer warns about function parameters that appear in doctests now.
This commit is contained in:
@@ -19,11 +19,15 @@ import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
|
||||
import com.intellij.codeInsight.controlflow.Instruction;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
@@ -44,9 +48,12 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
|
||||
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author oleg
|
||||
*/
|
||||
@@ -95,6 +102,38 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor {
|
||||
collectUsedReads(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyStringLiteralExpression(PyStringLiteralExpression pyString) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(pyString);
|
||||
if (owner != null && !(owner instanceof PsiFile)) {
|
||||
final PyStatement instrAnchor = PsiTreeUtil.getParentOfType(pyString, PyStatement.class);
|
||||
if (instrAnchor == null) return;
|
||||
final Instruction[] instructions = ControlFlowCache.getControlFlow(owner).getInstructions();
|
||||
final int startInstruction = ControlFlowUtil.findInstructionNumberByElement(instructions, instrAnchor);
|
||||
if (startInstruction < 0) return;
|
||||
final Project project = pyString.getProject();
|
||||
final List<Pair<PsiElement, TextRange>> pairs = InjectedLanguageManager.getInstance(project).getInjectedPsiFiles(pyString);
|
||||
if (pairs != null) {
|
||||
for (Pair<PsiElement, TextRange> pair : pairs) {
|
||||
pair.getFirst().accept(new PyRecursiveElementVisitor() {
|
||||
@Override
|
||||
public void visitPyReferenceExpression(PyReferenceExpression expr) {
|
||||
final PyExpression qualifier = expr.getQualifier();
|
||||
if (qualifier != null) {
|
||||
qualifier.accept(this);
|
||||
return;
|
||||
}
|
||||
final String name = expr.getName();
|
||||
if (name != null) {
|
||||
analyzeReadsInScope(name, owner, instructions, startInstruction, pyString);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectAllWrites(ScopeOwner owner) {
|
||||
final Instruction[] instructions = ControlFlowCache.getControlFlow(owner).getInstructions();
|
||||
for (Instruction instruction : instructions) {
|
||||
@@ -180,46 +219,53 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor {
|
||||
else {
|
||||
startInstruction = i;
|
||||
}
|
||||
// Check if the element is declared out of scope, mark all out of scope write accesses as used
|
||||
if (element instanceof PyReferenceExpression) {
|
||||
final PyReferenceExpression ref = (PyReferenceExpression)element;
|
||||
final ScopeOwner declOwner = ScopeUtil.getDeclarationScopeOwner(ref, name);
|
||||
if (declOwner != null && declOwner != owner) {
|
||||
Collection<PsiElement> writeElements = ScopeUtil.getReadWriteElements(name, declOwner, false, true);
|
||||
for (PsiElement e : writeElements) {
|
||||
myUsedElements.add(e);
|
||||
myUnusedElements.remove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlowUtil.iteratePrev(startInstruction, instructions, inst -> {
|
||||
final PsiElement element1 = inst.getElement();
|
||||
// Mark function as used
|
||||
if (element1 instanceof PyFunction) {
|
||||
if (name.equals(((PyFunction)element1).getName())){
|
||||
myUsedElements.add(element1);
|
||||
myUnusedElements.remove(element1);
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
// Mark write access as used
|
||||
else if (inst instanceof ReadWriteInstruction) {
|
||||
final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)inst;
|
||||
if (rwInstruction.getAccess().isWriteAccess() && name.equals(rwInstruction.getName())) {
|
||||
// For elements in scope
|
||||
if (element1 != null && PsiTreeUtil.isAncestor(owner, element1, false)) {
|
||||
myUsedElements.add(element1);
|
||||
myUnusedElements.remove(element1);
|
||||
}
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
analyzeReadsInScope(name, owner, instructions, startInstruction, as(element, PyReferenceExpression.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeReadsInScope(@NotNull String name,
|
||||
@NotNull ScopeOwner owner,
|
||||
@NotNull Instruction[] instructions,
|
||||
int startInstruction,
|
||||
@Nullable PsiElement scopeAnchor) {
|
||||
// Check if the element is declared out of scope, mark all out of scope write accesses as used
|
||||
if (scopeAnchor != null) {
|
||||
final ScopeOwner declOwner = ScopeUtil.getDeclarationScopeOwner(scopeAnchor, name);
|
||||
if (declOwner != null && declOwner != owner) {
|
||||
final Collection<PsiElement> writeElements = ScopeUtil.getReadWriteElements(name, declOwner, false, true);
|
||||
for (PsiElement e : writeElements) {
|
||||
myUsedElements.add(e);
|
||||
myUnusedElements.remove(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControlFlowUtil.iteratePrev(startInstruction, instructions, inst -> {
|
||||
final PsiElement instElement = inst.getElement();
|
||||
// Mark function as used
|
||||
if (instElement instanceof PyFunction) {
|
||||
if (name.equals(((PyFunction)instElement).getName())){
|
||||
myUsedElements.add(instElement);
|
||||
myUnusedElements.remove(instElement);
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
// Mark write access as used
|
||||
else if (inst instanceof ReadWriteInstruction) {
|
||||
final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)inst;
|
||||
if (rwInstruction.getAccess().isWriteAccess() && name.equals(rwInstruction.getName())) {
|
||||
// For elements in scope
|
||||
if (instElement != null && PsiTreeUtil.isAncestor(owner, instElement, false)) {
|
||||
myUsedElements.add(instElement);
|
||||
myUnusedElements.remove(instElement);
|
||||
}
|
||||
return ControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
return ControlFlowUtil.Operation.NEXT;
|
||||
});
|
||||
}
|
||||
|
||||
static class DontPerformException extends RuntimeException {}
|
||||
|
||||
private static boolean callsLocals(final ScopeOwner owner) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
def f(x, <weak_warning descr="Parameter 'y' value is not used">y</weak_warning>):
|
||||
"""
|
||||
>>> print(x)
|
||||
"""
|
||||
|
||||
|
||||
def bar():
|
||||
def tokenize():
|
||||
"""
|
||||
>>> tokenize()
|
||||
['foo', Escaped('='), 'bar', Escaped('\\'), 'baz']
|
||||
|
||||
"""
|
||||
@@ -0,0 +1,37 @@
|
||||
def simple(x):
|
||||
y = 1
|
||||
print(f"{x}{y}")
|
||||
|
||||
|
||||
def annotations(x, y):
|
||||
def g(x: f'{x}') -> f'{y}':
|
||||
return x
|
||||
|
||||
g()
|
||||
|
||||
|
||||
def default_value(x):
|
||||
def g(x=f'{x}'):
|
||||
return x
|
||||
|
||||
g()
|
||||
|
||||
|
||||
def super_classes(x):
|
||||
class C(f'{x}'):
|
||||
pass
|
||||
|
||||
C()
|
||||
|
||||
|
||||
def qualified_names():
|
||||
<weak_warning descr="Local variable 'foo' value is not used">foo</weak_warning> = 42
|
||||
bar = undefined()
|
||||
print(f'{bar.foo}')
|
||||
|
||||
|
||||
def nested(x):
|
||||
def g():
|
||||
print(f'{x}')
|
||||
|
||||
g()
|
||||
@@ -113,6 +113,16 @@ public class PythonInspectionsTest extends PyTestCase {
|
||||
doHighlightingTest(PyUnusedLocalInspection.class, LanguageLevel.PYTHON34);
|
||||
}
|
||||
|
||||
// PY-20805
|
||||
public void testUnusedLocalFStringReferences() {
|
||||
doHighlightingTest(PyUnusedLocalInspection.class, LanguageLevel.PYTHON36);
|
||||
}
|
||||
|
||||
// PY-8219
|
||||
public void testUnusedLocalDoctestReference() {
|
||||
doHighlightingTest(PyUnusedLocalInspection.class);
|
||||
}
|
||||
|
||||
public void testPyDictCreationInspection() {
|
||||
doHighlightingTest(PyDictCreationInspection.class, LanguageLevel.PYTHON26);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user