Skip to content

Commit 6725cb5

Browse files
committed
JS: Implement import resolution
1 parent ed4864e commit 6725cb5

File tree

11 files changed

+394
-93
lines changed

11 files changed

+394
-93
lines changed

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

Lines changed: 2 additions & 16 deletions
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.
@@ -725,22 +726,7 @@ abstract class ReExportDeclaration extends ExportDeclaration {
725726
cached
726727
Module getReExportedModule() {
727728
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-
)
729+
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
744730
}
745731
}
746732

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

Lines changed: 7 additions & 59 deletions
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,
@@ -138,39 +139,17 @@ abstract class Import extends AstNode {
138139
/**
139140
* Gets the module the path of this import resolves to.
140141
*/
141-
Module resolveImportedPath() {
142-
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
143-
}
142+
Module resolveImportedPath() { result.getFile() = this.getTargetFile() }
144143

145144
/**
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-
}
156-
157-
/**
158-
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
145+
* Gets the module the path of this import resolves to.
159146
*/
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-
}
147+
File getTargetFile() { result = ImportPathResolver::resolveExpr(this.getImportedPath()) }
169148

170149
/**
171-
* Gets the imported module, as determined by the TypeScript compiler, if any.
150+
* DEPRECATED. Use `getImportedModule()` instead.
172151
*/
173-
private Module resolveFromTypeScriptSymbol() {
152+
deprecated Module resolveFromTypeScriptSymbol() {
174153
exists(CanonicalName symbol |
175154
ast_node_symbol(this, symbol) and
176155
ast_node_symbol(result, symbol)
@@ -190,42 +169,11 @@ abstract class Import extends AstNode {
190169
Stages::Imports::ref() and
191170
if exists(this.resolveExternsImport())
192171
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-
)
172+
else result = this.resolveImportedPath()
200173
}
201174

202175
/**
203176
* Gets the data flow node that the default import of this import is available at.
204177
*/
205178
abstract DataFlow::Node getImportedModuleNode();
206179
}
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-
}

javascript/ql/lib/semmle/javascript/TSConfig.qll

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
private import javascript
6+
private import semmle.javascript.internal.paths.PathMapping
67

78
/**
89
* A TypeScript configuration file, usually named `tsconfig.json`.
@@ -178,3 +179,44 @@ private module ResolverConfig implements Folder::ResolveSig {
178179
}
179180

180181
private module Resolver = Folder::Resolve<ResolverConfig>;
182+
183+
/**
184+
* Gets a tsconfig file to use as fallback for handling paths in `c`.
185+
*
186+
* This holds for files and folders where no tsconfig seems to include it,
187+
* but it has one or more tsconfig files in parent directories.
188+
*/
189+
private TSConfig getFallbackTSConfig(Container c) {
190+
not c = any(TSConfig t).getAnIncludedContainer() and
191+
(
192+
c = result.getFolder()
193+
or
194+
result = getFallbackTSConfig(c.getParentContainer())
195+
)
196+
}
197+
198+
private class TSConfigPathMapping extends PathMapping, TSConfig {
199+
override File getAnAffectedFile() {
200+
result = this.getAnIncludedContainer()
201+
or
202+
this = getFallbackTSConfig(result)
203+
}
204+
205+
override predicate hasExactPathMapping(string pattern, Container newContext, string newPath) {
206+
exists(TSConfig tsconfig |
207+
tsconfig = this.getExtendedTSConfig*() and
208+
tsconfig.hasExactPathMapping(pattern, newPath) and
209+
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
210+
)
211+
}
212+
213+
override predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) {
214+
exists(TSConfig tsconfig |
215+
tsconfig = this.getExtendedTSConfig*() and
216+
tsconfig.hasPrefixPathMapping(pattern, newPath) and
217+
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
218+
)
219+
}
220+
221+
override predicate hasBaseUrl(Container base) { base = this.getBaseUrlFolder() }
222+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
private import javascript
2+
3+
/**
4+
* A path expression that can be constant-folded by concatenating subexpressions.
5+
*/
6+
abstract class PathConcatenation extends Expr {
7+
/** Gets the separator to insert between paths */
8+
string getSeparator() { result = "" }
9+
10+
/** Gets the `n`th operand to concatenate. */
11+
abstract Expr getOperand(int n);
12+
}
13+
14+
private class AddExprConcatenation extends PathConcatenation, AddExpr {
15+
override Expr getOperand(int n) {
16+
n = 0 and result = this.getLeftOperand()
17+
or
18+
n = 1 and result = this.getRightOperand()
19+
}
20+
}
21+
22+
private class TemplateConcatenation extends PathConcatenation, TemplateLiteral {
23+
override Expr getOperand(int n) { result = this.getElement(n) }
24+
}
25+
26+
private class JoinCallConcatenation extends PathConcatenation, CallExpr {
27+
JoinCallConcatenation() {
28+
this.getReceiver().(VarAccess).getName() = "path" and
29+
this.getCalleeName() = ["join", "resolve"]
30+
}
31+
32+
override Expr getOperand(int n) { result = this.getArgument(n) }
33+
34+
override string getSeparator() { result = "/" }
35+
}

0 commit comments

Comments
 (0)