Files
codeql/python/ql/src/Imports/UnusedImport.ql
2025-10-30 13:58:59 +00:00

154 lines
5.2 KiB
Plaintext

/**
* @name Unused import
* @description Import is not required as it is not used
* @kind problem
* @tags quality
* maintainability
* useless-code
* @problem.severity recommendation
* @sub-severity high
* @precision very-high
* @id py/unused-import
*/
import python
private import LegacyPointsTo
import Variables.Definition
import semmle.python.ApiGraphs
private predicate is_pytest_fixture(Import imp, Variable name) {
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
pytest_fixture = API::moduleImport("pytest").getMember("fixture") and
// The additional `.getReturn()` is to account for the difference between
// ```
// @pytest.fixture
// def foo():
// ...
// ```
// and
// ```
// @pytest.fixture(some, args, here)
// def foo():
// ...
// ```
decorator in [pytest_fixture, pytest_fixture.getReturn()] and
a = imp.getAName() and
a.getAsname().(Name).getVariable() = name and
a.getValue() = decorator.getReturn().getAValueReachableFromSource().asExpr()
)
}
predicate global_name_used(Module m, string name) {
exists(Name u, GlobalVariable v |
u.uses(v) and
v.getId() = name and
u.getEnclosingModule() = m
)
or
// A use of an undefined class local variable, will use the global variable
exists(Name u, LocalVariable v |
u.uses(v) and
v.getId() = name and
u.getEnclosingModule() = m and
not v.getScope().getEnclosingScope*() instanceof Function
)
}
/** Holds if a module has `__all__` but we don't understand it */
predicate all_not_understood(Module m) {
exists(GlobalVariable a | a.getId() = "__all__" and a.getScope() = m |
// `__all__` is not defined as a simple list
not m.declaredInAll(_)
or
// `__all__` is modified
exists(Call c | c.getFunc().(Attribute).getObject() = a.getALoad())
)
}
predicate imported_module_used_in_doctest(Import imp) {
exists(string modname, string docstring |
imp.getAName().getAsname().(Name).getId() = modname and
// Look for doctests containing the patterns:
// >>> …name…
// ... …name…
docstring = doctest_in_scope(imp.getScope()) and
docstring.regexpMatch("[\\s\\S]*(>>>|\\.\\.\\.).*" + modname + "[\\s\\S]*")
)
}
pragma[noinline]
private string doctest_in_scope(Scope scope) {
exists(StringLiteral doc |
doc.getEnclosingModule() = scope and
doc.isDocString() and
result = doc.getText() and
result.regexpMatch("[\\s\\S]*(>>>|\\.\\.\\.)[\\s\\S]*")
)
}
pragma[noinline]
private string typehint_annotation_in_module(Module module_scope) {
exists(StringLiteral annotation |
annotation = any(Arguments a).getAnAnnotation().getASubExpression*()
or
annotation = any(AnnAssign a).getAnnotation().getASubExpression*()
or
annotation = any(FunctionExpr f).getReturns().getASubExpression*()
|
annotation.(ExprWithPointsTo).pointsTo(Value::forString(result)) and
annotation.getEnclosingModule() = module_scope
)
}
pragma[noinline]
private string typehint_comment_in_file(File file) {
exists(Comment typehint |
file = typehint.getLocation().getFile() and
result = typehint.getText() and
result.matches("# type:%")
)
}
/** Holds if the imported alias `name` from `imp` is used in a typehint (in the same file as `imp`) */
predicate imported_alias_used_in_typehint(Import imp, Variable name) {
imp.getAName().getAsname().(Name).getVariable() = name and
exists(File file, Module module_scope |
module_scope = imp.getEnclosingModule() and
file = module_scope.getFile()
|
// Look for type hints containing the patterns:
// # type: …name…
typehint_comment_in_file(file).regexpMatch("# type:.*" + name.getId() + ".*")
or
// Type hint is inside a string annotation, as needed for forward references
typehint_annotation_in_module(module_scope).regexpMatch(".*\\b" + name.getId() + "\\b.*")
)
}
predicate unused_import(Import imp, Variable name) {
imp.getAName().getAsname().(Name).getVariable() = name and
not imp.getAnImportedModuleName() = "__future__" and
not imp.getEnclosingModule().declaredInAll(name.getId()) and
imp.getScope() = imp.getEnclosingModule() and
not global_name_used(imp.getScope(), name.getId()) and
// Imports in `__init__.py` are used to force module loading
not imp.getEnclosingModule().isPackageInit() and
// Name may be imported for use in epytext documentation
not exists(Comment cmt | cmt.getText().matches("%L{" + name.getId() + "}%") |
cmt.getLocation().getFile() = imp.getLocation().getFile()
) and
not name_acceptable_for_unused_variable(name) and
// Assume that opaque `__all__` includes imported module
not all_not_understood(imp.getEnclosingModule()) and
not imported_module_used_in_doctest(imp) and
not imported_alias_used_in_typehint(imp, name) and
not is_pytest_fixture(imp, name) and
// Only consider import statements that actually point-to something (possibly an unknown module).
// If this is not the case, it's likely that the import statement never gets executed.
imp.getAName().getValue().(ExprWithPointsTo).pointsTo(_)
}
from Stmt s, Variable name
where unused_import(s, name)
select s, "Import of '" + name.getId() + "' is not used."