Skip to content

Commit 169ae19

Browse files
authored
Merge pull request #19391 from asgerf/js/typescript-path-resolution
JS: Overhaul import resolution
2 parents aea676d + 1f308ee commit 169ae19

File tree

159 files changed

+1852
-468
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+1852
-468
lines changed

javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ private void setupFilters() {
404404
patterns.add("**/*.view.json"); // SAP UI5
405405
patterns.add("**/manifest.json");
406406
patterns.add("**/package.json");
407-
patterns.add("**/tsconfig*.json");
407+
patterns.add("**/*tsconfig*.json");
408408
patterns.add("**/codeql-javascript-*.json");
409409

410410
// include any explicitly specified extensions

javascript/extractor/src/com/semmle/js/extractor/JSONExtractor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public Context(Label parent, int childIndex) {
2929
private final boolean tolerateParseErrors;
3030

3131
public JSONExtractor(ExtractorConfig config) {
32-
this.tolerateParseErrors = config.isTolerateParseErrors();
32+
this.tolerateParseErrors = true;
3333
}
3434

3535
@Override

javascript/extractor/src/com/semmle/js/extractor/Main.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,14 @@ public void setupMatchers(ArgsParser ap) {
301301
// only extract HTML and JS by default
302302
addIncludesFor(includes, FileType.HTML);
303303
addIncludesFor(includes, FileType.JS);
304+
includes.add("**/.babelrc*.json");
305+
304306

305307
// extract TypeScript if `--typescript` or `--typescript-full` was specified
306-
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) addIncludesFor(includes, FileType.TYPESCRIPT);
308+
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) {
309+
addIncludesFor(includes, FileType.TYPESCRIPT);
310+
includes.add("**/*tsconfig*.json");
311+
}
307312

308313
// add explicit include patterns
309314
for (String pattern : ap.getZeroOrMore(P_INCLUDE))

javascript/ql/examples/snippets/importfrom.ql

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
import javascript
1212

1313
from ImportDeclaration id
14-
where id.getImportedPath().getValue() = "react"
14+
where id.getImportedPathString() = "react"
1515
select id

javascript/ql/lib/definitions.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private predicate importLookup(AstNode path, Module target, string kind) {
7070
kind = "I" and
7171
(
7272
exists(Import i |
73-
path = i.getImportedPath() and
73+
path = i.getImportedPathExpr() and
7474
target = i.getImportedModule()
7575
)
7676
or

javascript/ql/lib/semmle/javascript/AMD.qll

+18-10
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
6161
result = this.getArgument(1)
6262
}
6363

64+
/** DEPRECATED. Use `getDependencyExpr` instead. */
65+
deprecated PathExpr getDependency(int i) { result = this.getDependencyExpr(i) }
66+
67+
/** DEPRECATED. Use `getADependencyExpr` instead. */
68+
deprecated PathExpr getADependency() { result = this.getADependencyExpr() }
69+
6470
/** Gets the `i`th dependency of this module definition. */
65-
PathExpr getDependency(int i) {
71+
Expr getDependencyExpr(int i) {
6672
exists(Expr expr |
6773
expr = this.getDependencies().getElement(i) and
6874
not isPseudoDependency(expr.getStringValue()) and
@@ -71,8 +77,8 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
7177
}
7278

7379
/** Gets a dependency of this module definition. */
74-
PathExpr getADependency() {
75-
result = this.getDependency(_) or
80+
Expr getADependencyExpr() {
81+
result = this.getDependencyExpr(_) or
7682
result = this.getARequireCall().getAnArgument()
7783
}
7884

@@ -233,7 +239,7 @@ private class AmdDependencyPath extends PathExprCandidate {
233239
}
234240

235241
/** A constant path element appearing in an AMD dependency expression. */
236-
private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
242+
deprecated private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
237243
ConstantAmdDependencyPathElement() { this = any(AmdDependencyPath amd).getAPart() }
238244

239245
override string getValue() { result = this.getStringValue() }
@@ -261,11 +267,13 @@ private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
261267
* An AMD dependency, viewed as an import.
262268
*/
263269
private class AmdDependencyImport extends Import {
264-
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
270+
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependencyExpr() }
265271

266-
override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
272+
override Module getEnclosingModule() {
273+
this = result.(AmdModule).getDefine().getADependencyExpr()
274+
}
267275

268-
override PathExpr getImportedPath() { result = this }
276+
override Expr getImportedPathExpr() { result = this }
269277

270278
/**
271279
* Gets a file that looks like it might be the target of this import.
@@ -274,7 +282,7 @@ private class AmdDependencyImport extends Import {
274282
* adding well-known JavaScript file extensions like `.js`.
275283
*/
276284
private File guessTarget() {
277-
exists(PathString imported, string abspath, string dirname, string basename |
285+
exists(FilePath imported, string abspath, string dirname, string basename |
278286
this.targetCandidate(result, abspath, imported, dirname, basename)
279287
|
280288
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
@@ -296,9 +304,9 @@ private class AmdDependencyImport extends Import {
296304
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
297305
*/
298306
private predicate targetCandidate(
299-
File f, string abspath, PathString imported, string dirname, string basename
307+
File f, string abspath, FilePath imported, string dirname, string basename
300308
) {
301-
imported = this.getImportedPath().getValue() and
309+
imported = this.getImportedPathString() and
302310
f.getStem() = imported.getStem() and
303311
f.getAbsolutePath() = abspath and
304312
dirname = imported.getDirName() and

javascript/ql/lib/semmle/javascript/ES2015Modules.qll

+10-19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import javascript
44
private import semmle.javascript.internal.CachedStages
5+
private import semmle.javascript.internal.paths.PathExprResolver
56

67
/**
78
* An ECMAScript 2015 module.
@@ -91,7 +92,12 @@ private predicate hasDefaultExport(ES2015Module mod) {
9192
class ImportDeclaration extends Stmt, Import, @import_declaration {
9293
override ES2015Module getEnclosingModule() { result = this.getTopLevel() }
9394

94-
override PathExpr getImportedPath() { result = this.getChildExpr(-1) }
95+
/**
96+
* INTERNAL USE ONLY. DO NOT USE.
97+
*/
98+
string getRawImportPath() { result = this.getChildExpr(-1).getStringValue() }
99+
100+
override Expr getImportedPathExpr() { result = this.getChildExpr(-1) }
95101

96102
/**
97103
* Gets the object literal passed as part of the `with` (or `assert`) clause in this import declaration.
@@ -149,7 +155,7 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
149155
}
150156

151157
/** A literal path expression appearing in an `import` declaration. */
152-
private class LiteralImportPath extends PathExpr, ConstantString {
158+
deprecated private class LiteralImportPath extends PathExpr, ConstantString {
153159
LiteralImportPath() { exists(ImportDeclaration req | this = req.getChildExpr(-1)) }
154160

155161
override string getValue() { result = this.getStringValue() }
@@ -725,27 +731,12 @@ abstract class ReExportDeclaration extends ExportDeclaration {
725731
cached
726732
Module getReExportedModule() {
727733
Stages::Imports::ref() and
728-
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
729-
or
730-
result = this.resolveFromTypeRoot()
731-
}
732-
733-
/**
734-
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
735-
*/
736-
private Module resolveFromTypeRoot() {
737-
result.getFile() =
738-
min(TypeRootFolder typeRoot |
739-
|
740-
typeRoot.getModuleFile(this.getImportedPath().getStringValue())
741-
order by
742-
typeRoot.getSearchPriority(this.getFile().getParentContainer())
743-
)
734+
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
744735
}
745736
}
746737

747738
/** A literal path expression appearing in a re-export declaration. */
748-
private class LiteralReExportPath extends PathExpr, ConstantString {
739+
deprecated private class LiteralReExportPath extends PathExpr, ConstantString {
749740
LiteralReExportPath() { exists(ReExportDeclaration bred | this = bred.getImportedPath()) }
750741

751742
override string getValue() { result = this.getStringValue() }

javascript/ql/lib/semmle/javascript/Expr.qll

+2-2
Original file line numberDiff line numberDiff line change
@@ -2821,7 +2821,7 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import {
28212821
result = this.getSource().getFirstControlFlowNode()
28222822
}
28232823

2824-
override PathExpr getImportedPath() { result = this.getSource() }
2824+
override Expr getImportedPathExpr() { result = this.getSource() }
28252825

28262826
/**
28272827
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
@@ -2852,7 +2852,7 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import {
28522852
}
28532853

28542854
/** A literal path expression appearing in a dynamic import. */
2855-
private class LiteralDynamicImportPath extends PathExpr, ConstantString {
2855+
deprecated private class LiteralDynamicImportPath extends PathExpr, ConstantString {
28562856
LiteralDynamicImportPath() {
28572857
exists(DynamicImportExpr di | this.getParentExpr*() = di.getSource())
28582858
}

javascript/ql/lib/semmle/javascript/Files.qll

+15
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ private module Impl = Make<FsInput>;
2929

3030
class Container = Impl::Container;
3131

32+
module Folder = Impl::Folder;
33+
3234
/** A folder. */
3335
class Folder extends Container, Impl::Folder {
3436
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
@@ -73,6 +75,19 @@ class Folder extends Container, Impl::Folder {
7375
)
7476
}
7577

78+
/**
79+
* Gets an implementation file and/or a typings file from this folder that has the given `stem`.
80+
* This could be a single `.ts` file or a pair of `.js` and `.d.ts` files.
81+
*/
82+
File getJavaScriptFileOrTypings(string stem) {
83+
exists(File jsFile | jsFile = this.getJavaScriptFile(stem) |
84+
result = jsFile
85+
or
86+
not jsFile.getFileType().isTypeScript() and
87+
result = this.getFile(stem + ".d.ts")
88+
)
89+
}
90+
7691
/** Gets a subfolder contained in this folder. */
7792
Folder getASubFolder() { result = this.getAChildContainer() }
7893
}

javascript/ql/lib/semmle/javascript/HTML.qll

+8-2
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ module HTML {
214214
result = path.regexpCapture("file://(/.*)", 1)
215215
or
216216
not path.regexpMatch("(\\w+:)?//.*") and
217-
result = this.getSourcePath().(ScriptSrcPath).resolve(this.getSearchRoot()).toString()
217+
result = ResolveScriptSrc::resolve(this.getSearchRoot(), this.getSourcePath()).toString()
218218
)
219219
}
220220

@@ -274,10 +274,16 @@ module HTML {
274274
)
275275
}
276276

277+
private module ResolverConfig implements Folder::ResolveSig {
278+
predicate shouldResolve(Container base, string path) { scriptSrc(path, base) }
279+
}
280+
281+
private module ResolveScriptSrc = Folder::Resolve<ResolverConfig>;
282+
277283
/**
278284
* A path string arising from the `src` attribute of a `script` tag.
279285
*/
280-
private class ScriptSrcPath extends PathString {
286+
deprecated private class ScriptSrcPath extends PathString {
281287
ScriptSrcPath() { scriptSrc(this, _) }
282288

283289
override Folder getARootFolder() { scriptSrc(this, result) }

javascript/ql/lib/semmle/javascript/Modules.qll

+17-63
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import javascript
88
private import semmle.javascript.internal.CachedStages
9+
private import semmle.javascript.internal.paths.PathExprResolver
910

1011
/**
1112
* A module, which may either be an ECMAScript 2015-style module,
@@ -68,7 +69,7 @@ abstract class Module extends TopLevel {
6869
* This predicate is not part of the public API, it is only exposed to allow
6970
* overriding by subclasses.
7071
*/
71-
predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
72+
deprecated predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
7273
path.getEnclosingModule() = this and
7374
priority = 0 and
7475
exists(string v | v = path.getValue() |
@@ -89,7 +90,7 @@ abstract class Module extends TopLevel {
8990
* resolves to a folder containing a main module (such as `index.js`), then
9091
* that file is the result.
9192
*/
92-
File resolve(PathExpr path) {
93+
deprecated File resolve(PathExpr path) {
9394
path.getEnclosingModule() = this and
9495
(
9596
// handle the case where the import path is complete
@@ -122,8 +123,14 @@ abstract class Import extends AstNode {
122123
/** Gets the module in which this import appears. */
123124
abstract Module getEnclosingModule();
124125

126+
/** DEPRECATED. Use `getImportedPathExpr` instead. */
127+
deprecated PathExpr getImportedPath() { result = this.getImportedPathExpr() }
128+
125129
/** Gets the (unresolved) path that this import refers to. */
126-
abstract PathExpr getImportedPath();
130+
abstract Expr getImportedPathExpr();
131+
132+
/** Gets the imported path as a string. */
133+
final string getImportedPathString() { result = this.getImportedPathExpr().getStringValue() }
127134

128135
/**
129136
* Gets an externs module the path of this import resolves to.
@@ -132,45 +139,23 @@ abstract class Import extends AstNode {
132139
* path is assumed to be a possible target of the import.
133140
*/
134141
Module resolveExternsImport() {
135-
result.isExterns() and result.getName() = this.getImportedPath().getValue()
142+
result.isExterns() and result.getName() = this.getImportedPathString()
136143
}
137144

138145
/**
139146
* Gets the module the path of this import resolves to.
140147
*/
141-
Module resolveImportedPath() {
142-
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
143-
}
144-
145-
/**
146-
* Gets a module with a `@providesModule` JSDoc tag that matches
147-
* the imported path.
148-
*/
149-
private Module resolveAsProvidedModule() {
150-
exists(JSDocTag tag |
151-
tag.getTitle() = "providesModule" and
152-
tag.getParent().getComment().getTopLevel() = result and
153-
tag.getDescription().trim() = this.getImportedPath().getValue()
154-
)
155-
}
148+
Module resolveImportedPath() { result.getFile() = this.getImportedFile() }
156149

157150
/**
158-
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
151+
* Gets the module the path of this import resolves to.
159152
*/
160-
private Module resolveFromTypeRoot() {
161-
result.getFile() =
162-
min(TypeRootFolder typeRoot |
163-
|
164-
typeRoot.getModuleFile(this.getImportedPath().getValue())
165-
order by
166-
typeRoot.getSearchPriority(this.getFile().getParentContainer())
167-
)
168-
}
153+
File getImportedFile() { result = ImportPathResolver::resolveExpr(this.getImportedPathExpr()) }
169154

170155
/**
171-
* Gets the imported module, as determined by the TypeScript compiler, if any.
156+
* DEPRECATED. Use `getImportedModule()` instead.
172157
*/
173-
private Module resolveFromTypeScriptSymbol() {
158+
deprecated Module resolveFromTypeScriptSymbol() {
174159
exists(CanonicalName symbol |
175160
ast_node_symbol(this, symbol) and
176161
ast_node_symbol(result, symbol)
@@ -190,42 +175,11 @@ abstract class Import extends AstNode {
190175
Stages::Imports::ref() and
191176
if exists(this.resolveExternsImport())
192177
then result = this.resolveExternsImport()
193-
else (
194-
result = this.resolveAsProvidedModule() or
195-
result = this.resolveImportedPath() or
196-
result = this.resolveFromTypeRoot() or
197-
result = this.resolveFromTypeScriptSymbol() or
198-
result = resolveNeighbourPackage(this.getImportedPath().getValue())
199-
)
178+
else result = this.resolveImportedPath()
200179
}
201180

202181
/**
203182
* Gets the data flow node that the default import of this import is available at.
204183
*/
205184
abstract DataFlow::Node getImportedModuleNode();
206185
}
207-
208-
/**
209-
* Gets a module imported from another package in the same repository.
210-
*
211-
* No support for importing from folders inside the other package.
212-
*/
213-
private Module resolveNeighbourPackage(PathString importPath) {
214-
exists(PackageJson json | importPath = json.getPackageName() and result = json.getMainModule())
215-
or
216-
exists(string package |
217-
result.getFile().getParentContainer() = getPackageFolder(package) and
218-
importPath = package + "/" + [result.getFile().getBaseName(), result.getFile().getStem()]
219-
)
220-
}
221-
222-
/**
223-
* Gets the folder for a package that has name `package` according to a package.json file in the resulting folder.
224-
*/
225-
pragma[noinline]
226-
private Folder getPackageFolder(string package) {
227-
exists(PackageJson json |
228-
json.getPackageName() = package and
229-
result = json.getFile().getParentContainer()
230-
)
231-
}

0 commit comments

Comments
 (0)