Files
codeql/javascript/ql/lib/semmle/javascript/NPM.qll
Asger F 2172c4863f Merge pull request #15380 from asgerf/js/endpoint-naming
JS: Add library for naming endpoints
2024-02-14 10:48:13 +01:00

411 lines
14 KiB
Plaintext

/**
* Provides classes for working with NPM module definitions and dependencies.
*/
import javascript
private import NodeModuleResolutionImpl
/** A `package.json` configuration object. */
class PackageJson extends JsonObject {
PackageJson() {
this.getJsonFile().getBaseName() = "package.json" and
this.isTopLevel()
}
/**
* Gets the name of this package.
* If the package is located under the package `pkg1` and its relative path is `foo/bar`, then the resulting package name will be `pkg1/foo/bar`.
*/
string getPackageName() {
result = this.getPropStringValue("name")
or
exists(
PackageJson parentPkg, Container currentDir, Container parentDir, string parentPkgName,
string pkgNameDiff
|
currentDir = this.getJsonFile().getParentContainer() and
parentDir = parentPkg.getJsonFile().getParentContainer() and
parentPkgName = parentPkg.getPropStringValue("name") and
parentDir.getAChildContainer+() = currentDir and
pkgNameDiff = currentDir.getAbsolutePath().suffix(parentDir.getAbsolutePath().length()) and
not exists(pkgNameDiff.indexOf("/node_modules/")) and
result = parentPkgName + pkgNameDiff and
not parentPkg.isPrivate()
)
}
/** Gets the version of this package. */
string getVersion() { result = this.getPropStringValue("version") }
/** Gets the description of this package. */
string getDescription() { result = this.getPropStringValue("description") }
/** Gets the array of keywords for this package. */
JsonArray getKeywords() { result = this.getPropValue("keywords") }
/** Gets a keyword for this package. */
string getAKeyword() { result = this.getKeywords().getElementStringValue(_) }
/** Gets the homepage URL of this package. */
string getHomepage() { result = this.getPropStringValue("homepage") }
/** Gets the bug tracker information of this package. */
BugTrackerInfo getBugs() { result = this.getPropValue("bugs") }
/** Gets the license information of this package. */
string getLicense() { result = this.getPropStringValue("license") }
/** Gets the author information of this package. */
ContributorInfo getAuthor() { result = this.getPropValue("author") }
/** Gets information for a contributor to this package. */
ContributorInfo getAContributor() {
result = this.getPropValue("contributors").getElementValue(_)
}
/** Gets the array of files for this package. */
JsonArray getFiles() { result = this.getPropValue("files") }
/** Gets a file for this package. */
string getAFile() { result = this.getFiles().getElementStringValue(_) }
/**
* Gets the main module of this package.
*
* This can be given by the `main` or `module` property, or via the
* `exports` property with the relative path `"."`.
*/
string getMain() { result = this.getExportedPath(".") }
/**
* Gets the path to the file exported with the given relative path.
*
* This can be given by the `exports` property, but also considers `main` and
* `module` paths to be exported under the relative path `"."`.
*/
string getExportedPath(string relativePath) {
result = MainModulePath::of(this, relativePath).getValue()
}
/** Gets the path of a command defined for this package. */
string getBin(string cmd) {
cmd = this.getPackageName() and result = this.getPropStringValue("bin")
or
result = this.getPropValue("bin").getPropValue(cmd).getStringValue()
}
/** Gets a manual page for this package. */
string getAManFile() {
result = this.getPropStringValue("man") or
result = this.getPropValue("man").getElementValue(_).getStringValue()
}
/** Gets information about the directories of this package. */
JsonObject getDirectories() { result = this.getPropValue("directories") }
/** Gets repository information for this package. */
RepositoryInfo getRepository() { result = this.getPropValue("repository") }
/** Gets information about the scripts of this package. */
JsonObject getScripts() { result = this.getPropValue("scripts") }
/** Gets configuration information for this package. */
JsonObject getConfig() { result = this.getPropValue("config") }
/** Gets the dependencies of this package. */
PackageDependencies getDependencies() { result = this.getPropValue("dependencies") }
/** Gets the development dependencies of this package. */
PackageDependencies getDevDependencies() { result = this.getPropValue("devDependencies") }
/** Gets the peer dependencies of this package. */
PackageDependencies getPeerDependencies() { result = this.getPropValue("peerDependencies") }
/** Gets the bundled dependencies of this package. */
PackageDependencies getBundledDependencies() {
result = this.getPropValue("bundledDependencies") or
result = this.getPropValue("bundleDependencies")
}
/** Gets the optional dependencies of this package. */
PackageDependencies getOptionalDependencies() {
result = this.getPropValue("optionalDependencies")
}
/**
* Gets a JSON object describing a group of dependencies of
* this package of the kind specified by `depkind`:
* `""` for normal dependencies, `"dev"` for `devDependencies`,
* `"bundled"` for `bundledDependencies` and `"opt"` for
* `optionalDependencies`.
*/
PackageDependencies getADependenciesObject(string depkind) {
result = this.getDependencies() and depkind = ""
or
result = this.getDevDependencies() and depkind = "dev"
or
result = this.getBundledDependencies() and depkind = "bundled"
or
result = this.getOptionalDependencies() and depkind = "opt"
}
/**
* Holds if this package declares a dependency (including
* optional, development and bundled dependencies) on the given version
* of the given package.
*
* This does _not_ consider peer dependencies, which are semantically
* different from the other dependency types.
*/
predicate declaresDependency(string pkg, string version) {
this.getADependenciesObject(_).getADependency(pkg, version)
}
/** Gets the engine dependencies of this package. */
PackageDependencies getEngines() { result = this.getPropValue("engines") }
/** Holds if this package has strict engine requirements. */
predicate isEngineStrict() { this.getPropValue("engineStrict").(JsonBoolean).getValue() = "true" }
/** Gets information about operating systems supported by this package. */
JsonArray getOSs() { result = this.getPropValue("os") }
/** Gets an operating system supported by this package. */
string getWhitelistedOS() {
result = this.getOSs().getElementStringValue(_) and
not result.matches("!%")
}
/** Gets an operating system not supported by this package. */
string getBlacklistedOS() {
exists(string str | str = this.getOSs().getElementStringValue(_) |
result = str.regexpCapture("!(.*)", 1)
)
}
/** Gets information about platforms supported by this package. */
JsonArray getCPUs() { result = this.getPropValue("cpu") }
/** Gets a platform supported by this package. */
string getWhitelistedCpu() {
result = this.getCPUs().getElementStringValue(_) and
not result.matches("!%")
}
/** Gets a platform not supported by this package. */
string getBlacklistedCpu() {
exists(string str | str = this.getCPUs().getElementStringValue(_) |
result = str.regexpCapture("!(.*)", 1)
)
}
/** Holds if this package prefers to be installed globally. */
predicate isPreferGlobal() { this.getPropValue("preferGlobal").(JsonBoolean).getValue() = "true" }
/** Holds if this is a private package. */
predicate isPrivate() { this.getPropValue("private").(JsonBoolean).getValue() = "true" }
/** Gets publishing configuration information about this package. */
JsonValue getPublishConfig() { result = this.getPropValue("publishConfig") }
/**
* Gets the main module of this package.
*/
Module getMainModule() { result = this.getExportedModule(".") }
/**
* Gets the module exported under the given relative path.
*
* The main module is considered exported under the path `"."`.
*/
Module getExportedModule(string relativePath) {
result =
min(Module m, int prio |
m.getFile() = resolveMainModule(this, prio, relativePath)
|
m order by prio
)
}
/**
* Gets the `types` or `typings` field of this package.
*/
string getTypings() { result = this.getPropStringValue(["types", "typings"]) }
/**
* Gets the file containing the typings of this package, which can either be from the `types` or
* `typings` field, or derived from the `main` or `module` fields.
*/
File getTypingsFile() {
result =
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
or
not exists(TypingsModulePathString::of(this)) and
exists(File mainFile |
mainFile = this.getMainModule().getFile() and
result =
mainFile
.getParentContainer()
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
)
}
/**
* Gets the module containing the typings of this package, which can either be from the `types` or
* `typings` field, or derived from the `main` or `module` fields.
*/
Module getTypingsModule() { result.getFile() = this.getTypingsFile() }
}
/**
* A representation of bug tracker information for an NPM package.
*/
class BugTrackerInfo extends JsonValue {
BugTrackerInfo() {
exists(PackageJson pkg | pkg.getPropValue("bugs") = this) and
(this instanceof JsonObject or this instanceof JsonString)
}
/** Gets the bug tracker URL. */
string getUrl() {
result = this.getPropValue("url").getStringValue() or
result = this.getStringValue()
}
/** Gets the bug reporting email address. */
string getEmail() { result = this.getPropValue("email").getStringValue() }
}
/**
* A representation of contributor information for an NPM package.
*/
class ContributorInfo extends JsonValue {
ContributorInfo() {
exists(PackageJson pkg |
this = pkg.getPropValue("author") or
this = pkg.getPropValue("contributors").getElementValue(_)
) and
(this instanceof JsonObject or this instanceof JsonString)
}
/**
* Gets the `i`th item of information about a contributor, where the first
* item is their name, the second their email address, and the third their
* homepage URL.
*/
private string parseInfo(int group) {
result = this.getStringValue().regexpCapture("(.*?)(?: <(.*?)>)?(?: \\((.*)?\\))", group)
}
/** Gets the contributor's name. */
string getName() {
result = this.getPropValue("name").getStringValue() or
result = this.parseInfo(1)
}
/** Gets the contributor's email address. */
string getEmail() {
result = this.getPropValue("email").getStringValue() or
result = this.parseInfo(2)
}
/** Gets the contributor's homepage URL. */
string getUrl() {
result = this.getPropValue("url").getStringValue() or
result = this.parseInfo(3)
}
}
/**
* A representation of repository information for an NPM package.
*/
class RepositoryInfo extends JsonObject {
RepositoryInfo() { exists(PackageJson pkg | this = pkg.getPropValue("repository")) }
/** Gets the repository type. */
string getType() { result = this.getPropStringValue("type") }
/** Gets the repository URL. */
string getUrl() { result = this.getPropStringValue("url") }
}
/**
* A representation of package dependencies for an NPM package.
*/
class PackageDependencies extends JsonObject {
PackageDependencies() {
exists(PackageJson pkg, string name |
name.regexpMatch("(.+D|d)ependencies|engines") and
this = pkg.getPropValue(name)
)
}
/** Holds if this package depends on version 'version' of package 'pkg'. */
predicate getADependency(string pkg, string version) { version = this.getPropStringValue(pkg) }
}
/**
* An NPM package.
*/
class NpmPackage extends @folder {
/** The `package.json` file of this package. */
PackageJson pkg;
NpmPackage() { pkg.getJsonFile().getParentContainer() = this }
/** Gets a textual representation of this package. */
string toString() { result = this.(Folder).toString() }
/** Gets the full file system path of this package. */
string getPath() { result = this.(Folder).getAbsolutePath() }
/** Gets the `package.json` object of this package. */
PackageJson getPackageJson() { result = pkg }
/** Gets the name of this package. */
string getPackageName() { result = this.getPackageJson().getPackageName() }
/** Gets the `node_modules` folder of this package. */
Folder getNodeModulesFolder() {
result.getBaseName() = "node_modules" and
result.getParentContainer() = this
}
/**
* Gets a file belonging to this package.
*
* We only consider files to belong to the nearest enclosing package,
* and files inside the `node_modules` folder of a package are not
* considered to belong to that package.
*/
File getAFile() { this = packageInternalParent*(result.getParentContainer()) }
/**
* Gets a Node.js module belonging to this package.
*
* We only consider modules to belong to the nearest enclosing package,
* and modules inside the `node_modules` folder of a package are not
* considered to belong to that package.
*/
Module getAModule() { result.getFile() = this.getAFile() }
/**
* Gets the main module of this package.
*/
Module getMainModule() { result = pkg.getMainModule() }
/**
* Holds if this package declares a dependency on version `v` of package `p`.
*/
predicate declaresDependency(string p, string v) { pkg.declaresDependency(p, v) }
}
/**
* Gets the parent folder of `c`, provided that they belong to the same NPM
* package; that is, `c` must not be a `node_modules` folder.
*/
private Folder packageInternalParent(Container c) {
result = c.getParentContainer() and
not c.(Folder).getBaseName() = "node_modules"
}