@@ -14,13 +14,13 @@ import python
1414import Variables.Definition
1515
1616predicate global_name_used ( Module m , Variable name ) {
17- exists ( Name u , GlobalVariable v |
17+ exists ( Name u , GlobalVariable v |
1818 u .uses ( v ) and
1919 v .getId ( ) = name .getId ( ) and
2020 u .getEnclosingModule ( ) = m
2121 )
2222 or
23- /* A use of an undefined class local variable, will use the global variable */
23+ // A use of an undefined class local variable, will use the global variable
2424 exists ( Name u , LocalVariable v |
2525 u .uses ( v ) and
2626 v .getId ( ) = name .getId ( ) and
@@ -31,24 +31,21 @@ predicate global_name_used(Module m, Variable name) {
3131
3232/** Holds if a module has `__all__` but we don't understand it */
3333predicate all_not_understood ( Module m ) {
34- exists ( GlobalVariable a |
35- a .getId ( ) = "__all__" and a .getScope ( ) = m |
36- /* __all__ is not defined as a simple list */
34+ exists ( GlobalVariable a | a .getId ( ) = "__all__" and a .getScope ( ) = m |
35+ // `__all__` is not defined as a simple list
3736 not m .declaredInAll ( _)
3837 or
39- /* __all__ is modified */
38+ // ` __all__` is modified
4039 exists ( Call c | c .getFunc ( ) .( Attribute ) .getObject ( ) = a .getALoad ( ) )
4140 )
4241}
4342
4443predicate imported_module_used_in_doctest ( Import imp ) {
4544 exists ( string modname |
46- imp .getAName ( ) .getAsname ( ) .( Name ) .getId ( ) = modname
47- and
48- /* Look for doctests containing the patterns:
49- * >>> …name…
50- * ... …name…
51- */
45+ imp .getAName ( ) .getAsname ( ) .( Name ) .getId ( ) = modname and
46+ // Look for doctests containing the patterns:
47+ // >>> …name…
48+ // ... …name…
5249 exists ( StrConst doc |
5350 doc .getEnclosingModule ( ) = imp .getScope ( ) and
5451 doc .isDocString ( ) and
@@ -58,52 +55,52 @@ predicate imported_module_used_in_doctest(Import imp) {
5855}
5956
6057predicate imported_module_used_in_typehint ( Import imp ) {
61- exists ( string modname |
62- imp .getAName ( ) .getAsname ( ) .( Name ) .getId ( ) = modname
63- and
64- /* Look for typehints containing the patterns:
65- * # type: …name…
66- */
58+ exists ( string modname , Location loc |
59+ imp .getAName ( ) .getAsname ( ) .( Name ) .getId ( ) = modname and
60+ loc . getFile ( ) = imp . getScope ( ) . ( Module ) . getFile ( )
61+ |
62+ // Look for type hints containing the patterns:
63+ // # type: …name…
6764 exists ( Comment typehint |
68- typehint . getLocation ( ) . getFile ( ) = imp . getScope ( ) . ( Module ) . getFile ( ) and
65+ loc = typehint . getLocation ( ) and
6966 typehint .getText ( ) .regexpMatch ( "# type:.*" + modname + ".*" )
7067 )
68+ or
69+ // Type hint is inside a string annotation, as needed for forward references
70+ exists ( string typehint , Expr annotation |
71+ annotation = any ( Arguments a ) .getAnAnnotation ( )
72+ or
73+ annotation = any ( AnnAssign a ) .getAnnotation ( )
74+ |
75+ annotation .pointsTo ( Value:: forString ( typehint ) ) and
76+ loc = annotation .getLocation ( ) and
77+ typehint .regexpMatch ( ".*\\b" + modname + "\\b.*" )
78+ )
7179 )
7280}
7381
74-
7582predicate unused_import ( Import imp , Variable name ) {
76- ( ( Name ) imp .getAName ( ) .getAsname ( ) ) .getVariable ( ) = name
77- and
78- not imp .getAnImportedModuleName ( ) = "__future__"
79- and
80- not imp .getEnclosingModule ( ) .declaredInAll ( name .getId ( ) )
81- and
82- imp .getScope ( ) = imp .getEnclosingModule ( )
83- and
84- not global_name_used ( imp .getScope ( ) , name )
85- and
86- /* Imports in __init__.py are used to force module loading */
87- not imp .getEnclosingModule ( ) .isPackageInit ( )
88- and
89- /* Name may be imported for use in epytext documentation */
90- not exists ( Comment cmt |
91- cmt .getText ( ) .matches ( "%L{" + name .getId ( ) + "}%" ) |
83+ imp .getAName ( ) .getAsname ( ) .( Name ) .getVariable ( ) = name and
84+ not imp .getAnImportedModuleName ( ) = "__future__" and
85+ not imp .getEnclosingModule ( ) .declaredInAll ( name .getId ( ) ) and
86+ imp .getScope ( ) = imp .getEnclosingModule ( ) and
87+ not global_name_used ( imp .getScope ( ) , name ) and
88+ // Imports in `__init__.py` are used to force module loading
89+ not imp .getEnclosingModule ( ) .isPackageInit ( ) and
90+ // Name may be imported for use in epytext documentation
91+ not exists ( Comment cmt | cmt .getText ( ) .matches ( "%L{" + name .getId ( ) + "}%" ) |
9292 cmt .getLocation ( ) .getFile ( ) = imp .getLocation ( ) .getFile ( )
93- )
94- and
95- not name_acceptable_for_unused_variable ( name )
96- and
97- /* Assume that opaque `__all__` includes imported module */
98- not all_not_understood ( imp .getEnclosingModule ( ) )
99- and
100- not imported_module_used_in_doctest ( imp )
101- and
102- not imported_module_used_in_typehint ( imp )
93+ ) and
94+ not name_acceptable_for_unused_variable ( name ) and
95+ // Assume that opaque `__all__` includes imported module
96+ not all_not_understood ( imp .getEnclosingModule ( ) ) and
97+ not imported_module_used_in_doctest ( imp ) and
98+ not imported_module_used_in_typehint ( imp ) and
99+ // Only consider import statements that actually point-to something (possibly an unknown module).
100+ // If this is not the case, it's likely that the import statement never gets executed.
101+ imp .getAName ( ) .getValue ( ) .pointsTo ( _)
103102}
104103
105-
106104from Stmt s , Variable name
107105where unused_import ( s , name )
108106select s , "Import of '" + name .getId ( ) + "' is not used."
109-
0 commit comments