Skip to content

Commit 99d8b48

Browse files
authored
Merge pull request #2712 from asger-semmle/typescript-resolve-imports
TS: Resolve imports using TypeScript
2 parents d995d5a + 513854a commit 99d8b48

File tree

17 files changed

+2490
-11
lines changed

17 files changed

+2490
-11
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
* Imports with the `.js` extension can now be resolved to a TypeScript file,
88
when the import refers to a file generated by TypeScript.
99

10-
- The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
10+
* Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved.
11+
12+
* The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
1113

1214
* Support for the following frameworks and libraries has been improved:
1315
- [react](https://www.npmjs.com/package/react)

javascript/extractor/lib/typescript/src/ast_extractor.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,13 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
251251
}
252252
}
253253
}
254-
if (isNamedNodeWithSymbol(node)) {
255-
let symbol = typeChecker.getSymbolAtLocation(node.name);
254+
let symbolNode =
255+
isNamedNodeWithSymbol(node) ? node.name :
256+
ts.isImportDeclaration(node) ? node.moduleSpecifier :
257+
ts.isExternalModuleReference(node) ? node.expression :
258+
null;
259+
if (symbolNode != null) {
260+
let symbol = typeChecker.getSymbolAtLocation(symbolNode);
256261
if (symbol != null) {
257262
node.$symbol = typeTable.getSymbolId(symbol);
258263
}

javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.semmle.js.ast;
22

3+
import com.semmle.ts.ast.INodeWithSymbol;
34
import java.util.List;
45

56
/**
@@ -14,13 +15,15 @@
1415
* import "m";
1516
* </pre>
1617
*/
17-
public class ImportDeclaration extends Statement {
18+
public class ImportDeclaration extends Statement implements INodeWithSymbol {
1819
/** List of import specifiers detailing how declarations are imported; may be empty. */
1920
private final List<ImportSpecifier> specifiers;
2021

2122
/** The module from which declarations are imported. */
2223
private final Literal source;
2324

25+
private int symbol = -1;
26+
2427
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
2528
super("ImportDeclaration", loc);
2629
this.specifiers = specifiers;
@@ -39,4 +42,14 @@ public List<ImportSpecifier> getSpecifiers() {
3942
public <C, R> R accept(Visitor<C, R> v, C c) {
4043
return v.visit(this, c);
4144
}
45+
46+
@Override
47+
public int getSymbol() {
48+
return this.symbol;
49+
}
50+
51+
@Override
52+
public void setSymbol(int symbol) {
53+
this.symbol = symbol;
54+
}
4255
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,7 @@ public Label visit(ImportDeclaration nd, Context c) {
15551555
Label lbl = super.visit(nd, c);
15561556
visit(nd.getSource(), lbl, -1);
15571557
visitAll(nd.getSpecifiers(), lbl);
1558+
emitNodeSymbol(nd, lbl);
15581559
return lbl;
15591560
}
15601561

@@ -1705,6 +1706,7 @@ public Label visit(ExportWholeDeclaration nd, Context c) {
17051706
public Label visit(ExternalModuleReference nd, Context c) {
17061707
Label key = super.visit(nd, c);
17071708
visit(nd.getExpression(), key, 0);
1709+
emitNodeSymbol(nd, key);
17081710
return key;
17091711
}
17101712

@@ -2061,12 +2063,14 @@ public Label visit(XMLDotDotExpression nd, Context c) {
20612063

20622064
@Override
20632065
public Label visit(AssignmentPattern nd, Context c) {
2064-
additionalErrors.add(new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart()));
2066+
additionalErrors.add(
2067+
new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart()));
20652068
return super.visit(nd, c);
20662069
}
20672070
}
20682071

2069-
public List<ParseError> extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) {
2072+
public List<ParseError> extract(
2073+
Node root, Platform platform, SourceType sourceType, int toplevelKind) {
20702074
lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract);
20712075
trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind);
20722076
locationManager.emitNodeLocation(root, toplevelLabel);

javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,9 @@ private Node convertExpressionWithTypeArguments(JsonObject node, SourceLocation
12021202

12031203
private Node convertExternalModuleReference(JsonObject node, SourceLocation loc)
12041204
throws ParseError {
1205-
return new ExternalModuleReference(loc, convertChild(node, "expression"));
1205+
ExternalModuleReference moduleRef = new ExternalModuleReference(loc, convertChild(node, "expression"));
1206+
attachSymbolInformation(moduleRef, node);
1207+
return moduleRef;
12061208
}
12071209

12081210
private Node convertFalseKeyword(SourceLocation loc) {
@@ -1366,7 +1368,9 @@ private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throw
13661368
}
13671369
}
13681370
}
1369-
return new ImportDeclaration(loc, specifiers, src);
1371+
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src);
1372+
attachSymbolInformation(importDecl, node);
1373+
return importDecl;
13701374
}
13711375

