diff --git a/crates/oxc_ast/src/utf8_to_utf16.rs b/crates/oxc_ast/src/utf8_to_utf16.rs index 4226ea0047..01af904708 100644 --- a/crates/oxc_ast/src/utf8_to_utf16.rs +++ b/crates/oxc_ast/src/utf8_to_utf16.rs @@ -1,6 +1,7 @@ //! Convert UTF-8 span offsets to UTF-16. use oxc_span::Span; +use oxc_syntax::module_record::{ModuleRecord, VisitMutModuleRecord}; use crate::{ast::Program, visit::VisitMut}; @@ -41,6 +42,11 @@ impl Utf8ToUtf16 { } } + /// Convert spans in ModuleRecord to UTF-16 + pub fn convert_module_record(&mut self, module_record: &mut ModuleRecord<'_>) { + self.visit_module_record(module_record); + } + #[expect(clippy::cast_possible_truncation)] fn build_table(&mut self, source_text: &str) { // Translation from UTF-8 byte offset to UTF-16 char offset: @@ -102,6 +108,12 @@ impl VisitMut<'_> for Utf8ToUtf16 { } } +impl VisitMutModuleRecord for Utf8ToUtf16 { + fn visit_span(&mut self, span: &mut Span) { + self.convert_span(span); + } +} + #[cfg(test)] mod test { use oxc_allocator::Allocator; diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index cd3c3aa29a..3a9a7311f2 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -387,6 +387,87 @@ pub struct DynamicImport { pub module_request: Span, } +#[expect(missing_docs)] +pub trait VisitMutModuleRecord { + fn visit_module_record(&mut self, module_record: &mut ModuleRecord) { + module_record.requested_modules.values_mut().for_each(|e| { + e.iter_mut().for_each(|e| self.visit_requested_module(e)); + }); + module_record.import_entries.iter_mut().for_each(|e| self.visit_import_entry(e)); + module_record.local_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e)); + module_record.indirect_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e)); + module_record.star_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e)); + module_record.dynamic_imports.iter_mut().for_each(|e| self.visit_dynamic_import(e)); + module_record.import_metas.iter_mut().for_each(|e| self.visit_span(e)); + } + + fn visit_requested_module(&mut self, requested_module: &mut RequestedModule) { + self.visit_span(&mut requested_module.span); + self.visit_span(&mut requested_module.statement_span); + } + + fn visit_import_entry(&mut self, import_entry: &mut ImportEntry) { + self.visit_span(&mut import_entry.statement_span); + self.visit_name_span(&mut import_entry.module_request); + self.visit_name_span(&mut import_entry.local_name); + self.visit_import_import_name(&mut import_entry.import_name); + } + + fn visit_import_import_name(&mut self, import_import_name: &mut ImportImportName) { + match import_import_name { + ImportImportName::Name(name_span) => self.visit_name_span(name_span), + ImportImportName::NamespaceObject => {} + ImportImportName::Default(span) => self.visit_span(span), + } + } + + fn visit_export_entry(&mut self, export_entry: &mut ExportEntry) { + self.visit_span(&mut export_entry.statement_span); + self.visit_span(&mut export_entry.span); + if let Some(module_request) = &mut export_entry.module_request { + self.visit_name_span(module_request); + } + self.visit_export_import_name(&mut export_entry.import_name); + self.visit_export_export_name(&mut export_entry.export_name); + self.visit_export_local_name(&mut export_entry.local_name); + } + + fn visit_export_import_name(&mut self, export_import_name: &mut ExportImportName) { + match export_import_name { + ExportImportName::Name(name_span) => self.visit_name_span(name_span), + ExportImportName::All | ExportImportName::AllButDefault | ExportImportName::Null => {} + } + } + + fn visit_export_export_name(&mut self, export_export_name: &mut ExportExportName) { + match export_export_name { + ExportExportName::Name(name_span) => self.visit_name_span(name_span), + ExportExportName::Default(span) => self.visit_span(span), + ExportExportName::Null => {} + } + } + + fn visit_export_local_name(&mut self, export_local_name: &mut ExportLocalName) { + match export_local_name { + ExportLocalName::Name(name_span) | ExportLocalName::Default(name_span) => { + self.visit_name_span(name_span); + } + ExportLocalName::Null => {} + } + } + + fn visit_dynamic_import(&mut self, dynamic_import: &mut DynamicImport) { + self.visit_span(&mut dynamic_import.module_request); + self.visit_span(&mut dynamic_import.span); + } + + fn visit_name_span(&mut self, name_span: &mut NameSpan) { + self.visit_span(&mut name_span.span); + } + + fn visit_span(&mut self, _span: &mut Span) {} +} + #[cfg(test)] mod test { use oxc_span::Span; diff --git a/napi/parser/src/lib.rs b/napi/parser/src/lib.rs index ec0b2b3fdd..8d321bdaf7 100644 --- a/napi/parser/src/lib.rs +++ b/napi/parser/src/lib.rs @@ -86,16 +86,15 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption }) .collect::>(); - let module = EcmaScriptModule::from(&ret.module_record); - if options.convert_span_utf16.unwrap_or(false) { - // TODO: fix spans in `module_record` and `errors` + // TODO: fix spans in `errors` // Empty `comments` so comment spans don't get converted twice ret.program.comments.clear(); let mut converter = Utf8ToUtf16::new(); converter.convert(&mut ret.program); + converter.convert_module_record(&mut ret.module_record); for comment in &mut comments { comment.start = converter.convert_offset(comment.start); @@ -103,7 +102,7 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption } } let program = serde_json::to_string(&ret.program).unwrap(); - + let module = EcmaScriptModule::from(&ret.module_record); ParseResult { source_text, program, module, comments, errors } } diff --git a/napi/parser/test/parse.test.ts b/napi/parser/test/parse.test.ts index f5fe520f4f..2f3e29a266 100644 --- a/napi/parser/test/parse.test.ts +++ b/napi/parser/test/parse.test.ts @@ -121,6 +121,90 @@ it('utf16 span', async () => { ] `); } + { + const ret = await parseAsync('test.js', `"🤨";import x from "x"; export { x };import("y");import.meta.z`, { + convertSpanUtf16: true, + }); + expect(ret.module).toMatchInlineSnapshot(` + { + "dynamicImports": [ + { + "end": 48, + "moduleRequest": { + "end": 47, + "start": 44, + }, + "start": 37, + }, + ], + "hasModuleSyntax": true, + "importMetas": [ + { + "end": 60, + "start": 49, + }, + ], + "staticExports": [ + { + "end": 23, + "entries": [ + { + "end": 34, + "exportName": { + "end": 34, + "kind": "Name", + "name": "x", + "start": 33, + }, + "importName": { + "end": 13, + "kind": "Name", + "name": "x", + "start": 12, + }, + "localName": { + "kind": "None", + }, + "moduleRequest": { + "end": 22, + "start": 19, + "value": "x", + }, + "start": 33, + }, + ], + "start": 5, + }, + ], + "staticImports": [ + { + "end": 23, + "entries": [ + { + "importName": { + "end": 13, + "kind": "Default", + "start": 12, + }, + "isType": false, + "localName": { + "end": 13, + "start": 12, + "value": "x", + }, + }, + ], + "moduleRequest": { + "end": 22, + "start": 19, + "value": "x", + }, + "start": 5, + }, + ], + } + `); + } }); describe('error', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c0dcae4c4..86094a54f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,8 +112,6 @@ importers: npm/runtime: {} - npm/wasm-web: {} - tasks/benchmark/codspeed: devDependencies: axios: