fix(napi/parser): utf16 span for module record (#9093)

I (and mostly copilot) wrote `VisitMutModuleRecord` to visit all spans as I wasn't sure how `VisitMut` is generated. I'm looking into visit generator now if it's something I can do. Please let me know if manually writing it down is fine for now or I should try macro (or maybe a completely different approach).
This commit is contained in:
hi-ogawa committed 2025-02-14 08:00:57 +00:00
1 parent 329de9478a
commit 15f23f1556
5 files changed
+180 -6

No files matched your search

+12
View File
@@ -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;
+81
View File
@@ -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;
+3 -4
View File
@@ -86,16 +86,15 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption
})
.collect::<Vec<Comment>>();
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 }
}
+84
View File
@@ -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', () => {
-2
View File
@@ -112,8 +112,6 @@ importers:
npm/runtime: {}
npm/wasm-web: {}
tasks/benchmark/codspeed:
devDependencies:
axios: