From bab2a79055f0127f996246d0a9cf1cd9e3445ce2 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 5 Sep 2025 11:38:16 +0200 Subject: [PATCH] JS: Add parsing support in JS parser --- .../src/com/semmle/jcorn/Parser.java | 21 ++++++++++++------- .../src/com/semmle/jcorn/flow/FlowParser.java | 5 +++-- .../TypeScript/ImportDefer/test-js.js | 3 +++ .../TypeScript/ImportDefer/test.expected | 1 + 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 javascript/ql/test/library-tests/TypeScript/ImportDefer/test-js.js diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index 9c2eeb9e241..63f49e14891 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -61,6 +61,7 @@ import com.semmle.js.ast.IfStatement; import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier; +import com.semmle.js.ast.ImportPhaseModifier; import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.LabeledStatement; import com.semmle.js.ast.Literal; @@ -3587,6 +3588,7 @@ public class Parser { } protected ImportDeclaration parseImportRest(SourceLocation loc) { + ImportPhaseModifier[] phaseModifier = { ImportPhaseModifier.NONE }; List specifiers; Literal source; // import '...' @@ -3594,27 +3596,32 @@ public class Parser { specifiers = new ArrayList(); source = (Literal) this.parseExprAtom(null); } else { - specifiers = this.parseImportSpecifiers(); + specifiers = this.parseImportSpecifiers(phaseModifier); this.expectContextual("from"); if (this.type != TokenType.string) this.unexpected(); source = (Literal) this.parseExprAtom(null); } Expression attributes = this.parseImportOrExportAttributesAndSemicolon(); 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. - protected List parseImportSpecifiers() { + protected List parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) { List nodes = new ArrayList(); boolean first = true; if (this.type == TokenType.name) { // import defaultObj, { x, y as z } from '...' SourceLocation loc = new SourceLocation(this.startLoc); Identifier local = this.parseIdent(false); - this.checkLVal(local, true, null); - nodes.add(this.finishNode(new ImportDefaultSpecifier(loc, local))); - if (!this.eat(TokenType.comma)) return nodes; + // Parse `import defer *` as the beginning of a deferred import, instead of a default import specifier + if (this.type == TokenType.star && local.getName().equals("defer")) { + 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) { SourceLocation loc = new SourceLocation(this.startLoc); @@ -3647,7 +3654,7 @@ public class Parser { if (this.type == TokenType.string) { // Arbitrary Module Namespace Identifiers // 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(); imported = this.finishNode(new Identifier(loc, str)); // only makes sense if there is a local identifier diff --git a/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java b/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java index 1213c15d99f..cb2dad0f978 100644 --- a/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java +++ b/javascript/extractor/src/com/semmle/jcorn/flow/FlowParser.java @@ -14,6 +14,7 @@ import com.semmle.js.ast.Expression; import com.semmle.js.ast.ExpressionStatement; import com.semmle.js.ast.FieldDefinition; import com.semmle.js.ast.Identifier; +import com.semmle.js.ast.ImportPhaseModifier; import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.Literal; import com.semmle.js.ast.MethodDefinition; @@ -1064,13 +1065,13 @@ public class FlowParser extends ESNextParser { } @Override - protected List parseImportSpecifiers() { + protected List parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) { String kind = null; if (flow()) { kind = flowParseImportSpecifiers(); } - List specs = super.parseImportSpecifiers(); + List specs = super.parseImportSpecifiers(phaseModifier); if (kind != null || specs.isEmpty()) return null; return specs; } diff --git a/javascript/ql/test/library-tests/TypeScript/ImportDefer/test-js.js b/javascript/ql/test/library-tests/TypeScript/ImportDefer/test-js.js new file mode 100644 index 00000000000..a3a80435538 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ImportDefer/test-js.js @@ -0,0 +1,3 @@ +import defer * as deferred from "somewhere"; +import * as normal from "somewhere"; +import defer from "somewhere"; diff --git a/javascript/ql/test/library-tests/TypeScript/ImportDefer/test.expected b/javascript/ql/test/library-tests/TypeScript/ImportDefer/test.expected index 8bd8edc152e..c5d2d2e3f3d 100644 --- a/javascript/ql/test/library-tests/TypeScript/ImportDefer/test.expected +++ b/javascript/ql/test/library-tests/TypeScript/ImportDefer/test.expected @@ -1 +1,2 @@ +| test-js.js:1:1:1:44 | import ... where"; | | tst.ts:1:1:1:44 | import ... where"; |