mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-959 Inspection to detect unused inner lambdas/functions
This commit is contained in:
@@ -173,11 +173,12 @@ INSP.init.incompatible.to.new=Signature is not compatible to __new__
|
||||
# PyTrailingSemicolonInspection
|
||||
INSP.NAME.trailing.semicolon=Trailing semicolon in statement
|
||||
|
||||
# PyUnusedLocalVariableInspection
|
||||
INSP.NAME.unused=Unused local variable
|
||||
# PyUnusedLocalInspection
|
||||
INSP.NAME.unused=Unused local
|
||||
INSP.unused.locals.parameter.isnot.used=Parameter ''{0}'' value is not used
|
||||
INSP.unused.locals.local.variable.isnot.used=Local variable ''{0}'' value is not used
|
||||
INSP.unused.locals.replace.with.wildcard=Replace with _
|
||||
INSP.unused.locals.local.function.isnot.used=Local function ''{0}'' is not used
|
||||
|
||||
# PyUnboundLocalVariableInspection
|
||||
INSP.NAME.unbound=Unbound local variable
|
||||
|
||||
@@ -9,10 +9,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
* @author oleg
|
||||
*/
|
||||
public class PyControlFlowUtil {
|
||||
public static void iterateWriteAccessFor(@NotNull final String variableName,
|
||||
final int statInstruction,
|
||||
@NotNull final Instruction[] instructions,
|
||||
@NotNull final Function<ReadWriteInstruction, Operation> closure) {
|
||||
public static void iteratePrev(final int statInstruction,
|
||||
@NotNull final Instruction[] instructions,
|
||||
@NotNull final Function<Instruction, Operation> closure) {
|
||||
final ControlFlowUtil.Stack stack = new ControlFlowUtil.Stack(instructions.length);
|
||||
final boolean[] visited = new boolean[instructions.length];
|
||||
|
||||
@@ -24,16 +23,11 @@ public class PyControlFlowUtil {
|
||||
}
|
||||
visited[num] = true;
|
||||
final Instruction instr = instructions[num];
|
||||
if (instr instanceof ReadWriteInstruction) {
|
||||
final ReadWriteInstruction rwInstr = (ReadWriteInstruction)instr;
|
||||
if (variableName.equals(rwInstr.getName()) && rwInstr.getAccess().isWriteAccess()){
|
||||
final Operation nextOperation = closure.fun(rwInstr);
|
||||
if (nextOperation == Operation.CONTINUE) {
|
||||
continue;
|
||||
} else if (nextOperation == Operation.BREAK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
final Operation nextOperation = closure.fun(instr);
|
||||
if (nextOperation == Operation.CONTINUE) {
|
||||
continue;
|
||||
} else if (nextOperation == Operation.BREAK) {
|
||||
break;
|
||||
}
|
||||
for (Instruction pred : instr.allPred()) {
|
||||
stack.push(pred.num());
|
||||
|
||||
+4
-4
@@ -13,8 +13,8 @@ import javax.swing.*;
|
||||
/**
|
||||
* @author oleg
|
||||
*/
|
||||
public class PyUnusedLocalVariableInspection extends PyInspection {
|
||||
private final ThreadLocal<PyUnusedLocalVariableInspectionVisitor> myLastVisitor = new ThreadLocal<PyUnusedLocalVariableInspectionVisitor>();
|
||||
public class PyUnusedLocalInspection extends PyInspection {
|
||||
private final ThreadLocal<PyUnusedLocalInspectionVisitor> myLastVisitor = new ThreadLocal<PyUnusedLocalInspectionVisitor>();
|
||||
|
||||
public boolean ignoreTupleUnpacking = true;
|
||||
|
||||
@@ -26,14 +26,14 @@ public class PyUnusedLocalVariableInspection extends PyInspection {
|
||||
|
||||
@NotNull
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
final PyUnusedLocalVariableInspectionVisitor visitor = new PyUnusedLocalVariableInspectionVisitor(holder, ignoreTupleUnpacking);
|
||||
final PyUnusedLocalInspectionVisitor visitor = new PyUnusedLocalInspectionVisitor(holder, ignoreTupleUnpacking);
|
||||
myLastVisitor.set(visitor);
|
||||
return visitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inspectionFinished(LocalInspectionToolSession session) {
|
||||
final PyUnusedLocalVariableInspectionVisitor visitor = myLastVisitor.get();
|
||||
final PyUnusedLocalInspectionVisitor visitor = myLastVisitor.get();
|
||||
assert visitor != null;
|
||||
visitor.registerProblems();
|
||||
myLastVisitor.remove();
|
||||
+68
-39
@@ -36,12 +36,12 @@ import java.util.Set;
|
||||
/**
|
||||
* @author oleg
|
||||
*/
|
||||
class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor {
|
||||
class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor {
|
||||
private final boolean myIgnoreTupleUnpacking;
|
||||
private final HashSet<PsiElement> myUnusedElements;
|
||||
private final HashSet<PsiElement> myUsedElements;
|
||||
|
||||
public PyUnusedLocalVariableInspectionVisitor(final ProblemsHolder holder, boolean ignoreTupleUnpacking) {
|
||||
public PyUnusedLocalInspectionVisitor(final ProblemsHolder holder, boolean ignoreTupleUnpacking) {
|
||||
super(holder);
|
||||
myIgnoreTupleUnpacking = ignoreTupleUnpacking;
|
||||
myUnusedElements = new HashSet<PsiElement>();
|
||||
@@ -81,14 +81,18 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor {
|
||||
// Iteration over write accesses
|
||||
for (int i = 0; i < instructions.length; i++) {
|
||||
final Instruction instruction = instructions[i];
|
||||
if (instruction instanceof ReadWriteInstruction) {
|
||||
final PsiElement element = instruction.getElement();
|
||||
if (element instanceof PyFunction && owner instanceof PyFunction){
|
||||
if (!myUsedElements.contains(element)){
|
||||
myUnusedElements.add(element);
|
||||
}
|
||||
}
|
||||
else if (instruction instanceof ReadWriteInstruction) {
|
||||
final String name = ((ReadWriteInstruction)instruction).getName();
|
||||
// Ignore empty, wildcards or global names
|
||||
if (name == null || "_".equals(name) || scope.isGlobal(name)) {
|
||||
continue;
|
||||
}
|
||||
final PsiElement element = instruction.getElement();
|
||||
|
||||
// Ignore elements out of scope
|
||||
if (element == null || !PsiTreeUtil.isAncestor(node, element, false)){
|
||||
continue;
|
||||
@@ -151,20 +155,36 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor {
|
||||
}
|
||||
|
||||
PyControlFlowUtil
|
||||
.iterateWriteAccessFor(name, number, instructions, new Function<ReadWriteInstruction, PyControlFlowUtil.Operation>() {
|
||||
public PyControlFlowUtil.Operation fun(final ReadWriteInstruction rwInstr) {
|
||||
final PsiElement instrElement = rwInstr.getElement();
|
||||
// Ignore elements out of scope
|
||||
if (instrElement == null || !PsiTreeUtil.isAncestor(node, instrElement, false)){
|
||||
.iteratePrev(number, instructions, new Function<Instruction, PyControlFlowUtil.Operation>() {
|
||||
public PyControlFlowUtil.Operation fun(final Instruction inst) {
|
||||
final PsiElement element = inst.getElement();
|
||||
// Mark function as used
|
||||
if (element instanceof PyFunction){
|
||||
if (name.equals(((PyFunction)element).getName())){
|
||||
myUsedElements.add(element);
|
||||
myUnusedElements.remove(element);
|
||||
return PyControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
}
|
||||
// Mark write access as used
|
||||
else if (inst instanceof ReadWriteInstruction) {
|
||||
final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)inst;
|
||||
if (!name.equals(rwInstruction.getName()) || !rwInstruction.getAccess().isWriteAccess()) {
|
||||
return PyControlFlowUtil.Operation.NEXT;
|
||||
}
|
||||
// Ignore elements out of scope
|
||||
if (element == null || !PsiTreeUtil.isAncestor(node, element, false)) {
|
||||
return PyControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
myUsedElements.add(element);
|
||||
myUnusedElements.remove(element);
|
||||
// In case when assignment is inside try part we should move further
|
||||
if (PsiTreeUtil.getParentOfType(element, PyTryPart.class) != null) {
|
||||
return PyControlFlowUtil.Operation.NEXT;
|
||||
}
|
||||
return PyControlFlowUtil.Operation.CONTINUE;
|
||||
}
|
||||
myUsedElements.add(instrElement);
|
||||
myUnusedElements.remove(instrElement);
|
||||
// In case when assignment is inside try part we should move further
|
||||
if (PsiTreeUtil.getParentOfType(instrElement, PyTryPart.class) != null){
|
||||
return PyControlFlowUtil.Operation.NEXT;
|
||||
}
|
||||
return PyControlFlowUtil.Operation.CONTINUE;
|
||||
return PyControlFlowUtil.Operation.NEXT;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -172,7 +192,7 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean callsLocals(ScopeOwner owner) {
|
||||
private static boolean callsLocals(final ScopeOwner owner) {
|
||||
try {
|
||||
owner.acceptChildren(new PyRecursiveElementVisitor(){
|
||||
@Override
|
||||
@@ -201,31 +221,40 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor {
|
||||
void registerProblems() {
|
||||
// Register problems
|
||||
for (PsiElement element : myUnusedElements) {
|
||||
final String name = element.getText();
|
||||
if (element instanceof PyNamedParameter || element.getParent() instanceof PyNamedParameter) {
|
||||
// Ignore unused self parameters as obligatory
|
||||
if ("self".equals(name) && PyPsiUtils.isMethodContext(element)) {
|
||||
continue;
|
||||
}
|
||||
// cls for @classmethod decorated methods
|
||||
if ("cls".equals(name)) {
|
||||
final Set<PyFunction.Flag> flagSet = PyUtil.detectDecorationsAndWrappersOf(PsiTreeUtil.getParentOfType(element, PyFunction.class));
|
||||
if (flagSet.contains(PyFunction.Flag.CLASSMETHOD)) {
|
||||
// Local function
|
||||
if (element instanceof PyFunction){
|
||||
registerWarning(((PyFunction)element).getNameIdentifier(),
|
||||
PyBundle.message("INSP.unused.locals.local.function.isnot.used",
|
||||
((PyFunction)element).getName()));
|
||||
}
|
||||
// Local variable or parameter
|
||||
else {
|
||||
final String name = element.getText();
|
||||
if (element instanceof PyNamedParameter || element.getParent() instanceof PyNamedParameter) {
|
||||
// Ignore unused self parameters as obligatory
|
||||
if ("self".equals(name) && PyPsiUtils.isMethodContext(element)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
registerWarning(element, PyBundle.message("INSP.unused.locals.parameter.isnot.used", name));
|
||||
}
|
||||
else {
|
||||
if (myIgnoreTupleUnpacking && isTupleUnpacking(element)) {
|
||||
continue;
|
||||
}
|
||||
if (PyForStatementNavigator.getPyForStatementByIterable(element) != null) {
|
||||
registerProblem(element, PyBundle.message("INSP.unused.locals.local.variable.isnot.used", name),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, new ReplaceWithWildCard());
|
||||
// cls for @classmethod decorated methods
|
||||
if ("cls".equals(name)) {
|
||||
final Set<PyFunction.Flag> flagSet = PyUtil.detectDecorationsAndWrappersOf(PsiTreeUtil.getParentOfType(element, PyFunction.class));
|
||||
if (flagSet.contains(PyFunction.Flag.CLASSMETHOD)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
registerWarning(element, PyBundle.message("INSP.unused.locals.parameter.isnot.used", name));
|
||||
}
|
||||
else {
|
||||
registerWarning(element, PyBundle.message("INSP.unused.locals.local.variable.isnot.used", name));
|
||||
if (myIgnoreTupleUnpacking && isTupleUnpacking(element)) {
|
||||
continue;
|
||||
}
|
||||
if (PyForStatementNavigator.getPyForStatementByIterable(element) != null) {
|
||||
registerProblem(element, PyBundle.message("INSP.unused.locals.local.variable.isnot.used", name),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL, null, new ReplaceWithWildCard());
|
||||
}
|
||||
else {
|
||||
registerWarning(element, PyBundle.message("INSP.unused.locals.local.variable.isnot.used", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider {
|
||||
PyInitNewSignatureInspection.class,
|
||||
PyTrailingSemicolonInspection.class,
|
||||
PyReturnFromInitInspection.class,
|
||||
PyUnusedLocalVariableInspection.class,
|
||||
PyUnusedLocalInspection.class,
|
||||
PyDeprecatedModulesInspection.class,
|
||||
PyDictCreationInspection.class,
|
||||
PyExceptClausesOrderInspection.class,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<problems>
|
||||
<problem>
|
||||
<file>test.py</file>
|
||||
<line>2</line>
|
||||
<description>"Local function 'bar' is not used"</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.py</file>
|
||||
<line>13</line>
|
||||
<description>"Local function 'bar1' is not used"</description>
|
||||
</problem>
|
||||
</problems>
|
||||
@@ -0,0 +1,15 @@
|
||||
def foo():
|
||||
def bar(): pass #fail
|
||||
|
||||
|
||||
def baz(cond):
|
||||
def bzz(): pass #pass
|
||||
if cond:
|
||||
return bzz
|
||||
else:
|
||||
return None
|
||||
|
||||
def bar():
|
||||
def bar1():pass #fail
|
||||
def bar1():pass #pass
|
||||
return bar1
|
||||
@@ -36,6 +36,10 @@
|
||||
<description>"Local variable 'i' value is not used"</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.py</file>
|
||||
<line>51</line>
|
||||
<description>"Local function 'test' is not used"</description>
|
||||
</problem> <problem>
|
||||
<file>test.py</file>
|
||||
<line>83</line>
|
||||
<description>"Parameter 'x' value is not used"</description>
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
|
||||
import com.jetbrains.python.inspections.PyInspection;
|
||||
import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection;
|
||||
import com.jetbrains.python.inspections.PyUnusedLocalVariableInspection;
|
||||
import com.jetbrains.python.inspections.PyUnusedLocalInspection;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -23,7 +23,7 @@ public class PySuppressInspectionsTest extends PyLightFixtureTestCase {
|
||||
}
|
||||
|
||||
public void testSuppressedUnusedLocal() throws Exception {
|
||||
doTestHighlighting(PyUnusedLocalVariableInspection.class);
|
||||
doTestHighlighting(PyUnusedLocalInspection.class);
|
||||
}
|
||||
|
||||
private void doTestHighlighting(final Class<? extends PyInspection> inspectionClass) throws Exception {
|
||||
|
||||
@@ -87,13 +87,18 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase {
|
||||
}
|
||||
|
||||
public void testPyUnusedLocalVariableInspection() {
|
||||
PyUnusedLocalVariableInspection inspection = new PyUnusedLocalVariableInspection();
|
||||
PyUnusedLocalInspection inspection = new PyUnusedLocalInspection();
|
||||
inspection.ignoreTupleUnpacking = false;
|
||||
doTest(getTestName(false), inspection);
|
||||
}
|
||||
|
||||
public void testPyUnusedVariableTupleUnpacking() {
|
||||
doHighlightingTest(PyUnusedLocalVariableInspection.class);
|
||||
doHighlightingTest(PyUnusedLocalInspection.class);
|
||||
}
|
||||
|
||||
public void testPyUnusedLocalFunctionInspection() {
|
||||
PyUnusedLocalInspection inspection = new PyUnusedLocalInspection();
|
||||
doTest(getTestName(false), inspection);
|
||||
}
|
||||
|
||||
public void testPyDictCreationInspection() throws Throwable {
|
||||
|
||||
Reference in New Issue
Block a user