Skip to content

Commit

Permalink
Add support for ES2025 Import Attributes
Browse files Browse the repository at this point in the history
FEATURE: Support ES2025 import attributes.

Closes #1289
  • Loading branch information
ota-meshi authored Oct 27, 2024
1 parent cc5ec01 commit f55d97c
Show file tree
Hide file tree
Showing 10 changed files with 978 additions and 9 deletions.
4 changes: 3 additions & 1 deletion acorn-loose/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ lp.parseExprImport = function() {
}

lp.parseDynamicImport = function(node) {
node.source = this.parseExprList(tt.parenR)[0] || this.dummyString()
const list = this.parseExprList(tt.parenR)
node.source = list[0] || this.dummyString()
node.options = list[1] || null
return this.finishNode(node, "ImportExpression")
}

Expand Down
37 changes: 37 additions & 0 deletions acorn-loose/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ lp.parseExport = function() {
}
}
node.source = this.eatContextual("from") ? this.parseExprAtom() : this.dummyString()
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
this.semicolon()
return this.finishNode(node, "ExportAllDeclaration")
}
Expand Down Expand Up @@ -488,6 +490,8 @@ lp.parseExport = function() {
node.declaration = null
node.specifiers = this.parseExportSpecifierList()
node.source = this.eatContextual("from") ? this.parseExprAtom() : null
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
this.semicolon()
}
return this.finishNode(node, "ExportNamedDeclaration")
Expand All @@ -511,6 +515,8 @@ lp.parseImport = function() {
node.source = this.eatContextual("from") && this.tok.type === tt.string ? this.parseExprAtom() : this.dummyString()
if (elt) node.specifiers.unshift(elt)
}
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
this.semicolon()
return this.finishNode(node, "ImportDeclaration")
}
Expand Down Expand Up @@ -548,6 +554,37 @@ lp.parseImportSpecifiers = function() {
return elts
}

lp.parseWithClause = function() {
let nodes = []
if (!this.eat(tt._with)) {
return nodes
}

let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
this.pushCx()
this.eat(tt.braceL)
if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
const attr = this.startNode()
attr.key = this.tok.type === tt.string ? this.parseExprAtom() : this.parseIdent()
if (this.eat(tt.colon)) {
if (this.tok.type === tt.string)
attr.value = this.parseExprAtom()
else attr.value = this.dummyString()
} else {
if (isDummy(attr.key)) break
if (this.tok.type === tt.string)
attr.value = this.parseExprAtom()
else break
}
nodes.push(this.finishNode(attr, "ImportAttribute"))
this.eat(tt.comma)
}
this.eat(tt.braceR)
this.popCx()
return nodes
}

