mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
223 lines
7.4 KiB
Plaintext
223 lines
7.4 KiB
Plaintext
/**
|
|
* Provides a class for working with `tsconfig.json` files.
|
|
*/
|
|
|
|
private import javascript
|
|
private import semmle.javascript.internal.paths.PathMapping
|
|
|
|
/**
|
|
* A TypeScript configuration file, usually named `tsconfig.json`.
|
|
*/
|
|
class TSConfig extends JsonObject {
|
|
TSConfig() {
|
|
this.getJsonFile().getBaseName().matches("%tsconfig%.json") and
|
|
this.isTopLevel()
|
|
}
|
|
|
|
/** Gets the folder containing this file. */
|
|
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
|
|
|
|
/** Gets the `compilerOptions` object. */
|
|
JsonObject getCompilerOptions() { result = this.getPropValue("compilerOptions") }
|
|
|
|
/** Gets the string value in the `extends` property. */
|
|
string getExtendsPath() { result = this.getPropStringValue("extends") }
|
|
|
|
/** Gets the file referred to by the `extends` property. */
|
|
File getExtendedFile() { result = Resolver::resolve(this.getFolder(), this.getExtendsPath()) }
|
|
|
|
/** Gets the `TSConfig` file referred to by the `extends` property. */
|
|
TSConfig getExtendedTSConfig() { result.getJsonFile() = this.getExtendedFile() }
|
|
|
|
/** Gets the string value in the `baseUrl` property. */
|
|
string getBaseUrlPath() { result = this.getCompilerOptions().getPropStringValue("baseUrl") }
|
|
|
|
/** Gets the folder referred to by the `baseUrl` property in this file, not taking `extends` into account. */
|
|
Folder getOwnBaseUrlFolder() {
|
|
result = Resolver::resolve(this.getFolder(), this.getBaseUrlPath())
|
|
}
|
|
|
|
/** Gets the effective baseUrl folder for this tsconfig file. */
|
|
Folder getBaseUrlFolder() {
|
|
result = this.getOwnBaseUrlFolder()
|
|
or
|
|
not exists(this.getOwnBaseUrlFolder()) and
|
|
result = this.getExtendedTSConfig().getBaseUrlFolder()
|
|
}
|
|
|
|
/** Gets the effective baseUrl folder for this tsconfig file, or its enclosing folder if there is no baseUrl. */
|
|
Folder getBaseUrlFolderOrOwnFolder() {
|
|
result = this.getBaseUrlFolder()
|
|
or
|
|
not exists(this.getBaseUrlFolder()) and
|
|
result = this.getFolder()
|
|
}
|
|
|
|
/** Gets a path mentioned in the `include` property. */
|
|
string getAnIncludePath() {
|
|
result = this.getPropStringValue("include")
|
|
or
|
|
result = this.getPropValue("include").(JsonArray).getElementStringValue(_)
|
|
}
|
|
|
|
/**
|
|
* Gets a file or folder refenced by a path the `include` property, possibly
|
|
* inherited from an extended tsconfig file.
|
|
*
|
|
* Does not include all the files within includes directories, use `getAnIncludedContainer` for that.
|
|
*/
|
|
Container getAnIncludePathTarget() {
|
|
result = Resolver::resolve(this.getFolder(), this.getAnIncludePath())
|
|
or
|
|
not exists(this.getPropValue("include")) and
|
|
result = this.getExtendedTSConfig().getAnIncludePathTarget()
|
|
}
|
|
|
|
/**
|
|
* Gets a file or folder inside the directory tree mentioned in the `include` property.
|
|
*/
|
|
Container getAnIncludedContainer() {
|
|
result = this.getAnIncludePathTarget()
|
|
or
|
|
result = this.getAnIncludedContainer().getAChildContainer()
|
|
}
|
|
|
|
/** Gets the path mentioned in the `rootDir` property. */
|
|
string getRootDirPath() { result = this.getCompilerOptions().getPropStringValue("rootDir") }
|
|
|
|
private Container getOwnRootDir() {
|
|
result = Resolver::resolve(this.getFolder(), this.getRootDirPath())
|
|
}
|
|
|
|
/** Gets the file or folder referenced by the `rootDir` property. */
|
|
Container getRootDir() {
|
|
result = this.getOwnRootDir()
|
|
or
|
|
not exists(this.getRootDirPath()) and
|
|
result = this.getExtendedTSConfig().getOwnRootDir()
|
|
}
|
|
|
|
private string getATopLevelIncludePath() {
|
|
result = this.getAnIncludePath().(FilePath).getComponent(0)
|
|
}
|
|
|
|
private string getUniqueTopLevelIncludePath() {
|
|
result = unique( | | this.getATopLevelIncludePath())
|
|
}
|
|
|
|
/**
|
|
* Gets the folder referred to by the `rootDir` property, or if absent, an effective root dir
|
|
* derived from `include` paths.
|
|
*/
|
|
Container getEffectiveRootDir() {
|
|
result = this.getRootDir()
|
|
or
|
|
not exists(this.getRootDir()) and
|
|
(
|
|
result = this.getFolder().getFolder(this.getUniqueTopLevelIncludePath())
|
|
or
|
|
not exists(this.getUniqueTopLevelIncludePath()) and
|
|
exists(this.getATopLevelIncludePath()) and
|
|
result = this.getFolder()
|
|
or
|
|
not exists(this.getATopLevelIncludePath()) and
|
|
result = this.getExtendedTSConfig().getEffectiveRootDir()
|
|
)
|
|
}
|
|
|
|
private JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") }
|
|
|
|
/**
|
|
* Holds if this has a path mapping from `pattern` to `newPath`.
|
|
*
|
|
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/*` pattern to `./src/*`.
|
|
*
|
|
* Does not include path mappings from extended tsconfig files.
|
|
*/
|
|
predicate hasPathMapping(string pattern, string newPath) {
|
|
this.getPathMappings().getPropStringValue(pattern) = newPath
|
|
or
|
|
this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath
|
|
}
|
|
|
|
/**
|
|
* Holds if this has an exact path mapping (i.e. no wildcards) from `pattern` to `newPath`.
|
|
*
|
|
* For example, `"paths": { "@": "./src/index.ts" }` maps the `@` path to `./src/index.ts`.
|
|
*
|
|
* Does not include path mappings from extended tsconfig files.
|
|
*/
|
|
predicate hasExactPathMapping(string pattern, string newPath) {
|
|
this.hasPathMapping(pattern, newPath) and
|
|
not pattern.matches("%*%")
|
|
}
|
|
|
|
/**
|
|
* Holds if this has a path mapping from the `pattern` prefix to the `newPath` prefix.
|
|
* The trailing `*` is not included.
|
|
*
|
|
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/` pattern to `./src/`.
|
|
*
|
|
* Does not include path mappings from extended tsconfig files.
|
|
*/
|
|
predicate hasPrefixPathMapping(string pattern, string newPath) {
|
|
this.hasPathMapping(pattern + "*", newPath + "*")
|
|
}
|
|
}
|
|
|
|
/** For resolving paths in a tsconfig file, except `paths` mappings. */
|
|
private module ResolverConfig implements Folder::ResolveSig {
|
|
predicate shouldResolve(Container base, string path) {
|
|
exists(TSConfig cfg |
|
|
base = cfg.getFolder() and
|
|
path =
|
|
[cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getRootDirPath(), cfg.getAnIncludePath()]
|
|
)
|
|
}
|
|
|
|
predicate allowGlobs() { any() } // "include" can use globs
|
|
}
|
|
|
|
private module Resolver = Folder::Resolve<ResolverConfig>;
|
|
|
|
/**
|
|
* Gets a tsconfig file to use as fallback for handling paths in `c`.
|
|
*
|
|
* This holds for files and folders where no tsconfig seems to include it,
|
|
* but it has one or more tsconfig files in parent directories.
|
|
*/
|
|
private TSConfig getFallbackTSConfig(Container c) {
|
|
not c = any(TSConfig t).getAnIncludedContainer() and
|
|
(
|
|
c = result.getFolder()
|
|
or
|
|
result = getFallbackTSConfig(c.getParentContainer())
|
|
)
|
|
}
|
|
|
|
private class TSConfigPathMapping extends PathMapping, TSConfig {
|
|
override File getAnAffectedFile() {
|
|
result = this.getAnIncludedContainer()
|
|
or
|
|
this = getFallbackTSConfig(result)
|
|
}
|
|
|
|
override predicate hasExactPathMapping(string pattern, Container newContext, string newPath) {
|
|
exists(TSConfig tsconfig |
|
|
tsconfig = this.getExtendedTSConfig*() and
|
|
tsconfig.hasExactPathMapping(pattern, newPath) and
|
|
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
|
|
)
|
|
}
|
|
|
|
override predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) {
|
|
exists(TSConfig tsconfig |
|
|
tsconfig = this.getExtendedTSConfig*() and
|
|
tsconfig.hasPrefixPathMapping(pattern, newPath) and
|
|
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
|
|
)
|
|
}
|
|
|
|
override predicate hasBaseUrl(Container base) { base = this.getBaseUrlFolder() }
|
|
}
|