mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
109 lines
3.8 KiB
Plaintext
109 lines
3.8 KiB
Plaintext
/**
|
|
* @name Unused npm dependency
|
|
* @description If unnecessary package dependencies are included in package.json, the
|
|
* package will become harder to install.
|
|
* @kind problem
|
|
* @problem.severity recommendation
|
|
* @id js/node/unused-npm-dependency
|
|
* @tags maintainability
|
|
* frameworks/node.js
|
|
* @precision low
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Holds if the NPM package `pkg` declares a dependency on package `name`,
|
|
* and `dep` is the corresponding declaration in the `package.json` file.
|
|
*/
|
|
predicate declaresDependency(NPMPackage pkg, string name, JSONValue dep) {
|
|
dep = pkg.getPackageJSON().getDependencies().getPropValue(name)
|
|
}
|
|
|
|
/**
|
|
* Gets a path expression in a module belonging to `pkg`.
|
|
*/
|
|
PathExpr getAPathExpr(NPMPackage pkg) { result.getEnclosingModule() = pkg.getAModule() }
|
|
|
|
/**
|
|
* Gets a URL-valued attribute in a module or HTML file belonging to `pkg`.
|
|
*/
|
|
DOM::AttributeDefinition getAURLAttribute(NPMPackage pkg) {
|
|
result.getFile() = pkg.getAFile() and
|
|
DOM::isUrlValuedAttribute(result)
|
|
}
|
|
|
|
/**
|
|
* Gets the name of a script in the 'scripts' object of `pkg`.
|
|
* The script makes use of a declared `dependency` of `pkg`.
|
|
*/
|
|
string getPackageScriptNameWithDependency(NPMPackage pkg, string dependency) {
|
|
exists(JSONObject scriptsObject, string scriptName, string script |
|
|
declaresDependency(pkg, dependency, _) and
|
|
scriptsObject = pkg.getPackageJSON().getPropValue("scripts") and
|
|
script = scriptsObject.getPropStringValue(scriptName) and
|
|
script.regexpMatch(".*\\b\\Q" + dependency + "\\E\\b.*") and
|
|
result = scriptName
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if the NPM package `pkg` declares a dependency on package `name`,
|
|
* and uses it at least once.
|
|
*/
|
|
predicate usesDependency(NPMPackage pkg, string name) {
|
|
declaresDependency(pkg, name, _) and
|
|
(
|
|
// there is a path expression (e.g., in a `require` or `import`) that
|
|
// references `pkg`
|
|
exists(PathExpr path | path = getAPathExpr(pkg) |
|
|
// check whether the path is `name` or starts with `name/`, ignoring a prefix that ends with '!' (example: "scriptloader!moment")
|
|
path.getValue().regexpMatch("(.*!)?\\Q" + name + "\\E(/.*)?")
|
|
)
|
|
or
|
|
// there is an HTML URL attribute that may reference `pkg`
|
|
exists(DOM::AttributeDefinition attr | attr = getAURLAttribute(pkg) |
|
|
// check whether the URL contains `node_modules/name`
|
|
attr.getStringValue().regexpMatch(".*\\bnode_modules/\\Q" + name + "\\E(/.*)?")
|
|
)
|
|
or
|
|
// there is a reference in a package.json white-listed script
|
|
exists(string packageScriptName |
|
|
packageScriptName = getPackageScriptNameWithDependency(pkg, name)
|
|
|
|
|
packageScriptName = "preinstall" or
|
|
packageScriptName = "install" or
|
|
packageScriptName = "postinstall"
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `pkg` implicitly requires module `name`.
|
|
*
|
|
* Currently, the only implicit requires that are recognized are Express
|
|
* view engine definitions, which (may) implicitly require the specified
|
|
* engine as a module.
|
|
*/
|
|
predicate implicitRequire(NPMPackage pkg, string name) {
|
|
// look for Express `set('view engine', ...)` calls
|
|
exists(MethodCallExpr setViewEngine, string engine |
|
|
Express::appCreation().flowsToExpr(setViewEngine.getReceiver()) and
|
|
setViewEngine.getMethodName() = "set" and
|
|
setViewEngine.getArgument(0).getStringValue() = "view engine" and
|
|
setViewEngine.getArgument(1).getStringValue() = engine and
|
|
setViewEngine.getTopLevel() = pkg.getAModule()
|
|
|
|
|
// chop off leading dot, if any
|
|
if engine.matches(".%") then name = engine.suffix(1) else name = engine
|
|
)
|
|
}
|
|
|
|
from NPMPackage pkg, string name, JSONValue dep
|
|
where
|
|
exists(pkg.getAModule()) and
|
|
declaresDependency(pkg, name, dep) and
|
|
not usesDependency(pkg, name) and
|
|
not implicitRequire(pkg, name)
|
|
select dep, "Unused dependency '" + name + "'."
|