Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ES2025 Import Attributes #1325

Merged
merged 2 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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