JS: Add parsing support in JS parser

This commit is contained in:
Asger F
2025-09-05 11:38:16 +02:00
parent 215602c963
commit bab2a79055
4 changed files with 21 additions and 9 deletions

View File

@@ -61,6 +61,7 @@ import com.semmle.js.ast.IfStatement;
import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDeclaration;
import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportDefaultSpecifier;
import com.semmle.js.ast.ImportNamespaceSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier;
import com.semmle.js.ast.ImportPhaseModifier;
import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.ImportSpecifier;
import com.semmle.js.ast.LabeledStatement; import com.semmle.js.ast.LabeledStatement;
import com.semmle.js.ast.Literal; import com.semmle.js.ast.Literal;
@@ -3587,6 +3588,7 @@ public class Parser {
} }
protected ImportDeclaration parseImportRest(SourceLocation loc) { protected ImportDeclaration parseImportRest(SourceLocation loc) {
ImportPhaseModifier[] phaseModifier = { ImportPhaseModifier.NONE };
List<ImportSpecifier> specifiers; List<ImportSpecifier> specifiers;
Literal source; Literal source;
// import '...' // import '...'
@@ -3594,27 +3596,32 @@ public class Parser {
specifiers = new ArrayList<ImportSpecifier>(); specifiers = new ArrayList<ImportSpecifier>();
source = (Literal) this.parseExprAtom(null); source = (Literal) this.parseExprAtom(null);
} else { } else {
specifiers = this.parseImportSpecifiers(); specifiers = this.parseImportSpecifiers(phaseModifier);
this.expectContextual("from"); this.expectContextual("from");
if (this.type != TokenType.string) this.unexpected(); if (this.type != TokenType.string) this.unexpected();
source = (Literal) this.parseExprAtom(null); source = (Literal) this.parseExprAtom(null);
} }
Expression attributes = this.parseImportOrExportAttributesAndSemicolon(); Expression attributes = this.parseImportOrExportAttributesAndSemicolon();
if (specifiers == null) return null; if (specifiers == null) return null;
return this.finishNode(new ImportDeclaration(loc, specifiers, source, attributes)); return this.finishNode(new ImportDeclaration(loc, specifiers, source, attributes, phaseModifier[0]));
} }
// Parses a comma-separated list of module imports. // Parses a comma-separated list of module imports.
protected List<ImportSpecifier> parseImportSpecifiers() { protected List<ImportSpecifier> parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) {
List<ImportSpecifier> nodes = new ArrayList<ImportSpecifier>(); List<ImportSpecifier> nodes = new ArrayList<ImportSpecifier>();
boolean first = true; boolean first = true;
if (this.type == TokenType.name) { if (this.type == TokenType.name) {
// import defaultObj, { x, y as z } from '...' // import defaultObj, { x, y as z } from '...'
SourceLocation loc = new SourceLocation(this.startLoc); SourceLocation loc = new SourceLocation(this.startLoc);
Identifier local = this.parseIdent(false); Identifier local = this.parseIdent(false);
this.checkLVal(local, true, null); // Parse `import defer *` as the beginning of a deferred import, instead of a default import specifier
nodes.add(this.finishNode(new ImportDefaultSpecifier(loc, local))); if (this.type == TokenType.star && local.getName().equals("defer")) {
if (!this.eat(TokenType.comma)) return nodes; phaseModifier[0] = ImportPhaseModifier.DEFER;
} else {
this.checkLVal(local, true, null);
nodes.add(this.finishNode(new ImportDefaultSpecifier(loc, local)));
if (!this.eat(TokenType.comma)) return nodes;
}
} }
if (this.type == TokenType.star) { if (this.type == TokenType.star) {
SourceLocation loc = new SourceLocation(this.startLoc); SourceLocation loc = new SourceLocation(this.startLoc);
@@ -3647,7 +3654,7 @@ public class Parser {
if (this.type == TokenType.string) { if (this.type == TokenType.string) {
// Arbitrary Module Namespace Identifiers // Arbitrary Module Namespace Identifiers
// e.g. `import { "Foo::new" as Foo_new } from "./foo.wasm"` // e.g. `import { "Foo::new" as Foo_new } from "./foo.wasm"`
Expression string = this.parseExprAtom(null); Expression string = this.parseExprAtom(null);
String str = ((Literal)string).getStringValue(); String str = ((Literal)string).getStringValue();
imported = this.finishNode(new Identifier(loc, str)); imported = this.finishNode(new Identifier(loc, str));
// only makes sense if there is a local identifier // only makes sense if there is a local identifier

View File

@@ -14,6 +14,7 @@ import com.semmle.js.ast.Expression;
import com.semmle.js.ast.ExpressionStatement; import com.semmle.js.ast.ExpressionStatement;
import com.semmle.js.ast.FieldDefinition; import com.semmle.js.ast.FieldDefinition;
import com.semmle.js.ast.Identifier; import com.semmle.js.ast.Identifier;
import com.semmle.js.ast.ImportPhaseModifier;
import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.ImportSpecifier;
import com.semmle.js.ast.Literal; import com.semmle.js.ast.Literal;
import com.semmle.js.ast.MethodDefinition; import com.semmle.js.ast.MethodDefinition;
@@ -1064,13 +1065,13 @@ public class FlowParser extends ESNextParser {
} }
@Override @Override
protected List<ImportSpecifier> parseImportSpecifiers() { protected List<ImportSpecifier> parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) {
String kind = null; String kind = null;
if (flow()) { if (flow()) {
kind = flowParseImportSpecifiers(); kind = flowParseImportSpecifiers();
} }
List<ImportSpecifier> specs = super.parseImportSpecifiers(); List<ImportSpecifier> specs = super.parseImportSpecifiers(phaseModifier);
if (kind != null || specs.isEmpty()) return null; if (kind != null || specs.isEmpty()) return null;
return specs; return specs;
} }

View File

@@ -0,0 +1,3 @@
import defer * as deferred from "somewhere";
import * as normal from "somewhere";
import defer from "somewhere";

View File

@@ -1 +1,2 @@
| test-js.js:1:1:1:44 | import ... where"; |
| tst.ts:1:1:1:44 | import ... where"; | | tst.ts:1:1:1:44 | import ... where"; |