lp.parseExportSpecifierList = function() {
let elts = []
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
Expand Down
11 changes: 11 additions & 0 deletions acorn-walk/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,19 +347,30 @@ base.ExportNamedDeclaration = base.ExportDefaultDeclaration = (node, st, c) => {
if (node.declaration)
c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression")
if (node.source) c(node.source, st, "Expression")
if (node.attributes)
for (let attr of node.attributes)
c(attr, st)
}
base.ExportAllDeclaration = (node, st, c) => {
if (node.exported)
c(node.exported, st)
c(node.source, st, "Expression")
for (let attr of node.attributes)
c(attr, st)
}
base.ImportAttribute = (node, st, c) => {
c(node.value, st, "Expression")
}
base.ImportDeclaration = (node, st, c) => {
for (let spec of node.specifiers)
c(spec, st)
c(node.source, st, "Expression")
for (let attr of node.attributes)
c(attr, st)
}
base.ImportExpression = (node, st, c) => {
c(node.source, st, "Expression")
if (node.options) c(node.options, st, "Expression")
}
base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore

Expand Down
12 changes: 11 additions & 1 deletion acorn/src/acorn.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export interface ImportDeclaration extends Node {
type: "ImportDeclaration"
specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>
source: Literal
attributes: Array<ImportAttribute>
}

export interface ImportSpecifier extends Node {
Expand All @@ -421,11 +422,18 @@ export interface ImportNamespaceSpecifier extends Node {
local: Identifier
}

export interface ImportAttribute extends Node {
type: "ImportAttribute"
key: Identifier | Literal
value: Literal
}

export interface ExportNamedDeclaration extends Node {
type: "ExportNamedDeclaration"
declaration?: Declaration | null
specifiers: Array<ExportSpecifier>
source?: Literal | null
attributes: Array<ImportAttribute>
}

export interface ExportSpecifier extends Node {
Expand Down Expand Up @@ -454,6 +462,7 @@ export interface ExportAllDeclaration extends Node {
type: "ExportAllDeclaration"
source: Literal
exported?: Identifier | Literal | null
attributes: Array<ImportAttribute>
}

export interface AwaitExpression extends Node {
Expand All @@ -469,6 +478,7 @@ export interface ChainExpression extends Node {
export interface ImportExpression extends Node {
type: "ImportExpression"
source: Expression
options: Expression | null
}

export interface ParenthesizedExpression extends Node {
Expand Down Expand Up @@ -562,7 +572,7 @@ export type ModuleDeclaration =
| ExportDefaultDeclaration
| ExportAllDeclaration

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
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

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

Expand Down
31 changes: 25 additions & 6 deletions acorn/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,13 +546,32 @@ pp.parseDynamicImport = function(node) {
// Parse node.source.
node.source = this.parseMaybeAssign()

// Verify ending.
if (!this.eat(tt.parenR)) {
const errorPos = this.start
if (this.eat(tt.comma) && this.eat(tt.parenR)) {
this.raiseRecoverable(errorPos, "Trailing comma is not allowed in import()")
if (this.options.ecmaVersion >= 16) {
if (!this.eat(tt.parenR)) {
this.expect(tt.comma)
if (!this.afterTrailingComma(tt.parenR)) {
node.options = this.parseMaybeAssign()
if (!this.eat(tt.parenR)) {
this.expect(tt.comma)
if (!this.afterTrailingComma(tt.parenR)) {
this.unexpected()
}
}
} else {
node.options = null
}
} else {
this.unexpected(errorPos)
node.options = null
}
} else {
// Verify ending.
if (!this.eat(tt.parenR)) {
const errorPos = this.start
if (this.eat(tt.comma) && this.eat(tt.parenR)) {
this.raiseRecoverable(errorPos, "Trailing comma is not allowed in import()")
} else {
this.unexpected(errorPos)
}
}
}

Expand Down
41 changes: 41 additions & 0 deletions acorn/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,8 @@ pp.parseExportAllDeclaration = function(node, exports) {
this.expectContextual("from")
if (this.type !== tt.string) this.unexpected()
node.source = this.parseExprAtom()
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
this.semicolon()
return this.finishNode(node, "ExportAllDeclaration")
}
Expand Down Expand Up @@ -889,6 +891,8 @@ pp.parseExport = function(node, exports) {
if (this.eatContextual("from")) {
if (this.type !== tt.string) this.unexpected()
node.source = this.parseExprAtom()
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
} else {
for (let spec of node.specifiers) {
// check for keywords used as local names
Expand Down Expand Up @@ -1017,6 +1021,8 @@ pp.parseImport = function(node) {
this.expectContextual("from")
node.source = this.type === tt.string ? this.parseExprAtom() : this.unexpected()
}
if (this.options.ecmaVersion >= 16)
node.attributes = this.parseWithClause()
this.semicolon()
return this.finishNode(node, "ImportDeclaration")
}
Expand Down Expand Up @@ -1077,6 +1083,41 @@ pp.parseImportSpecifiers = function() {
return nodes
}

pp.parseWithClause = function() {
let nodes = []
if (!this.eat(tt._with)) {
return nodes
}
this.expect(tt.braceL)
const attributeKeys = {}
let first = true
while (!this.eat(tt.braceR)) {
if (!first) {
this.expect(tt.comma)
if (this.afterTrailingComma(tt.braceR)) break
} else first = false

const attr = this.parseImportAttribute()
const keyName = attr.key.type === "Identifier" ? attr.key.name : attr.key.value
if (hasOwn(attributeKeys, keyName))
this.raiseRecoverable(attr.key.start, "Duplicate attribute key '" + keyName + "'")
attributeKeys[keyName] = true
nodes.push(attr)
}
return nodes
}

pp.parseImportAttribute = function() {
const node = this.startNode()
node.key = this.type === tt.string ? this.parseExprAtom() : this.parseIdent(this.options.allowReserved !== "never")
this.expect(tt.colon)
if (this.type !== tt.string) {
this.unexpected()
}
node.value = this.parseExprAtom()
return this.finishNode(node, "ImportAttribute")
}

pp.parseModuleExportName = function() {
if (this.options.ecmaVersion >= 13 && this.type === tt.string) {
const stringLiteral = this.parseLiteral(this.value)
Expand Down
1 change: 0 additions & 1 deletion bin/test262.unsupported-features
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
decorators
explicit-resource-management
regexp-modifiers
import-attributes
source-phase-imports
4 changes: 4 additions & 0 deletions bin/test262.whitelist
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Todhri.js (defau
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Todhri.js (strict mode)
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Tulu_Tigalari.js (default)
built-ins/RegExp/property-escapes/generated/Script_Extensions_-_Tulu_Tigalari.js (strict mode)
language/import/import-attributes/json-invalid.js (default)
language/import/import-attributes/json-invalid.js (strict mode)
language/import/import-attributes/json-named-bindings.js (default)
language/import/import-attributes/json-named-bindings.js (strict mode)
1 change: 1 addition & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
require("./tests-numeric-separators.js");
require("./tests-class-features-2022.js");
require("./tests-module-string-names.js");
require("./tests-import-attributes.js");
var acorn = require("../acorn")
var acorn_loose = require("../acorn-loose")

Expand Down
Loading

0 comments on commit f55d97c

Please sign in to comment.