mirror of
https://github.com/github/codeql.git
synced 2026-03-01 05:13:41 +01:00
154 lines
5.2 KiB
Plaintext
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."
|