Files
codeql/javascript/ql/src/Declarations/UnusedVariable.ql
2020-08-05 14:32:09 +00:00

200 lines
5.8 KiB
Plaintext

/**
* @name Unused variable, import, function or class
* @description Unused variables, imports, functions or classes may be a symptom of a bug
* and should be examined carefully.
* @kind problem
* @problem.severity recommendation
* @id js/unused-local-variable
* @tags maintainability
* @precision very-high
*/
import javascript
import UnusedVariable
/**
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
* contains externs declarations.
*/
predicate mentionedInJSDocComment(UnusedLocal v) {
exists(Externs ext, JSDoc jsdoc |
ext = v.getADeclaration().getTopLevel() and jsdoc.getComment().getTopLevel() = ext
|
jsdoc.getComment().getText().regexpMatch("(?s).*\\b" + v.getName() + "\\b.*")
)
}
/**
* Holds if `v` is declared in an object pattern that also contains a rest pattern.
*
* This is often used to filter out properties; for example, `var { x: v, ...props } = o`
* copies all properties of `o` into `props`, except for `x` which is copied into `v`.
*/
predicate isPropertyFilter(UnusedLocal v) {
exists(ObjectPattern op | exists(op.getRest()) |
op.getAPropertyPattern().getValuePattern() = v.getADeclaration()
)
}
predicate hasJsxInScope(UnusedLocal v) {
any(JSXNode n).getParent+() = v.getScope().getScopeElement()
}
/**
* Holds if `v` is a "React" variable that is implicitly used by a JSX element.
*/
predicate isReactForJSX(UnusedLocal v) {
hasJsxInScope(v) and
(
v.getName() = "React"
or
exists(TopLevel tl | tl = v.getADeclaration().getTopLevel() |
// legacy `@jsx` pragmas
exists(JSXPragma p | p.getTopLevel() = tl | p.getDOMName() = v.getName())
or
// JSX pragma from a .babelrc file
exists(Babel::TransformReactJsxConfig plugin |
plugin.appliesTo(tl) and
plugin.getJsxFactoryVariableName() = v.getName()
)
)
)
}
/**
* Holds if `decl` is both a variable declaration and a type declaration,
* and the declared type has a use.
*/
predicate isUsedAsType(VarDecl decl) { exists(decl.(TypeDecl).getLocalTypeName().getAnAccess()) }
/**
* Holds if `decl` declares a local alias for a namespace that is used from inside a type.
*/
predicate isUsedAsNamespace(VarDecl decl) {
exists(decl.(LocalNamespaceDecl).getLocalNamespaceName().getAnAccess())
}
/**
* Holds if the given identifier belongs to a decorated class or enum.
*/
predicate isDecorated(VarDecl decl) {
exists(ClassDefinition cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_)))
or
exists(EnumDeclaration cd | cd.getIdentifier() = decl | exists(cd.getDecorator(_)))
}
/**
* Holds if this is the name of an enum member.
*/
predicate isEnumMember(VarDecl decl) { decl = any(EnumMember member).getIdentifier() }
/**
* Gets a description of the declaration `vd`, which is either of the form
* "function f", "variable v" or "class c".
*/
string describeVarDecl(VarDecl vd) {
if vd = any(Function f).getIdentifier()
then result = "function " + vd.getName()
else
if vd = any(ClassDefinition c).getIdentifier()
then result = "class " + vd.getName()
else result = "variable " + vd.getName()
}
/**
* An import statement that provides variable declarations.
*/
class ImportVarDeclProvider extends Stmt {
ImportVarDeclProvider() {
this instanceof ImportDeclaration or
this instanceof ImportEqualsDeclaration
}
/**
* Gets a variable declaration of this import.
*/
VarDecl getAVarDecl() {
result = this.(ImportDeclaration).getASpecifier().getLocal() or
result = this.(ImportEqualsDeclaration).getIdentifier()
}
/**
* Gets an unacceptable unused variable declared by this import.
*/
UnusedLocal getAnUnacceptableUnusedLocal() {
result = getAVarDecl().getVariable() and
not whitelisted(result)
}
}
/**
* Holds if it is acceptable that `v` is unused.
*/
predicate whitelisted(UnusedLocal v) {
// exclude variables mentioned in JSDoc comments in externs
mentionedInJSDocComment(v)
or
// exclude variables used to filter out unwanted properties
isPropertyFilter(v)
or
// exclude imports of React that are implicitly referenced by JSX
isReactForJSX(v)
or
// exclude names that are used as types
exists(VarDecl vd | v = vd.getVariable() |
isUsedAsType(vd)
or
// exclude names that are used as namespaces from inside a type
isUsedAsNamespace(vd)
or
// exclude decorated functions and classes
isDecorated(vd)
or
// exclude names of enum members; they also define property names
isEnumMember(vd)
or
// ignore ambient declarations - too noisy
vd.isAmbient()
)
or
exists(DirectEval eval |
// eval nearby
eval.getEnclosingFunction() = v.getADeclaration().getEnclosingFunction() and
// ... but not on the RHS
not v.getAnAssignedExpr() = eval
)
}
/**
* Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`.
*/
predicate unusedNonImports(VarDecl vd, string msg) {
exists(UnusedLocal v |
v = vd.getVariable() and
msg = "Unused " + describeVarDecl(vd) + "." and
not vd = any(ImportVarDeclProvider p).getAVarDecl() and
not whitelisted(v)
)
}
/**
* Holds if `provider` declares one or more unused variables, as explained by `msg`.
*/
predicate unusedImports(ImportVarDeclProvider provider, string msg) {
exists(string imports, string names |
imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and
names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and
msg = "Unused " + imports + " " + names + "."
)
}
from ASTNode sel, string msg
where
(
unusedNonImports(sel, msg) or
unusedImports(sel, msg)
) and
// avoid reporting if the definition is unreachable
sel.getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock
select sel, msg