Skip to content

Commit 59e1cbc

Browse files
committed
JS: Add tsconfig class
1 parent ef32a03 commit 59e1cbc

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* Provides a class for working with `tsconfig.json` files.
3+
*/
4+
5+
private import javascript
6+
7+
/**
8+
* A TypeScript configuration file, usually named `tsconfig.json`.
9+
*/
10+
class TSConfig extends JsonObject {
11+
TSConfig() {
12+
this.getJsonFile().getBaseName().matches("%tsconfig%.json") and
13+
this.isTopLevel()
14+
}
15+
16+
/** Gets the folder containing this file. */
17+
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
18+
19+
/** Gets the `compilerOptions` object. */
20+
JsonObject getCompilerOptions() { result = this.getPropValue("compilerOptions") }
21+
22+
/** Gets the string value in the `extends` property. */
23+
string getExtendsPath() { result = this.getPropStringValue("extends") }
24+
25+
/** Gets the file referred to by the `extends` property. */
26+
File getExtendedFile() { result = Resolver::resolve(this.getFolder(), this.getExtendsPath()) }
27+
28+
/** Gets the `TSConfig` file referred to by the `extends` property. */
29+
TSConfig getExtendedTSConfig() { result.getJsonFile() = this.getExtendedFile() }
30+
31+
/** Gets the string value in the `baseUrl` property. */
32+
string getBaseUrlPath() { result = this.getCompilerOptions().getPropStringValue("baseUrl") }
33+
34+
/** Gets the folder referred to by the `baseUrl` property in this file, not taking `extends` into account. */
35+
Folder getOwnBaseUrlFolder() {
36+
result = Resolver::resolve(this.getFolder(), this.getBaseUrlPath())
37+
}
38+
39+
/** Gets the effective baseUrl folder for this tsconfig file. */
40+
Folder getBaseUrlFolder() {
41+
result = this.getOwnBaseUrlFolder()
42+
or
43+
not exists(this.getOwnBaseUrlFolder()) and
44+
result = this.getExtendedTSConfig().getBaseUrlFolder()
45+
}
46+
47+
/** Gets the effective baseUrl folder for this tsconfig file, or its enclosing folder if there is no baseUrl. */
48+
Folder getBaseUrlFolderOrOwnFolder() {
49+
result = this.getBaseUrlFolder()
50+
or
51+
not exists(this.getBaseUrlFolder()) and
52+
result = this.getFolder()
53+
}
54+
55+
/** Gets a path mentioned in the `include` property. */
56+
string getAnIncludePath() {
57+
result = this.getPropStringValue("include")
58+
or
59+
result = this.getPropValue("include").(JsonArray).getElementStringValue(_)
60+
}
61+
62+
/**
63+
* Gets a file or folder refenced by a path the `include` property, possibly
64+
* inherited from an extended tsconfig file.
65+
*
66+
* Does not include all the files within includes directories, use `getAnIncludedContainer` for that.
67+
*/
68+
Container getAnIncludePathTarget() {
69+
result = Resolver::resolve(this.getFolder(), this.getAnIncludePath())
70+
or
71+
not exists(this.getPropValue("include")) and
72+
result = this.getExtendedTSConfig().getAnIncludePathTarget()
73+
}
74+
75+
/**
76+
* Gets a file or folder inside the directory tree mentioned in the `include` property.
77+
*/
78+
Container getAnIncludedContainer() {
79+
result = this.getAnIncludePathTarget()
80+
or
81+
result = this.getAnIncludedContainer().getAChildContainer()
82+
}
83+
84+
/** Gets the path mentioned in the `rootDir` property. */
85+
string getRootDirPath() { result = this.getCompilerOptions().getPropStringValue("rootDir") }
86+
87+
private Container getOwnRootDir() {
88+
result = Resolver::resolve(this.getFolder(), this.getRootDirPath())
89+
}
90+
91+
/** Gets the file or folder referenced by the `rootDir` property. */
92+
Container getRootDir() {
93+
result = this.getOwnRootDir()
94+
or
95+
not exists(this.getRootDirPath()) and
96+
result = this.getExtendedTSConfig().getOwnRootDir()
97+
}
98+
99+
private string getATopLevelIncludePath() {
100+
result = this.getAnIncludePath().(FilePath).getComponent(0)
101+
}
102+
103+
private string getUniqueTopLevelIncludePath() {
104+
result = unique( | | this.getATopLevelIncludePath())
105+
}
106+
107+
/**
108+
* Gets the folder referred to by the `rootDir` property, or if absent, an effective root dir
109+
* derived from `include` paths.
110+
*/
111+
Container getEffectiveRootDir() {
112+
result = this.getRootDir()
113+
or
114+
not exists(this.getRootDir()) and
115+
(
116+
result = this.getFolder().getFolder(this.getUniqueTopLevelIncludePath())
117+
or
118+
not exists(this.getUniqueTopLevelIncludePath()) and
119+
exists(this.getATopLevelIncludePath()) and
120+
result = this.getFolder()
121+
or
122+
not exists(this.getATopLevelIncludePath()) and
123+
result = this.getExtendedTSConfig().getEffectiveRootDir()
124+
)
125+
}
126+
127+
private JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") }
128+
129+
/**
130+
* Holds if this has a path mapping from `pattern` to `newPath`.
131+
*
132+
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/*` pattern to `./src/*`.
133+
*
134+
* Does not include path mappings from extended tsconfig files.
135+
*/
136+
predicate hasPathMapping(string pattern, string newPath) {
137+
this.getPathMappings().getPropStringValue(pattern) = newPath
138+
or
139+
this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath
140+
}
141+
142+
/**
143+
* Holds if this has an exact path mapping from `pattern` to `newPath`.
144+
*
145+
* For example, `"paths": { "@": "./src/index.ts" }` maps the `@` path to `./src/index.ts`.
146+
*
147+
* Does not include path mappings from extended tsconfig files.
148+
*/
149+
predicate hasExactPathMapping(string pattern, string newPath) {
150+
this.hasPathMapping(pattern, newPath) and
151+
not pattern.matches("%*%")
152+
}
153+
154+
/**
155+
* Holds if this has a path mapping from the `pattern` prefix to the `newPath` prefix.
156+
* The trailing `*` is not included.
157+
*
158+
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/` pattern to `./src/`.
159+
*
160+
* Does not include path mappings from extended tsconfig files.
161+
*/
162+
predicate hasPrefixPathMapping(string pattern, string newPath) {
163+
this.hasPathMapping(pattern + "*", newPath + "*")
164+
}
165+
}
166+
167+
/** For resolving paths in a tsconfig file, except `paths` mappings. */
168+
private module ResolverConfig implements Folder::ResolveSig {
169+
predicate shouldResolve(Container base, string path) {
170+
exists(TSConfig cfg |
171+
base = cfg.getFolder() and
172+
path =
173+
[cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getRootDirPath(), cfg.getAnIncludePath()]
174+
)
175+
}
176+
177+
predicate allowGlobs() { any() } // "include" can use globs
178+
}
179+
180+
private module Resolver = Folder::Resolve<ResolverConfig>;

0 commit comments

Comments
 (0)