13721376
private Node convertImportEqualsDeclaration(JsonObject node, SourceLocation loc)

javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import com.semmle.js.ast.SourceLocation;
55
import com.semmle.js.ast.Visitor;
66

7-
public class ExternalModuleReference extends Expression {
7+
public class ExternalModuleReference extends Expression implements INodeWithSymbol {
88
private final Expression expression;
9+
private int symbol = -1;
910

1011
public ExternalModuleReference(SourceLocation loc, Expression expression) {
1112
super("ExternalModuleReference", loc);
@@ -20,4 +21,14 @@ public Expression getExpression() {
2021
public <C, R> R accept(Visitor<C, R> v, C c) {
2122
return v.visit(this, c);
2223
}
24+
25+
@Override
26+
public int getSymbol() {
27+
return this.symbol;
28+
}
29+
30+
@Override
31+
public void setSymbol(int symbol) {
32+
this.symbol = symbol;
33+
}
2334
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Unresolvable imports
3+
* @description The number of imports that could not be resolved to a module.
4+
* @kind metric
5+
* @metricType project
6+
* @metricAggregate sum
7+
* @tags meta
8+
* @id js/meta/unresolvable-imports
9+
*/
10+
11+
import javascript
12+
import CallGraphQuality
13+
14+
Import unresolvableImport() {
15+
not exists(result.getImportedModule())
16+
}
17+
18+
select projectRoot(), count(unresolvableImport())

javascript/ql/src/semmle/javascript/Modules.qll

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,16 @@ abstract class Import extends ASTNode {
148148
)
149149
}
150150

151+
/**
152+
* Gets the imported module, as determined by the TypeScript compiler, if any.
153+
*/
154+
private Module resolveFromTypeScriptSymbol() {
155+
exists(CanonicalName symbol |
156+
ast_node_symbol(this, symbol) and
157+
ast_node_symbol(result, symbol)
158+
)
159+
}
160+
151161
/**
152162
* Gets the module this import refers to.
153163
*
@@ -162,7 +172,8 @@ abstract class Import extends ASTNode {
162172
else (
163173
result = resolveAsProvidedModule() or
164174
result = resolveImportedPath() or
165-
result = resolveFromTypeRoot()
175+
result = resolveFromTypeRoot() or
176+
result = resolveFromTypeScriptSymbol()
166177
)
167178
}
168179

javascript/ql/src/semmlecode.javascript.dbscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ case @symbol.kind of
688688
;
689689

690690
@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype;
691-
@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr;
691+
@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference;
692692

693693
ast_node_symbol(
694694
unique int node: @ast_node_with_symbol ref,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
symbols
2+
| src/lib/foo.ts:1:1:4:0 | <toplevel> | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
3+
| src/lib/foo.ts:1:8:3:1 | functio ... 123;\\n} | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
4+
| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
5+
| test/test_foo.ts:1:1:7:0 | <toplevel> | library-tests/TypeScript/PathMapping/test/test_foo.ts |
6+
| test/test_foo.ts:2:17:2:32 | require("@/foo") | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
7+
| test/test_foo.ts:4:1:4:5 | foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
8+
| test/test_foo.ts:6:1:6:12 | foolib.foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
9+
#select
10+
| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | src/lib/foo.ts:1:1:4:0 | <toplevel> |
11+
| test/test_foo.ts:2:17:2:32 | require("@/foo") | src/lib/foo.ts:1:1:4:0 | <toplevel> |

0 commit comments

Comments
 (0)