From 6d3734bd21247056cb625e3fee23f629da21b345 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Thu, 17 Apr 2025 01:19:53 +0000 Subject: [PATCH] fix(ast_visit): `Utf8ToUtf16Converter` process decorators before class (#10449) #10438 altered span of `Class` so it starts later in the case of `@dec export class C {}` and `@dec export default class {}`. Update `Utf8ToUtf16Converter` to handle this odd case, and visit decorators before the `export` keyword before processing the span start of `ExportNamedDeclaration` / `ExportDefaultDeclaration`, so that span offsets are still processed in ascending order. --- .../src/generated/utf8_to_utf16_converter.rs | 85 ++++++++++++++++--- .../ast_tools/src/generators/utf8_to_utf16.rs | 83 ++++++++++++++++++ 2 files changed, 156 insertions(+), 12 deletions(-) diff --git a/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs b/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs index 209af92c4f091..8e9ad2376666e 100644 --- a/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs +++ b/crates/oxc_ast_visit/src/generated/utf8_to_utf16_converter.rs @@ -512,18 +512,6 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } - fn visit_export_named_declaration(&mut self, it: &mut ExportNamedDeclaration<'a>) { - self.convert_offset(&mut it.span.start); - walk_mut::walk_export_named_declaration(self, it); - self.convert_offset(&mut it.span.end); - } - - fn visit_export_default_declaration(&mut self, it: &mut ExportDefaultDeclaration<'a>) { - self.convert_offset(&mut it.span.start); - walk_mut::walk_export_default_declaration(self, it); - self.convert_offset(&mut it.span.end); - } - fn visit_export_all_declaration(&mut self, it: &mut ExportAllDeclaration<'a>) { self.convert_offset(&mut it.span.start); walk_mut::walk_export_all_declaration(self, it); @@ -1133,6 +1121,28 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } + fn visit_export_named_declaration(&mut self, decl: &mut ExportNamedDeclaration<'a>) { + // Special case logic for `@dec export class C {}` + if let Some(Declaration::ClassDeclaration(class)) = &mut decl.declaration { + self.visit_export_class(class, &mut decl.span); + } else { + self.convert_offset(&mut decl.span.start); + walk_mut::walk_export_named_declaration(self, decl); + self.convert_offset(&mut decl.span.end); + } + } + + fn visit_export_default_declaration(&mut self, decl: &mut ExportDefaultDeclaration<'a>) { + // Special case logic for `@dec export default class {}` + if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &mut decl.declaration { + self.visit_export_class(class, &mut decl.span); + } else { + self.convert_offset(&mut decl.span.start); + walk_mut::walk_export_default_declaration(self, decl); + self.convert_offset(&mut decl.span.end); + } + } + fn visit_export_specifier(&mut self, it: &mut ExportSpecifier<'a>) { self.convert_offset(&mut it.span.start); match (&mut it.local, &mut it.exported) { @@ -1193,3 +1203,54 @@ impl<'a> VisitMut<'a> for Utf8ToUtf16Converter<'_> { self.convert_offset(&mut it.span.end); } } + +impl Utf8ToUtf16Converter<'_> { + /// Visit `ExportNamedDeclaration` or `ExportDefaultDeclaration` containing a `Class`. + /// e.g. `export class C {}`, `export default class {}` + /// + /// These need special handing because decorators before the `export` keyword + /// have `Span`s which are before the start of the export statement. + /// e.g. `@dec export class C {}`, `@dec export default class {}`. + /// So they need to be processed first. + fn visit_export_class(&mut self, class: &mut Class<'_>, export_decl_span: &mut Span) { + // Process decorators. + // Process decorators before the `export` keyword first. + // These have spans which are before the export statement span start. + // Then process export statement and `Class` start, then remaining decorators, + // which have spans within the span of `Class`. + let mut decl_start = export_decl_span.start; + for decorator in &mut class.decorators { + if decorator.span.start > decl_start { + // Process span start of export statement and `Class` + self.convert_offset(&mut export_decl_span.start); + self.convert_offset(&mut class.span.start); + // Prevent this branch being taken again + decl_start = u32::MAX; + } + self.visit_decorator(decorator); + } + // If didn't already, process span start of export statement and `Class` + if decl_start < u32::MAX { + self.convert_offset(&mut export_decl_span.start); + self.convert_offset(&mut class.span.start); + } + // Process rest of the class + if let Some(id) = &mut class.id { + self.visit_binding_identifier(id); + } + if let Some(type_parameters) = &mut class.type_parameters { + self.visit_ts_type_parameter_declaration(type_parameters); + } + if let Some(super_class) = &mut class.super_class { + self.visit_expression(super_class); + } + if let Some(super_type_arguments) = &mut class.super_type_arguments { + self.visit_ts_type_parameter_instantiation(super_type_arguments); + } + self.visit_ts_class_implementses(&mut class.implements); + self.visit_class_body(&mut class.body); + // Process span end of `Class` and export statement + self.convert_offset(&mut class.span.end); + self.convert_offset(&mut export_decl_span.end); + } +} diff --git a/tasks/ast_tools/src/generators/utf8_to_utf16.rs b/tasks/ast_tools/src/generators/utf8_to_utf16.rs index 62fd23667dcde..e1ba53b5cc8da 100644 --- a/tasks/ast_tools/src/generators/utf8_to_utf16.rs +++ b/tasks/ast_tools/src/generators/utf8_to_utf16.rs @@ -37,6 +37,8 @@ impl Generator for Utf8ToUtf16ConverterGenerator { /// 1. Types where a shorthand syntax means 2 nodes have same span e.g. `const {x} = y;`, `export {x}`. /// 2. `WithClause`, where `IdentifierName` for `with` keyword has span outside of the `WithClause`. /// 3. `TemplateLiteral`s, where `quasis` and `expressions` are interleaved. +/// 4. Decorators before `export` in `@dec export class C {}` / `@dec export default class {}` +/// have span before the start of `ExportNamedDeclaration` / `ExportDefaultDeclaration` span. /// /// Define custom visitors for these types, which ensure `convert_offset` is always called with offsets /// in ascending order. @@ -49,6 +51,8 @@ fn generate(schema: &Schema, codegen: &Codegen) -> TokenStream { let skip_type_ids = [ "ObjectProperty", "BindingProperty", + "ExportNamedDeclaration", + "ExportDefaultDeclaration", "ImportSpecifier", "ExportSpecifier", "WithClause", @@ -145,6 +149,30 @@ fn generate(schema: &Schema, codegen: &Codegen) -> TokenStream { self.convert_offset(&mut it.span.end); } + ///@@line_break + fn visit_export_named_declaration(&mut self, decl: &mut ExportNamedDeclaration<'a>) { + ///@ Special case logic for `@dec export class C {}` + if let Some(Declaration::ClassDeclaration(class)) = &mut decl.declaration { + self.visit_export_class(class, &mut decl.span); + } else { + self.convert_offset(&mut decl.span.start); + walk_mut::walk_export_named_declaration(self, decl); + self.convert_offset(&mut decl.span.end); + } + } + + ///@@line_break + fn visit_export_default_declaration(&mut self, decl: &mut ExportDefaultDeclaration<'a>) { + ///@ Special case logic for `@dec export default class {}` + if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &mut decl.declaration { + self.visit_export_class(class, &mut decl.span); + } else { + self.convert_offset(&mut decl.span.start); + walk_mut::walk_export_default_declaration(self, decl); + self.convert_offset(&mut decl.span.end); + } + } + ///@@line_break fn visit_export_specifier(&mut self, it: &mut ExportSpecifier<'a>) { self.convert_offset(&mut it.span.start); @@ -225,6 +253,61 @@ fn generate(schema: &Schema, codegen: &Codegen) -> TokenStream { self.convert_offset(&mut it.span.end); } } + + ///@@line_break + impl Utf8ToUtf16Converter<'_> { + /// Visit `ExportNamedDeclaration` or `ExportDefaultDeclaration` containing a `Class`. + /// e.g. `export class C {}`, `export default class {}` + /// + /// These need special handing because decorators before the `export` keyword + /// have `Span`s which are before the start of the export statement. + /// e.g. `@dec export class C {}`, `@dec export default class {}`. + /// So they need to be processed first. + fn visit_export_class(&mut self, class: &mut Class<'_>, export_decl_span: &mut Span) { + ///@ Process decorators. + ///@ Process decorators before the `export` keyword first. + ///@ These have spans which are before the export statement span start. + ///@ Then process export statement and `Class` start, then remaining decorators, + ///@ which have spans within the span of `Class`. + let mut decl_start = export_decl_span.start; + for decorator in &mut class.decorators { + if decorator.span.start > decl_start { + ///@ Process span start of export statement and `Class` + self.convert_offset(&mut export_decl_span.start); + self.convert_offset(&mut class.span.start); + ///@ Prevent this branch being taken again + decl_start = u32::MAX; + } + self.visit_decorator(decorator); + } + + ///@ If didn't already, process span start of export statement and `Class` + if decl_start < u32::MAX { + self.convert_offset(&mut export_decl_span.start); + self.convert_offset(&mut class.span.start); + } + + ///@ Process rest of the class + if let Some(id) = &mut class.id { + self.visit_binding_identifier(id); + } + if let Some(type_parameters) = &mut class.type_parameters { + self.visit_ts_type_parameter_declaration(type_parameters); + } + if let Some(super_class) = &mut class.super_class { + self.visit_expression(super_class); + } + if let Some(super_type_arguments) = &mut class.super_type_arguments { + self.visit_ts_type_parameter_instantiation(super_type_arguments); + } + self.visit_ts_class_implementses(&mut class.implements); + self.visit_class_body(&mut class.body); + + ///@ Process span end of `Class` and export statement + self.convert_offset(&mut class.span.end); + self.convert_offset(&mut export_decl_span.end); + } + } } }