Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8d58aad

Browse files
committed
TS: Support type-only import/export
1 parent 0351f0b commit 8d58aad

11 files changed

Lines changed: 138 additions & 11 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@ public class ExportNamedDeclaration extends ExportDeclaration {
1515
private final Statement declaration;
1616
private final List<ExportSpecifier> specifiers;
1717
private final Literal source;
18+
private final boolean hasTypeKeyword;
1819

1920
public ExportNamedDeclaration(
2021
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source) {
22+
this(loc, declaration, specifiers, source, false);
23+
}
24+
25+
public ExportNamedDeclaration(
26+
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source,
27+
boolean hasTypeKeyword) {
2128
super("ExportNamedDeclaration", loc);
2229
this.declaration = declaration;
2330
this.specifiers = specifiers;
2431
this.source = source;
32+
this.hasTypeKeyword = hasTypeKeyword;
2533
}
2634

2735
public Statement getDeclaration() {
@@ -48,4 +56,9 @@ public boolean hasSource() {
4856
public <C, R> R accept(Visitor<C, R> v, C c) {
4957
return v.visit(this, c);
5058
}
59+
60+
/** Returns true if this is an <code>export type</code> declaration. */
61+
public boolean hasTypeKeyword() {
62+
return hasTypeKeyword;
63+
}
5164
}

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

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

3-
import com.semmle.ts.ast.INodeWithSymbol;
43
import java.util.List;
54

5+
import com.semmle.ts.ast.INodeWithSymbol;
6+
67
/**
78
* An import declaration, which can be of one of the following forms:
89
*
@@ -24,10 +25,17 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
2425

2526
private int symbol = -1;
2627

28+
private boolean hasTypeKeyword;
29+
2730
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
31+
this(loc, specifiers, source, false);
32+
}
33+
34+
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, boolean hasTypeKeyword) {
2835
super("ImportDeclaration", loc);
2936
this.specifiers = specifiers;
3037
this.source = source;
38+
this.hasTypeKeyword = hasTypeKeyword;
3139
}
3240

3341
public Literal getSource() {
@@ -52,4 +60,9 @@ public int getSymbol() {
5260
public void setSymbol(int symbol) {
5361
this.symbol = symbol;
5462
}
63+
64+
/** Returns true if this is an <code>import type</code> declaration. */
65+
public boolean hasTypeKeyword() {
66+
return hasTypeKeyword;
67+
}
5568
}

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

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,22 @@ public enum IdContext {
250250
/** An identifier that declares a variable and a namespace. */
251251
varAndNamespaceDecl,
252252

253+
/**
254+
* An identifier that occurs in a type-only import.
255+
*
256+
* These may declare a type and/or a namespace, but for compatibility with our AST,
257+
* must be emitted as a VarDecl (with no variable binding).
258+
*/
259+
typeOnlyImport,
260+
261+
/**
262+
* An identifier that occurs in a type-only export.
263+
*
264+
* These may refer to a type and/or a namespace, but for compatibility with our AST,
265+
* must be emitted as an ExportVarAccess (with no variable binding).
266+
*/
267+
typeOnlyExport,
268+
253269
/** An identifier that declares a variable, type, and namepsace. */
254270
varAndTypeAndNamespaceDecl,
255271

@@ -278,7 +294,8 @@ public enum IdContext {
278294
* True if this occurs as part of a type annotation, i.e. it is {@link #typeBind} or {@link
279295
* #typeDecl}, {@link #typeLabel}, {@link #varInTypeBind}, or {@link #namespaceBind}.
280296
*
281-
* <p>Does not hold for {@link #varAndTypeDecl}.
297+
* <p>Does not hold for {@link #varAndTypeDecl} or {@link #typeOnlyImportExport} as these
298+
* do not occur in type annotations.
282299
*/
283300
public boolean isInsideType() {
284301
return this == typeBind
@@ -488,6 +505,14 @@ public Label visit(Identifier nd, Context c) {
488505
addVariableBinding("decl", key, name);
489506
addNamespaceBinding("namespacedecl", key, name);
490507
break;
508+
case typeOnlyImport:
509+
addTypeBinding("typedecl", key, name);
510+
addNamespaceBinding("namespacedecl", key, name);
511+
break;
512+
case typeOnlyExport:
513+
addTypeBinding("typebind", key, name);
514+
addNamespaceBinding("namespacebind", key, name);
515+
break;
491516
case varAndTypeAndNamespaceDecl:
492517
addVariableBinding("decl", key, name);
493518
addTypeBinding("typedecl", key, name);
@@ -1538,7 +1563,14 @@ public Label visit(ExportNamedDeclaration nd, Context c) {
15381563
Label lbl = super.visit(nd, c);
15391564
visit(nd.getDeclaration(), lbl, -1);
15401565
visit(nd.getSource(), lbl, -2);
1541-
visitAll(nd.getSpecifiers(), lbl, nd.hasSource() ? IdContext.label : IdContext.export, 0);
1566+
IdContext childContext =
1567+
nd.hasSource() ? IdContext.label :
1568+
nd.hasTypeKeyword() ? IdContext.typeOnlyExport :
1569+
IdContext.export;
1570+
visitAll(nd.getSpecifiers(), lbl, childContext, 0);
1571+
if (nd.hasTypeKeyword()) {
1572+
trapwriter.addTuple("hasTypeKeyword", lbl);
1573+
}
15421574
return lbl;
15431575
}
15441576

@@ -1554,16 +1586,20 @@ public Label visit(ExportSpecifier nd, Context c) {
15541586
public Label visit(ImportDeclaration nd, Context c) {
15551587
Label lbl = super.visit(nd, c);
15561588
visit(nd.getSource(), lbl, -1);
1557-
visitAll(nd.getSpecifiers(), lbl);
1589+
IdContext childContext = nd.hasTypeKeyword() ? IdContext.typeOnlyImport : IdContext.varAndTypeAndNamespaceDecl;
1590+
visitAll(nd.getSpecifiers(), lbl, childContext, 0);
15581591
emitNodeSymbol(nd, lbl);
1592+
if (nd.hasTypeKeyword()) {
1593+
trapwriter.addTuple("hasTypeKeyword", lbl);
1594+
}
15591595
return lbl;
15601596
}
15611597

15621598
@Override
15631599
public Label visit(ImportSpecifier nd, Context c) {
15641600
Label lbl = super.visit(nd, c);
15651601
visit(nd.getImported(), lbl, 0, IdContext.label);
1566-
visit(nd.getLocal(), lbl, 1, IdContext.varAndTypeAndNamespaceDecl);
1602+
visit(nd.getLocal(), lbl, 1, c.idcontext);
15671603
return lbl;
15681604
}
15691605

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.semmle.js.extractor;
22

3+
import java.util.EnumMap;
4+
import java.util.LinkedHashMap;
5+
import java.util.Map;
6+
37
import com.semmle.jcorn.TokenType;
48
import com.semmle.jcorn.jsx.JSXParser;
59
import com.semmle.js.ast.AssignmentExpression;
@@ -29,9 +33,6 @@
2933
import com.semmle.ts.ast.ExpressionWithTypeArguments;
3034
import com.semmle.ts.ast.TypeAssertion;
3135
import com.semmle.util.exception.CatastrophicError;
32-
import java.util.EnumMap;
33-
import java.util.LinkedHashMap;
34-
import java.util.Map;
3536

3637
/** Map from SpiderMonkey expression types to the numeric kinds used in the DB scheme. */
3738
public class ExprKinds {
@@ -154,6 +155,8 @@ public class ExprKinds {
154155
idKinds.put(IdContext.namespaceDecl, 78);
155156
idKinds.put(IdContext.varAndNamespaceDecl, 78);
156157
idKinds.put(IdContext.varAndTypeAndNamespaceDecl, 78);
158+
idKinds.put(IdContext.typeOnlyImport, 78);
159+
idKinds.put(IdContext.typeOnlyExport, 103);
157160
idKinds.put(IdContext.varBind, 79);
158161
idKinds.put(IdContext.export, 103);
159162
idKinds.put(IdContext.exportBase, 103);

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,11 +1179,12 @@ private Node convertExportAssignment(JsonObject node, SourceLocation loc) throws
11791179
private Node convertExportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
11801180
Literal source = tryConvertChild(node, "moduleSpecifier", Literal.class);
11811181
if (hasChild(node, "exportClause")) {
1182+
boolean hasTypeKeyword = node.get("isTypeOnly").getAsBoolean();
11821183
List<ExportSpecifier> specifiers =
11831184
hasKind(node.get("exportClause"), "NamespaceExport")
11841185
? Collections.singletonList(convertChild(node, "exportClause"))
11851186
: convertChildren(node.get("exportClause").getAsJsonObject(), "elements");
1186-
return new ExportNamedDeclaration(loc, null, specifiers, source);
1187+
return new ExportNamedDeclaration(loc, null, specifiers, source, hasTypeKeyword);
11871188
} else {
11881189
return new ExportAllDeclaration(loc, source);
11891190
}
@@ -1368,6 +1369,7 @@ private Node convertImportClause(JsonObject node, SourceLocation loc) throws Par
13681369
private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError {
13691370
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
13701371
List<ImportSpecifier> specifiers = new ArrayList<>();
1372+
boolean hasTypeKeyword = false;
13711373
if (hasChild(node, "importClause")) {
13721374
JsonObject importClause = node.get("importClause").getAsJsonObject();
13731375
if (hasChild(importClause, "name")) {
@@ -1381,8 +1383,9 @@ private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throw
13811383
specifiers.addAll(convertChildren(namedBindings, "elements"));
13821384
}
13831385
}
1386+
hasTypeKeyword = importClause.get("isTypeOnly").getAsBoolean();
13841387
}
1385-
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src);
1388+
ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src, hasTypeKeyword);
13861389
attachSymbolInformation(importDecl, node);
13871390
return importDecl;
13881391
}

javascript/ql/src/semmle/javascript/AST.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class ASTNode extends @ast_node, Locatable {
125125
* Holds if this is part of an ambient declaration or type annotation in a TypeScript file.
126126
*
127127
* A declaration is ambient if it occurs under a `declare` modifier or is
128-
* an interface declaration, type alias, or type annotation.
128+
* an interface declaration, type alias, type annotation, or type-only import/export declaration.
129129
*
130130
* The TypeScript compiler emits no code for ambient declarations, but they
131131
* can affect name resolution and type checking at compile-time.

javascript/ql/src/semmle/javascript/ES2015Modules.qll

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration {
7676
// `import { createServer } from 'http'`
7777
result = DataFlow::destructuredModuleImportNode(this)
7878
}
79+
80+
/** Holds if this is declared with the `type` keyword, so it only imports types. */
81+
predicate isTypeOnly() {
82+
hasTypeKeyword(this)
83+
}
84+
85+
override predicate isAmbient() {
86+
Stmt.super.isAmbient() or
87+
isTypeOnly()
88+
}
7989
}
8090

8191
/** A literal path expression appearing in an `import` declaration. */
@@ -256,6 +266,16 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration {
256266
* to module `a` or possibly to some other module from which `a` re-exports.
257267
*/
258268
abstract DataFlow::Node getSourceNode(string name);
269+
270+
/** Holds if is declared with the `type` keyword, so only types are exported. */
271+
predicate isTypeOnly() {
272+
hasTypeKeyword(this)
273+
}
274+
275+
override predicate isAmbient() {
276+
Stmt.super.isAmbient() or
277+
isTypeOnly()
278+
}
259279
}
260280

261281
/**
@@ -413,6 +433,18 @@ class ExportNamedDeclaration extends ExportDeclaration, @exportnameddeclaration
413433
}
414434
}
415435

436+
/**
437+
* An export declaration with the `type` modifier.
438+
*/
439+
private class TypeOnlyExportDeclaration extends ExportNamedDeclaration {
440+
TypeOnlyExportDeclaration() { isTypeOnly() }
441+
442+
override predicate exportsAs(LexicalName v, string name) {
443+
super.exportsAs(v, name) and
444+
not v instanceof Variable
445+
}
446+
}
447+
416448
/**
417449
* An export specifier in an export declaration.
418450
*

javascript/ql/src/semmlecode.javascript.dbscheme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ case @expr.kind of
385385

386386
@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
387387

388+
@import_or_export_declaration = @importdeclaration | @exportdeclaration;
389+
388390
@typeassertion = @astypeassertion | @prefixtypeassertion;
389391

390392
@classdefinition = @classdeclstmt | @classexpr;
@@ -518,6 +520,7 @@ hasPublicKeyword (int id: @property ref);
518520
hasPrivateKeyword (int id: @property ref);
519521
hasProtectedKeyword (int id: @property ref);
520522
hasReadonlyKeyword (int id: @property ref);
523+
hasTypeKeyword (int id: @import_or_export_declaration ref);
521524
isOptionalMember (int id: @property ref);
522525
hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
523526
isOptionalParameterDeclaration (unique int parameter: @pattern ref);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
getAVarReference
2+
| Foo | tst.ts:5:5:5:7 | Foo |
3+
getAnExportAccess
4+
| Foo | tst.ts:3:15:3:17 | Foo |
5+
getATypeDecl
6+
| Foo | tst.ts:1:15:1:17 | Foo |
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import javascript
2+
3+
query VarRef getAVarReference(Variable v) {
4+
result = v.getAReference()
5+
}
6+
7+
query VarRef getAnExportAccess(LocalTypeName t) {
8+
result = t.getAnExportAccess()
9+
}
10+
11+
query TypeDecl getATypeDecl(LocalTypeName t) {
12+
result = t.getADeclaration()
13+
}

0 commit comments

Comments
 (0)