mirror of
https://github.com/github/codeql.git
synced 2026-07-09 13:35:38 +02:00
282 lines
9.1 KiB
Plaintext
282 lines
9.1 KiB
Plaintext
private import javascript
|
|
|
|
signature module ResolvePathsSig {
|
|
/**
|
|
* Holds if `path` should be resolved to a file or folder, relative to `base`.
|
|
*/
|
|
predicate shouldResolve(Folder base, string path);
|
|
|
|
/**
|
|
* Gets an additional file or folder to consider a child of `base`.
|
|
*/
|
|
default Container getAnAdditionalChild(Container base, string name) { none() }
|
|
}
|
|
|
|
pragma[inline]
|
|
private Container getChild(Container parent, string name) {
|
|
result = parent.getFile(name)
|
|
or
|
|
result = parent.getFolder(name)
|
|
}
|
|
|
|
/**
|
|
* Provides a mechanism for resolving relative file paths.
|
|
*
|
|
* Absolute paths are not handled.
|
|
*/
|
|
module ResolvePaths<ResolvePathsSig Config> {
|
|
private import Config
|
|
|
|
private string getPathSegment(string path, int n) {
|
|
shouldResolve(_, path) and
|
|
result = path.replaceAll("\\", "/").splitAt("/", n)
|
|
}
|
|
|
|
private int getNumPathSegment(string path) {
|
|
result = strictcount(int n | exists(getPathSegment(path, n)))
|
|
}
|
|
|
|
private Container resolve(Container base, string path, int n) {
|
|
shouldResolve(base, path) and n = 0 and result = base
|
|
or
|
|
exists(Container cur, string segment |
|
|
cur = resolve(base, path, n - 1) and
|
|
segment = getPathSegment(path, n - 1)
|
|
|
|
|
result = getChild(cur, segment)
|
|
or
|
|
result = getAnAdditionalChild(cur, segment)
|
|
or
|
|
segment = [".", ""] and
|
|
result = cur
|
|
or
|
|
segment = ".." and
|
|
result = cur.getParentContainer()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets the file or folder that `path` resolves to when resolved from `base`.
|
|
*
|
|
* Only has results for the `base`, `path` pairs provided by `shouldResolve`
|
|
* in the instantiation of this module.
|
|
*/
|
|
Container resolve(Container base, string path) {
|
|
result = resolve(base, path, getNumPathSegment(path))
|
|
}
|
|
}
|
|
|
|
module PathResolution {
|
|
class TSConfig extends JsonObject {
|
|
TSConfig() {
|
|
this.getJsonFile().getBaseName().matches("%tsconfig%.json") and
|
|
this.isTopLevel()
|
|
}
|
|
|
|
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
|
|
|
|
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 = TSConfigResolve::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 = TSConfigResolve::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 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 mentioned in the `include` property.
|
|
*
|
|
* Does not include all the files within includes directories.
|
|
*/
|
|
Container getAnIncludedBaseContainer() {
|
|
result = TSConfigResolve::resolve(this.getFolder(), this.getAnIncludePath())
|
|
or
|
|
result = this.getExtendedTSConfig().getAnIncludedBaseContainer()
|
|
}
|
|
|
|
private predicate isPrimaryTSConfig() {
|
|
this.getJsonFile().getBaseName() = "tsconfig.json"
|
|
or
|
|
// Fallback in case we can't find the primary tsconfig file
|
|
not exists(this.getFolder().getFile("tsconfig.json")) and
|
|
not this = any(TSConfig tsc).getExtendedTSConfig()
|
|
}
|
|
|
|
/**
|
|
* Gets a file or folder inside the directory tree mentioned in the `include` property.
|
|
*/
|
|
Container getAnAffectedFile() {
|
|
this.isPrimaryTSConfig() and
|
|
result = this.getAnIncludedBaseContainer()
|
|
or
|
|
result = this.getAnAffectedFile().getAChildContainer()
|
|
}
|
|
|
|
JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") }
|
|
|
|
predicate hasPathMapping(string pattern, string newPath) {
|
|
this.getPathMappings().getPropStringValue(pattern) = newPath
|
|
or
|
|
// TODO: track priority
|
|
this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath
|
|
}
|
|
|
|
predicate hasExactPathMapping(string pattern, string newPath) {
|
|
this.hasPathMapping(pattern, newPath) and
|
|
not pattern.matches("%*%")
|
|
}
|
|
|
|
predicate hasPrefixPathMapping(string pattern, string newPath) {
|
|
this.hasPathMapping(pattern + "*", newPath + "*")
|
|
}
|
|
}
|
|
|
|
private module TSConfigResolveConfig implements ResolvePathsSig {
|
|
predicate shouldResolve(Folder base, string path) {
|
|
exists(TSConfig cfg |
|
|
base = cfg.getFolder() and
|
|
path = [cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getAnIncludePath()]
|
|
)
|
|
}
|
|
}
|
|
|
|
private module TSConfigResolve = ResolvePaths<TSConfigResolveConfig>;
|
|
|
|
bindingset[path]
|
|
private predicate isRelativePath(string path) { path.regexpMatch("\\.\\.?(?:[/\\\\].*)?") }
|
|
|
|
private module ResolvePathMappingConfig implements ResolvePathsSig {
|
|
additional predicate shouldResolve(TSConfig cfg, Container base, string path) {
|
|
(cfg.hasExactPathMapping(_, path) or cfg.hasPrefixPathMapping(_, path)) and
|
|
if isRelativePath(path)
|
|
then base = cfg.getFolder() // relative paths are resolved relative to tsconfig.json
|
|
else base = cfg.getBaseUrlFolder() // non-relative paths are resolved relative to the baseUrl
|
|
}
|
|
|
|
predicate shouldResolve(Folder base, string path) { shouldResolve(_, base, path) }
|
|
}
|
|
|
|
private module ResolvePathMapping = ResolvePaths<ResolvePathMappingConfig>;
|
|
|
|
private Container resolvePathMapping(TSConfig cfg, string path) {
|
|
exists(Container base |
|
|
ResolvePathMappingConfig::shouldResolve(cfg, base, path) and
|
|
result = ResolvePathMapping::resolve(base, path)
|
|
)
|
|
}
|
|
|
|
private TSConfig getTSConfigFromPathExpr(PathExpr expr) {
|
|
result.getAnAffectedFile() = expr.getFile()
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate replacedPath1(PathExpr expr, Container base, string newPath) {
|
|
expr = any(Import imprt).getImportedPath() and
|
|
exists(TSConfig config, string value, string mappedPath |
|
|
config = getTSConfigFromPathExpr(expr).getExtendedTSConfig*() and
|
|
value = expr.getValue()
|
|
|
|
|
config.hasExactPathMapping(value, mappedPath) and
|
|
base = resolvePathMapping(config, mappedPath) and
|
|
newPath = ""
|
|
or
|
|
exists(string pattern |
|
|
config.hasPrefixPathMapping(pattern, mappedPath) and
|
|
value = pattern + any(string s) and
|
|
base = resolvePathMapping(config, mappedPath) and
|
|
newPath = value.suffix(pattern.length())
|
|
)
|
|
)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
private predicate replacedPath(PathExpr expr, Container base, string newPath) {
|
|
replacedPath1(expr, base, newPath)
|
|
or
|
|
// resolve from baseUrl
|
|
expr = any(Import imprt).getImportedPath() and
|
|
not replacedPath1(expr, _, _) and
|
|
newPath = expr.getValue() and
|
|
newPath.charAt(0) != "." and
|
|
base = getTSConfigFromPathExpr(expr).getBaseUrlFolder()
|
|
}
|
|
|
|
private module ResolvePathExprConfig implements ResolvePathsSig {
|
|
additional predicate shouldResolve(PathExpr expr, Folder base, string path) {
|
|
expr = any(Import imprt).getImportedPath() and
|
|
(
|
|
replacedPath(expr, base, path)
|
|
or
|
|
not replacedPath(expr, _, _) and
|
|
isRelativePath(expr.getValue()) and
|
|
base = expr.getFile().getParentContainer() and
|
|
path = expr.getValue()
|
|
)
|
|
}
|
|
|
|
predicate shouldResolve(Folder base, string path) { shouldResolve(_, base, path) }
|
|
|
|
private predicate extensionCompilesTo(string original, string compilesTo) {
|
|
original = "ts" and
|
|
compilesTo = "js"
|
|
or
|
|
original = "tsx" and
|
|
compilesTo = ["jsx", "js"]
|
|
}
|
|
|
|
Container getAnAdditionalChild(Container base, string name) {
|
|
result = base.(Folder).getJavaScriptFile(name)
|
|
or
|
|
exists(string stem, string addedExt |
|
|
result = base.(Folder).getJavaScriptFile(stem) and
|
|
extensionCompilesTo(result.getExtension(), addedExt) and
|
|
name = result.getStem() + "." + addedExt and
|
|
not exists(base.(Folder).getFile(name))
|
|
)
|
|
}
|
|
}
|
|
|
|
private module ResolvePathExpr = ResolvePaths<ResolvePathExprConfig>;
|
|
|
|
private Container resolvePathExpr1(PathExpr expr) {
|
|
exists(Container base, string path |
|
|
ResolvePathExprConfig::shouldResolve(expr, base, path) and
|
|
result = ResolvePathExpr::resolve(base, path)
|
|
)
|
|
}
|
|
|
|
File resolvePathExpr(PathExpr expr) {
|
|
result = resolvePathExpr1(expr)
|
|
or
|
|
result = resolvePathExpr1(expr).(Folder).getJavaScriptFile("index")
|
|
}
|
|
}
|