Files
oxc/crates/oxc_parser/src/js/module.rs
T
Boshen 1b5a171d28 perf(parser): reuse export kind classification (#28424)
Reuse the classified export kind to avoid duplicate lookahead for type-only exports; remove the obsolete classifier.

Local arm64: type-export fixtures take 4–9% less parse time; ordinary corpus is flat (−0.36%, −0.24% with tokens). Largest slower control: +1.5%.

Validated parser and Node suites, unchanged conformance and allocations, and 63,568 differential comparisons.

AI assistance: Codex.
2026-10-29 15:09:00 +00:00

1446 lines
62 KiB
Rust

use oxc_allocator::{ArenaBox, ArenaVec, GetAllocator};
use oxc_ast::ast::*;
use oxc_span::GetSpan;
use rustc_hash::FxHashMap;
use super::FunctionKind;
use crate::{
ParserConfig as Config, ParserImpl, StatementContext, diagnostics,
lexer::Kind,
modifiers::{Modifier, ModifierKind, Modifiers},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ImportOrExport {
/// Some kind of `import` statement/declaration
Import,
/// Some kind of `export` statement/declaration
Export,
}
#[derive(Debug)]
enum ImportOrExportSpecifier<'a> {
/// An import specifier, such as `import { a } from 'b'`
Import(ImportSpecifier<'a>),
/// An export specifier, such as `export { a } from 'b'`
Export(ExportSpecifier<'a>),
}
impl<'a, C: Config> ParserImpl<'a, C> {
/// [Import Call](https://tc39.es/ecma262/#sec-import-calls)
/// `ImportCall` : import ( `AssignmentExpression` )
pub(crate) fn parse_import_expression(
&mut self,
start: u32,
phase: Option<ImportPhase>,
) -> Expression<'a> {
self.expect(Kind::LParen);
if self.eat(Kind::RParen) {
let error = diagnostics::import_requires_a_specifier(self.end_span(start));
return self.fatal_error(error);
}
let has_in = self.ctx.has_in();
self.ctx = self.ctx.and_in(true);
let expression = self.parse_import_argument();
let arguments = if self.eat(Kind::Comma) && !self.at(Kind::RParen) {
Some(self.parse_import_argument())
} else {
None
};
// Allow trailing comma
self.bump(Kind::Comma);
if !self.eat(Kind::RParen) {
let error = diagnostics::import_arguments(self.end_span(start));
return self.fatal_error(error);
}
self.ctx = self.ctx.and_in(has_in);
let expr =
ImportExpression::boxed(self.end_span(start), expression, arguments, phase, self);
self.module_record_builder.visit_import_expression(&expr);
Expression::ImportExpression(expr)
}
/// An argument of `ImportCall`. Spread elements are not allowed.
fn parse_import_argument(&mut self) -> Expression<'a> {
if self.at(Kind::Dot3) {
let error = diagnostics::dynamic_import_argument_spread(self.cur_token().span());
return self.fatal_error(error);
}
self.parse_assignment_expression_or_higher()
}
/// Section 16.2.2 Import Declaration
pub(crate) fn parse_import_declaration(
&mut self,
start: u32,
should_record_module_record: bool,
) -> Statement<'a> {
let token_after_import = self.cur_token();
let mut identifier_after_import: Option<BindingIdentifier<'_>> =
if self.cur_kind().is_binding_identifier() {
// `import something ...`
Some(self.parse_known_binding_identifier())
} else {
// `import ...`
None
};
let mut has_default_specifier = identifier_after_import.is_some();
let mut should_parse_specifiers = true;
let mut phase = None;
let mut import_kind = ImportOrExportKind::Value;
if self.at(Kind::Eq)
&& let Some(identifier_after_import) = identifier_after_import
{
// `import something = ...`
let decl = self.parse_ts_import_equals_declaration(
ImportOrExportKind::Value,
identifier_after_import,
start,
);
return Statement::from(decl);
} else if self.is_ts && token_after_import.kind() == Kind::Type {
// `import type ...`
let kind = self.cur_kind();
if kind == Kind::LCurly || kind == Kind::Star {
// `import type { ...`
// `import type * ...`
if token_after_import.escaped() {
self.error(diagnostics::escaped_keyword(token_after_import.span()));
}
import_kind = ImportOrExportKind::Type;
has_default_specifier = false;
} else if kind.is_binding_identifier() {
// `import type something ...`
let token = self.cur_token();
let identifier_after_type = self.parse_known_binding_identifier();
if token.kind() == Kind::From && self.at(Kind::Str) {
// `import type from 'source'`
if token.escaped() {
self.error(diagnostics::escaped_keyword(token.span()));
}
has_default_specifier = true;
import_kind = ImportOrExportKind::Value;
should_parse_specifiers = false;
} else {
if token_after_import.escaped() {
self.error(diagnostics::escaped_keyword(token_after_import.span()));
}
identifier_after_import = Some(identifier_after_type);
import_kind = ImportOrExportKind::Type;
if self.at(Kind::Eq) {
// `import type something = ...`
let decl = self.parse_ts_import_equals_declaration(
ImportOrExportKind::Type,
identifier_after_import.unwrap(),
start,
);
return Statement::from(decl);
} else if self.at(Kind::From) {
// `import type something from ...`
has_default_specifier = true;
should_parse_specifiers = false;
}
}
}
} else if token_after_import.kind() == Kind::Defer {
if self.at(Kind::Star) {
// `import defer * ...`
phase = Some(ImportPhase::Defer);
has_default_specifier = false;
} else if self.at(Kind::LCurly) {
// `import defer { ... } from 'source'`
self.error(diagnostics::named_import_not_allowed_in_defer(
token_after_import.span(),
));
phase = Some(ImportPhase::Defer);
has_default_specifier = false;
} else if self.cur_kind().is_binding_identifier()
&& self.cur_kind() != Kind::From
&& self.cur_kind() != Kind::As
{
// `import defer x from 'source'`
self.error(diagnostics::default_import_not_allowed_in_defer(
token_after_import.span(),
));
// Parse the identifier as the default specifier to continue recovery
identifier_after_import = Some(self.parse_known_binding_identifier());
phase = Some(ImportPhase::Defer);
}
// else: `import defer from 'source'` - defer is the binding name, no phase
} else if token_after_import.kind() == Kind::Source {
if self.cur_kind().is_binding_identifier() {
// `import source something ...`
let token = self.cur_token();
let identifier_after_source = self.parse_known_binding_identifier();
if token.kind() == Kind::From {
// `import source from ...`
if self.at(Kind::From) {
// `import source from from ...`
identifier_after_import = Some(identifier_after_source);
phase = Some(ImportPhase::Source);
has_default_specifier = true;
} else if self.at(Kind::Str) {
// `import source from 'source'`
if token.escaped() {
self.error(diagnostics::escaped_keyword(token.span()));
}
has_default_specifier = true;
should_parse_specifiers = false;
}
} else {
self.expect_without_advance(Kind::From);
// `import source something from ...`
identifier_after_import = Some(identifier_after_source);
phase = Some(ImportPhase::Source);
has_default_specifier = true;
}
} else if self.at(Kind::Star) {
// `import source * as ns from 'source'`
self.error(diagnostics::only_default_import_allowed_in_source_phase(
token_after_import.span(),
));
phase = Some(ImportPhase::Source);
has_default_specifier = false;
} else if self.at(Kind::LCurly) {
// `import source { ... } from 'source'`
self.error(diagnostics::only_default_import_allowed_in_source_phase(
token_after_import.span(),
));
phase = Some(ImportPhase::Source);
has_default_specifier = false;
}
// else: `import source from 'source'` - source is the binding name, no phase
}
// The first identifier is a keyword only after resolving the import phase.
if phase.is_some() && token_after_import.escaped() {
self.error(diagnostics::escaped_keyword(token_after_import.span()));
}
let specifiers = if self.at(Kind::Str) {
if has_default_specifier && !should_parse_specifiers {
match identifier_after_import {
Some(identifier_after_import) => {
// Special case: `import type from 'source'` where we already consumed `type` and `from`
Some(ArenaVec::from_value_in(
ImportDeclarationSpecifier::new_import_default_specifier(
identifier_after_import.span,
identifier_after_import,
self,
),
self,
))
}
None => unreachable!(),
}
} else {
if has_default_specifier {
// `import something 'source'`
self.expect_without_advance(Kind::From);
}
None
}
} else {
let default_specifier =
if has_default_specifier { identifier_after_import } else { None };
Some(self.parse_import_declaration_specifiers(default_specifier, import_kind))
};
let source = self.parse_literal_string();
let with_clause = self.parse_import_attributes();
self.asi();
let span = self.end_span(start);
let import_decl = ImportDeclaration::boxed(
span,
specifiers,
source,
phase,
with_clause,
import_kind,
self,
);
if should_record_module_record {
self.module_record_builder.visit_import_declaration(&import_decl);
}
Statement::ImportDeclaration(import_decl)
}
// Full Syntax: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#syntax>
fn parse_import_declaration_specifiers(
&mut self,
// A default specifier, if we already saw any identifier after `import`
default_specifier: Option<BindingIdentifier<'a>>,
import_kind: ImportOrExportKind,
) -> ArenaVec<'a, ImportDeclarationSpecifier<'a>> {
// If there is a default specifier, create a Vec with the default specifier in it,
// otherwise, create an empty Vec.
let mut specifiers = if default_specifier.is_some() {
ArenaVec::with_capacity_in(1, self)
} else {
ArenaVec::new_in(self)
};
if let Some(default_specifier) = default_specifier {
let default_span = default_specifier.span;
let specifier = ImportDeclarationSpecifier::new_import_default_specifier(
default_specifier.span,
default_specifier,
self,
);
specifiers.push(specifier);
if self.eat(Kind::Comma) {
match self.cur_kind() {
// import defaultExport, * as name from "module-name";
Kind::Star => {
if self.is_ts && import_kind == ImportOrExportKind::Type {
self.error(diagnostics::type_only_import_default_and_named(
default_span,
));
}
let specifier = self.parse_import_namespace_specifier();
specifiers.push(specifier);
}
// import defaultExport, { export1 [ , [...] ] } from "module-name";
Kind::LCurly => {
if self.is_ts && import_kind == ImportOrExportKind::Type {
self.error(diagnostics::type_only_import_default_and_named(
default_span,
));
}
self.parse_import_specifiers_into(&mut specifiers, import_kind);
}
_ => return self.unexpected(),
}
}
} else if self.at(Kind::Star) {
// import * as name from "module-name";
let specifier = self.parse_import_namespace_specifier();
specifiers.push(specifier);
} else if self.at(Kind::LCurly) {
// import { export1 , export2 as alias2 , [...] } from "module-name";
self.parse_import_specifiers_into(&mut specifiers, import_kind);
}
self.expect(Kind::From);
specifiers
}
// import * as name from "module-name"
fn parse_import_namespace_specifier(&mut self) -> ImportDeclarationSpecifier<'a> {
let start = self.cur_start();
self.bump_any(); // advance `*`
self.expect(Kind::As);
let local = self.parse_binding_identifier();
let span = self.end_span(start);
ImportDeclarationSpecifier::new_import_namespace_specifier(span, local, self)
}
// import { export1 , export2 as alias2 , [...] } from "module-name";
fn parse_import_specifiers_into(
&mut self,
specifiers: &mut ArenaVec<'a, ImportDeclarationSpecifier<'a>>,
import_kind: ImportOrExportKind,
) {
debug_assert!(self.at(Kind::LCurly));
let opening_span = self.cur_token().span();
self.advance(Kind::LCurly);
self.context_remove(self.ctx, |p| {
let _ = p.parse_delimited_list_into(
specifiers,
Kind::RCurly,
Kind::Comma,
opening_span,
|parser| parser.parse_import_specifier(import_kind),
);
});
self.expect(Kind::RCurly);
}
/// [Import Attributes](https://tc39.es/proposal-import-attributes)
fn parse_import_attributes(&mut self) -> Option<ArenaBox<'a, WithClause<'a>>> {
let keyword_kind = self.cur_kind();
let keyword = match keyword_kind {
Kind::With => WithClauseKeyword::With,
Kind::Assert if !self.cur_token().is_on_new_line() => WithClauseKeyword::Assert,
_ => return None,
};
self.advance(keyword_kind);
let start = self.cur_start();
let opening_span = self.cur_token().span();
self.expect(Kind::LCurly);
let (with_entries, _) = self.context_remove(self.ctx, |p| {
p.parse_delimited_list(
Kind::RCurly,
Kind::Comma,
opening_span,
Self::parse_import_attribute,
)
});
self.expect(Kind::RCurly);
let mut keys = FxHashMap::default();
for e in &with_entries {
let key = e.key.as_js_str();
let span = e.key.span();
if let Some(old_span) = keys.insert(key, span) {
let name = key.as_str().unwrap_or_else(|| span.source_text(self.source_text));
self.error(diagnostics::duplicate_import_attribute_key(name, old_span, span));
}
}
Some(WithClause::boxed(self.end_span(start), keyword, with_entries, self))
}
fn parse_import_attribute(&mut self) -> ImportAttribute<'a> {
let start = self.cur_start();
let key = match self.cur_kind() {
Kind::Str => ImportAttributeKey::StringLiteral(self.parse_known_literal_string()),
_ => ImportAttributeKey::Identifier(self.parse_identifier_name()),
};
self.expect(Kind::Colon);
if !self.at(Kind::Str) {
return self.fatal_error(diagnostics::import_attribute_value_must_be_string_literal(
self.cur_token().span(),
));
}
let value = self.parse_known_literal_string();
ImportAttribute::new(self.end_span(start), key, value, self)
}
pub(crate) fn parse_ts_export_assignment_declaration(
&mut self,
start: u32,
) -> ArenaBox<'a, TSExportAssignment<'a>> {
debug_assert!(self.at(Kind::Eq));
self.advance(Kind::Eq);
let expression = self.parse_assignment_expression_or_higher();
self.asi();
if self.ctx.has_top_level() {
self.module_record_builder.set_module_syntax();
}
TSExportAssignment::boxed(self.end_span(start), expression, self)
}
pub(crate) fn parse_ts_export_namespace(
&mut self,
start: u32,
) -> ArenaBox<'a, TSNamespaceExportDeclaration<'a>> {
debug_assert!(self.at(Kind::As));
self.advance(Kind::As);
self.expect(Kind::Namespace);
let id = self.parse_identifier_name();
self.asi();
if self.ctx.has_top_level() {
self.module_record_builder.set_module_syntax();
}
TSNamespaceExportDeclaration::boxed(self.end_span(start), id, self)
}
/// Check the parse goal and placement of the `import` / `export` declaration at the current token.
pub(crate) fn check_module_declaration(&mut self, stmt_ctx: StatementContext) {
// TypeScript cannot tell script from module by syntax,
// and permits module declarations in namespaces and ambient modules.
if self.is_ts {
return;
}
let statement = if self.at(Kind::Import) { "import statement" } else { "export statement" };
let span = self.cur_token().span();
if self.source_type.is_script() || self.source_type.is_commonjs() {
self.error(diagnostics::module_code(statement, span));
} else if stmt_ctx != StatementContext::Program {
self.error(diagnostics::top_level(statement, span));
}
}
/// [Exports](https://tc39.es/ecma262/#sec-exports)
pub(crate) fn parse_export_declaration(
&mut self,
start: u32,
mut decorators: ArenaVec<'a, Decorator<'a>>,
stmt_ctx: StatementContext,
) -> Statement<'a> {
self.check_module_declaration(stmt_ctx);
self.bump_any(); // bump `export`
// export /* @__NO_SIDE_EFFECTS__ */ ...
let no_side_effects_comments =
self.lexer.trivia_builder.previous_token_no_side_effects_comments();
// `export` is unambiguously module syntax (ECMA-262 §16.2.3): commit to the
// Module goal so the declaration parses under `Await` on the first pass and
// isn't reparsed. e.g. `@foo export default class C { x = await + 1 }`
if self.source_type.is_unambiguous() && self.ctx.has_top_level() {
self.ctx = self.ctx.and_await(true);
}
let decl = match self.cur_kind() {
// `export import A = B`
Kind::Import => {
let import_start = self.cur_start();
self.bump_any();
// Pass `should_record_module_record: false` to prevent an `import` module record
// being created. It's an export not an import.
let stmt = self.parse_import_declaration(import_start, false);
if stmt.is_declaration() {
let export_decl = ExportDeclaration::boxed(
self.end_span(start),
stmt.into_declaration(),
self,
);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_declaration(&export_decl);
}
ModuleDeclaration::ExportDeclaration(export_decl)
} else {
return self.fatal_error(diagnostics::unexpected_export(stmt.span()));
}
}
Kind::At => {
let class_start = self.cur_start();
let after_export_decorators = self.parse_decorators();
if !decorators.is_empty() {
for decorator in &after_export_decorators {
self.error(diagnostics::decorators_in_export_and_class(decorator.span));
}
}
decorators.extend(after_export_decorators);
let modifiers = self.parse_modifiers(false, false);
let class_decl = self.parse_class_declaration(class_start, &modifiers, decorators);
let decl = Declaration::ClassDeclaration(class_decl);
let export_decl = ExportDeclaration::boxed(self.end_span(start), decl, self);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_declaration(&export_decl);
}
ModuleDeclaration::ExportDeclaration(export_decl)
}
Kind::Eq if self.is_ts => ModuleDeclaration::TSExportAssignment(
self.parse_ts_export_assignment_declaration(start),
),
Kind::As if self.is_ts && self.lexer.peek_token().kind() == Kind::Namespace => {
// `export as namespace ...`
ModuleDeclaration::TSNamespaceExportDeclaration(
self.parse_ts_export_namespace(start),
)
}
Kind::Default => ModuleDeclaration::ExportDefaultDeclaration(
self.parse_export_default_declaration(start, decorators),
),
Kind::Star => ModuleDeclaration::ExportAllDeclaration(
self.parse_export_all_declaration(start, ImportOrExportKind::Value),
),
Kind::LCurly => self.parse_export_named_specifiers(start, ImportOrExportKind::Value),
Kind::Type if self.is_ts => {
let next_kind = self.lexer.peek_token().kind();
match next_kind {
// `export type { ...`
Kind::LCurly => {
self.bump_any(); // bump `type`
self.parse_export_named_specifiers(start, ImportOrExportKind::Type)
}
// `export type * as ...`
Kind::Star => {
self.bump_any(); // bump `type`
ModuleDeclaration::ExportAllDeclaration(
self.parse_export_all_declaration(start, ImportOrExportKind::Type),
)
}
_ => ModuleDeclaration::ExportDeclaration(
self.parse_exported_declaration(start, decorators),
),
}
}
_ => {
if self.at(Kind::Export) {
self.error(diagnostics::modifier_already_seen(Modifier::new(
self.cur_token().start(),
ModifierKind::Export,
)));
self.bump_any();
}
ModuleDeclaration::ExportDeclaration(
self.parse_exported_declaration(start, decorators),
)
}
};
let mut stmt = Statement::from(decl);
if let Some(comments) = no_side_effects_comments {
self.apply_no_side_effects_comments_to_statement(comments, &mut stmt);
}
stmt
}
// export NamedExports ;
// NamedExports :
// { }
// { ExportsList }
// { ExportsList , }
// ExportsList :
// ExportSpecifier
// ExportsList , ExportSpecifier
// ExportSpecifier :
// ModuleExportName
// ModuleExportName as ModuleExportName
fn parse_export_named_specifiers(
&mut self,
start: u32,
export_kind: ImportOrExportKind,
) -> ModuleDeclaration<'a> {
let opening_span = self.cur_token().span();
self.expect(Kind::LCurly);
let (mut specifiers, _) = self.context_remove(self.ctx, |p| {
p.parse_delimited_list(Kind::RCurly, Kind::Comma, opening_span, |parser| {
parser.parse_export_specifier(export_kind)
})
});
self.expect(Kind::RCurly);
let (source, with_clause) = if self.eat(Kind::From) {
let source = self.parse_literal_string();
(Some(source), self.parse_import_attributes())
} else {
(None, None)
};
// ExportDeclaration : export NamedExports ;
if source.is_none() {
for specifier in &mut specifiers {
match &specifier.local {
// It is a Syntax Error if ReferencedBindings of NamedExports contains any StringLiterals.
ModuleExportName::StringLiteral(literal) => {
let local = self.allocator().alloc_str(&specifier.local.to_string());
let exported = self.allocator().alloc_str(&specifier.exported.to_string());
self.error(diagnostics::export_named_string(local, exported, literal.span));
}
// For each IdentifierName n in ReferencedBindings of NamedExports:
// It is a Syntax Error if StringValue of n is a ReservedWord or the StringValue of n
// is one of "implements", "interface", "let", "package", "private", "protected", "public", or "static".
ModuleExportName::IdentifierName(ident) => {
let match_result = Kind::match_keyword(&ident.name);
if match_result.is_reserved_keyword()
|| match_result.is_future_reserved_keyword()
{
let local = self.allocator().alloc_str(&specifier.local.to_string());
let exported =
self.allocator().alloc_str(&specifier.exported.to_string());
self.error(diagnostics::export_reserved_word(
local, exported, ident.span,
));
}
// `local` becomes a reference for `export { local }`.
specifier.local = ModuleExportName::new_identifier_reference(
ident.span, ident.name, self,
);
}
// No prior code path should lead to parsing `ModuleExportName` as `IdentifierReference`.
ModuleExportName::IdentifierReference(_) => unreachable!(),
}
}
}
self.asi();
let span = self.end_span(start);
if let Some(source) = source {
let export_from_decl = ExportFromDeclaration::boxed(
span,
specifiers,
source,
export_kind,
with_clause,
self,
);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_from_declaration(&export_from_decl);
}
ModuleDeclaration::ExportFromDeclaration(export_from_decl)
} else {
let export_named_decl =
ExportNamedDeclaration::boxed(span, specifiers, export_kind, self);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_named_declaration(&export_named_decl);
}
ModuleDeclaration::ExportNamedDeclaration(export_named_decl)
}
}
// export Declaration
fn parse_exported_declaration(
&mut self,
start: u32,
decorators: ArenaVec<'a, Decorator<'a>>,
) -> ArenaBox<'a, ExportDeclaration<'a>> {
let decl_start = self.cur_start();
let reserved_ctx = self.ctx;
let modifiers =
if self.is_ts { self.eat_modifiers_before_declaration() } else { Modifiers::empty() };
self.ctx = self.ctx.union_ambient_if(modifiers.contains_declare());
let declaration = self.parse_declaration(decl_start, &modifiers, decorators);
self.ctx = reserved_ctx;
let export_decl = ExportDeclaration::boxed(self.end_span(start), declaration, self);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_declaration(&export_decl);
}
export_decl
}
// export default HoistableDeclaration[~Yield, +Await, +Default]
// export default ClassDeclaration[~Yield, +Await, +Default]
// export default AssignmentExpression[+In, ~Yield, +Await] ;
fn parse_export_default_declaration(
&mut self,
start: u32,
decorators: ArenaVec<'a, Decorator<'a>>,
) -> ArenaBox<'a, ExportDefaultDeclaration<'a>> {
let default_keyword_span = self.cur_token().span();
self.advance(Kind::Default);
let declaration = self.parse_export_default_declaration_kind(decorators);
let span = self.end_span(start);
let export_default_decl = ExportDefaultDeclaration::boxed(span, declaration, self);
if self.ctx.has_top_level() {
self.module_record_builder
.visit_export_default_declaration(&export_default_decl, default_keyword_span);
}
export_default_decl
}
fn parse_export_default_declaration_kind(
&mut self,
mut decorators: ArenaVec<'a, Decorator<'a>>,
) -> ExportDefaultDeclarationKind<'a> {
let decl_start = self.cur_start();
// export default /* @__NO_SIDE_EFFECTS__ */ ...
let no_side_effects_comments =
self.lexer.trivia_builder.previous_token_no_side_effects_comments();
// export default @decorator ...
if self.at(Kind::At) {
let after_export_decorators = self.parse_decorators();
// @decorator export default @decorator ...
if !decorators.is_empty() {
for decorator in &after_export_decorators {
self.error(diagnostics::decorators_in_export_and_class(decorator.span));
}
}
decorators.extend(after_export_decorators);
}
let function_start = self.cur_start();
// ExportDeclaration :
// export default HoistableDeclaration
// export default ClassDeclaration
// export default [lookahead ∉ { function, async [no LineTerminator here] function, class }]
// AssignmentExpression ;
// <https://tc39.es/ecma262/#prod-ExportDeclaration>
//
// `abstract`/`interface` (TS) and `async` are contextual keywords, so each can be the start
// of the `AssignmentExpression` (`export default async;`). Peek the token after the keyword
// to apply the lookahead restriction without consuming it, committing only when it confirms
// a declaration. `[no LineTerminator here]` maps to `!next.is_on_new_line()`.
let kind = self.cur_kind();
if matches!(kind, Kind::Abstract | Kind::Async | Kind::Interface) {
let modifier_start = self.cur_start();
let next = self.lexer.peek_token();
if !next.is_on_new_line() {
// export default abstract class C {}
if kind == Kind::Abstract && next.kind() == Kind::Class {
self.bump_any();
let modifiers = Modifiers::new_single(ModifierKind::Abstract, modifier_start);
return ExportDefaultDeclarationKind::ClassDeclaration(
self.parse_class_declaration(decl_start, &modifiers, decorators),
);
}
// export default async function f() {}
if kind == Kind::Async && next.kind() == Kind::Function {
self.bump_any();
for decorator in &decorators {
self.error(diagnostics::decorators_are_not_valid_here(decorator.span));
}
let mut func = self.parse_function_impl(
function_start,
/* r#async */ true,
FunctionKind::DefaultExport,
);
if let Some(comments) = no_side_effects_comments {
func.pure = true;
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
}
return ExportDefaultDeclarationKind::FunctionDeclaration(func);
}
// export default interface I {}
if kind == Kind::Interface {
self.bump_any();
for decorator in &decorators {
self.error(diagnostics::decorators_are_not_valid_here(decorator.span));
}
if let Declaration::TSInterfaceDeclaration(decl) =
self.parse_ts_interface_declaration(modifier_start, &Modifiers::empty())
{
return ExportDefaultDeclarationKind::TSInterfaceDeclaration(decl);
}
}
}
// Used as an identifier (`export default async;`); nothing consumed, so fall through.
}
let kind = self.cur_kind();
// export default class C {}
if kind == Kind::Class {
return ExportDefaultDeclarationKind::ClassDeclaration(self.parse_class_declaration(
decl_start,
&Modifiers::empty(),
decorators,
));
}
for decorator in &decorators {
self.error(diagnostics::decorators_are_not_valid_here(decorator.span));
}
// export default function f() {}
if kind == Kind::Function {
let mut func = self.parse_function_impl(
function_start,
/* r#async */ false,
FunctionKind::DefaultExport,
);
if let Some(comments) = no_side_effects_comments {
func.pure = true;
self.lexer.trivia_builder.mark_no_side_effects_comments_applied(comments);
}
return ExportDefaultDeclarationKind::FunctionDeclaration(func);
}
// export default 1 + 1;
let decl = ExportDefaultDeclarationKind::from(self.parse_assignment_expression_or_higher());
self.asi();
decl
}
// export ExportFromClause FromClause ;
// ExportFromClause :
// *
// * as ModuleExportName
// NamedExports
fn parse_export_all_declaration(
&mut self,
start: u32,
export_kind: ImportOrExportKind,
) -> ArenaBox<'a, ExportAllDeclaration<'a>> {
self.bump_any(); // bump `star`
let exported = self.eat(Kind::As).then(|| self.parse_module_export_name());
self.expect(Kind::From);
let source = self.parse_literal_string();
let with_clause = self.parse_import_attributes();
self.asi();
let span = self.end_span(start);
let export_all_decl =
ExportAllDeclaration::boxed(span, exported, source, with_clause, export_kind, self);
if self.ctx.has_top_level() {
self.module_record_builder.visit_export_all_declaration(&export_all_decl);
}
export_all_decl
}
// ImportSpecifier :
// ImportedBinding
// ModuleExportName as ImportedBinding
pub(crate) fn parse_import_specifier(
&mut self,
parent_import_kind: ImportOrExportKind,
) -> ImportDeclarationSpecifier<'a> {
match self.parse_import_or_export_specifier(ImportOrExport::Import, parent_import_kind) {
ImportOrExportSpecifier::Import(specifier) => {
ImportDeclarationSpecifier::ImportSpecifier(self.alloc(specifier))
}
ImportOrExportSpecifier::Export(_) => unreachable!(),
}
}
fn parse_import_or_export_specifier(
&mut self,
specifier_type: ImportOrExport,
parent_kind: ImportOrExportKind,
) -> ImportOrExportSpecifier<'a> {
let specifier_start = self.cur_start();
let type_or_name_token = self.cur_token();
let type_or_name_token_kind = type_or_name_token.kind();
let mut check_identifier_token = self.cur_token();
let mut kind = ImportOrExportKind::Value;
let mut can_parse_as_keyword = true;
let mut property_name: Option<ModuleExportName<'a>> = None;
let mut name = self.parse_module_export_name();
if self.is_ts && type_or_name_token_kind == Kind::Type {
// If the first token of an import/export specifier is 'type', there are a lot of possibilities,
// especially if we see 'as' afterwards:
//
// import { type } from "mod"; - isTypeOnly: false, name: type
// import { type as } from "mod"; - isTypeOnly: true, name: as
// import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type
// import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as
if self.at(Kind::As) {
// { type as ...? }
let first_as = self.parse_known_identifier_name();
if self.at(Kind::As) {
// { type as as ...? }
let second_as = self.parse_known_identifier_name();
if self.can_parse_module_export_name() {
// { type as as something }
// { type as as "something" }
kind = ImportOrExportKind::Type;
property_name = Some(ModuleExportName::new_identifier_name(
first_as.span,
first_as.name,
self,
));
check_identifier_token = self.cur_token();
name = self.parse_module_export_name();
can_parse_as_keyword = false;
} else {
// { type as as }
property_name = Some(name);
name = ModuleExportName::new_identifier_name(
second_as.span,
second_as.name,
self,
);
can_parse_as_keyword = false;
}
} else if self.can_parse_module_export_name() {
// { type as something }
// { type as "something" }
property_name = Some(name);
can_parse_as_keyword = false;
check_identifier_token = self.cur_token();
name = self.parse_module_export_name();
} else {
// { type as }
kind = ImportOrExportKind::Type;
name =
ModuleExportName::new_identifier_name(first_as.span, first_as.name, self);
}
} else if self.can_parse_module_export_name() {
// { type something ...? }
// { type "something" ...? }
kind = ImportOrExportKind::Type;
check_identifier_token = self.cur_token();
name = self.parse_module_export_name();
}
}
if can_parse_as_keyword && self.eat(Kind::As) {
property_name = Some(name);
check_identifier_token = self.cur_token();
name = self.parse_module_export_name();
}
if kind == ImportOrExportKind::Type && type_or_name_token.escaped() {
self.error(diagnostics::escaped_keyword(type_or_name_token.span()));
}
match specifier_type {
ImportOrExport::Import => {
// `import type { type } from 'mod';`
if parent_kind == ImportOrExportKind::Type && kind == ImportOrExportKind::Type {
self.error(diagnostics::type_modifier_on_named_type_import(
type_or_name_token.span(),
));
}
if !name.is_identifier() {
self.error(diagnostics::identifier_expected(name.span()));
} else if check_identifier_token.kind().is_reserved_keyword() {
self.error(diagnostics::identifier_reserved_word(
check_identifier_token.span(),
check_identifier_token.kind().to_str(),
));
}
let local_name = name.name().as_str();
let local = BindingIdentifier::new(name.span(), self.ident(local_name), self);
let imported = property_name.unwrap_or(name);
ImportOrExportSpecifier::Import(ImportSpecifier::new(
self.end_span(specifier_start),
imported,
local,
kind,
self,
))
}
ImportOrExport::Export => {
// `export type { type } from 'mod';`
if parent_kind == ImportOrExportKind::Type && kind == ImportOrExportKind::Type {
self.error(diagnostics::type_modifier_on_named_type_export(
type_or_name_token.span(),
));
}
let local = match property_name {
Some(property_name) => property_name,
None => self.duplicate_module_export_name(&name),
};
ImportOrExportSpecifier::Export(ExportSpecifier::new(
self.end_span(specifier_start),
local,
name,
kind,
self,
))
}
}
}
// ModuleExportName :
// IdentifierName
// StringLiteral
pub(crate) fn parse_module_export_name(&mut self) -> ModuleExportName<'a> {
match self.cur_kind() {
Kind::Str => {
let literal = self.parse_known_literal_string();
// ModuleExportName : StringLiteral
// It is a Syntax Error if IsStringWellFormedUnicode(the SV of StringLiteral) is false.
// The error is fatal so that no AST ever carries an ill-formed export name.
if !literal.is_string_well_formed_unicode() {
let error = diagnostics::export_lone_surrogate(literal.span);
return self.fatal_error(error);
}
ModuleExportName::StringLiteral(literal)
}
_ => ModuleExportName::IdentifierName(self.parse_identifier_name()),
}
}
/// Build a 2nd [`ModuleExportName`] node matching `name`.
///
/// Not `name.clone()` - `clone` copies `node_id` and the ID cells, bypassing the AST builder.
fn duplicate_module_export_name(&self, name: &ModuleExportName<'a>) -> ModuleExportName<'a> {
match name {
ModuleExportName::IdentifierName(ident) => {
ModuleExportName::new_identifier_name(ident.span, ident.name, self)
}
ModuleExportName::IdentifierReference(ident) => {
ModuleExportName::new_identifier_reference(ident.span, ident.name, self)
}
ModuleExportName::StringLiteral(literal) => {
ModuleExportName::new_string_literal(literal.span, literal.value, literal.raw, self)
}
}
}
fn parse_export_specifier(
&mut self,
parent_export_kind: ImportOrExportKind,
) -> ExportSpecifier<'a> {
match self.parse_import_or_export_specifier(ImportOrExport::Export, parent_export_kind) {
ImportOrExportSpecifier::Export(specifier) => specifier,
ImportOrExportSpecifier::Import(_) => unreachable!(),
}
}
fn can_parse_module_export_name(&self) -> bool {
self.cur_kind().is_identifier_name() || self.at(Kind::Str)
}
}
#[cfg(test)]
mod test {
use oxc_allocator::{Allocator, ArenaBox};
use oxc_ast::ast::{ImportDeclarationSpecifier, ImportOrExportKind, ImportPhase, Statement};
use oxc_span::{GetSpan, SourceType, Span};
use crate::Parser;
#[test]
fn test_parse_import_declaration() {
let src = "import foo from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "foo");
});
let src = "import type foo from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "foo");
});
let src = "import type from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "type");
});
let src = "import type from from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "from");
assert!(matches!(specifiers[0], ImportDeclarationSpecifier::ImportDefaultSpecifier(_)));
});
let src = "import type type from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "type");
assert!(matches!(specifiers[0], ImportDeclarationSpecifier::ImportDefaultSpecifier(_)));
});
let src = "import type { type } from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "type");
});
let src = "import './a'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_none());
});
let src = "import type, { bar } from 'foo';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 2);
assert_eq!(specifiers[0].name(), "type");
assert_eq!(specifiers[1].name(), "bar");
});
let src = "import { foo, bar } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 2);
assert_eq!(specifiers[0].name(), "foo");
assert_eq!(specifiers[1].name(), "bar");
});
let src = "import type { foo, bar } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 2);
assert_eq!(specifiers[0].name(), "foo");
assert_eq!(specifiers[1].name(), "bar");
});
let src = "import type { from, type } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 2);
assert_eq!(specifiers[0].name(), "from");
assert_eq!(specifiers[1].name(), "type");
});
let src = "import defaultItem, { type from, type } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 3);
assert_eq!(specifiers[0].name(), "defaultItem");
assert_eq!(specifiers[1].name(), "from");
assert_eq!(specifiers[2].name(), "type");
});
let src = "import { type as as } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = &specifiers[0] {
assert_eq!(specifier.local.name, "as");
assert_eq!(specifier.imported.name(), "type");
} else {
panic!("Expected ImportSpecifier, found: {:?}", specifiers[0]);
}
});
let src = "import { type as as x } from 'baz';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = &specifiers[0] {
assert_eq!(specifier.import_kind, ImportOrExportKind::Type);
assert_eq!(specifier.imported.name(), "as");
// The first `as` (14-16) is the imported name; the second `as` (17-19) is the keyword
assert_eq!(specifier.imported.span(), Span::new(14, 16));
assert_eq!(specifier.local.name, "x");
assert_eq!(specifier.local.span, Span::new(20, 21));
} else {
panic!("Expected ImportSpecifier, found: {:?}", specifiers[0]);
}
});
let src = "import * as foo from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "foo");
});
let src = "import type * as foo from 'bar';";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "foo");
});
let src = "import defer * as ns from 'x'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, Some(ImportPhase::Defer));
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "ns");
});
let src = "import source x from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, Some(ImportPhase::Source));
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "x");
});
let src = "import source from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, None);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "source");
});
let src = "import source from from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, Some(ImportPhase::Source));
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "from");
});
let src = "import source as from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, Some(ImportPhase::Source));
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "as");
});
let src = "import source source from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, Some(ImportPhase::Source));
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "source");
});
let src = "import defer from 'source'";
parse_and_assert_import_declarations(src, |declarations| {
assert_eq!(declarations.len(), 1);
let decl = declarations[0];
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.phase, None);
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers.len(), 1);
assert_eq!(specifiers[0].name(), "defer");
});
let src = "import foo = bar";
parse_and_assert_statements(src, |statements| {
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.id.name, "foo");
} else {
panic!("Expected TSImportEqualsDeclaration, found: {:?}", statements[0]);
}
});
// `import type foo = bar` builds the AST but is a semantic error (TS1392:
// an import alias cannot use `import type`), reported by the parser.
let src = "import type foo = bar";
parse_and_assert_statements_with_error(src, |statements| {
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
assert_eq!(decl.id.name, "foo");
} else {
panic!("Expected TSImportEqualsDeclaration, found: {:?}", statements[0]);
}
});
let src = "import type from = require('./a')";
parse_and_assert_statements(src, |statements| {
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {
assert_eq!(decl.import_kind, ImportOrExportKind::Type);
assert_eq!(decl.id.name, "from");
} else {
panic!("Expected TSImportEqualsDeclaration, found: {:?}", statements[0]);
}
});
let src = "import from = b";
parse_and_assert_statements(src, |statements| {
if let Statement::TSImportEqualsDeclaration(decl) = statements[0] {
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert_eq!(decl.id.name, "from");
} else {
panic!("Expected TSImportEqualsDeclaration, found: {:?}", statements[0]);
}
});
}
// https://github.com/oxc-project/oxc/issues/11505
#[test]
fn test_type_from_js_file() {
let src = "import type from '../type.js'";
let source_type = SourceType::default().with_typescript(false);
let allocator = Allocator::default();
let ret = Parser::new(&allocator, src, source_type).parse();
assert!(
ret.diagnostics.is_empty(),
"Failed to parse source: {src:?}, error: {:?}",
ret.diagnostics
);
let declarations = ret.program.body.iter().collect::<Vec<_>>();
assert_eq!(declarations.len(), 1);
let Statement::ImportDeclaration(decl) = declarations[0] else {
panic!("Expected ImportDeclaration, found: {:?}", declarations[0]);
};
assert_eq!(decl.import_kind, ImportOrExportKind::Value);
assert!(decl.specifiers.is_some());
let specifiers = decl.specifiers.as_ref().unwrap();
assert_eq!(specifiers[0].name(), "type");
}
fn parse_and_assert_statements(
src: &'static str,
// takes a function which accepts the list of statements
f: fn(Vec<&oxc_ast::ast::Statement<'_>>) -> (),
) {
let source_type = SourceType::default().with_typescript(true);
let allocator = Allocator::default();
let ret = Parser::new(&allocator, src, source_type).parse();
assert!(
ret.diagnostics.is_empty(),
"Failed to parse source: {src:?}, error: {:?}",
ret.diagnostics
);
f(ret.program.body.iter().collect::<Vec<_>>());
}
/// Like [`parse_and_assert_statements`] but for a recoverable error: the parser
/// reports a diagnostic yet still builds the AST, which `f` asserts on.
fn parse_and_assert_statements_with_error(
src: &'static str,
f: fn(Vec<&oxc_ast::ast::Statement<'_>>) -> (),
) {
let source_type = SourceType::default().with_typescript(true);
let allocator = Allocator::default();
let ret = Parser::new(&allocator, src, source_type).parse();
assert!(!ret.diagnostics.is_empty(), "Expected a parse error for source: {src:?}");
f(ret.program.body.iter().collect::<Vec<_>>());
}
fn parse_and_assert_import_declarations(
src: &'static str,
// takes a function which accepts the list of statements
f: fn(Vec<&ArenaBox<'_, oxc_ast::ast::ImportDeclaration<'_>>>) -> (),
) {
let source_type = SourceType::default().with_typescript(true);
let allocator = Allocator::default();
let ret = Parser::new(&allocator, src, source_type).parse();
assert!(
ret.diagnostics.is_empty(),
"Failed to parse source: {src:?}, error: {:?}",
ret.diagnostics
);
let statements =
ret.program
.body
.iter()
.filter_map(|s| {
if let Statement::ImportDeclaration(decl) = s { Some(decl) } else { None }
})
.collect::<Vec<_>>();
f(statements);
}
}