Skip to content

Commit feb7c49

Browse files
authored
Merge pull request github#12382 from asgerf/js/import-assertion
JS: Support import assertions
2 parents 32e8b13 + d74da30 commit feb7c49

25 files changed

+3179
-306
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ const astProperties: string[] = [
224224
"argument",
225225
"argumentExpression",
226226
"arguments",
227+
"assertClause",
227228
"assertsModifier",
228229
"asteriskToken",
229230
"attributes",

javascript/extractor/src/com/semmle/jcorn/ESNextParser.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,8 @@ protected ExportDeclaration parseExportRest(SourceLocation exportStart, Set<Stri
314314
this.parseExportSpecifiersMaybe(specifiers, exports);
315315
}
316316
Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
317-
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
317+
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
318+
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source, assertion));
318319
}
319320

320321
return super.parseExportRest(exportStart, exports);
@@ -330,7 +331,8 @@ protected ExportDeclaration parseExportAll(
330331
List<ExportSpecifier> specifiers = CollectionUtil.makeList(nsSpec);
331332
this.parseExportSpecifiersMaybe(specifiers, exports);
332333
Literal source = (Literal) this.parseExportFrom(specifiers, null, true);
333-
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source));
334+
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
335+
return this.finishNode(new ExportNamedDeclaration(exportStart, null, specifiers, source, assertion));
334336
}
335337

