Skip to content

Commit 9c2ca9a

Browse files
author
Esben Sparre Andreasen
committed
JS: make js/unused-local-variable flag import statements
1 parent c65bc5c commit 9c2ca9a

File tree

4 files changed

+93
-27
lines changed

4 files changed

+93
-27
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
3636
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
3737
| Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. |
38+
| Unused variable, import, function or class | Fewer results | This rule now flags import statements with multiple unused imports once. |
3839
| User-controlled bypass of security check | Fewer results | This rule no longer flags conditions that guard early returns. The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
3940
| Whitespace contradicts operator precedence | Fewer false-positive results | This rule no longer flags operators with asymmetric whitespace. |
4041

javascript/ql/src/Declarations/UnusedVariable.ql

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -94,36 +94,95 @@ predicate isEnumMember(VarDecl decl) {
9494
}
9595

9696
/**
97-
* Gets a description of the declaration `vd`, which is either of the form "function f" if
98-
* it is a function name, or "variable v" if it is not.
97+
* Gets a description of the declaration `vd`, which is either of the form
98+
* "function f", "variable v" or "class c".
9999
*/
100-
string describe(VarDecl vd) {
100+
string describeVarDecl(VarDecl vd) {
101101
if vd = any(Function f).getId() then
102102
result = "function " + vd.getName()
103103
else if vd = any(ClassDefinition c).getIdentifier() then
104104
result = "class " + vd.getName()
105-
else if (vd = any(ImportSpecifier im).getLocal() or vd = any(ImportEqualsDeclaration im).getId()) then
106-
result = "import " + vd.getName()
107105
else
108106
result = "variable " + vd.getName()
109107
}
110108

111-
from VarDecl vd, UnusedLocal v
112-
where v = vd.getVariable() and
113-
// exclude variables mentioned in JSDoc comments in externs
114-
not mentionedInJSDocComment(v) and
115-
// exclude variables used to filter out unwanted properties
116-
not isPropertyFilter(v) and
117-
// exclude imports of React that are implicitly referenced by JSX
118-
not isReactImportForJSX(v) and
119-
// exclude names that are used as types
120-
not isUsedAsType(vd) and
121-
// exclude names that are used as namespaces from inside a type
122-
not isUsedAsNamespace(vd) and
123-
// exclude decorated functions and classes
124-
not isDecorated(vd) and
125-
// exclude names of enum members; they also define property names
126-
not isEnumMember(vd) and
127-
// ignore ambient declarations - too noisy
128-
not vd.isAmbient()
129-
select vd, "Unused " + describe(vd) + "."
109+
/**
110+
* An import statement that provides variable declarations.
111+
*/
112+
class ImportVarDeclProvider extends Stmt {
113+
114+
ImportVarDeclProvider() {
115+
this instanceof ImportDeclaration or
116+
this instanceof ImportEqualsDeclaration
117+
}
118+
119+
/**
120+
* Gets a variable declaration of this import.
121+
*/
122+
VarDecl getAVarDecl() {
123+
result = this.(ImportDeclaration).getASpecifier().getLocal() or
124+
result = this.(ImportEqualsDeclaration).getId()
125+
}
126+
127+
/**
128+
* Gets an unacceptable unused variable declared by this import.
129+
*/
130+
UnusedLocal getAnUnacceptableUnusedLocal() {
131+
result = getAVarDecl().getVariable() and
132+
not whitelisted(result)
133+
}
134+
135+
}
136+
137+
/**
138+
* Holds if it is acceptable that `v` is unused.
139+
*/
140+
predicate whitelisted(UnusedLocal v) {
141+
// exclude variables mentioned in JSDoc comments in externs
142+
mentionedInJSDocComment(v) or
143+
// exclude variables used to filter out unwanted properties
144+
isPropertyFilter(v) or
145+
// exclude imports of React that are implicitly referenced by JSX
146+
isReactImportForJSX(v) or
147+
// exclude names that are used as types
148+
exists (VarDecl vd |
149+
v = vd.getVariable() |
150+
isUsedAsType(vd) or
151+
// exclude names that are used as namespaces from inside a type
152+
isUsedAsNamespace(vd)or
153+
// exclude decorated functions and classes
154+
isDecorated(vd) or
155+
// exclude names of enum members; they also define property names
156+
isEnumMember(vd) or
157+
// ignore ambient declarations - too noisy
158+
vd.isAmbient()
159+
)
160+
}
161+
162+
/**
163+
* Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`.
164+
*/
165+
predicate unusedNonImports(VarDecl vd, string msg) {
166+
exists (UnusedLocal v |
167+
v = vd.getVariable() and
168+
msg = "Unused " + describeVarDecl(vd) + "." and
169+
not vd = any(ImportVarDeclProvider p).getAVarDecl() and
170+
not whitelisted(v)
171+
)
172+
}
173+
174+
/**
175+
* Holds if `provider` declares one or more unused variables, as explained by `msg`.
176+
*/
177+
predicate unusedImports(ImportVarDeclProvider provider, string msg) {
178+
exists (string imports, string names |
179+
imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and
180+
names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and
181+
msg = "Unused " + imports + " " + names + "."
182+
)
183+
}
184+
185+
from ASTNode sel, string msg
186+
where unusedNonImports(sel, msg) or
187+
unusedImports(sel, msg)
188+
select sel, msg
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
| decorated.ts:1:9:1:21 | actionHandler | Unused import actionHandler. |
1+
| decorated.ts:1:1:1:126 | import ... where'; | Unused import actionHandler. |
22
| decorated.ts:4:10:4:12 | fun | Unused function fun. |
33
| externs.js:6:5:6:13 | iAmUnused | Unused variable iAmUnused. |
4+
| multi-imports.js:1:1:1:29 | import ... om 'x'; | Unused imports a, b, d. |
5+
| multi-imports.js:2:1:2:42 | import ... om 'x'; | Unused imports alphabetically, ordered. |
46
| typeoftype.ts:9:7:9:7 | y | Unused variable y. |
5-
| unusedShadowed.ts:1:8:1:8 | T | Unused import T. |
6-
| unusedShadowed.ts:2:8:2:13 | object | Unused import object. |
7+
| unusedShadowed.ts:1:1:1:26 | import ... where'; | Unused import T. |
8+
| unusedShadowed.ts:2:1:2:31 | import ... where'; | Unused import object. |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {a, b, c, d} from 'x';
2+
import {ordered, alphabetically} from 'x';
3+
4+
c();

0 commit comments

Comments
 (0)