mirror of
https://github.com/github/codeql.git
synced 2025-12-21 11:16:30 +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 List<ExportSpecifier> specifiers;
|
||||
private final Literal source;
|
||||
private final boolean hasTypeKeyword;
|
||||
|
||||
public ExportNamedDeclaration(
|
||||
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);
|
||||
this.declaration = declaration;
|
||||
this.specifiers = specifiers;
|
||||
this.source = source;
|
||||
this.hasTypeKeyword = hasTypeKeyword;
|
||||
}
|
||||
|
||||
public Statement getDeclaration() {
|
||||
@@ -48,4 +56,9 @@ public class ExportNamedDeclaration extends ExportDeclaration {
|
||||
public <C, R> R accept(Visitor<C, R> v, C 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;
|
||||
|
||||
import com.semmle.ts.ast.INodeWithSymbol;
|
||||
import java.util.List;
|
||||
|
||||
import com.semmle.ts.ast.INodeWithSymbol;
|
||||
|
||||
/**
|
||||
* 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 boolean hasTypeKeyword;
|
||||
|
||||
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);
|
||||
this.specifiers = specifiers;
|
||||
this.source = source;
|
||||
this.hasTypeKeyword = hasTypeKeyword;
|
||||
}
|
||||
|
||||
public Literal getSource() {
|
||||
@@ -52,4 +60,9 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
|
||||
public void setSymbol(int 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. */
|
||||
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. */
|
||||
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
|
||||
* #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() {
|
||||
return this == typeBind
|
||||
@@ -488,6 +505,14 @@ public class ASTExtractor {
|
||||
addVariableBinding("decl", key, name);
|
||||
addNamespaceBinding("namespacedecl", key, name);
|
||||
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:
|
||||
addVariableBinding("decl", key, name);
|
||||
addTypeBinding("typedecl", key, name);
|
||||
@@ -1538,7 +1563,14 @@ public class ASTExtractor {
|
||||
Label lbl = super.visit(nd, c);
|
||||
visit(nd.getDeclaration(), lbl, -1);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1554,8 +1586,12 @@ public class ASTExtractor {
|
||||
public Label visit(ImportDeclaration nd, Context c) {
|
||||
Label lbl = super.visit(nd, c);
|
||||
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);
|
||||
if (nd.hasTypeKeyword()) {
|
||||
trapwriter.addTuple("hasTypeKeyword", lbl);
|
||||
}
|
||||
return lbl;
|
||||
}
|
||||
|
||||
@@ -1563,7 +1599,7 @@ public class ASTExtractor {
|
||||
public Label visit(ImportSpecifier nd, Context c) {
|
||||
Label lbl = super.visit(nd, c);
|
||||
visit(nd.getImported(), lbl, 0, IdContext.label);
|
||||
visit(nd.getLocal(), lbl, 1, IdContext.varAndTypeAndNamespaceDecl);
|
||||
visit(nd.getLocal(), lbl, 1, c.idcontext);
|
||||
return lbl;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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.jsx.JSXParser;
|
||||
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.TypeAssertion;
|
||||
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. */
|
||||
public class ExprKinds {
|
||||
@@ -154,6 +155,8 @@ public class ExprKinds {
|
||||
idKinds.put(IdContext.namespaceDecl, 78);
|
||||
idKinds.put(IdContext.varAndNamespaceDecl, 78);
|
||||
idKinds.put(IdContext.varAndTypeAndNamespaceDecl, 78);
|
||||
idKinds.put(IdContext.typeOnlyImport, 78);
|
||||
idKinds.put(IdContext.typeOnlyExport, 103);
|
||||
idKinds.put(IdContext.varBind, 79);
|
||||
idKinds.put(IdContext.export, 103);
|
||||
idKinds.put(IdContext.exportBase, 103);
|
||||
|
||||
@@ -1179,11 +1179,12 @@ public class TypeScriptASTConverter {
|
||||
private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||
if (hasChild(node, "exportClause")) {
|
||||
boolean hasTypeKeyword = node.get("isTypeOnly").getAsBoolean();
|
||||
List<ExportSpecifier> specifiers =
|
||||
hasKind(node.get("exportClause"), "NamespaceExport")
|
||||
? Collections.singletonList(convertChild(node, "exportClause"))
|
||||
: convertChildren(node.get("exportClause").getAsJsonObject(), "elements");
|
||||
return new ExportNamedDeclaration(loc, null, specifiers, source);
|
||||
return new ExportNamedDeclaration(loc, null, specifiers, source, hasTypeKeyword);
|
||||
} else {
|
||||
return new ExportAllDeclaration(loc, source);
|
||||
}
|
||||
@@ -1368,6 +1369,7 @@ public class TypeScriptASTConverter {
|
||||
private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
|
||||
List<ImportSpecifier> specifiers = new ArrayList<>();
|
||||
boolean hasTypeKeyword = false;
|
||||
if (hasChild(node, "importClause")) {
|
||||
JsonObject importClause = node.get("importClause").getAsJsonObject();
|
||||
if (hasChild(importClause, "name")) {
|
||||
@@ -1381,8 +1383,9 @@ public class TypeScriptASTConverter {
|
||||
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);
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
* can affect name resolution and type checking at compile-time.
|
||||
|
||||
@@ -76,6 +76,16 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration {
|
||||
// `import { createServer } from 'http'`
|
||||
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. */
|
||||
@@ -256,6 +266,16 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration {
|
||||
* to module `a` or possibly to some other module from which `a` re-exports.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -385,6 +385,8 @@ case @expr.kind of
|
||||
|
||||
@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
|
||||
|
||||
@import_or_export_declaration = @importdeclaration | @exportdeclaration;
|
||||
|
||||
@typeassertion = @astypeassertion | @prefixtypeassertion;
|
||||
|
||||
@classdefinition = @classdeclstmt | @classexpr;
|
||||
@@ -518,6 +520,7 @@ hasPublicKeyword (int id: @property ref);
|
||||
hasPrivateKeyword (int id: @property ref);
|
||||
hasProtectedKeyword (int id: @property ref);
|
||||
hasReadonlyKeyword (int id: @property ref);
|
||||
hasTypeKeyword (int id: @import_or_export_declaration ref);
|
||||
isOptionalMember (int id: @property ref);
|
||||
hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator 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