Skip to content

Commit 212edc2

Browse files
author
Max Schaefer
authored
Merge pull request #307 from esben-semmle/js/unused-import
JS: make js/unused-local-variable flag import statements
2 parents 135271e + 9c2ca9a commit 212edc2

File tree

10 files changed

+115
-27
lines changed

10 files changed

+115
-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

javascript/ql/src/semmle/javascript/Util.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,16 @@ bindingset[s]
1111
string capitalize(string s) {
1212
result = s.charAt(0).toUpperCase() + s.suffix(1)
1313
}
14+
15+
/**
16+
* Gets the pluralization for `n` occurrences of `noun`.
17+
*
18+
* For example, the pluralization of `"function"` for `n = 2` is `"functions"`.
19+
*/
20+
bindingset[noun, n]
21+
string pluralize(string noun, int n) {
22+
if n = 1 then
23+
result = noun
24+
else
25+
result = noun + "s"
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| X | X | Xx | XX | Xx | XX |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import semmle.javascript.Util
2+
3+
select capitalize("x"), capitalize("X"), capitalize("xx"), capitalize("XX"), capitalize("Xx"), capitalize("xX")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| xs | x | xs | xs |
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import semmle.javascript.Util
2+
3+
select pluralize("x", 0), pluralize("x", 1), pluralize("x", 2), pluralize("x", -1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// used by qltest to identify the language
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)