feat(ast): implement new proposal-import-attributes (#1476)

- [Import Attributes](https://tc39.es/proposal-import-attributes)
This commit is contained in:
magic-akari 2023-11-25 15:56:09 +08:00 committed by GitHub
parent 46d1086c3e
commit 9ff0ffcc6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 186 additions and 101 deletions

View file

@ -1912,8 +1912,8 @@ pub struct ImportDeclaration<'a> {
/// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'`
pub specifiers: Option<Vec<'a, ImportDeclarationSpecifier>>,
pub source: StringLiteral,
pub assertions: Option<Vec<'a, ImportAttribute>>, // Some(vec![]) for empty assertion
pub import_kind: ImportOrExportKind, // `import type { foo } from 'bar'`
pub with_clause: Option<WithClause<'a>>, // Some(vec![]) for empty assertion
pub import_kind: ImportOrExportKind, // `import type { foo } from 'bar'`
}
#[derive(Debug, Hash)]
@ -1958,6 +1958,15 @@ pub struct ImportNamespaceSpecifier {
pub local: BindingIdentifier,
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
pub struct WithClause<'a> {
#[cfg_attr(feature = "serde", serde(flatten))]
pub span: Span,
pub attributes_keyword: IdentifierName, // `with` or `assert`
pub with_entries: Vec<'a, ImportAttribute>,
}
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
pub struct ImportAttribute {
@ -2027,8 +2036,8 @@ pub struct ExportAllDeclaration<'a> {
pub span: Span,
pub exported: Option<ModuleExportName>,
pub source: StringLiteral,
pub assertions: Option<Vec<'a, ImportAttribute>>, // Some(vec![]) for empty assertion
pub export_kind: ImportOrExportKind, // `export type *`
pub with_clause: Option<WithClause<'a>>, // Some(vec![]) for empty assertion
pub export_kind: ImportOrExportKind, // `export type *`
}
impl<'a> ExportAllDeclaration<'a> {

View file

@ -985,10 +985,10 @@ impl<'a> AstBuilder<'a> {
span: Span,
specifiers: Option<Vec<'a, ImportDeclarationSpecifier>>,
source: StringLiteral,
assertions: Option<Vec<'a, ImportAttribute>>,
with_clause: Option<WithClause<'a>>,
import_kind: ImportOrExportKind,
) -> Box<'a, ImportDeclaration<'a>> {
self.alloc(ImportDeclaration { span, specifiers, source, assertions, import_kind })
self.alloc(ImportDeclaration { span, specifiers, source, with_clause, import_kind })
}
pub fn export_all_declaration(
@ -996,10 +996,10 @@ impl<'a> AstBuilder<'a> {
span: Span,
exported: Option<ModuleExportName>,
source: StringLiteral,
assertions: Option<Vec<'a, ImportAttribute>>,
with_clause: Option<WithClause<'a>>,
export_kind: ImportOrExportKind,
) -> Box<'a, ExportAllDeclaration<'a>> {
self.alloc(ExportAllDeclaration { span, exported, source, assertions, export_kind })
self.alloc(ExportAllDeclaration { span, exported, source, with_clause, export_kind })
}
pub fn export_default_declaration(

View file

@ -649,7 +649,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ImportDeclaration<'a> {
p.print(b'\'');
p.print_str(self.source.value.as_bytes());
p.print(b'\'');
self.assertions.gen(p, ctx);
self.with_clause.gen(p, ctx);
p.print_semicolon_after_statement();
return;
}
@ -713,17 +713,23 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ImportDeclaration<'a> {
p.print_str(b" from ");
}
self.source.gen(p, ctx);
self.assertions.gen(p, ctx);
self.with_clause.gen(p, ctx);
p.print_semicolon_after_statement();
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for Option<Vec<'a, ImportAttribute>> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for Option<WithClause<'a>> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
if let Some(assertions) = &self {
p.print_str(b"assert");
p.print_block(assertions, Separator::Comma, ctx);
};
if let Some(with_clause) = self {
with_clause.gen(p, ctx);
}
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for WithClause<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
self.attributes_keyword.gen(p, ctx);
p.print_block(&self.with_entries, Separator::Comma, ctx);
}
}
@ -804,7 +810,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportAllDeclaration<'a> {
p.print_str(b" from");
self.source.gen(p, ctx);
self.assertions.gen(p, ctx);
self.with_clause.gen(p, ctx);
p.print_semicolon_after_statement();
}

View file

@ -571,7 +571,7 @@ impl<'a> Gen for ImportDeclaration<'a> {
p.print(b'\'');
p.print_str(self.source.value.as_bytes());
p.print(b'\'');
self.assertions.gen(p);
self.with_clause.gen(p);
p.print_semicolon_after_statement();
return;
}
@ -644,19 +644,25 @@ impl<'a> Gen for ImportDeclaration<'a> {
p.print_str(b" from ");
}
self.source.gen(p);
self.assertions.gen(p);
self.with_clause.gen(p);
p.print_semicolon_after_statement();
}
}
impl<'a> Gen for Option<Vec<'a, ImportAttribute>> {
impl<'a> Gen for Option<WithClause<'a>> {
fn gen(&self, p: &mut Formatter) {
if let Some(assertions) = &self {
if let Some(with_clause) = self {
p.print_space();
p.print_str(b"assert");
with_clause.attributes_keyword.gen(p);
p.print_space();
p.print_block(assertions, Separator::Comma);
};
with_clause.with_entries.gen(p);
}
}
}
impl<'a> Gen for Vec<'a, ImportAttribute> {
fn gen(&self, p: &mut Formatter) {
p.print_block(self, Separator::Comma);
}
}
@ -735,7 +741,7 @@ impl<'a> Gen for ExportAllDeclaration<'a> {
p.print_str(b" from");
p.print_space();
self.source.gen(p);
self.assertions.gen(p);
self.with_clause.gen(p);
p.print_semicolon_after_statement();
}

View file

@ -58,14 +58,14 @@ impl<'a> Parser<'a> {
};
let source = self.parse_literal_string()?;
let assertions = self.parse_import_attributes()?;
let with_clause = self.parse_import_attributes()?;
self.asi()?;
let span = self.end_span(span);
let decl = ModuleDeclaration::ImportDeclaration(self.ast.import_declaration(
span,
specifiers,
source,
assertions,
with_clause,
import_kind,
));
Ok(self.ast.module_declaration(decl))
@ -135,19 +135,23 @@ impl<'a> Parser<'a> {
Ok(specifiers)
}
/// [Import assertion](https://tc39.es/proposal-import-assertions)
fn parse_import_attributes(&mut self) -> Result<Option<Vec<'a, ImportAttribute>>> {
if !self.at(Kind::Assert) || self.cur_token().is_on_new_line {
return Ok(None);
}
self.bump_any();
/// [Import Attributes](https://tc39.es/proposal-import-attributes)
fn parse_import_attributes(&mut self) -> Result<Option<WithClause<'a>>> {
let span = self.start_span();
let attributes_keyword = match self.cur_kind() {
Kind::Assert if !self.cur_token().is_on_new_line => self.parse_identifier_name()?,
Kind::With => self.parse_identifier_name()?,
_ => {
return Ok(None);
}
};
let ctx = self.ctx;
self.ctx = Context::default();
let entries = AssertEntries::parse(self)?.elements;
let with_entries = AssertEntries::parse(self)?.elements;
self.ctx = ctx;
Ok(Some(entries))
Ok(Some(WithClause { span: self.end_span(span), attributes_keyword, with_entries }))
}
pub(crate) fn parse_ts_export_assignment_declaration(
@ -365,10 +369,10 @@ impl<'a> Parser<'a> {
let exported = self.eat(Kind::As).then(|| self.parse_module_export_name()).transpose()?;
self.expect(Kind::From)?;
let source = self.parse_literal_string()?;
let assertions = self.parse_import_attributes()?;
let with_clause = self.parse_import_attributes()?;
self.asi()?;
let span = self.end_span(span);
Ok(self.ast.export_all_declaration(span, exported, source, assertions, export_kind))
Ok(self.ast.export_all_declaration(span, exported, source, with_clause, export_kind))
}
// ImportSpecifier :

View file

@ -1,3 +1,3 @@
codegen_test262 Summary:
AST Parsed : 44738/44738 (100.00%)
Positive Passed: 44738/44738 (100.00%)
AST Parsed : 45668/45668 (100.00%)
Positive Passed: 45668/45668 (100.00%)

View file

@ -1,3 +1,3 @@
minifier_test262 Summary:
AST Parsed : 44738/44738 (100.00%)
Positive Passed: 44738/44738 (100.00%)
AST Parsed : 45668/45668 (100.00%)
Positive Passed: 45668/45668 (100.00%)

View file

@ -1,7 +1,11 @@
parser_test262 Summary:
AST Parsed : 44161/44161 (100.00%)
Positive Passed: 44161/44161 (100.00%)
Negative Passed: 3918/3918 (100.00%)
AST Parsed : 45097/45097 (100.00%)
Positive Passed: 45097/45097 (100.00%)
Negative Passed: 3925/3929 (99.90%)
Expect Syntax Error: "language/import/import-assertions/json-invalid.js"
Expect Syntax Error: "language/import/import-assertions/json-named-bindings.js"
Expect Syntax Error: "language/import/import-attributes/json-invalid.js"
Expect Syntax Error: "language/import/import-attributes/json-named-bindings.js"
× '0'-prefixed octal literals and octal escape sequences are deprecated
╭─[annexB/language/expressions/template-literal/legacy-octal-escape-sequence-strict.js:17:1]
17 │
@ -12971,13 +12975,6 @@ Negative Passed: 3918/3918 (100.00%)
· ─────────────────
╰────
× The keyword 'yield' is reserved
╭─[language/expressions/dynamic-import/2nd-param-yield-ident-invalid.js:18:1]
18 │
19 │ import('./empty_FIXTURE.js', yield);
· ─────
╰────
× Keywords cannot contain escape characters
╭─[language/expressions/dynamic-import/escape-sequence-import.js:34:1]
34 │
@ -12985,6 +12982,20 @@ Negative Passed: 3918/3918 (100.00%)
· ───────────
╰────
× The keyword 'yield' is reserved
╭─[language/expressions/dynamic-import/import-assertions/2nd-param-yield-ident-invalid.js:18:1]
18 │
19 │ import('./empty_FIXTURE.js', yield);
· ─────
╰────
× The keyword 'yield' is reserved
╭─[language/expressions/dynamic-import/import-attributes/2nd-param-yield-ident-invalid.js:18:1]
18 │
19 │ import('./empty_FIXTURE.js', yield);
· ─────
╰────
× Cannot assign to this expression
╭─[language/expressions/dynamic-import/syntax/invalid/invalid-assignmenttargettype-syntax-error-1-update-expression.js:45:1]
45 │
@ -18593,6 +18604,27 @@ Negative Passed: 3918/3918 (100.00%)
· ▲
╰────
× Keywords cannot contain escape characters
╭─[language/literals/boolean/false-with-unicode.js:19:1]
19 │
20 │ f\u{61}lse;
· ──────────
╰────
× Keywords cannot contain escape characters
╭─[language/literals/boolean/true-with-unicode.js:19:1]
19 │
20 │ tru\u{65};
· ─────────
╰────
× Keywords cannot contain escape characters
╭─[language/literals/null/null-with-unicode.js:19:1]
19 │
20 │ n\u{75}ll;
· ─────────
╰────
× '0'-prefixed octal literals and octal escape sequences are deprecated
╭─[language/literals/numeric/7.8.3-1gs.js:14:1]
14 │
@ -19320,45 +19352,6 @@ Negative Passed: 3918/3918 (100.00%)
· ─
╰────
× Identifier `test262_a` has already been declared
╭─[language/module-code/early-dup-assert-key-export.js:20:1]
20 │ export * from './import-assertion-3_FIXTURE.js' assert {
21 │ test262_a: '',
· ────┬────
· ╰── `test262_a` has already been declared here
22 │ test262_b: '',
23 │ 'test262_\u0061': ''
· ────────┬───────
· ╰── It can not be redeclared here
24 │ };
╰────
× Identifier `test262_a` has already been declared
╭─[language/module-code/early-dup-assert-key-import-nobinding.js:21:1]
21 │ import './import-assertion-2_FIXTURE.js' assert {
22 │ test262_a: '',
· ────┬────
· ╰── `test262_a` has already been declared here
23 │ test262_b: '',
24 │ 'test262_\u0061': ''
· ────────┬───────
· ╰── It can not be redeclared here
25 │ };
╰────
× Identifier `test262_a` has already been declared
╭─[language/module-code/early-dup-assert-key-import-withbinding.js:21:1]
21 │ import x from './import-assertion-1_FIXTURE.js' assert {
22 │ test262_a: '',
· ────┬────
· ╰── `test262_a` has already been declared here
23 │ test262_b: '',
24 │ 'test262_\u0061': ''
· ────────┬───────
· ╰── It can not be redeclared here
25 │ };
╰────
× Duplicated export 'z'
╭─[language/module-code/early-dup-export-as-star-as.js:17:1]
17 │ var x;
@ -19739,6 +19732,78 @@ Negative Passed: 3918/3918 (100.00%)
22 │
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-assertions/early-dup-assert-key-export.js:20:1]
20 │ export * from './import-assertion-3_FIXTURE.js' assert {
21 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
22 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
23 │ };
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-assertions/early-dup-assert-key-import-nobinding.js:21:1]
21 │ import './import-assertion-2_FIXTURE.js' assert {
22 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
23 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
24 │ };
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-assertions/early-dup-assert-key-import-withbinding.js:21:1]
21 │ import x from './import-assertion-1_FIXTURE.js' assert {
22 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
23 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
24 │ };
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-attributes/early-dup-attribute-key-export.js:20:1]
20 │ export * from './import-attribute-3_FIXTURE.js' with {
21 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
22 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
23 │ };
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-attributes/early-dup-attribute-key-import-nobinding.js:21:1]
21 │ import './import-attribute-2_FIXTURE.js' with {
22 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
23 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
24 │ };
╰────
× Identifier `type` has already been declared
╭─[language/module-code/import-attributes/early-dup-attribute-key-import-withbinding.js:21:1]
21 │ import x from './import-attribute-1_FIXTURE.js' with {
22 │ type: 'json',
· ──┬─
· ╰── `type` has already been declared here
23 │ 'typ\u0065': ''
· ─────┬─────
· ╰── It can not be redeclared here
24 │ };
╰────
× Private identifier '#x' is not allowed outside class bodies
╭─[language/module-code/invalid-private-names-call-expression-bad-reference.js:39:1]
39 │

View file

@ -15803,28 +15803,23 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts"
1 │ import * as f from "./first" assert {
╰────
× Expected a semicolon or an implicit semicolon after a statement, but found none
× Expected `{` but found `EOF`
╭─[conformance/importAttributes/importAttributes4.ts:1:1]
1 │ import * as f from "./first" with
· ─
╰────
help: Try insert a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
× Expected `}` but found `EOF`
╭─[conformance/importAttributes/importAttributes5.ts:1:1]
1 │ import * as f from "./first" with {
· ─
╰────
help: Try insert a semicolon here
× Expected a semicolon or an implicit semicolon after a statement, but found none
× Unexpected token
╭─[conformance/importAttributes/importAttributes6.ts:2:1]
2 │ // @filename: mod.mts
3 │ import * as thing1 from "./mod.mjs" with { field: 0 };
· ─
·
4 │ import * as thing2 from "./mod.mjs" with { field: `a` };
╰────
help: Try insert a semicolon here
× The keyword 'interface' is reserved
╭─[conformance/interfaces/interfaceDeclarations/asiPreventsParsingAsInterface05.ts:2:1]

View file

@ -86,6 +86,8 @@ impl<T: Case> Suite<T> for Test262Suite<T> {
fn skip_test_path(&self, path: &Path) -> bool {
let path = path.to_string_lossy();
// ignore markdown files
path.ends_with(".md") ||
// ignore fixtures
path.contains("_FIXTURE") ||
// ignore regexp as we don't have a regexp parser for now
@ -159,8 +161,6 @@ impl Case for Test262Case {
// Regex parser is required. See https://github.com/oxc-project/oxc/issues/385#issuecomment-1755566240
"regexp-v-flag",
"regexp-unicode-property-escapes",
// Stage 3 `https://github.com/tc39/proposal-json-modules`
"json-modules",
// Stage 3 `https://github.com/tc39/proposal-decorators`
"decorators",
]

@ -1 +1 @@
Subproject commit c4642dd714175b5d27939c920abc6059c9fddb06
Subproject commit 2060494f280ba89d71a0f51d4ff171bafe476a05