Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fc00e0a

Browse files
authored
Merge pull request #796 from markshannon/python-import-used-in-doctest
Python: Fix 'unused import' for doctests and typehints.
2 parents 247d615 + 4ef3f46 commit fc00e0a

4 files changed

Lines changed: 53 additions & 0 deletions

File tree

change-notes/1.20/analysis-python.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Removes false positives seen when using Python 3.6, but not when using earlier v
2020

2121
| **Query** | **Expected impact** | **Change** |
2222
|----------------------------|------------------------|------------------------------------------------------------------|
23+
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a `doctest` string are no longer reported. |
24+
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a type-hint comment are no longer reported. |
2325

2426
## Changes to code extraction
2527

python/ql/src/Imports/UnusedImport.ql

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,37 @@ predicate all_not_understood(Module m) {
4141
)
4242
}
4343

44+
predicate imported_module_used_in_doctest(Import imp) {
45+
exists(string modname |
46+
imp.getAName().getAsname().(Name).getId() = modname
47+
and
48+
/* Look for doctests containing the patterns:
49+
* >>> …name…
50+
* ... …name…
51+
*/
52+
exists(StrConst doc |
53+
doc.getEnclosingModule() = imp.getScope() and
54+
doc.isDocString() and
55+
doc.getText().regexpMatch("[\\s\\S]*(>>>|\\.\\.\\.).*" + modname + "[\\s\\S]*")
56+
)
57+
)
58+
}
59+
60+
predicate 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+
*/
67+
exists(Comment typehint |
68+
typehint.getLocation().getFile() = imp.getScope().(Module).getFile() and
69+
typehint.getText().regexpMatch("# type:.*" + modname + ".*")
70+
)
71+
)
72+
}
73+
74+
4475
predicate unused_import(Import imp, Variable name) {
4576
((Name)imp.getAName().getAsname()).getVariable() = name
4677
and
@@ -65,6 +96,10 @@ predicate unused_import(Import imp, Variable name) {
6596
and
6697
/* Assume that opaque `__all__` includes imported module */
6798
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)
68103
}
69104

70105

python/ql/src/semmle/python/strings.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ string repr(Expr e) {
5757
else
5858
result = e.toString()
5959
}
60+

python/ql/test/query-tests/Imports/unused/imports_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,18 @@ def __init__(self):
6161
#Use it
6262
different
6363

64+
import used_in_doctest
65+
66+
def f():
67+
'''
68+
>>> unrelated
69+
>>> used_in_doctest.thing() == f()
70+
True
71+
'''
72+
return 5
73+
74+
#Used in Python2 type hint
75+
import typing
76+
77+
foo = None # type: typing.Optional[int]
78+

0 commit comments

Comments
 (0)