TS: Resolve imports using TypeScript symbols

This commit is contained in:
Asger Feldthaus
2020-01-28 15:14:08 +00:00
parent abb95135c1
commit 9abf5f06e6
13 changed files with 98 additions and 11 deletions

View File

@@ -7,7 +7,9 @@
* Imports with the `.js` extension can now be resolved to a TypeScript file,
when the import refers to a file generated by TypeScript.
- The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
* Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved.
* The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
* Support for the following frameworks and libraries has been improved:
- [react](https://www.npmjs.com/package/react)

View File

@@ -251,8 +251,13 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
}
}
}
if (isNamedNodeWithSymbol(node)) {
let symbol = typeChecker.getSymbolAtLocation(node.name);
let symbolNode =
isNamedNodeWithSymbol(node) ? node.name :
ts.isImportDeclaration(node) ? node.moduleSpecifier :
ts.isExternalModuleReference(node) ? node.expression :
null;
if (symbolNode != null) {
let symbol = typeChecker.getSymbolAtLocation(symbolNode);
if (symbol != null) {
node.$symbol = typeTable.getSymbolId(symbol);
}

View File

@@ -1,5 +1,6 @@
package com.semmle.js.ast;
import com.semmle.ts.ast.INodeWithSymbol;
import java.util.List;
/**
@@ -14,13 +15,15 @@ import java.util.List;
* import "m";
* </pre>
*/
public class ImportDeclaration extends Statement {
public class ImportDeclaration extends Statement implements INodeWithSymbol {
/** List of import specifiers detailing how declarations are imported; may be empty. */
private final List<ImportSpecifier> specifiers;
/** The module from which declarations are imported. */
private final Literal source;
private int symbol = -1;
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
super("ImportDeclaration", loc);
this.specifiers = specifiers;
@@ -39,4 +42,14 @@ public class ImportDeclaration extends Statement {
public <C, R> R accept(Visitor<C, R> v, C c) {
return v.visit(this, c);
}
@Override
public int getSymbol() {
return this.symbol;
}
@Override
public void setSymbol(int symbol) {
this.symbol = symbol;
}
}

View File

@@ -1555,6 +1555,7 @@ public class ASTExtractor {
Label lbl = super.visit(nd, c);
visit(nd.getSource(), lbl, -1);
visitAll(nd.getSpecifiers(), lbl);
emitNodeSymbol(nd, lbl);
return lbl;
}
@@ -1705,6 +1706,7 @@ public class ASTExtractor {
public Label visit(ExternalModuleReference nd, Context c) {
Label key = super.visit(nd, c);
visit(nd.getExpression(), key, 0);
emitNodeSymbol(nd, key);
return key;
}
@@ -2061,12 +2063,14 @@ public class ASTExtractor {
@Override
public Label visit(AssignmentPattern nd, Context c) {
additionalErrors.add(new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart()));
additionalErrors.add(
new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart()));
return super.visit(nd, c);
}
}
public List<ParseError> extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) {
public List<ParseError> extract(
Node root, Platform platform, SourceType sourceType, int toplevelKind) {
lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract);
trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind);
locationManager.emitNodeLocation(root, toplevelLabel);

View File

@@ -1202,7 +1202,9 @@ public class TypeScriptASTConverter {
private Node convertExternalModuleReference(JsonObject node, SourceLocation loc)
throws ParseError {
return new ExternalModuleReference(loc, convertChild(node, "expression"));
ExternalModuleReference moduleRef = new ExternalModuleReference(loc, convertChild(node, "expression"));
attachSymbolInformation(moduleRef, node);
return moduleRef;
}
private Node convertFalseKeyword(SourceLocation loc) {
@@ -1366,7 +1368,9 @@ public class TypeScriptASTConverter {
}
}
}
return new ImportDeclaration(loc, specifiers, src);
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src);
attachSymbolInformation(importDecl, node);
return importDecl;
}
private Node convertImportEqualsDeclaration(JsonObject node, SourceLocation loc)

View File

@@ -4,8 +4,9 @@ import com.semmle.js.ast.Expression;
import com.semmle.js.ast.SourceLocation;
import com.semmle.js.ast.Visitor;
public class ExternalModuleReference extends Expression {
public class ExternalModuleReference extends Expression implements INodeWithSymbol {
private final Expression expression;
private int symbol = -1;
public ExternalModuleReference(SourceLocation loc, Expression expression) {
super("ExternalModuleReference", loc);
@@ -20,4 +21,14 @@ public class ExternalModuleReference extends Expression {
public <C, R> R accept(Visitor<C, R> v, C c) {
return v.visit(this, c);
}
@Override
public int getSymbol() {
return this.symbol;
}
@Override
public void setSymbol(int symbol) {
this.symbol = symbol;
}
}

View File

@@ -148,6 +148,16 @@ abstract class Import extends ASTNode {
)
}
/**
* Gets the imported module, as determined by the TypeScript compiler, if any.
*/
private Module resolveFromTypeScriptSymbol() {
exists(CanonicalName symbol |
ast_node_symbol(this, symbol) and
ast_node_symbol(result, symbol)
)
}
/**
* Gets the module this import refers to.
*
@@ -162,7 +172,8 @@ abstract class Import extends ASTNode {
else (
result = resolveAsProvidedModule() or
result = resolveImportedPath() or
result = resolveFromTypeRoot()
result = resolveFromTypeRoot() or
result = resolveFromTypeScriptSymbol()
)
}

View File

@@ -688,7 +688,7 @@ case @symbol.kind of
;
@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype;
@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr;
@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference;
ast_node_symbol(
unique int node: @ast_node_with_symbol ref,

View File

@@ -0,0 +1,11 @@
symbols
| src/lib/foo.ts:1:1:4:0 | <toplevel> | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
| src/lib/foo.ts:1:8:3:1 | functio ... 123;\\n} | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
| test/test_foo.ts:1:1:7:0 | <toplevel> | library-tests/TypeScript/PathMapping/test/test_foo.ts |
| test/test_foo.ts:2:17:2:32 | require("@/foo") | library-tests/TypeScript/PathMapping/src/lib/foo.ts |
| test/test_foo.ts:4:1:4:5 | foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
| test/test_foo.ts:6:1:6:12 | foolib.foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts |
#select
| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | src/lib/foo.ts:1:1:4:0 | <toplevel> |
| test/test_foo.ts:2:17:2:32 | require("@/foo") | src/lib/foo.ts:1:1:4:0 | <toplevel> |

View File

@@ -0,0 +1,8 @@
import javascript
query predicate symbols(ASTNode astNode, CanonicalName symbol) {
ast_node_symbol(astNode, symbol)
}
from Import imprt
select imprt, imprt.getImportedModule()

View File

@@ -0,0 +1,3 @@
export function foo() {
return 123;
}

View File

@@ -0,0 +1,6 @@
import { foo } from "@/foo";
import foolib = require("@/foo");
foo();
foolib.foo();

View File

@@ -0,0 +1,9 @@
{
"include": ["."],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/lib/*"]
}
}
}