336338
return super.parseExportAll(exportStart, starLoc, exports);
@@ -435,8 +437,15 @@ private MetaProperty parseImportMeta(Position loc) {
435437
*/
436438
private DynamicImport parseDynamicImport(Position startLoc) {
437439
Expression source = parseMaybeAssign(false, null, null);
440+
Expression attributes = null;
441+
if (this.eat(TokenType.comma)) {
442+
if (this.type != TokenType.parenR) { // Skip if the comma was a trailing comma
443+
attributes = this.parseMaybeAssign(false, null, null);
444+
this.eat(TokenType.comma); // Allow trailing comma
445+
}
446+
}
438447
this.expect(TokenType.parenR);
439-
DynamicImport di = this.finishNode(new DynamicImport(new SourceLocation(startLoc), source));
448+
DynamicImport di = this.finishNode(new DynamicImport(new SourceLocation(startLoc), source, attributes));
440449
return di;
441450
}
442451

javascript/extractor/src/com/semmle/jcorn/Parser.java

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,13 +1648,13 @@ protected Expression parseExprAtom(DestructuringErrors refDestructuringErrors) {
16481648
return this.finishNode(node);
16491649
} else if (this.type == TokenType.pound) {
16501650
Position startLoc = this.startLoc;
1651-
// there is only one case where this is valid, and that is "Ergonomic brand checks for Private Fields", i.e. `#name in obj`.
1651+
// there is only one case where this is valid, and that is "Ergonomic brand checks for Private Fields", i.e. `#name in obj`.
16521652
Identifier id = parseIdent(true);
16531653
String op = String.valueOf(this.value);
16541654
if (!op.equals("in")) {
16551655
this.unexpected(startLoc);
16561656
}
1657-
return this.parseExprOp(id, this.start, startLoc, -1, false);
1657+
return this.parseExprOp(id, this.start, startLoc, -1, false);
16581658
} else if (this.type == TokenType.name) {
16591659
Position startLoc = this.startLoc;
16601660
Identifier id = this.parseIdent(this.type != TokenType.name);
@@ -2783,7 +2783,7 @@ protected Statement parseBreakContinueStatement(Position startLoc, String keywor
27832783
boolean isBreak = keyword.equals("break");
27842784
this.next();
27852785
Identifier label = null;
2786-
if (this.eat(TokenType.semi) || this.insertSemicolon()) {
2786+
if (this.eagerlyTrySemicolon()) {
27872787
label = null;
27882788
} else if (this.type != TokenType.name) {
27892789
this.unexpected();
@@ -2893,6 +2893,15 @@ protected Statement parseIfStatement(Position startLoc) {
28932893
new IfStatement(new SourceLocation(startLoc), test, consequent, alternate));
28942894
}
28952895

2896+
/**
2897+
* Consumes or inserts a semicolon if possible, and returns true if a semicolon was consumed or inserted.
2898+
*
2899+
* Returns false if there was no semicolon and insertion was not possible.
2900+
*/
2901+
protected boolean eagerlyTrySemicolon() {
2902+
return this.eat(TokenType.semi) || this.insertSemicolon();
2903+
}
2904+
28962905
protected ReturnStatement parseReturnStatement(Position startLoc) {
28972906
if (!this.inFunction && !this.options.allowReturnOutsideFunction())
28982907
this.raise(this.start, "'return' outside of function");
@@ -2902,7 +2911,7 @@ protected ReturnStatement parseReturnStatement(Position startLoc) {
29022911
// optional arguments, we eagerly look for a semicolon or the
29032912
// possibility to insert one.
29042913
Expression argument;
2905-
if (this.eat(TokenType.semi) || this.insertSemicolon()) {
2914+
if (this.eagerlyTrySemicolon()) {
29062915
argument = null;
29072916
} else {
29082917
argument = this.parseExpression(false, null);
@@ -3404,6 +3413,7 @@ protected ExportDeclaration parseExportRest(SourceLocation loc, Set<String> expo
34043413
Statement declaration;
34053414
List<ExportSpecifier> specifiers;
34063415
Expression source = null;
3416+
Expression assertion = null;
34073417
if (this.shouldParseExportStatement()) {
34083418
declaration = this.parseStatement(true, false);
34093419
if (declaration == null) return null;
@@ -3419,11 +3429,13 @@ protected ExportDeclaration parseExportRest(SourceLocation loc, Set<String> expo
34193429
declaration = null;
34203430
specifiers = this.parseExportSpecifiers(exports);
34213431
source = parseExportFrom(specifiers, source, false);
3432+
assertion = parseImportOrExportAssertionAndSemicolon();
34223433
}
34233434
return this.finishNode(
3424-
new ExportNamedDeclaration(loc, declaration, specifiers, (Literal) source));
3435+
new ExportNamedDeclaration(loc, declaration, specifiers, (Literal) source, assertion));
34253436
}
34263437

3438+
/** Parses the 'from' clause of an export, not including the assertion or semicolon. */
34273439
protected Expression parseExportFrom(
34283440
List<ExportSpecifier> specifiers, Expression source, boolean expectFrom) {
34293441
if (this.eatContextual("from")) {
@@ -3442,14 +3454,14 @@ protected Expression parseExportFrom(
34423454

34433455
source = null;
34443456
}
3445-
this.semicolon();
34463457
return source;
34473458
}
34483459

34493460
protected ExportDeclaration parseExportAll(
34503461
SourceLocation loc, Position starLoc, Set<String> exports) {
34513462
Expression source = parseExportFrom(null, null, true);
3452-
return this.finishNode(new ExportAllDeclaration(loc, (Literal) source));
3463+
Expression assertion = parseImportOrExportAssertionAndSemicolon();
3464+
return this.finishNode(new ExportAllDeclaration(loc, (Literal) source, assertion));
34533465
}
34543466

34553467
private void checkExport(Set<String> exports, String name, Position pos) {
@@ -3514,6 +3526,16 @@ protected Statement parseImport(Position startLoc) {
35143526
return parseImportRest(loc);
35153527
}
35163528

3529+
protected Expression parseImportOrExportAssertionAndSemicolon() {
3530+
Expression result = null;
3531+
if (!this.eagerlyTrySemicolon()) {
3532+
this.expectContextual("assert");
3533+
result = this.parseObj(false, null);
3534+
this.semicolon();
3535+
}
3536+
return result;
3537+
}
3538+
35173539
protected ImportDeclaration parseImportRest(SourceLocation loc) {
35183540
List<ImportSpecifier> specifiers;
35193541
Literal source;
@@ -3527,9 +3549,9 @@ protected ImportDeclaration parseImportRest(SourceLocation loc) {
35273549
if (this.type != TokenType.string) this.unexpected();
35283550
source = (Literal) this.parseExprAtom(null);
35293551
}
3530-
this.semicolon();
3552+
Expression assertion = this.parseImportOrExportAssertionAndSemicolon();
35313553
if (specifiers == null) return null;
3532-
return this.finishNode(new ImportDeclaration(loc, specifiers, source));
3554+
return this.finishNode(new ImportDeclaration(loc, specifiers, source, assertion));
35333555
}
35343556

35353557
// Parses a comma-separated list of module imports.

javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,10 +943,12 @@ protected ExportDeclaration parseExportRest(SourceLocation loc, Set<String> expo
943943
// `export type { foo, bar };`
944944
List<ExportSpecifier> specifiers = this.parseExportSpecifiers(exports);
945945
this.parseExportFrom(specifiers, null, false);
946+
this.parseImportOrExportAssertionAndSemicolon();
946947
return null;
947948
} else if (this.eat(TokenType.star)) {
948949
if (this.eatContextual("as")) this.parseIdent(true);
949950
this.parseExportFrom(null, null, true);
951+
this.parseImportOrExportAssertionAndSemicolon();
950952
return null;
951953
} else {
952954
// `export type Foo = Bar;`

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22

33
public class DynamicImport extends Expression {
44
private final Expression source;
5+
private final Expression attributes;
56

6-
public DynamicImport(SourceLocation loc, Expression source) {
7+
public DynamicImport(SourceLocation loc, Expression source, Expression attributes) {
78
super("DynamicImport", loc);
89
this.source = source;
10+
this.attributes = attributes;
911
}
1012

1113
public Expression getSource() {
1214
return source;
1315
}
1416

17+
/** Returns the second "argument" provided to the import, such as <code>{ assert: { type: "json" }}</code>. */
18+
public Expression getAttributes() {
19+
return attributes;
20+
}
21+
1522
@Override
1623
public <C, R> R accept(Visitor<C, R> v, C c) {
1724
return v.visit(this, c);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@
99
*/
1010
public class ExportAllDeclaration extends ExportDeclaration {
1111
private final Literal source;
12+
private final Expression assertion;
1213

13-
public ExportAllDeclaration(SourceLocation loc, Literal source) {
14+
public ExportAllDeclaration(SourceLocation loc, Literal source, Expression assertion) {
1415
super("ExportAllDeclaration", loc);
1516
this.source = source;
17+
this.assertion = assertion;
1618
}
1719

1820
public Literal getSource() {
1921
return source;
2022
}
2123

24+
public Expression getAssertion() {
25+
return assertion;
26+
}
27+
2228
@Override
2329
public <C, R> R accept(Visitor<C, R> v, C c) {
2430
return v.visit(this, c);

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

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

2021
public ExportNamedDeclaration(
21-
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source) {
22-
this(loc, declaration, specifiers, source, false);
22+
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source, Expression assertion) {
23+
this(loc, declaration, specifiers, source, assertion, false);
2324
}
2425

2526
public ExportNamedDeclaration(
2627
SourceLocation loc, Statement declaration, List<ExportSpecifier> specifiers, Literal source,
27-
boolean hasTypeKeyword) {
28+
Expression assertion, boolean hasTypeKeyword) {
2829
super("ExportNamedDeclaration", loc);
2930
this.declaration = declaration;
3031
this.specifiers = specifiers;
3132
this.source = source;
33+
this.assertion = assertion;
3234
this.hasTypeKeyword = hasTypeKeyword;
3335
}
3436

@@ -57,6 +59,11 @@ public <C, R> R accept(Visitor<C, R> v, C c) {
5759
return v.visit(this, c);
5860
}
5961

62+
/** Returns the expression after the <code>assert</code> keyword, if any, such as <code>{ type: "json" }</code>. */
63+
public Expression getAssertion() {
64+
return assertion;
65+
}
66+
6067
/** Returns true if this is an <code>export type</code> declaration. */
6168
public boolean hasTypeKeyword() {
6269
return hasTypeKeyword;

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,21 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
2323
/** The module from which declarations are imported. */
2424
private final Literal source;
2525

26+
private final Expression assertion;
27+
2628
private int symbol = -1;
2729

2830
private boolean hasTypeKeyword;
2931

30-
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source) {
31-
this(loc, specifiers, source, false);
32+
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression assertion) {
33+
this(loc, specifiers, source, assertion, false);
3234
}
3335

34-
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, boolean hasTypeKeyword) {
36+
public ImportDeclaration(SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression assertion, boolean hasTypeKeyword) {
3537
super("ImportDeclaration", loc);
3638
this.specifiers = specifiers;
3739
this.source = source;
40+
this.assertion = assertion;
3841
this.hasTypeKeyword = hasTypeKeyword;
3942
}
4043

@@ -46,6 +49,11 @@ public List<ImportSpecifier> getSpecifiers() {
4649
return specifiers;
4750
}
4851

52+
/** Returns the expression after the <code>assert</code> keyword, if any, such as <code>{ type: "json" }</code>. */
53+
public Expression getAssertion() {
54+
return assertion;
55+
}
56+
4957
@Override
5058
public <C, R> R accept(Visitor<C, R> v, C c) {
5159
return v.visit(this, c);

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ public MetaProperty visit(MetaProperty nd, Void c) {
523523

524524
@Override
525525
public ExportAllDeclaration visit(ExportAllDeclaration nd, Void c) {
526-
return new ExportAllDeclaration(visit(nd.getLoc()), copy(nd.getSource()));
526+
return new ExportAllDeclaration(visit(nd.getLoc()), copy(nd.getSource()), copy(nd.getAssertion()));
527527
}
528528

529529
@Override
@@ -537,7 +537,8 @@ public ExportNamedDeclaration visit(ExportNamedDeclaration nd, Void c) {
537537
visit(nd.getLoc()),
538538
copy(nd.getDeclaration()),
539539
copy(nd.getSpecifiers()),
540-
copy(nd.getSource()));
540+
copy(nd.getSource()),
541+
copy(nd.getAssertion()));
541542
}
542543

543544
@Override
@@ -558,7 +559,7 @@ public ExportSpecifier visit(ExportSpecifier nd, Void c) {
558559
@Override
559560
public ImportDeclaration visit(ImportDeclaration nd, Void c) {
560561
return new ImportDeclaration(
561-
visit(nd.getLoc()), copy(nd.getSpecifiers()), copy(nd.getSource()));
562+
visit(nd.getLoc()), copy(nd.getSpecifiers()), copy(nd.getSource()), copy(nd.getAssertion()), nd.hasTypeKeyword());
562563
}
563564

564565
@Override
@@ -678,7 +679,7 @@ public INode visit(ExternalModuleReference nd, Void c) {
678679

679680
@Override
680681
public INode visit(DynamicImport nd, Void c) {
681-
return new DynamicImport(visit(nd.getLoc()), copy(nd.getSource()));
682+
return new DynamicImport(visit(nd.getLoc()), copy(nd.getSource()), copy(nd.getAttributes()));
682683
}
683684

684685
@Override

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,7 @@ public Label visit(MetaProperty nd, Context c) {
17591759
public Label visit(ExportAllDeclaration nd, Context c) {
17601760
Label lbl = super.visit(nd, c);
17611761
visit(nd.getSource(), lbl, 0);
1762+
visit(nd.getAssertion(), lbl, -10);
17621763
return lbl;
17631764
}
17641765

@@ -1774,6 +1775,7 @@ public Label visit(ExportNamedDeclaration nd, Context c) {
17741775
Label lbl = super.visit(nd, c);
17751776
visit(nd.getDeclaration(), lbl, -1);
17761777
visit(nd.getSource(), lbl, -2);
1778+
visit(nd.getAssertion(), lbl, -10);
17771779
IdContext childContext =
17781780
nd.hasSource()
17791781
? IdContext.LABEL
@@ -1797,6 +1799,7 @@ public Label visit(ExportSpecifier nd, Context c) {
17971799
public Label visit(ImportDeclaration nd, Context c) {
17981800
Label lbl = super.visit(nd, c);
17991801
visit(nd.getSource(), lbl, -1);
1802+
visit(nd.getAssertion(), lbl, -10);
18001803
IdContext childContext =
18011804
nd.hasTypeKeyword()
18021805
? IdContext.TYPE_ONLY_IMPORT
@@ -1972,6 +1975,7 @@ public Label visit(ExternalModuleReference nd, Context c) {
19721975
public Label visit(DynamicImport nd, Context c) {
19731976
Label key = super.visit(nd, c);
19741977
visit(nd.getSource(), key, 0);
1978+
visit(nd.getAttributes(), key, 1);
19751979
return key;
19761980
}
19771981

0 commit comments

Comments
 (0)