Skip to content

Commit f55d97c

Browse files
authored
Add support for ES2025 Import Attributes
FEATURE: Support ES2025 import attributes. Closes #1289
1 parent cc5ec01 commit f55d97c

File tree

10 files changed

+978
-9
lines changed

10 files changed

+978
-9
lines changed

acorn-loose/src/expression.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,9 @@ lp.parseExprImport = function() {
352352
}
353353

354354
lp.parseDynamicImport = function(node) {
355-
node.source = this.parseExprList(tt.parenR)[0] || this.dummyString()
355+
const list = this.parseExprList(tt.parenR)
356+
node.source = list[0] || this.dummyString()
357+
node.options = list[1] || null
356358
return this.finishNode(node, "ImportExpression")
357359
}
358360

acorn-loose/src/statement.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ lp.parseExport = function() {
461461
}
462462
}
463463
node.source = this.eatContextual("from") ? this.parseExprAtom() : this.dummyString()
464+
if (this.options.ecmaVersion >= 16)
465+
node.attributes = this.parseWithClause()
464466
this.semicolon()
465467
return this.finishNode(node, "ExportAllDeclaration")
466468
}
@@ -488,6 +490,8 @@ lp.parseExport = function() {
488490
node.declaration = null
489491
node.specifiers = this.parseExportSpecifierList()
490492
node.source = this.eatContextual("from") ? this.parseExprAtom() : null
493+
if (this.options.ecmaVersion >= 16)
494+
node.attributes = this.parseWithClause()
491495
this.semicolon()
492496
}
493497
return this.finishNode(node, "ExportNamedDeclaration")
@@ -511,6 +515,8 @@ lp.parseImport = function() {
511515
node.source = this.eatContextual("from") && this.tok.type === tt.string ? this.parseExprAtom() : this.dummyString()
512516
if (elt) node.specifiers.unshift(elt)
513517
}
518+
if (this.options.ecmaVersion >= 16)
519+
node.attributes = this.parseWithClause()
514520
this.semicolon()
515521
return this.finishNode(node, "ImportDeclaration")
516522
}
@@ -548,6 +554,37 @@ lp.parseImportSpecifiers = function() {
548554
return elts
549555
}
550556

