mirror of
https://github.com/github/codeql.git
synced 2025-12-21 19:26:31 +01:00
TS: Support type-only import/export
This commit is contained in:
@@ -15,13 +15,21 @@ public class ExportNamedDeclaration extends ExportDeclaration {
|
|||||||
private final Statement declaration;
|
private final Statement declaration;
|
||||||
private final List<ExportSpecifier> specifiers;
|
private final List<ExportSpecifier> specifiers;
|
||||||
private final Literal source;
|
private final Literal source;
|
||||||
|
private final boolean hasTypeKeyword;
|
||||||
|
|
||||||
public ExportNamedDeclaration(
|
public ExportNamedDeclaration(
|
||||||
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source) {
|
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source) {
|
||||||
|
this(loc, declaration, specifiers, source, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExportNamedDeclaration(
|
||||||
|
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source,
|
||||||
|
boolean hasTypeKeyword) {
|
||||||
super("ExportNamedDeclaration", loc);
|
super("ExportNamedDeclaration", loc);
|
||||||
this.declaration = declaration;
|
this.declaration = declaration;
|
||||||
this.specifiers = specifiers;
|
this.specifiers = specifiers;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.hasTypeKeyword = hasTypeKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Statement getDeclaration() {
|
public Statement getDeclaration() {
|
||||||
@@ -48,4 +56,9 @@ public class ExportNamedDeclaration extends ExportDeclaration {
|
|||||||
public <C, R> R accept(Visitor<C, R> v, C c) {
|
public <C, R> R accept(Visitor<C, R> v, C c) {
|
||||||
return v.visit(this, c);
|
return v.visit(this, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if this is an <code>export type</code> declaration. */
|
||||||
|
public boolean hasTypeKeyword() {
|
||||||
|
return hasTypeKeyword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.semmle.js.ast;
|
package com.semmle.js.ast;
|
||||||
|
|
||||||
import com.semmle.ts.ast.INodeWithSymbol;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.semmle.ts.ast.INodeWithSymbol;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An import declaration, which can be of one of the following forms:
|
* An import declaration, which can be of one of the following forms:
|
||||||
*
|
*
|
||||||
@@ -24,10 +25,17 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
|
|||||||
|
|
||||||
private int symbol = -1;
|
private int symbol = -1;
|
||||||
|
|
||||||
|
private boolean hasTypeKeyword;
|
||||||
|
|
||||||
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
|
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
|
||||||
|
this(loc, specifiers, source, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, boolean hasTypeKeyword) {
|
||||||
super("ImportDeclaration", loc);
|
super("ImportDeclaration", loc);
|
||||||
this.specifiers = specifiers;
|
this.specifiers = specifiers;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.hasTypeKeyword = hasTypeKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Literal getSource() {
|
public Literal getSource() {
|
||||||
@@ -52,4 +60,9 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
|
|||||||
public void setSymbol(int symbol) {
|
public void setSymbol(int symbol) {
|
||||||
this.symbol = symbol;
|
this.symbol = symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if this is an <code>import type</code> declaration. */
|
||||||
|
public boolean hasTypeKeyword() {
|
||||||
|
return hasTypeKeyword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,22 @@ public class ASTExtractor {
|
|||||||
/** An identifier that declares a variable and a namespace. */
|
/** An identifier that declares a variable and a namespace. */
|
||||||
varAndNamespaceDecl,
|
varAndNamespaceDecl,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier that occurs in a type-only import.
|
||||||
|
*
|
||||||
|
* These may declare a type and/or a namespace, but for compatibility with our AST,
|
||||||
|
* must be emitted as a VarDecl (with no variable binding).
|
||||||
|
*/
|
||||||
|
typeOnlyImport,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier that occurs in a type-only export.
|
||||||
|
*
|
||||||
|
* These may refer to a type and/or a namespace, but for compatibility with our AST,
|
||||||
|
* must be emitted as an ExportVarAccess (with no variable binding).
|
||||||
|
*/
|
||||||
|
typeOnlyExport,
|
||||||
|
|
||||||
/** An identifier that declares a variable, type, and namepsace. */
|
/** An identifier that declares a variable, type, and namepsace. */
|
||||||
varAndTypeAndNamespaceDecl,
|
varAndTypeAndNamespaceDecl,
|
||||||
|
|
||||||
@@ -278,7 +294,8 @@ public class ASTExtractor {
|
|||||||
* True if this occurs as part of a type annotation, i.e. it is {@link #typeBind} or {@link
|
* True if this occurs as part of a type annotation, i.e. it is {@link #typeBind} or {@link
|
||||||
* #typeDecl}, {@link #typeLabel}, {@link #varInTypeBind}, or {@link #namespaceBind}.
|
* #typeDecl}, {@link #typeLabel}, {@link #varInTypeBind}, or {@link #namespaceBind}.
|
||||||
*
|
*
|
||||||
* <p>Does not hold for {@link #varAndTypeDecl}.
|
* <p>Does not hold for {@link #varAndTypeDecl} or {@link #typeOnlyImportExport} as these
|
||||||
|
* do not occur in type annotations.
|
||||||
*/
|
*/
|
||||||
public boolean isInsideType() {
|
public boolean isInsideType() {
|
||||||
return this == typeBind
|
return this == typeBind
|
||||||
@@ -488,6 +505,14 @@ public class ASTExtractor {
|
|||||||
addVariableBinding("decl", key, name);
|
addVariableBinding("decl", key, name);
|
||||||
addNamespaceBinding("namespacedecl", key, name);
|
addNamespaceBinding("namespacedecl", key, name);
|
||||||
break;
|
break;
|
||||||
|
case typeOnlyImport:
|
||||||
|
addTypeBinding("typedecl", key, name);
|
||||||
|
addNamespaceBinding("namespacedecl", key, name);
|
||||||
|
break;
|
||||||
|
case typeOnlyExport:
|
||||||
|
addTypeBinding("typebind", key, name);
|
||||||
|
addNamespaceBinding("namespacebind", key, name);
|
||||||
|
break;
|
||||||
case varAndTypeAndNamespaceDecl:
|
case varAndTypeAndNamespaceDecl:
|
||||||
addVariableBinding("decl", key, name);
|
addVariableBinding("decl", key, name);
|
||||||
addTypeBinding("typedecl", key, name);
|
addTypeBinding("typedecl", key, name);
|
||||||
@@ -1538,7 +1563,14 @@ public class ASTExtractor {
|
|||||||
Label lbl = super.visit(nd, c);
|
Label lbl = super.visit(nd, c);
|
||||||
visit(nd.getDeclaration(), lbl, -1);
|
visit(nd.getDeclaration(), lbl, -1);
|
||||||
visit(nd.getSource(), lbl, -2);
|
visit(nd.getSource(), lbl, -2);
|
||||||
visitAll(nd.getSpecifiers(), lbl, nd.hasSource() ? IdContext.label : IdContext.export, 0);
|
IdContext childContext =
|
||||||
|
nd.hasSource() ? IdContext.label :
|
||||||
|
nd.hasTypeKeyword() ? IdContext.typeOnlyExport :
|
||||||
|
IdContext.export;
|
||||||
|
visitAll(nd.getSpecifiers(), lbl, childContext, 0);
|
||||||
|
if (nd.hasTypeKeyword()) {
|
||||||
|
trapwriter.addTuple("hasTypeKeyword", lbl);
|
||||||
|
}
|
||||||
return lbl;
|
return lbl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1554,8 +1586,12 @@ public class ASTExtractor {
|
|||||||
public Label visit(ImportDeclaration nd, Context c) {
|
public Label visit(ImportDeclaration nd, Context c) {
|
||||||
Label lbl = super.visit(nd, c);
|
Label lbl = super.visit(nd, c);
|
||||||
visit(nd.getSource(), lbl, -1);
|
visit(nd.getSource(), lbl, -1);
|
||||||
visitAll(nd.getSpecifiers(), lbl);
|
IdContext childContext = nd.hasTypeKeyword() ? IdContext.typeOnlyImport : IdContext.varAndTypeAndNamespaceDecl;
|
||||||
|
visitAll(nd.getSpecifiers(), lbl, childContext, 0);
|
||||||
emitNodeSymbol(nd, lbl);
|
emitNodeSymbol(nd, lbl);
|
||||||
|
if (nd.hasTypeKeyword()) {
|
||||||
|
trapwriter.addTuple("hasTypeKeyword", lbl);
|
||||||
|
}
|
||||||
return lbl;
|
return lbl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1563,7 +1599,7 @@ public class ASTExtractor {
|
|||||||
public Label visit(ImportSpecifier nd, Context c) {
|
public Label visit(ImportSpecifier nd, Context c) {
|
||||||
Label lbl = super.visit(nd, c);
|
Label lbl = super.visit(nd, c);
|
||||||
visit(nd.getImported(), lbl, 0, IdContext.label);
|
visit(nd.getImported(), lbl, 0, IdContext.label);
|
||||||
visit(nd.getLocal(), lbl, 1, IdContext.varAndTypeAndNamespaceDecl);
|
visit(nd.getLocal(), lbl, 1, c.idcontext);
|
||||||
return lbl;
|
return lbl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package com.semmle.js.extractor;
|
package com.semmle.js.extractor;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.semmle.jcorn.TokenType;
|
import com.semmle.jcorn.TokenType;
|
||||||
import com.semmle.jcorn.jsx.JSXParser;
|
import com.semmle.jcorn.jsx.JSXParser;
|
||||||
import com.semmle.js.ast.AssignmentExpression;
|
import com.semmle.js.ast.AssignmentExpression;
|
||||||
@@ -29,9 +33,6 @@ import com.semmle.ts.ast.DecoratorList;
|
|||||||
import com.semmle.ts.ast.ExpressionWithTypeArguments;
|
import com.semmle.ts.ast.ExpressionWithTypeArguments;
|
||||||
import com.semmle.ts.ast.TypeAssertion;
|
import com.semmle.ts.ast.TypeAssertion;
|
||||||
import com.semmle.util.exception.CatastrophicError;
|
import com.semmle.util.exception.CatastrophicError;
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** Map from SpiderMonkey expression types to the numeric kinds used in the DB scheme. */
|
/** Map from SpiderMonkey expression types to the numeric kinds used in the DB scheme. */
|
||||||
public class ExprKinds {
|
public class ExprKinds {
|
||||||
@@ -154,6 +155,8 @@ public class ExprKinds {
|
|||||||
idKinds.put(IdContext.namespaceDecl, 78);
|
idKinds.put(IdContext.namespaceDecl, 78);
|
||||||
idKinds.put(IdContext.varAndNamespaceDecl, 78);
|
idKinds.put(IdContext.varAndNamespaceDecl, 78);
|
||||||
idKinds.put(IdContext.varAndTypeAndNamespaceDecl, 78);
|
idKinds.put(IdContext.varAndTypeAndNamespaceDecl, 78);
|
||||||
|
idKinds.put(IdContext.typeOnlyImport, 78);
|
||||||
|
idKinds.put(IdContext.typeOnlyExport, 103);
|
||||||
idKinds.put(IdContext.varBind, 79);
|
idKinds.put(IdContext.varBind, 79);
|
||||||
idKinds.put(IdContext.export, 103);
|
idKinds.put(IdContext.export, 103);
|
||||||
idKinds.put(IdContext.exportBase, 103);
|
idKinds.put(IdContext.exportBase, 103);
|
||||||
|
|||||||
@@ -1179,11 +1179,12 @@ public class TypeScriptASTConverter {
|
|||||||
private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||||
Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||||
if (hasChild(node, "exportClause")) {
|
if (hasChild(node, "exportClause")) {
|
||||||
|
boolean hasTypeKeyword = node.get("isTypeOnly").getAsBoolean();
|
||||||
List<ExportSpecifier> specifiers =
|
List<ExportSpecifier> specifiers =
|
||||||
hasKind(node.get("exportClause"), "NamespaceExport")
|
hasKind(node.get("exportClause"), "NamespaceExport")
|
||||||
? Collections.singletonList(convertChild(node, "exportClause"))
|
? Collections.singletonList(convertChild(node, "exportClause"))
|
||||||
: convertChildren(node.get("exportClause").getAsJsonObject(), "elements");
|
: convertChildren(node.get("exportClause").getAsJsonObject(), "elements");
|
||||||
return new ExportNamedDeclaration(loc, null, specifiers, source);
|
return new ExportNamedDeclaration(loc, null, specifiers, source, hasTypeKeyword);
|
||||||
} else {
|
} else {
|
||||||
return new ExportAllDeclaration(loc, source);
|
return new ExportAllDeclaration(loc, source);
|
||||||
}
|
}
|
||||||
@@ -1368,6 +1369,7 @@ public class TypeScriptASTConverter {
|
|||||||
private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||||
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||||
List<ImportSpecifier> specifiers = new ArrayList<>();
|
List<ImportSpecifier> specifiers = new ArrayList<>();
|
||||||
|
boolean hasTypeKeyword = false;
|
||||||
if (hasChild(node, "importClause")) {
|
if (hasChild(node, "importClause")) {
|
||||||
JsonObject importClause = node.get("importClause").getAsJsonObject();
|
JsonObject importClause = node.get("importClause").getAsJsonObject();
|
||||||
if (hasChild(importClause, "name")) {
|
if (hasChild(importClause, "name")) {
|
||||||
@@ -1381,8 +1383,9 @@ public class TypeScriptASTConverter {
|
|||||||
specifiers.addAll(convertChildren(namedBindings, "elements"));
|
specifiers.addAll(convertChildren(namedBindings, "elements"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hasTypeKeyword = importClause.get("isTypeOnly").getAsBoolean();
|
||||||
}
|
}
|
||||||
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src);
|
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src, hasTypeKeyword);
|
||||||
attachSymbolInformation(importDecl, node);
|
attachSymbolInformation(importDecl, node);
|
||||||
return importDecl;
|
return importDecl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class ASTNode extends @ast_node, Locatable {
|
|||||||
* Holds if this is part of an ambient declaration or type annotation in a TypeScript file.
|
* Holds if this is part of an ambient declaration or type annotation in a TypeScript file.
|
||||||
*
|
*
|
||||||
* A declaration is ambient if it occurs under a `declare` modifier or is
|
* A declaration is ambient if it occurs under a `declare` modifier or is
|
||||||
* an interface declaration, type alias, or type annotation.
|
* an interface declaration, type alias, type annotation, or type-only import/export declaration.
|
||||||
*
|
*
|
||||||
* The TypeScript compiler emits no code for ambient declarations, but they
|
* The TypeScript compiler emits no code for ambient declarations, but they
|
||||||
* can affect name resolution and type checking at compile-time.
|
* can affect name resolution and type checking at compile-time.
|
||||||
|
|||||||
@@ -76,6 +76,16 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration {
|
|||||||
// `import { createServer } from 'http'`
|
// `import { createServer } from 'http'`
|
||||||
result = DataFlow::destructuredModuleImportNode(this)
|
result = DataFlow::destructuredModuleImportNode(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Holds if this is declared with the `type` keyword, so it only imports types. */
|
||||||
|
predicate isTypeOnly() {
|
||||||
|
hasTypeKeyword(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate isAmbient() {
|
||||||
|
Stmt.super.isAmbient() or
|
||||||
|
isTypeOnly()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A literal path expression appearing in an `import` declaration. */
|
/** A literal path expression appearing in an `import` declaration. */
|
||||||
@@ -256,6 +266,16 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration {
|
|||||||
* to module `a` or possibly to some other module from which `a` re-exports.
|
* to module `a` or possibly to some other module from which `a` re-exports.
|
||||||
*/
|
*/
|
||||||
abstract DataFlow::Node getSourceNode(string name);
|
abstract DataFlow::Node getSourceNode(string name);
|
||||||
|
|
||||||
|
/** Holds if is declared with the `type` keyword, so only types are exported. */
|
||||||
|
predicate isTypeOnly() {
|
||||||
|
hasTypeKeyword(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override predicate isAmbient() {
|
||||||
|
Stmt.super.isAmbient() or
|
||||||
|
isTypeOnly()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -413,6 +433,18 @@ class ExportNamedDeclaration extends ExportDeclaration, @exportnameddeclaration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An export declaration with the `type` modifier.
|
||||||
|
*/
|
||||||
|
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration {
|
||||||
|
TypeOnlyExportDeclaration() { isTypeOnly() }
|
||||||
|
|
||||||
|
override predicate exportsAs(LexicalName v, string name) {
|
||||||
|
super.exportsAs(v, name) and
|
||||||
|
not v instanceof Variable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An export specifier in an export declaration.
|
* An export specifier in an export declaration.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -385,6 +385,8 @@ case @expr.kind of
|
|||||||
|
|
||||||
@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
|
@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
|
||||||
|
|
||||||
|
@import_or_export_declaration = @importdeclaration | @exportdeclaration;
|
||||||
|
|
||||||
@typeassertion = @astypeassertion | @prefixtypeassertion;
|
@typeassertion = @astypeassertion | @prefixtypeassertion;
|
||||||
|
|
||||||
@classdefinition = @classdeclstmt | @classexpr;
|
@classdefinition = @classdeclstmt | @classexpr;
|
||||||
@@ -518,6 +520,7 @@ hasPublicKeyword (int id: @property ref);
|
|||||||
hasPrivateKeyword (int id: @property ref);
|
hasPrivateKeyword (int id: @property ref);
|
||||||
hasProtectedKeyword (int id: @property ref);
|
hasProtectedKeyword (int id: @property ref);
|
||||||
hasReadonlyKeyword (int id: @property ref);
|
hasReadonlyKeyword (int id: @property ref);
|
||||||
|
hasTypeKeyword (int id: @import_or_export_declaration ref);
|
||||||
isOptionalMember (int id: @property ref);
|
isOptionalMember (int id: @property ref);
|
||||||
hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
|
hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
|
||||||
isOptionalParameterDeclaration (unique int parameter: @pattern ref);
|
isOptionalParameterDeclaration (unique int parameter: @pattern ref);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
getAVarReference
|
||||||
|
| Foo | tst.ts:5:5:5:7 | Foo |
|
||||||
|
getAnExportAccess
|
||||||
|
| Foo | tst.ts:3:15:3:17 | Foo |
|
||||||
|
getATypeDecl
|
||||||
|
| Foo | tst.ts:1:15:1:17 | Foo |
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import javascript
|
||||||
|
|
||||||
|
query VarRef getAVarReference(Variable v) {
|
||||||
|
result = v.getAReference()
|
||||||
|
}
|
||||||
|
|
||||||
|
query VarRef getAnExportAccess(LocalTypeName t) {
|
||||||
|
result = t.getAnExportAccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
query TypeDecl getATypeDecl(LocalTypeName t) {
|
||||||
|
result = t.getADeclaration()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { Foo } from "foo";
|
||||||
|
|
||||||
|
export type { Foo };
|
||||||
|
|
||||||
|
var Foo = 45;
|
||||||
Reference in New Issue
Block a user