From 8a788b8f4b1eb66d68734fc621fa0d74d3f951a6 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:50:42 +0000 Subject: [PATCH] feat(parser)!: Build `ModuleRecord` directly in parser (#7546) This has the benefit of: * expose dynamic import / import meta info from parser * 1 less ast shallow in semantic builder * no ast walk in oxc's module lexer * some more benefits coming soon --- crates/oxc/src/compiler.rs | 5 +- crates/oxc_linter/src/frameworks.rs | 2 +- crates/oxc_linter/src/rules/import/export.rs | 5 +- .../oxc_linter/src/rules/import/namespace.rs | 6 +- .../src/rules/oxc/no_barrel_file.rs | 6 +- crates/oxc_linter/src/service/module_cache.rs | 5 +- crates/oxc_linter/src/service/runtime.rs | 12 +- .../tests/integration/main.rs | 1 - crates/oxc_parser/src/diagnostics.rs | 8 + crates/oxc_parser/src/js/statement.rs | 7 +- crates/oxc_parser/src/lib.rs | 27 +- .../src/module_record.rs} | 323 ++++++++++++++++-- crates/oxc_semantic/examples/semantic.rs | 1 - crates/oxc_semantic/src/builder.rs | 41 +-- crates/oxc_semantic/src/checker/javascript.rs | 56 --- crates/oxc_semantic/src/checker/mod.rs | 24 +- crates/oxc_semantic/src/lib.rs | 7 +- crates/oxc_semantic/src/module_record/mod.rs | 278 --------------- crates/oxc_semantic/src/unresolved_stack.rs | 11 + crates/oxc_syntax/src/node.rs | 8 + crates/oxc_wasm/src/lib.rs | 12 +- tasks/benchmark/benches/linter.rs | 1 - tasks/benchmark/benches/semantic.rs | 7 +- tasks/coverage/snapshots/parser_babel.snap | 322 +++++++++-------- tasks/coverage/snapshots/parser_test262.snap | 12 +- .../coverage/snapshots/parser_typescript.snap | 22 +- 26 files changed, 598 insertions(+), 611 deletions(-) rename crates/{oxc_semantic/src/module_record/builder.rs => oxc_parser/src/module_record.rs} (52%) delete mode 100644 crates/oxc_semantic/src/module_record/mod.rs diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index 39332e654c47a..664563780f893 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -143,7 +143,7 @@ pub trait CompilerInterface { /* Semantic */ - let mut semantic_return = self.semantic(&program, source_path); + let mut semantic_return = self.semantic(&program); if !semantic_return.errors.is_empty() { self.handle_errors(semantic_return.errors); return; @@ -231,7 +231,7 @@ pub trait CompilerInterface { Parser::new(allocator, source_text, source_type).with_options(self.parse_options()).parse() } - fn semantic<'a>(&self, program: &Program<'a>, source_path: &Path) -> SemanticBuilderReturn<'a> { + fn semantic<'a>(&self, program: &Program<'a>) -> SemanticBuilderReturn<'a> { let mut builder = SemanticBuilder::new(); if self.transform_options().is_some() { @@ -242,7 +242,6 @@ pub trait CompilerInterface { builder .with_check_syntax_error(self.check_semantic_error()) .with_scope_tree_child_ids(self.semantic_child_scope_ids()) - .build_module_record(source_path, program) .build(program) } diff --git a/crates/oxc_linter/src/frameworks.rs b/crates/oxc_linter/src/frameworks.rs index 54f429ffbb457..7d4de0a5f95ad 100644 --- a/crates/oxc_linter/src/frameworks.rs +++ b/crates/oxc_linter/src/frameworks.rs @@ -1,7 +1,7 @@ use std::{hash, path::Path}; use bitflags::bitflags; -use oxc_semantic::ModuleRecord; +use oxc_syntax::module_record::ModuleRecord; bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/oxc_linter/src/rules/import/export.rs b/crates/oxc_linter/src/rules/import/export.rs index a02ae0d419642..f9c7847da1558 100644 --- a/crates/oxc_linter/src/rules/import/export.rs +++ b/crates/oxc_linter/src/rules/import/export.rs @@ -1,10 +1,11 @@ use std::path::PathBuf; +use rustc_hash::{FxHashMap, FxHashSet}; + use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_macros::declare_oxc_lint; -use oxc_semantic::ModuleRecord; use oxc_span::{CompactStr, Span}; -use rustc_hash::{FxHashMap, FxHashSet}; +use oxc_syntax::module_record::ModuleRecord; use crate::{context::LintContext, rule::Rule}; diff --git a/crates/oxc_linter/src/rules/import/namespace.rs b/crates/oxc_linter/src/rules/import/namespace.rs index 491c04f07859c..7b69b34d3d6f0 100644 --- a/crates/oxc_linter/src/rules/import/namespace.rs +++ b/crates/oxc_linter/src/rules/import/namespace.rs @@ -6,9 +6,11 @@ use oxc_ast::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_semantic::{AstNode, ModuleRecord}; +use oxc_semantic::AstNode; use oxc_span::{GetSpan, Span}; -use oxc_syntax::module_record::{ExportExportName, ExportImportName, ImportImportName}; +use oxc_syntax::module_record::{ + ExportExportName, ExportImportName, ImportImportName, ModuleRecord, +}; use crate::{context::LintContext, rule::Rule}; diff --git a/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs index 1f89e79b6be2b..3146b690985f6 100644 --- a/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs +++ b/crates/oxc_linter/src/rules/oxc/no_barrel_file.rs @@ -1,7 +1,9 @@ use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_macros::declare_oxc_lint; -use oxc_semantic::ModuleRecord; -use oxc_syntax::module_graph_visitor::{ModuleGraphVisitorBuilder, VisitFoldWhile}; +use oxc_syntax::{ + module_graph_visitor::{ModuleGraphVisitorBuilder, VisitFoldWhile}, + module_record::ModuleRecord, +}; use crate::{context::LintContext, rule::Rule}; diff --git a/crates/oxc_linter/src/service/module_cache.rs b/crates/oxc_linter/src/service/module_cache.rs index 3bb4196c5f75e..3bd6d74f8f711 100644 --- a/crates/oxc_linter/src/service/module_cache.rs +++ b/crates/oxc_linter/src/service/module_cache.rs @@ -1,12 +1,13 @@ use std::{ + num::NonZeroUsize, path::Path, sync::{Arc, Condvar, Mutex}, }; use dashmap::{mapref::one::Ref, DashMap}; -use oxc_semantic::ModuleRecord; use rustc_hash::{FxBuildHasher, FxHashMap}; -use std::num::NonZeroUsize; + +use oxc_syntax::module_record::ModuleRecord; type FxDashMap = DashMap; diff --git a/crates/oxc_linter/src/service/runtime.rs b/crates/oxc_linter/src/service/runtime.rs index 389a67c493f28..8f5d95ac79982 100644 --- a/crates/oxc_linter/src/service/runtime.rs +++ b/crates/oxc_linter/src/service/runtime.rs @@ -11,8 +11,9 @@ use oxc_allocator::Allocator; use oxc_diagnostics::{DiagnosticSender, DiagnosticService, Error, OxcDiagnostic}; use oxc_parser::{ParseOptions, Parser}; use oxc_resolver::Resolver; -use oxc_semantic::{ModuleRecord, SemanticBuilder}; +use oxc_semantic::SemanticBuilder; use oxc_span::{SourceType, VALID_EXTENSIONS}; +use oxc_syntax::module_record::ModuleRecord; use rayon::{iter::ParallelBridge, prelude::ParallelIterator}; use rustc_hash::FxHashSet; @@ -215,9 +216,11 @@ impl Runtime { .with_cfg(true) .with_scope_tree_child_ids(true) .with_build_jsdoc(true) - .with_check_syntax_error(check_syntax_errors) - .build_module_record(path, &ret.program); - let module_record = semantic_builder.module_record(); + .with_check_syntax_error(check_syntax_errors); + + let mut module_record = ret.module_record; + module_record.resolved_absolute_path = path.to_path_buf(); + let module_record = Arc::new(module_record); if self.resolver.is_some() { self.modules.add_resolved_module(path, Arc::clone(&module_record)); @@ -294,6 +297,7 @@ impl Runtime { }; let mut semantic = semantic_ret.semantic; + semantic.set_module_record(&module_record); semantic.set_irregular_whitespaces(ret.irregular_whitespaces); self.linter.run(path, Rc::new(semantic)) } diff --git a/crates/oxc_module_lexer/tests/integration/main.rs b/crates/oxc_module_lexer/tests/integration/main.rs index 52efc7ed87fda..9f3d12a2d1b47 100644 --- a/crates/oxc_module_lexer/tests/integration/main.rs +++ b/crates/oxc_module_lexer/tests/integration/main.rs @@ -71,7 +71,6 @@ pub fn parse(source: &str) -> ModuleLexer { let allocator = Allocator::default(); let source_type = SourceType::mjs(); let ret = Parser::new(&allocator, source, source_type).parse(); - assert!(ret.errors.is_empty(), "{source} should not produce errors.\n{:?}", ret.errors); let module_lexer = oxc_module_lexer::ModuleLexer::new().build(&ret.program); // Copy data over because `ModuleLexer<'a>` can't be returned ModuleLexer { diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index 8c0d459ba22cc..9004040a298ca 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -448,6 +448,14 @@ pub fn cover_initialized_name(span: Span) -> OxcDiagnostic { .with_label(span) } +#[cold] +pub fn duplicate_export(x0: &str, span1: Span, span2: Span) -> OxcDiagnostic { + OxcDiagnostic::error(format!("Duplicated export '{x0}'")).with_labels([ + span1.label("Export has already been declared here"), + span2.label("It cannot be redeclared here"), + ]) +} + // ================================= MODIFIERS ================================= #[cold] diff --git a/crates/oxc_parser/src/js/statement.rs b/crates/oxc_parser/src/js/statement.rs index 12e61c17f79c3..0d933bf2ece76 100644 --- a/crates/oxc_parser/src/js/statement.rs +++ b/crates/oxc_parser/src/js/statement.rs @@ -42,8 +42,11 @@ impl<'a> ParserImpl<'a> { } let stmt = self.parse_statement_list_item(StatementContext::StatementList)?; - if is_top_level && stmt.is_module_declaration() { - self.set_source_type_to_module_if_unambiguous(); + if is_top_level { + if let Some(module_decl) = stmt.as_module_declaration() { + self.set_source_type_to_module_if_unambiguous(); + self.module_record_builder.visit_module_declaration(module_decl); + } } // Section 11.2.1 Directive Prologue diff --git a/crates/oxc_parser/src/lib.rs b/crates/oxc_parser/src/lib.rs index 4b991161d68e3..9f999f15a2f23 100644 --- a/crates/oxc_parser/src/lib.rs +++ b/crates/oxc_parser/src/lib.rs @@ -69,6 +69,7 @@ mod context; mod cursor; mod modifiers; +mod module_record; mod state; mod js; @@ -84,7 +85,6 @@ mod lexer; #[doc(hidden)] pub mod lexer; -use context::{Context, StatementContext}; use oxc_allocator::{Allocator, Box as ArenaBox}; use oxc_ast::{ ast::{Expression, Program}, @@ -92,9 +92,12 @@ use oxc_ast::{ }; use oxc_diagnostics::{OxcDiagnostic, Result}; use oxc_span::{ModuleKind, SourceType, Span}; +use oxc_syntax::module_record::ModuleRecord; use crate::{ + context::{Context, StatementContext}, lexer::{Kind, Lexer, Token}, + module_record::ModuleRecordBuilder, state::ParserState, }; @@ -150,6 +153,9 @@ pub struct ParserReturn<'a> { /// semantic analysis with syntax error checking enabled. pub program: Program<'a>, + /// See + pub module_record: ModuleRecord, + /// Syntax errors encountered while parsing. /// /// This list is not comprehensive. Oxc offloads more-expensive checks to [semantic @@ -363,6 +369,9 @@ struct ParserImpl<'a> { /// Ast builder for creating AST nodes ast: AstBuilder<'a>, + /// Module Record Builder + module_record_builder: ModuleRecordBuilder, + /// Precomputed typescript detection is_ts: bool, } @@ -380,6 +389,8 @@ impl<'a> ParserImpl<'a> { options: ParseOptions, unique: UniquePromise, ) -> Self { + let mut module_record_builder = ModuleRecordBuilder::default(); + module_record_builder.module_record.not_esm = true; Self { options, lexer: Lexer::new(allocator, source_text, source_type, unique), @@ -391,6 +402,7 @@ impl<'a> ParserImpl<'a> { state: ParserState::default(), ctx: Self::default_context(source_type, options), ast: AstBuilder::new(allocator), + module_record_builder, is_ts: source_type.is_typescript(), } } @@ -432,10 +444,21 @@ impl<'a> ParserImpl<'a> { errors.reserve(self.lexer.errors.len() + self.errors.len()); errors.extend(self.lexer.errors); errors.extend(self.errors); + // Skip checking for exports in TypeScript { + if !self.source_type.is_typescript() { + errors.extend(self.module_record_builder.errors()); + } } let irregular_whitespaces = self.lexer.trivia_builder.irregular_whitespaces.into_boxed_slice(); - ParserReturn { program, errors, irregular_whitespaces, panicked, is_flow_language } + ParserReturn { + program, + module_record: self.module_record_builder.build(), + errors, + irregular_whitespaces, + panicked, + is_flow_language, + } } pub fn parse_expression(mut self) -> std::result::Result, Vec> { diff --git a/crates/oxc_semantic/src/module_record/builder.rs b/crates/oxc_parser/src/module_record.rs similarity index 52% rename from crates/oxc_semantic/src/module_record/builder.rs rename to crates/oxc_parser/src/module_record.rs index 3e6ea9a33b5ca..11817200ec210 100644 --- a/crates/oxc_semantic/src/module_record/builder.rs +++ b/crates/oxc_parser/src/module_record.rs @@ -1,10 +1,11 @@ -use std::path::PathBuf; - use oxc_ast::ast::*; +use oxc_diagnostics::OxcDiagnostic; use oxc_ecmascript::BoundNames; use oxc_span::{CompactStr, GetSpan, Span}; use oxc_syntax::module_record::*; +use crate::diagnostics; + #[derive(Default)] pub struct ModuleRecordBuilder { pub module_record: ModuleRecord, @@ -12,28 +13,42 @@ pub struct ModuleRecordBuilder { } impl ModuleRecordBuilder { - pub fn new(resolved_absolute_path: PathBuf) -> Self { - Self { module_record: ModuleRecord::new(resolved_absolute_path), ..Self::default() } - } - - pub fn visit(&mut self, program: &Program) { - self.module_record.not_esm = true; - // This avoids additional checks on TypeScript `TsModuleBlock` which - // also has `ModuleDeclaration`s. - for stmt in &program.body { - if let Some(module_decl) = stmt.as_module_declaration() { - self.module_record.not_esm = false; - self.visit_module_declaration(module_decl); - } - } - + pub fn build(mut self) -> ModuleRecord { // The `ParseModule` algorithm requires `importedBoundNames` (import entries) to be // resolved before resolving export entries. self.resolve_export_entries(); + self.module_record } - pub fn build(self) -> ModuleRecord { - self.module_record + pub fn errors(&self) -> Vec { + let mut errors = vec![]; + + let module_record = &self.module_record; + + // It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. + for name_span in &module_record.exported_bindings_duplicated { + let old_span = module_record.exported_bindings[name_span.name()]; + errors.push(diagnostics::duplicate_export( + name_span.name(), + name_span.span(), + old_span, + )); + } + + for span in &module_record.export_default_duplicated { + let old_span = module_record.export_default.unwrap(); + errors.push(diagnostics::duplicate_export("default", *span, old_span)); + } + + // `export default x;` + // `export { y as default };` + if let (Some(span), Some(default_span)) = + (module_record.exported_bindings.get("default"), &module_record.export_default) + { + errors.push(diagnostics::duplicate_export("default", *default_span, *span)); + } + + errors } fn add_module_request(&mut self, name_span: &NameSpan, is_type: bool, is_import: bool) { @@ -150,6 +165,7 @@ impl ModuleRecordBuilder { } pub fn visit_module_declaration(&mut self, module_decl: &ModuleDeclaration) { + self.module_record.not_esm = false; match module_decl { ModuleDeclaration::ImportDeclaration(import_decl) => { self.visit_import_declaration(import_decl); @@ -346,3 +362,272 @@ impl ModuleRecordBuilder { } } } + +#[cfg(test)] +mod module_record_tests { + use oxc_allocator::Allocator; + use oxc_span::{SourceType, Span}; + use oxc_syntax::module_record::*; + + use crate::Parser; + + fn build(source_text: &str) -> ModuleRecord { + let source_type = SourceType::mjs(); + let allocator = Allocator::default(); + let ret = Parser::new(&allocator, source_text, source_type).parse(); + ret.module_record + } + + // Table 55 gives examples of ImportEntry records fields used to represent the syntactic import forms: + // `https://tc39.es/ecma262/#table-import-forms-mapping-to-importentry-records` + + #[test] + fn import_default() { + let module_record = build("import v from 'mod'"); + let import_entry = ImportEntry { + module_request: NameSpan::new("mod".into(), Span::new(14, 19)), + import_name: ImportImportName::Default(Span::new(7, 8)), + local_name: NameSpan::new("v".into(), Span::new(7, 8)), + is_type: false, + }; + assert_eq!(module_record.import_entries.len(), 1); + assert_eq!(module_record.import_entries[0], import_entry); + } + + #[test] + fn import_namespace() { + let module_record = build("import * as ns from 'mod'"); + let import_entry = ImportEntry { + module_request: NameSpan::new("mod".into(), Span::new(20, 25)), + import_name: ImportImportName::NamespaceObject, + local_name: NameSpan::new("ns".into(), Span::new(12, 14)), + is_type: false, + }; + assert_eq!(module_record.import_entries.len(), 1); + assert_eq!(module_record.import_entries[0], import_entry); + } + + #[test] + fn import_specifier() { + let module_record = build("import { x } from 'mod'"); + let import_entry = ImportEntry { + module_request: NameSpan::new("mod".into(), Span::new(18, 23)), + import_name: ImportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + local_name: NameSpan::new("x".into(), Span::new(9, 10)), + is_type: false, + }; + assert_eq!(module_record.import_entries.len(), 1); + assert_eq!(module_record.import_entries[0], import_entry); + } + + #[test] + fn import_specifier_alias() { + let module_record = build("import { x as v } from 'mod'"); + let import_entry = ImportEntry { + module_request: NameSpan::new("mod".into(), Span::new(23, 28)), + import_name: ImportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + local_name: NameSpan::new("v".into(), Span::new(14, 15)), + is_type: false, + }; + assert_eq!(module_record.import_entries.len(), 1); + assert_eq!(module_record.import_entries[0], import_entry); + } + + #[test] + fn import_without_binding() { + let module_record = build("import 'mod'"); + assert!(module_record.import_entries.is_empty()); + } + + // Table 57 gives examples of the ExportEntry record fields used to represent the syntactic export forms + // `https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records` + + #[test] + fn export_star() { + // ExportDeclaration : export ExportFromClause FromClause ; + // ExportFromClause : * + let module_record = build("export * from 'mod'"); + let export_entry = ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(14, 19))), + import_name: ExportImportName::AllButDefault, + span: Span::new(0, 19), + ..ExportEntry::default() + }; + assert_eq!(module_record.star_export_entries.len(), 1); + assert_eq!(module_record.star_export_entries[0], export_entry); + } + + #[test] + fn export_star_as_namespace() { + // ExportDeclaration : export ExportFromClause FromClause ; + // ExportFromClause : * as ModuleExportName + let module_record = build("export * as ns from 'mod'"); + let export_entry = ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(20, 25))), + import_name: ExportImportName::All, + export_name: ExportExportName::Name(NameSpan::new("ns".into(), Span::new(12, 14))), + span: Span::new(0, 25), + ..ExportEntry::default() + }; + assert_eq!(module_record.indirect_export_entries.len(), 1); + assert_eq!(module_record.indirect_export_entries[0], export_entry); + } + + #[test] + fn named_exports() { + // ExportDeclaration : export NamedExports ; + // ExportSpecifier : ModuleExportName + let module_record = build("export { x }"); + let export_entry = ExportEntry { + export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + span: Span::new(9, 10), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn named_exports_alias() { + // ExportDeclaration : export NamedExports ; + // ExportSpecifier : ModuleExportName as ModuleExportName + let module_record = build("export { x as v }"); + let export_entry = ExportEntry { + export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))), + local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + span: Span::new(9, 15), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn named_exports_from() { + // ExportDeclaration : export ExportFromClause FromClause ; + // ExportSpecifier : ModuleExportName + let module_record = build("export { x } from 'mod'"); + let export_entry = ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))), + export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + span: Span::new(9, 10), + ..ExportEntry::default() + }; + assert_eq!(module_record.indirect_export_entries.len(), 1); + assert_eq!(module_record.indirect_export_entries[0], export_entry); + } + + #[test] + fn named_exports_alias_from() { + // ExportDeclaration : export ExportFromClause FromClause ; + // ExportSpecifier : ModuleExportName as ModuleExportName + let module_record = build("export { x as v } from 'mod'"); + let export_entry = ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(23, 28))), + export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))), + import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + span: Span::new(9, 15), + ..ExportEntry::default() + }; + assert_eq!(module_record.indirect_export_entries.len(), 1); + assert_eq!(module_record.indirect_export_entries[0], export_entry); + } + + #[test] + fn export_declaration() { + // ExportDeclaration : export VariableStatement + let module_record = build("export var v"); + let export_entry = ExportEntry { + export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(11, 12))), + local_name: ExportLocalName::Name(NameSpan::new("v".into(), Span::new(11, 12))), + span: Span::new(7, 12), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn export_default_declaration() { + // ExportDeclaration : export default HoistableDeclaration + let module_record = build("export default function f() {}"); + let export_entry = ExportEntry { + export_name: ExportExportName::Default(Span::new(7, 14)), + local_name: ExportLocalName::Name(NameSpan::new("f".into(), Span::new(24, 25))), + span: Span::new(15, 30), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn export_default_function_expression() { + // ExportDeclaration : export default HoistableDeclaration + let module_record = build("export default function() {}"); + let export_entry = ExportEntry { + export_name: ExportExportName::Default(Span::new(7, 14)), + local_name: ExportLocalName::Null, + span: Span::new(15, 28), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn export_default_expression() { + // ExportDeclaration : export default HoistableDeclaration + let module_record = build("export default 42"); + let export_entry = ExportEntry { + export_name: ExportExportName::Default(Span::new(7, 14)), + local_name: ExportLocalName::Null, + span: Span::new(15, 17), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn export_named_default() { + let module_record = build("export { default }"); + let export_entry = ExportEntry { + export_name: ExportExportName::Name(NameSpan::new("default".into(), Span::new(9, 16))), + local_name: ExportLocalName::Name(NameSpan::new("default".into(), Span::new(9, 16))), + span: Span::new(9, 16), + ..ExportEntry::default() + }; + assert_eq!(module_record.local_export_entries.len(), 1); + assert_eq!(module_record.local_export_entries[0], export_entry); + } + + #[test] + fn indirect_export_entries() { + let module_record = + build("import { x } from 'mod';export { x };export * as ns from 'mod';"); + assert_eq!(module_record.indirect_export_entries.len(), 2); + assert_eq!( + module_record.indirect_export_entries[0], + ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))), + span: Span::new(33, 34), + import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), + export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(33, 34))), + local_name: ExportLocalName::Null, + } + ); + assert_eq!( + module_record.indirect_export_entries[1], + ExportEntry { + module_request: Some(NameSpan::new("mod".into(), Span::new(57, 62))), + span: Span::new(37, 63), + import_name: ExportImportName::All, + export_name: ExportExportName::Name(NameSpan::new("ns".into(), Span::new(49, 51))), + local_name: ExportLocalName::Null, + } + ); + } +} diff --git a/crates/oxc_semantic/examples/semantic.rs b/crates/oxc_semantic/examples/semantic.rs index 82838b6a9a9e0..836a6fe2e2eda 100644 --- a/crates/oxc_semantic/examples/semantic.rs +++ b/crates/oxc_semantic/examples/semantic.rs @@ -35,7 +35,6 @@ fn main() -> std::io::Result<()> { let program = parser_ret.program; let semantic = SemanticBuilder::new() - .build_module_record(path, &program) // Enable additional syntax checks not performed by the parser .with_check_syntax_error(true) .build(&program); diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 691fd4591afb0..a2641ad91d399 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -2,7 +2,6 @@ use std::{ cell::{Cell, RefCell}, - path::Path, sync::Arc, }; @@ -15,7 +14,6 @@ use oxc_cfg::{ }; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{Atom, CompactStr, SourceType, Span}; -use oxc_syntax::module_record::ModuleRecord; use crate::{ binder::Binder, @@ -24,7 +22,6 @@ use crate::{ diagnostics::redeclaration, jsdoc::JSDocBuilder, label::UnusedLabels, - module_record::ModuleRecordBuilder, node::{AstNodes, NodeFlags, NodeId}, reference::{Reference, ReferenceFlags, ReferenceId}, scope::{Bindings, ScopeFlags, ScopeId, ScopeTree}, @@ -66,7 +63,7 @@ pub struct SemanticBuilder<'a> { pub(crate) source_type: SourceType, /// Semantic early errors such as redeclaration errors. - errors: RefCell>, + pub(crate) errors: RefCell>, // states pub(crate) current_node_id: NodeId, @@ -88,9 +85,7 @@ pub struct SemanticBuilder<'a> { pub(crate) scope: ScopeTree, pub(crate) symbols: SymbolTable, - unresolved_references: UnresolvedReferencesStack<'a>, - - pub(crate) module_record: Arc, + pub(crate) unresolved_references: UnresolvedReferencesStack<'a>, unused_labels: UnusedLabels<'a>, build_jsdoc: bool, @@ -143,7 +138,6 @@ impl<'a> SemanticBuilder<'a> { scope, symbols: SymbolTable::default(), unresolved_references: UnresolvedReferencesStack::new(), - module_record: Arc::new(ModuleRecord::default()), unused_labels: UnusedLabels::default(), build_jsdoc: false, jsdoc: JSDocBuilder::default(), @@ -222,25 +216,6 @@ impl<'a> SemanticBuilder<'a> { self } - /// Get the built module record from `build_module_record` - pub fn module_record(&self) -> Arc { - Arc::clone(&self.module_record) - } - - /// Build the module record with a shallow AST visit - #[must_use] - pub fn build_module_record( - mut self, - resolved_absolute_path: &Path, - program: &Program<'a>, - ) -> Self { - let mut module_record_builder = - ModuleRecordBuilder::new(resolved_absolute_path.to_path_buf()); - module_record_builder.visit(program); - self.module_record = Arc::new(module_record_builder.build()); - self - } - /// Finalize the builder. /// /// # Panics @@ -292,16 +267,14 @@ impl<'a> SemanticBuilder<'a> { ); stats.assert_accurate(actual_stats); } - - // Checking syntax error on module record requires scope information from the previous AST pass - if self.check_syntax_error { - checker::check_module_record(&self); - } } let comments = self.alloc(&program.comments); debug_assert_eq!(self.unresolved_references.scope_depth(), 1); + if self.check_syntax_error && !self.source_type.is_typescript() { + checker::check_unresolved_exports(&self); + } self.scope.root_unresolved_references = self .unresolved_references .into_root() @@ -316,11 +289,11 @@ impl<'a> SemanticBuilder<'a> { source_type: self.source_type, comments, irregular_whitespaces: [].into(), + module_record: Arc::new(oxc_syntax::module_record::ModuleRecord::default()), nodes: self.nodes, scopes: self.scope, symbols: self.symbols, classes: self.class_table_builder.build(), - module_record: Arc::clone(&self.module_record), jsdoc, unused_labels: self.unused_labels.labels, cfg: self.cfg.map(ControlFlowGraphBuilder::build), @@ -1878,6 +1851,7 @@ impl<'a> SemanticBuilder<'a> { } else { self.current_reference_flags = ReferenceFlags::Read | ReferenceFlags::Type; } + self.current_node_flags |= NodeFlags::ExportSpecifier; } AstKind::ImportSpecifier(specifier) => { specifier.bind(self); @@ -2040,6 +2014,7 @@ impl<'a> SemanticBuilder<'a> { if !self.current_reference_flags.is_type_only() { self.current_reference_flags = ReferenceFlags::empty(); } + self.current_node_flags -= NodeFlags::ExportSpecifier; } AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => { self.function_stack.pop(); diff --git a/crates/oxc_semantic/src/checker/javascript.rs b/crates/oxc_semantic/src/checker/javascript.rs index 132551e4fb6b3..a1457ef6f88ae 100644 --- a/crates/oxc_semantic/src/checker/javascript.rs +++ b/crates/oxc_semantic/src/checker/javascript.rs @@ -6,7 +6,6 @@ use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_ecmascript::{IsSimpleParameterList, PropName}; use oxc_span::{GetSpan, ModuleKind, Span}; use oxc_syntax::{ - module_record::ExportLocalName, number::NumberBase, operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator}, }; @@ -49,61 +48,6 @@ pub fn check_duplicate_class_elements(ctx: &SemanticBuilder<'_>) { }); } -fn undefined_export(x0: &str, span1: Span) -> OxcDiagnostic { - OxcDiagnostic::error(format!("Export '{x0}' is not defined")).with_label(span1) -} - -fn duplicate_export(x0: &str, span1: Span, span2: Span) -> OxcDiagnostic { - OxcDiagnostic::error(format!("Duplicated export '{x0}'")).with_labels([ - span1.label("Export has already been declared here"), - span2.label("It cannot be redeclared here"), - ]) -} - -pub fn check_module_record(ctx: &SemanticBuilder<'_>) { - // Skip checkking for exports in TypeScript for now - if ctx.source_type.is_typescript() { - return; - } - - let module_record = &ctx.module_record; - - // It is a Syntax Error if any element of the ExportedBindings of ModuleItemList - // does not also occur in either the VarDeclaredNames of ModuleItemList, or the LexicallyDeclaredNames of ModuleItemList. - module_record - .local_export_entries - .iter() - .filter_map(|export_entry| match &export_entry.local_name { - ExportLocalName::Name(name_span) => Some(name_span), - _ => None, - }) - .filter(|name_span| { - ctx.scope.get_binding(ctx.current_scope_id, name_span.name().as_ref()).is_none() - }) - .for_each(|name_span| { - ctx.error(undefined_export(name_span.name(), name_span.span())); - }); - - // It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. - for name_span in &module_record.exported_bindings_duplicated { - let old_span = module_record.exported_bindings[name_span.name()]; - ctx.error(duplicate_export(name_span.name(), name_span.span(), old_span)); - } - - for span in &module_record.export_default_duplicated { - let old_span = module_record.export_default.unwrap(); - ctx.error(duplicate_export("default", *span, old_span)); - } - - // `export default x;` - // `export { y as default };` - if let (Some(span), Some(default_span)) = - (module_record.exported_bindings.get("default"), &module_record.export_default) - { - ctx.error(duplicate_export("default", *default_span, *span)); - } -} - fn class_static_block_await(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Cannot use await in class static initialization block").with_label(span) } diff --git a/crates/oxc_semantic/src/checker/mod.rs b/crates/oxc_semantic/src/checker/mod.rs index a21d5e9bbc07c..13fadeb0012cd 100644 --- a/crates/oxc_semantic/src/checker/mod.rs +++ b/crates/oxc_semantic/src/checker/mod.rs @@ -2,12 +2,13 @@ use oxc_ast::{ ast::{DoWhileStatement, ForStatement, WhileStatement}, AstKind, }; +use oxc_diagnostics::OxcDiagnostic; +use oxc_span::Span; mod javascript; mod typescript; use javascript as js; -pub use javascript::check_module_record; use typescript as ts; use crate::{builder::SemanticBuilder, AstNode}; @@ -119,3 +120,24 @@ pub fn check<'a>(node: &AstNode<'a>, ctx: &SemanticBuilder<'a>) { _ => {} } } + +#[cold] +fn undefined_export(x0: &str, span1: Span) -> OxcDiagnostic { + OxcDiagnostic::error(format!("Export '{x0}' is not defined")).with_label(span1) +} + +/// It is a Syntax Error if any element of the ExportedBindings of ModuleItemList +/// does not also occur in either the VarDeclaredNames of ModuleItemList, or the LexicallyDeclaredNames of ModuleItemList. +pub fn check_unresolved_exports(ctx: &SemanticBuilder<'_>) { + for reference_ids in ctx.unresolved_references.root().values() { + for reference_id in reference_ids { + let reference = ctx.symbols.get_reference(*reference_id); + let node = ctx.nodes.get_node(reference.node_id()); + if node.flags().has_export_specifier() { + if let AstKind::IdentifierReference(ident) = node.kind() { + ctx.errors.borrow_mut().push(undefined_export(&ident.name, ident.span)); + } + } + } + } +} diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 72f92fa97f63b..966ef9e0a4f5f 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -13,8 +13,8 @@ use oxc_ast::{ }; use oxc_cfg::ControlFlowGraph; use oxc_span::{GetSpan, SourceType, Span}; +use oxc_syntax::module_record::ModuleRecord; pub use oxc_syntax::{ - module_record::ModuleRecord, scope::{ScopeFlags, ScopeId}, symbol::{SymbolFlags, SymbolId}, }; @@ -28,7 +28,6 @@ mod class; mod diagnostics; mod jsdoc; mod label; -mod module_record; mod node; mod reference; mod scope; @@ -135,6 +134,10 @@ impl<'a> Semantic<'a> { self.irregular_whitespaces = irregular_whitespaces; } + pub fn set_module_record(&mut self, module_record: &Arc) { + self.module_record = Arc::clone(module_record); + } + /// Trivias (comments) found while parsing pub fn comments(&self) -> &[Comment] { self.comments diff --git a/crates/oxc_semantic/src/module_record/mod.rs b/crates/oxc_semantic/src/module_record/mod.rs deleted file mode 100644 index 3ab5ab0a71ff1..0000000000000 --- a/crates/oxc_semantic/src/module_record/mod.rs +++ /dev/null @@ -1,278 +0,0 @@ -mod builder; - -pub use builder::ModuleRecordBuilder; - -#[cfg(test)] -mod module_record_tests { - use std::{path::Path, sync::Arc}; - - use oxc_allocator::Allocator; - use oxc_parser::Parser; - use oxc_span::{SourceType, Span}; - use oxc_syntax::module_record::*; - - use crate::SemanticBuilder; - - fn build(source_text: &str) -> Arc { - let source_type = SourceType::mjs(); - let allocator = Allocator::default(); - let ret = Parser::new(&allocator, source_text, source_type).parse(); - let semantic_ret = SemanticBuilder::new() - .build_module_record(Path::new(""), &ret.program) - .build(&ret.program); - Arc::clone(&semantic_ret.semantic.module_record) - } - - // Table 55 gives examples of ImportEntry records fields used to represent the syntactic import forms: - // `https://tc39.es/ecma262/#table-import-forms-mapping-to-importentry-records` - - #[test] - fn import_default() { - let module_record = build("import v from 'mod'"); - let import_entry = ImportEntry { - module_request: NameSpan::new("mod".into(), Span::new(14, 19)), - import_name: ImportImportName::Default(Span::new(7, 8)), - local_name: NameSpan::new("v".into(), Span::new(7, 8)), - is_type: false, - }; - assert_eq!(module_record.import_entries.len(), 1); - assert_eq!(module_record.import_entries[0], import_entry); - } - - #[test] - fn import_namespace() { - let module_record = build("import * as ns from 'mod'"); - let import_entry = ImportEntry { - module_request: NameSpan::new("mod".into(), Span::new(20, 25)), - import_name: ImportImportName::NamespaceObject, - local_name: NameSpan::new("ns".into(), Span::new(12, 14)), - is_type: false, - }; - assert_eq!(module_record.import_entries.len(), 1); - assert_eq!(module_record.import_entries[0], import_entry); - } - - #[test] - fn import_specifier() { - let module_record = build("import { x } from 'mod'"); - let import_entry = ImportEntry { - module_request: NameSpan::new("mod".into(), Span::new(18, 23)), - import_name: ImportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - local_name: NameSpan::new("x".into(), Span::new(9, 10)), - is_type: false, - }; - assert_eq!(module_record.import_entries.len(), 1); - assert_eq!(module_record.import_entries[0], import_entry); - } - - #[test] - fn import_specifier_alias() { - let module_record = build("import { x as v } from 'mod'"); - let import_entry = ImportEntry { - module_request: NameSpan::new("mod".into(), Span::new(23, 28)), - import_name: ImportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - local_name: NameSpan::new("v".into(), Span::new(14, 15)), - is_type: false, - }; - assert_eq!(module_record.import_entries.len(), 1); - assert_eq!(module_record.import_entries[0], import_entry); - } - - #[test] - fn import_without_binding() { - let module_record = build("import 'mod'"); - assert!(module_record.import_entries.is_empty()); - } - - // Table 57 gives examples of the ExportEntry record fields used to represent the syntactic export forms - // `https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records` - - #[test] - fn export_star() { - // ExportDeclaration : export ExportFromClause FromClause ; - // ExportFromClause : * - let module_record = build("export * from 'mod'"); - let export_entry = ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(14, 19))), - import_name: ExportImportName::AllButDefault, - span: Span::new(0, 19), - ..ExportEntry::default() - }; - assert_eq!(module_record.star_export_entries.len(), 1); - assert_eq!(module_record.star_export_entries[0], export_entry); - } - - #[test] - fn export_star_as_namespace() { - // ExportDeclaration : export ExportFromClause FromClause ; - // ExportFromClause : * as ModuleExportName - let module_record = build("export * as ns from 'mod'"); - let export_entry = ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(20, 25))), - import_name: ExportImportName::All, - export_name: ExportExportName::Name(NameSpan::new("ns".into(), Span::new(12, 14))), - span: Span::new(0, 25), - ..ExportEntry::default() - }; - assert_eq!(module_record.indirect_export_entries.len(), 1); - assert_eq!(module_record.indirect_export_entries[0], export_entry); - } - - #[test] - fn named_exports() { - // ExportDeclaration : export NamedExports ; - // ExportSpecifier : ModuleExportName - let module_record = build("export { x }"); - let export_entry = ExportEntry { - export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - span: Span::new(9, 10), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn named_exports_alias() { - // ExportDeclaration : export NamedExports ; - // ExportSpecifier : ModuleExportName as ModuleExportName - let module_record = build("export { x as v }"); - let export_entry = ExportEntry { - export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))), - local_name: ExportLocalName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - span: Span::new(9, 15), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn named_exports_from() { - // ExportDeclaration : export ExportFromClause FromClause ; - // ExportSpecifier : ModuleExportName - let module_record = build("export { x } from 'mod'"); - let export_entry = ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))), - export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - span: Span::new(9, 10), - ..ExportEntry::default() - }; - assert_eq!(module_record.indirect_export_entries.len(), 1); - assert_eq!(module_record.indirect_export_entries[0], export_entry); - } - - #[test] - fn named_exports_alias_from() { - // ExportDeclaration : export ExportFromClause FromClause ; - // ExportSpecifier : ModuleExportName as ModuleExportName - let module_record = build("export { x as v } from 'mod'"); - let export_entry = ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(23, 28))), - export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(14, 15))), - import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - span: Span::new(9, 15), - ..ExportEntry::default() - }; - assert_eq!(module_record.indirect_export_entries.len(), 1); - assert_eq!(module_record.indirect_export_entries[0], export_entry); - } - - #[test] - fn export_declaration() { - // ExportDeclaration : export VariableStatement - let module_record = build("export var v"); - let export_entry = ExportEntry { - export_name: ExportExportName::Name(NameSpan::new("v".into(), Span::new(11, 12))), - local_name: ExportLocalName::Name(NameSpan::new("v".into(), Span::new(11, 12))), - span: Span::new(7, 12), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn export_default_declaration() { - // ExportDeclaration : export default HoistableDeclaration - let module_record = build("export default function f() {}"); - let export_entry = ExportEntry { - export_name: ExportExportName::Default(Span::new(7, 14)), - local_name: ExportLocalName::Name(NameSpan::new("f".into(), Span::new(24, 25))), - span: Span::new(15, 30), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn export_default_function_expression() { - // ExportDeclaration : export default HoistableDeclaration - let module_record = build("export default function() {}"); - let export_entry = ExportEntry { - export_name: ExportExportName::Default(Span::new(7, 14)), - local_name: ExportLocalName::Null, - span: Span::new(15, 28), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn export_default_expression() { - // ExportDeclaration : export default HoistableDeclaration - let module_record = build("export default 42"); - let export_entry = ExportEntry { - export_name: ExportExportName::Default(Span::new(7, 14)), - local_name: ExportLocalName::Null, - span: Span::new(15, 17), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn export_named_default() { - let module_record = build("export { default }"); - let export_entry = ExportEntry { - export_name: ExportExportName::Name(NameSpan::new("default".into(), Span::new(9, 16))), - local_name: ExportLocalName::Name(NameSpan::new("default".into(), Span::new(9, 16))), - span: Span::new(9, 16), - ..ExportEntry::default() - }; - assert_eq!(module_record.local_export_entries.len(), 1); - assert_eq!(module_record.local_export_entries[0], export_entry); - } - - #[test] - fn indirect_export_entries() { - let module_record = - build("import { x } from 'mod';export { x };export * as ns from 'mod';"); - assert_eq!(module_record.indirect_export_entries.len(), 2); - assert_eq!( - module_record.indirect_export_entries[0], - ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(18, 23))), - span: Span::new(33, 34), - import_name: ExportImportName::Name(NameSpan::new("x".into(), Span::new(9, 10))), - export_name: ExportExportName::Name(NameSpan::new("x".into(), Span::new(33, 34))), - local_name: ExportLocalName::Null, - } - ); - assert_eq!( - module_record.indirect_export_entries[1], - ExportEntry { - module_request: Some(NameSpan::new("mod".into(), Span::new(57, 62))), - span: Span::new(37, 63), - import_name: ExportImportName::All, - export_name: ExportExportName::Name(NameSpan::new("ns".into(), Span::new(49, 51))), - local_name: ExportLocalName::Null, - } - ); - } -} diff --git a/crates/oxc_semantic/src/unresolved_stack.rs b/crates/oxc_semantic/src/unresolved_stack.rs index 02269cb4c7125..ebba979791c9c 100644 --- a/crates/oxc_semantic/src/unresolved_stack.rs +++ b/crates/oxc_semantic/src/unresolved_stack.rs @@ -62,6 +62,17 @@ impl<'a> UnresolvedReferencesStack<'a> { self.current_scope_depth } + /// Get unresolved references hash map for current scope + #[inline] + pub(crate) fn root(&self) -> &TempUnresolvedReferences<'a> { + // SAFETY: `stack.len() > current_scope_depth` initially. + // Thereafter, `stack` never shrinks, only grows. + // `current_scope_depth` is only increased in `increment_scope_depth`, + // and it grows `stack` to ensure `stack.len()` always exceeds `current_scope_depth`. + // So this read is always guaranteed to be in bounds. + unsafe { self.stack.get_unchecked(0) } + } + /// Get unresolved references hash map for current scope #[inline] pub(crate) fn current_mut(&mut self) -> &mut TempUnresolvedReferences<'a> { diff --git a/crates/oxc_syntax/src/node.rs b/crates/oxc_syntax/src/node.rs index be2dbdaa44703..ff94ecd95bc31 100644 --- a/crates/oxc_syntax/src/node.rs +++ b/crates/oxc_syntax/src/node.rs @@ -82,6 +82,8 @@ bitflags! { const Class = 1 << 1; /// Set functions containing yield statements const HasYield = 1 << 2; + /// Set for `export { specifier }` + const ExportSpecifier = 1 << 3; } } @@ -103,4 +105,10 @@ impl NodeFlags { pub fn has_yield(&self) -> bool { self.contains(Self::HasYield) } + + /// Returns `true` if this function has an export specifier. + #[inline] + pub fn has_export_specifier(&self) -> bool { + self.contains(Self::ExportSpecifier) + } } diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index fe043b5acfb26..734170cb20f00 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -212,11 +212,8 @@ impl Oxc { // Estimate transformer will triple scopes, symbols, references semantic_builder = semantic_builder.with_excess_capacity(2.0); } - let semantic_ret = semantic_builder - .with_check_syntax_error(true) - .with_cfg(true) - .build_module_record(&path, &program) - .build(&program); + let semantic_ret = + semantic_builder.with_check_syntax_error(true).with_cfg(true).build(&program); self.control_flow_graph = semantic_ret.semantic.cfg().map_or_else(String::default, |cfg| { cfg.debug_dot(DebugDotContext::new( @@ -302,10 +299,7 @@ impl Oxc { fn run_linter(&mut self, run_options: &OxcRunOptions, path: &Path, program: &Program) { // Only lint if there are no syntax errors if run_options.lint.unwrap_or_default() && self.diagnostics.borrow().is_empty() { - let semantic_ret = SemanticBuilder::new() - .with_cfg(true) - .build_module_record(path, program) - .build(program); + let semantic_ret = SemanticBuilder::new().with_cfg(true).build(program); let semantic = Rc::new(semantic_ret.semantic); let linter_ret = Linter::default().run(path, Rc::clone(&semantic)); let diagnostics = linter_ret.into_iter().map(|e| e.error).collect(); diff --git a/tasks/benchmark/benches/linter.rs b/tasks/benchmark/benches/linter.rs index b6145e9ec32ea..6e0fc16cbfb5d 100644 --- a/tasks/benchmark/benches/linter.rs +++ b/tasks/benchmark/benches/linter.rs @@ -36,7 +36,6 @@ fn bench_linter(criterion: &mut Criterion) { .with_build_jsdoc(true) .with_scope_tree_child_ids(true) .with_cfg(true) - .build_module_record(path, &ret.program) .build(&ret.program); let linter = LinterBuilder::all().with_fix(FixKind::All).build(); let semantic = Rc::new(semantic_ret.semantic); diff --git a/tasks/benchmark/benches/semantic.rs b/tasks/benchmark/benches/semantic.rs index 764d59cde2555..96422cdc9da5a 100644 --- a/tasks/benchmark/benches/semantic.rs +++ b/tasks/benchmark/benches/semantic.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use oxc_allocator::Allocator; use oxc_benchmark::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use oxc_parser::Parser; @@ -22,10 +20,7 @@ fn bench_semantic(criterion: &mut Criterion) { // We return `error`s to be dropped outside of the measured section, as usually // code would have no errors. One of our benchmarks `cal.com.tsx` has a lot of errors, // but that's atypical, so don't want to include it in benchmark time. - let ret = SemanticBuilder::new() - .with_build_jsdoc(true) - .build_module_record(Path::new(""), &ret.program) - .build(&ret.program); + let ret = SemanticBuilder::new().with_build_jsdoc(true).build(&ret.program); let ret = black_box(ret); ret.errors }); diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 491db2628be27..b12f7f3f461ca 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -3041,13 +3041,6 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It cannot be redeclared here ╰──── - × Export 'foo' is not defined - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-export-default-and-export-as-default/input.js:2:10] - 1 │ export default function() {}; - 2 │ export { foo as default }; - · ─── - ╰──── - × Duplicated export 'default' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-export-default-and-export-as-default/input.js:1:8] 1 │ export default function() {}; @@ -3059,16 +3052,9 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── × Export 'foo' is not defined - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export/input.js:1:10] - 1 │ export { foo }; - · ─── - 2 │ export { bar as foo }; - ╰──── - - × Export 'bar' is not defined - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export/input.js:2:10] - 1 │ export { foo }; - 2 │ export { bar as foo }; + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-export-default-and-export-as-default/input.js:2:10] + 1 │ export default function() {}; + 2 │ export { foo as default }; · ─── ╰──── @@ -3082,6 +3068,20 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It cannot be redeclared here ╰──── + × Export 'foo' is not defined + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export/input.js:1:10] + 1 │ export { foo }; + · ─── + 2 │ export { bar as foo }; + ╰──── + + × Export 'bar' is not defined + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export/input.js:2:10] + 1 │ export { foo }; + 2 │ export { bar as foo }; + · ─── + ╰──── + × Duplicated export 'Foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-class-declaration/input.js:1:10] 1 │ export { Foo }; @@ -3102,34 +3102,24 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It cannot be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring10/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { a: [{foo}] } = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring10/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { a: [{foo}] } = bar; · ─┬─ - · ╰── It cannot be redeclared here - ╰──── - - × Identifier `foo4` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring11/input.js:1:17] - 1 │ export function foo4() {}; - · ──┬─ - · ╰── `foo4` has already been declared here - 2 │ export const [{ a: [{ foo }], b: { foo2: [{ foo3: foo4 }] } }] = bar; - · ──┬─ - · ╰── It can not be redeclared here + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo4' @@ -3143,13 +3133,13 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── × Identifier `foo4` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring12/input.js:1:17] + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring11/input.js:1:17] 1 │ export function foo4() {}; · ──┬─ · ╰── `foo4` has already been declared here - 2 │ export const { a: [{ foo }], b: { foo2: [{ foo3: foo4 }] } } = bar; - · ──┬─ - · ╰── It can not be redeclared here + 2 │ export const [{ a: [{ foo }], b: { foo2: [{ foo3: foo4 }] } }] = bar; + · ──┬─ + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo4' @@ -3163,13 +3153,13 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── × Identifier `foo4` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring13/input.js:1:17] + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring12/input.js:1:17] 1 │ export function foo4() {}; · ──┬─ · ╰── `foo4` has already been declared here - 2 │ export const { a: [{ foo4: foo }], b, c: { foo2: [{ foo3: foo4 }] } } = bar; - · ──┬─ - · ╰── It can not be redeclared here + 2 │ export const { a: [{ foo }], b: { foo2: [{ foo3: foo4 }] } } = bar; + · ──┬─ + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo4' @@ -3182,296 +3172,306 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It cannot be redeclared here ╰──── - × Identifier `foo` has already been declared + × Identifier `foo4` has already been declared + ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring13/input.js:1:17] + 1 │ export function foo4() {}; + · ──┬─ + · ╰── `foo4` has already been declared here + 2 │ export const { a: [{ foo4: foo }], b, c: { foo2: [{ foo3: foo4 }] } } = bar; + · ──┬─ + · ╰── It can not be redeclared here + ╰──── + + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring14/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { foo2: foo } = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring14/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { foo2: foo } = bar; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `foo2` has already been declared + × Duplicated export 'foo2' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring15/input.js:1:21] 1 │ export const { foo: foo2 } = bar; · ──┬─ - · ╰── `foo2` has already been declared here + · ╰── Export has already been declared here 2 │ export const foo2 = 1; · ──┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'foo2' + × Identifier `foo2` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring15/input.js:1:21] 1 │ export const { foo: foo2 } = bar; · ──┬─ - · ╰── Export has already been declared here + · ╰── `foo2` has already been declared here 2 │ export const foo2 = 1; · ──┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring16/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const [bar, ...foo] = baz; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring16/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const [bar, ...foo] = baz; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `bar` has already been declared + × Duplicated export 'bar' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring17/input.js:1:23] 1 │ export const [foo, ...bar] = baz; · ─┬─ - · ╰── `bar` has already been declared here + · ╰── Export has already been declared here 2 │ export const bar = 1; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'bar' + × Identifier `bar` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring17/input.js:1:23] 1 │ export const [foo, ...bar] = baz; · ─┬─ - · ╰── Export has already been declared here + · ╰── `bar` has already been declared here 2 │ export const bar = 1; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring18/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const [bar, [baz, ...foo]] = qux; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring18/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const [bar, [baz, ...foo]] = qux; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring19/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { bar: [baz, ...foo] } = qux; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here 3 │ ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring19/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { bar: [baz, ...foo] } = qux; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here 3 │ ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring2/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { foo } = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring2/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { foo } = bar; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring3/input.js:1:16] 1 │ export const { foo } = bar; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export function foo() {}; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring3/input.js:1:16] 1 │ export const { foo } = bar; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export function foo() {}; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring4/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const [foo] = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring4/input.js:1:17] 1 │ export function foo() {}; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const [foo] = bar; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring5/input.js:1:15] 1 │ export const [foo] = bar; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export function foo() {}; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring5/input.js:1:15] 1 │ export const [foo] = bar; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export function foo() {}; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring6/input.js:1:16] 1 │ export const { foo } = bar; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const [foo] = bar2; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring6/input.js:1:16] 1 │ export const { foo } = bar; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const [foo] = bar2; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring7/input.js:1:15] 1 │ export const [foo] = bar; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { foo } = bar2; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring7/input.js:1:15] 1 │ export const [foo] = bar; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { foo } = bar2; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `Foo` has already been declared + × Duplicated export 'Foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring8/input.js:1:14] 1 │ export class Foo {}; · ─┬─ - · ╰── `Foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { Foo } = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'Foo' + × Identifier `Foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring8/input.js:1:14] 1 │ export class Foo {}; · ─┬─ - · ╰── Export has already been declared here + · ╰── `Foo` has already been declared here 2 │ export const { Foo } = bar; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `Foo` has already been declared + × Duplicated export 'Foo' ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring9/input.js:1:14] 1 │ export class Foo {}; · ─┬─ - · ╰── `Foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const [Foo] = bar; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'Foo' + × Identifier `Foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2015/modules/duplicate-named-export-destructuring9/input.js:1:14] 1 │ export class Foo {}; · ─┬─ - · ╰── Export has already been declared here + · ╰── `Foo` has already been declared here 2 │ export const [Foo] = bar; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo' @@ -5432,54 +5432,44 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 3 │ } ╰──── - × Identifier `foo` has already been declared + × Duplicated export 'foo' ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/11/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── `foo` has already been declared here + · ╰── Export has already been declared here 2 │ export const { bar, ...foo } = baz; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'foo' + × Identifier `foo` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/11/input.js:1:14] 1 │ export const foo = 1; · ─┬─ - · ╰── Export has already been declared here + · ╰── `foo` has already been declared here 2 │ export const { bar, ...foo } = baz; · ─┬─ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── - × Identifier `bar` has already been declared + × Duplicated export 'bar' ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/12/input.js:1:24] 1 │ export const { foo, ...bar } = baz; · ─┬─ - · ╰── `bar` has already been declared here + · ╰── Export has already been declared here 2 │ export const bar = 1; · ─┬─ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'bar' + × Identifier `bar` has already been declared ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/12/input.js:1:24] 1 │ export const { foo, ...bar } = baz; · ─┬─ - · ╰── Export has already been declared here + · ╰── `bar` has already been declared here 2 │ export const bar = 1; · ─┬─ - · ╰── It cannot be redeclared here - ╰──── - - × Identifier `foo` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/13/input.js:1:14] - 1 │ export const foo = 1; - · ─┬─ - · ╰── `foo` has already been declared here - 2 │ export const { bar: { baz, ...foo } } = qux; - · ─┬─ - · ╰── It can not be redeclared here + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo' @@ -5493,13 +5483,13 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── × Identifier `foo` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/14/input.js:1:14] + ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/13/input.js:1:14] 1 │ export const foo = 1; · ─┬─ · ╰── `foo` has already been declared here - 2 │ export const [bar, { baz, ...foo }] = qux; - · ─┬─ - · ╰── It can not be redeclared here + 2 │ export const { bar: { baz, ...foo } } = qux; + · ─┬─ + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo' @@ -5513,13 +5503,13 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── × Identifier `foo` has already been declared - ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/15/input.js:1:14] + ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/14/input.js:1:14] 1 │ export const foo = 1; · ─┬─ · ╰── `foo` has already been declared here - 2 │ export const [bar, [{ baz, ...foo }]] = qux; - · ─┬─ - · ╰── It can not be redeclared here + 2 │ export const [bar, { baz, ...foo }] = qux; + · ─┬─ + · ╰── It can not be redeclared here ╰──── × Duplicated export 'foo' @@ -5532,6 +5522,16 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ╰── It cannot be redeclared here ╰──── + × Identifier `foo` has already been declared + ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/15/input.js:1:14] + 1 │ export const foo = 1; + · ─┬─ + · ╰── `foo` has already been declared here + 2 │ export const [bar, [{ baz, ...foo }]] = qux; + · ─┬─ + · ╰── It can not be redeclared here + ╰──── + × Invalid rest element ╭─[babel/packages/babel-parser/test/fixtures/es2018/object-rest-spread/16/input.js:1:9] 1 │ var {...{z}} = { z: 1}; @@ -8232,18 +8232,6 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── help: Did you mean `export { "吾道一以貫之。" as "忠恕。" } from 'some-module'`? - × Export '學而時習之,不亦說乎?' is not defined - ╭─[babel/packages/babel-parser/test/fixtures/es2022/module-string-names/string-exported-binding-without-from/input.js:1:10] - 1 │ export { "學而時習之,不亦說乎?", "吾道一以貫之。" as "忠恕。" }; - · ──────────────────────── - ╰──── - - × Export '吾道一以貫之。' is not defined - ╭─[babel/packages/babel-parser/test/fixtures/es2022/module-string-names/string-exported-binding-without-from/input.js:1:47] - 1 │ export { "學而時習之,不亦說乎?", "吾道一以貫之。" as "忠恕。" }; - · ──────────────── - ╰──── - × Unexpected token ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-1/input.js:5:11] 4 │ method() { diff --git a/tasks/coverage/snapshots/parser_test262.snap b/tasks/coverage/snapshots/parser_test262.snap index 4af9fd27465f2..b069ccaa62df0 100644 --- a/tasks/coverage/snapshots/parser_test262.snap +++ b/tasks/coverage/snapshots/parser_test262.snap @@ -22136,26 +22136,26 @@ Expect Syntax Error: tasks/coverage/test262/test/language/import/import-attribut · ╰── It cannot be redeclared here ╰──── - × Identifier `f` has already been declared + × Duplicated export 'f' ╭─[test262/test/language/module-code/early-dup-export-decl.js:17:17] 16 │ 17 │ export function f() {} · ┬ - · ╰── `f` has already been declared here + · ╰── Export has already been declared here 18 │ export function *f() {} · ┬ - · ╰── It can not be redeclared here + · ╰── It cannot be redeclared here ╰──── - × Duplicated export 'f' + × Identifier `f` has already been declared ╭─[test262/test/language/module-code/early-dup-export-decl.js:17:17] 16 │ 17 │ export function f() {} · ┬ - · ╰── Export has already been declared here + · ╰── `f` has already been declared here 18 │ export function *f() {} · ┬ - · ╰── It cannot be redeclared here + · ╰── It can not be redeclared here ╰──── × Duplicated export 'default' diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 036272dfe11c3..cb5e13b83a371 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -22418,6 +22418,17 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private 13 │ deleted() { ╰──── + × Duplicated export 'default' + ╭─[typescript/tests/cases/conformance/salsa/plainJSBinderErrors.ts:1:8] + 1 │ export default 12 + · ───┬─── + · ╰── Export has already been declared here + 2 │ export default 13 + · ───┬─── + · ╰── It cannot be redeclared here + 3 │ const await = 1 + ╰──── + × The keyword 'await' is reserved ╭─[typescript/tests/cases/conformance/salsa/plainJSBinderErrors.ts:3:7] 2 │ export default 13 @@ -22521,17 +22532,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/salsa/private · ───────── ╰──── - × Duplicated export 'default' - ╭─[typescript/tests/cases/conformance/salsa/plainJSBinderErrors.ts:1:8] - 1 │ export default 12 - · ───┬─── - · ╰── Export has already been declared here - 2 │ export default 13 - · ───┬─── - · ╰── It cannot be redeclared here - 3 │ const await = 1 - ╰──── - × Expected `in` but found `Identifier` ╭─[typescript/tests/cases/conformance/salsa/plainJSGrammarErrors.ts:4:5] 3 │ q = #unbound