557+
lp.parseWithClause = function() {
558+
let nodes = []
559+
if (!this.eat(tt._with)) {
560+
return nodes
561+
}
562+
563+
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
564+
this.pushCx()
565+
this.eat(tt.braceL)
566+
if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
567+
while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
568+
const attr = this.startNode()
569+
attr.key = this.tok.type === tt.string ? this.parseExprAtom() : this.parseIdent()
570+
if (this.eat(tt.colon)) {
571+
if (this.tok.type === tt.string)
572+
attr.value = this.parseExprAtom()
573+
else attr.value = this.dummyString()
574+
} else {
575+
if (isDummy(attr.key)) break
576+
if (this.tok.type === tt.string)
577+
attr.value = this.parseExprAtom()
578+
else break
579+
}
580+
nodes.push(this.finishNode(attr, "ImportAttribute"))
581+
this.eat(tt.comma)
582+
}
583+
this.eat(tt.braceR)
584+
this.popCx()
585+
return nodes
586+
}
587+
551588
lp.parseExportSpecifierList = function() {
552589
let elts = []
553590
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart

acorn-walk/src/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,19 +347,30 @@ base.ExportNamedDeclaration = base.ExportDefaultDeclaration = (node, st, c) => {
347347
if (node.declaration)
348348
c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression")
349349
if (node.source) c(node.source, st, "Expression")
350+
if (node.attributes)
351+
for (let attr of node.attributes)
352+
c(attr, st)
350353
}
351354
base.ExportAllDeclaration = (node, st, c) => {
352355
if (node.exported)
353356
c(node.exported, st)
354357
c(node.source, st, "Expression")
358+
for (let attr of node.attributes)
359+
c(attr, st)
360+
}
361+
base.ImportAttribute = (node, st, c) => {
362+
c(node.value, st, "Expression")
355363
}
356364
base.ImportDeclaration = (node, st, c) => {
357365
for (let spec of node.specifiers)
358366
c(spec, st)
359367
c(node.source, st, "Expression")
368+
for (let attr of node.attributes)
369+
c(attr, st)
360370
}
361371
base.ImportExpression = (node, st, c) => {
362372
c(node.source, st, "Expression")
373+
if (node.options) c(node.options, st, "Expression")
363374
}
364375
base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore
365376

acorn/src/acorn.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ export interface ImportDeclaration extends Node {
403403
type: "ImportDeclaration"
404404
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>
405405
source: Literal
406+
attributes: Array<ImportAttribute>
406407
}
407408

408409
export interface ImportSpecifier extends Node {
@@ -421,11 +422,18 @@ export interface ImportNamespaceSpecifier extends Node {
421422
local: Identifier
422423
}
423424

425+
export interface ImportAttribute extends Node {
426+
type: "ImportAttribute"
427+
key: Identifier | Literal
428+
value: Literal
429+
}
430+
424431
export interface ExportNamedDeclaration extends Node {
425432
type: "ExportNamedDeclaration"
426433
declaration?: Declaration | null
427434
specifiers: Array<ExportSpecifier>
428435
source?: Literal | null
436+
attributes: Array<ImportAttribute>
429437
}
430438

431439
export interface ExportSpecifier extends Node {
@@ -454,6 +462,7 @@ export interface ExportAllDeclaration extends Node {
454462
type: "ExportAllDeclaration"
455463
source: Literal
456464
exported?: Identifier | Literal | null
465+
attributes: Array<ImportAttribute>
457466
}
458467

459468
export interface AwaitExpression extends Node {
@@ -469,6 +478,7 @@ export interface ChainExpression extends Node {
469478
export interface ImportExpression extends Node {
470479
type: "ImportExpression"
471480
source: Expression
481+
options: Expression | null
472482
}
473483

474484
export interface ParenthesizedExpression extends Node {
@@ -562,7 +572,7 @@ export type ModuleDeclaration =
562572
| ExportDefaultDeclaration
563573
| ExportAllDeclaration
564574

565-
export type AnyNode = Statement | Expression | Declaration | ModuleDeclaration | Literal | Program | SwitchCase | CatchClause | Property | Super | SpreadElement | TemplateElement | AssignmentProperty | ObjectPattern | ArrayPattern | RestElement | AssignmentPattern | ClassBody | MethodDefinition | MetaProperty | ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier | ExportSpecifier | AnonymousFunctionDeclaration | AnonymousClassDeclaration | PropertyDefinition | PrivateIdentifier | StaticBlock | VariableDeclarator
575+
export type AnyNode = Statement | Expression | Declaration | ModuleDeclaration | Literal | Program | SwitchCase | CatchClause | Property | Super | SpreadElement | TemplateElement | AssignmentProperty | ObjectPattern | ArrayPattern | RestElement | AssignmentPattern | ClassBody | MethodDefinition | MetaProperty | ImportAttribute | ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier | ExportSpecifier | AnonymousFunctionDeclaration | AnonymousClassDeclaration | PropertyDefinition | PrivateIdentifier | StaticBlock | VariableDeclarator
566576

567577
export function parse(input: string, options: Options): Program
568578

acorn/src/expression.js

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,13 +546,32 @@ pp.parseDynamicImport = function(node) {
546546
// Parse node.source.
547547
node.source = this.parseMaybeAssign()
548548

549-
// Verify ending.
550-
if (!this.eat(tt.parenR)) {
551-
const errorPos = this.start
552-
if (this.eat(tt.comma) && this.eat(tt.parenR)) {
553-
this.raiseRecoverable(errorPos, "Trailing comma is not allowed in import()")
549+
if (this.options.ecmaVersion >= 16) {
550+
if (!this.eat(tt.parenR)) {
551+
this.expect(tt.comma)
552+
if (!this.afterTrailingComma(tt.parenR)) {
553+
node.options = this.parseMaybeAssign()
554+
if (!this.eat(tt.parenR)) {
555+
this.expect(tt.comma)
556+
if (!this.afterTrailingComma(tt.parenR)) {
557+
this.unexpected()
558+
}
559+
}
560+
} else {
561+
node.options = null
562+
}
554563
} else {
555-
this.unexpected(errorPos)
564+
node.options = null
565+
}
566+
} else {
567+
// Verify ending.
568+
if (!this.eat(tt.parenR)) {
569+
const errorPos = this.start
570+
if (this.eat(tt.comma) && this.eat(tt.parenR)) {
571+
this.raiseRecoverable(errorPos, "Trailing comma is not allowed in import()")
572+
} else {
573+
this.unexpected(errorPos)
574+
}
556575
}
557576
}
558577

acorn/src/statement.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,8 @@ pp.parseExportAllDeclaration = function(node, exports) {
859859
this.expectContextual("from")
860860
if (this.type !== tt.string) this.unexpected()
861861
node.source = this.parseExprAtom()
862+
if (this.options.ecmaVersion >= 16)
863+
node.attributes = this.parseWithClause()
862864
this.semicolon()
863865
return this.finishNode(node, "ExportAllDeclaration")
864866
}
@@ -889,6 +891,8 @@ pp.parseExport = function(node, exports) {
889891
if (this.eatContextual("from")) {
890892
if (this.type !== tt.string) this.unexpected()
891893
node.source = this.parseExprAtom()
894+
if (this.options.ecmaVersion >= 16)
895+
node.attributes = this.parseWithClause()
892896
} else {
893897
for (let spec of node.specifiers) {
894898
// check for keywords used as local names
@@ -1017,6 +1021,8 @@ pp.parseImport = function(node) {
10171021
this.expectContextual("from")
10181022
node.source = this.type === tt.string ? this.parseExprAtom() : this.unexpected()
10191023
}
1024+
if (this.options.ecmaVersion >= 16)
1025+
node.attributes = this.parseWithClause()
10201026
this.semicolon()
10211027
return this.finishNode(node, "ImportDeclaration")
10221028
}
@@ -1077,6 +1083,41 @@ pp.parseImportSpecifiers = function() {
10771083
return nodes
10781084
}
10791085

1086+
pp.parseWithClause = function() {
1087+
let nodes = []
1088+
if (!this.eat(tt._with)) {
1089+
return nodes
1090+
}
1091+
this.expect(tt.braceL)
1092+
const attributeKeys = {}
1093+
let first = true
1094+
while (!this.eat(tt.braceR)) {
1095+
if (!first) {
1096+
this.expect(tt.comma)
1097+
if (this.afterTrailingComma(tt.braceR)) break
1098+
} else first = false
1099+
1100+
const attr = this.parseImportAttribute()
1101+
const keyName = attr.key.type === "Identifier" ? attr.key.name : attr.key.value
1102+
if (hasOwn(attributeKeys, keyName))
1103+
this.raiseRecoverable(attr.key.start, "Duplicate attribute key '" + keyName + "'")
1104+
attributeKeys[keyName] = true
1105+
nodes.push(attr)
1106+
}
1107+
return nodes
1108+
}
1109+
1110+
pp.parseImportAttribute = function() {
1111+
const node = this.startNode()
1112+
node.key = this.type === tt.string ? this.parseExprAtom() : this.parseIdent(this.options.allowReserved !== "never")
1113+
this.expect(tt.colon)
1114+
if (this.type !== tt.string) {
1115+
this.unexpected()
1116+
}
1117+
node.value = this.parseExprAtom()
1118+
return this.finishNode(node, "ImportAttribute")
1119+
}
1120+
10801121
pp.parseModuleExportName = function() {
10811122
if (this.options.ecmaVersion >= 13 && this.type === tt.string) {
10821123
const stringLiteral = this.parseLiteral(this.value)

bin/test262.unsupported-features

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
decorators
22
explicit-resource-management
33
regexp-modifiers
4-
import-attributes
54
source-phase-imports

bin/test262.whitelist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Todhri.js (defau
2626
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Todhri.js (strict mode)
2727
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Tulu_Tigalari.js (default)
2828
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Tulu_Tigalari.js (strict mode)
29+
language/import/import-attributes/json-invalid.js (default)
30+
language/import/import-attributes/json-invalid.js (strict mode)
31+
language/import/import-attributes/json-named-bindings.js (default)
32+
language/import/import-attributes/json-named-bindings.js (strict mode)

test/run.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
require("./tests-numeric-separators.js");
2929
require("./tests-class-features-2022.js");
3030
require("./tests-module-string-names.js");
31+
require("./tests-import-attributes.js");
3132
var acorn = require("../acorn")
3233
var acorn_loose = require("../acorn-loose")
3334

0 commit comments

Comments
 (0)