From dc0215c9066f30cc3aef878841f1fa46a15461d9 Mon Sep 17 00:00:00 2001 From: ottomated <31470743+ottomated@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:09:44 +0000 Subject: [PATCH] feat(ast_tools): Add #[estree(append_to)], remove some custom serialization code (#7149) Removed custom logic for ObjectPattern, ArrayPattern, etc. in favor of a custom attribute macro `#[estree(append_to = "foo")]` as part of #6347 --- Cargo.lock | 3 + crates/oxc_ast/Cargo.toml | 1 + crates/oxc_ast/src/ast/js.rs | 12 +-- crates/oxc_ast/src/generated/derive_estree.rs | 40 +++++++++ crates/oxc_ast/src/serialize.rs | 84 +------------------ crates/oxc_estree/Cargo.toml | 5 ++ crates/oxc_estree/src/lib.rs | 22 +++++ crates/oxc_regular_expression/Cargo.toml | 7 +- crates/oxc_span/Cargo.toml | 2 +- crates/oxc_syntax/Cargo.toml | 8 +- npm/oxc-types/types.d.ts | 4 + tasks/ast_tools/src/derives/estree.rs | 36 +++++++- tasks/ast_tools/src/markers.rs | 11 ++- 13 files changed, 140 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5debed54..746f6eaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1621,6 +1621,9 @@ dependencies = [ [[package]] name = "oxc_estree" version = "0.35.0" +dependencies = [ + "serde", +] [[package]] name = "oxc_index" diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index 8333bfb44..84979bc43 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -42,4 +42,5 @@ serialize = [ "oxc_span/serialize", "oxc_syntax/serialize", "oxc_syntax/to_js_string", + "oxc_estree/serialize", ] diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index ed9f4e62d..d260cdba0 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -805,12 +805,11 @@ pub use match_assignment_target_pattern; #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] -#[estree(custom_serialize)] pub struct ArrayAssignmentTarget<'a> { pub span: Span, #[estree(type = "Array")] pub elements: Vec<'a, Option>>, - #[estree(skip)] + #[estree(append_to = "elements")] pub rest: Option>, #[estree(skip)] pub trailing_comma: Option, @@ -822,12 +821,11 @@ pub struct ArrayAssignmentTarget<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] -#[estree(custom_serialize)] pub struct ObjectAssignmentTarget<'a> { pub span: Span, #[estree(type = "Array")] pub properties: Vec<'a, AssignmentTargetProperty<'a>>, - #[estree(skip)] + #[estree(append_to = "properties")] pub rest: Option>, } @@ -1482,12 +1480,11 @@ pub struct AssignmentPattern<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] -#[estree(custom_serialize)] pub struct ObjectPattern<'a> { pub span: Span, #[estree(type = "Array")] pub properties: Vec<'a, BindingProperty<'a>>, - #[estree(skip)] + #[estree(append_to = "properties")] pub rest: Option>>, } @@ -1506,12 +1503,11 @@ pub struct BindingProperty<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] -#[estree(custom_serialize)] pub struct ArrayPattern<'a> { pub span: Span, #[estree(type = "Array")] pub elements: Vec<'a, Option>>, - #[estree(skip)] + #[estree(append_to = "elements")] pub rest: Option>>, } diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index 04a53a30e..dfdee1872 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -707,6 +707,26 @@ impl<'a> Serialize for AssignmentTargetPattern<'a> { } } +impl<'a> Serialize for ArrayAssignmentTarget<'a> { + fn serialize(&self, serializer: S) -> Result { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "ArrayAssignmentTarget")?; + self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; + map.serialize_entry("elements", &oxc_estree::AppendTo(&self.elements, &self.rest))?; + map.end() + } +} + +impl<'a> Serialize for ObjectAssignmentTarget<'a> { + fn serialize(&self, serializer: S) -> Result { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "ObjectAssignmentTarget")?; + self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; + map.serialize_entry("properties", &oxc_estree::AppendTo(&self.properties, &self.rest))?; + map.end() + } +} + impl<'a> Serialize for AssignmentTargetRest<'a> { fn serialize(&self, serializer: S) -> Result { let mut map = serializer.serialize_map(None)?; @@ -1311,6 +1331,16 @@ impl<'a> Serialize for AssignmentPattern<'a> { } } +impl<'a> Serialize for ObjectPattern<'a> { + fn serialize(&self, serializer: S) -> Result { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "ObjectPattern")?; + self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; + map.serialize_entry("properties", &oxc_estree::AppendTo(&self.properties, &self.rest))?; + map.end() + } +} + impl<'a> Serialize for BindingProperty<'a> { fn serialize(&self, serializer: S) -> Result { let mut map = serializer.serialize_map(None)?; @@ -1324,6 +1354,16 @@ impl<'a> Serialize for BindingProperty<'a> { } } +impl<'a> Serialize for ArrayPattern<'a> { + fn serialize(&self, serializer: S) -> Result { + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "ArrayPattern")?; + self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; + map.serialize_entry("elements", &oxc_estree::AppendTo(&self.elements, &self.rest))?; + map.end() + } +} + impl<'a> Serialize for BindingRestElement<'a> { fn serialize(&self, serializer: S) -> Result { let mut map = serializer.serialize_map(None)?; diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 12b3b4fcf..58ffd0694 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -6,11 +6,9 @@ use serde::{ }; use crate::ast::{ - ArrayAssignmentTarget, ArrayPattern, AssignmentTargetMaybeDefault, AssignmentTargetProperty, - AssignmentTargetRest, BindingPattern, BindingPatternKind, BindingProperty, BindingRestElement, - Directive, Elision, FormalParameter, FormalParameterKind, FormalParameters, JSXElementName, - JSXIdentifier, JSXMemberExpressionObject, ObjectAssignmentTarget, ObjectPattern, Program, - RegExpFlags, Statement, StringLiteral, TSModuleBlock, TSTypeAnnotation, + BindingPatternKind, Directive, Elision, FormalParameter, FormalParameterKind, FormalParameters, + JSXElementName, JSXIdentifier, JSXMemberExpressionObject, Program, RegExpFlags, Statement, + StringLiteral, TSModuleBlock, TSTypeAnnotation, }; pub struct EcmaFormatter; @@ -61,82 +59,6 @@ impl Serialize for Elision { } } -/// Serialize `ArrayAssignmentTarget`, `ObjectAssignmentTarget`, `ObjectPattern`, `ArrayPattern` -/// to be estree compatible, with `elements`/`properties` and `rest` fields combined. - -impl<'a> Serialize for ArrayAssignmentTarget<'a> { - fn serialize(&self, serializer: S) -> Result { - let converted = SerArrayAssignmentTarget { - span: self.span, - elements: ElementsAndRest::new(&self.elements, &self.rest), - }; - converted.serialize(serializer) - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename = "ArrayAssignmentTarget", rename_all = "camelCase")] -struct SerArrayAssignmentTarget<'a, 'b> { - #[serde(flatten)] - span: Span, - elements: - ElementsAndRest<'b, Option>, AssignmentTargetRest<'a>>, -} - -impl<'a> Serialize for ObjectAssignmentTarget<'a> { - fn serialize(&self, serializer: S) -> Result { - let converted = SerObjectAssignmentTarget { - span: self.span, - properties: ElementsAndRest::new(&self.properties, &self.rest), - }; - converted.serialize(serializer) - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename = "ObjectAssignmentTarget")] -struct SerObjectAssignmentTarget<'a, 'b> { - #[serde(flatten)] - span: Span, - properties: ElementsAndRest<'b, AssignmentTargetProperty<'a>, AssignmentTargetRest<'a>>, -} - -impl<'a> Serialize for ObjectPattern<'a> { - fn serialize(&self, serializer: S) -> Result { - let converted = SerObjectPattern { - span: self.span, - properties: ElementsAndRest::new(&self.properties, &self.rest), - }; - converted.serialize(serializer) - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename = "ObjectPattern")] -struct SerObjectPattern<'a, 'b> { - #[serde(flatten)] - span: Span, - properties: ElementsAndRest<'b, BindingProperty<'a>, Box<'a, BindingRestElement<'a>>>, -} - -impl<'a> Serialize for ArrayPattern<'a> { - fn serialize(&self, serializer: S) -> Result { - let converted = SerArrayPattern { - span: self.span, - elements: ElementsAndRest::new(&self.elements, &self.rest), - }; - converted.serialize(serializer) - } -} - -#[derive(Serialize)] -#[serde(tag = "type", rename = "ArrayPattern")] -struct SerArrayPattern<'a, 'b> { - #[serde(flatten)] - span: Span, - elements: ElementsAndRest<'b, Option>, Box<'a, BindingRestElement<'a>>>, -} - /// Serialize `FormalParameters`, to be estree compatible, with `items` and `rest` fields combined /// and `argument` field flattened. impl<'a> Serialize for FormalParameters<'a> { diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index 01b0b802c..cdbbe45e2 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -13,6 +13,11 @@ rust-version.workspace = true description.workspace = true [dependencies] +serde = { workspace = true, optional = true } [lints] workspace = true + +[features] +default = [] +serialize = ["dep:serde"] diff --git a/crates/oxc_estree/src/lib.rs b/crates/oxc_estree/src/lib.rs index 9841846ee..c08502367 100644 --- a/crates/oxc_estree/src/lib.rs +++ b/crates/oxc_estree/src/lib.rs @@ -1,3 +1,25 @@ +#[cfg(feature = "serialize")] +use serde::ser::{Serialize, SerializeSeq, Serializer}; + /// Empty trait that will be used later for custom serialization and TypeScript /// generation for AST nodes. pub trait ESTree {} + +#[cfg(feature = "serialize")] +pub struct AppendTo<'a, TVec, TChild>(pub &'a [TVec], pub &'a Option); + +#[cfg(feature = "serialize")] +impl<'b, TVec: Serialize, TChild: Serialize> Serialize for AppendTo<'b, TVec, TChild> { + fn serialize(&self, serializer: S) -> Result { + if let Some(child) = self.1 { + let mut seq = serializer.serialize_seq(Some(self.0.len() + 1))?; + for element in self.0 { + seq.serialize_element(element)?; + } + seq.serialize_element(child)?; + seq.end() + } else { + self.0.serialize(serializer) + } + } +} diff --git a/crates/oxc_regular_expression/Cargo.toml b/crates/oxc_regular_expression/Cargo.toml index a431816ea..a4459275b 100644 --- a/crates/oxc_regular_expression/Cargo.toml +++ b/crates/oxc_regular_expression/Cargo.toml @@ -34,4 +34,9 @@ serde = { workspace = true, optional = true } [features] default = [] -serialize = ["dep:serde", "oxc_allocator/serialize", "oxc_span/serialize"] +serialize = [ + "dep:serde", + "oxc_allocator/serialize", + "oxc_span/serialize", + "oxc_estree/serialize", +] diff --git a/crates/oxc_span/Cargo.toml b/crates/oxc_span/Cargo.toml index 7a3a732fc..88b762b79 100644 --- a/crates/oxc_span/Cargo.toml +++ b/crates/oxc_span/Cargo.toml @@ -32,5 +32,5 @@ serde = { workspace = true, features = ["derive"], optional = true } [features] default = [] -serialize = ["compact_str/serde", "dep:serde"] +serialize = ["compact_str/serde", "dep:serde", "oxc_estree/serialize"] schemars = ["dep:schemars"] diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index b2bb5d5de..612a7d8be 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -41,7 +41,13 @@ wasm-bindgen = { workspace = true, optional = true } [features] default = [] to_js_string = ["dep:ryu-js"] -serialize = ["bitflags/serde", "dep:serde", "dep:wasm-bindgen", "oxc_index/serialize"] +serialize = [ + "bitflags/serde", + "dep:serde", + "dep:wasm-bindgen", + "oxc_index/serialize", + "oxc_estree/serialize", +] [package.metadata.cargo-shear] # We use `oxc_ast_macros::CloneIn` which expands to use `oxc_allocator`. diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index 5f0eda4da..d681add25 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -384,11 +384,13 @@ export type AssignmentTargetPattern = ArrayAssignmentTarget | ObjectAssignmentTa export interface ArrayAssignmentTarget extends Span { type: 'ArrayAssignmentTarget'; elements: Array; + rest: AssignmentTargetRest | null; } export interface ObjectAssignmentTarget extends Span { type: 'ObjectAssignmentTarget'; properties: Array; + rest: AssignmentTargetRest | null; } export interface AssignmentTargetRest extends Span { @@ -726,6 +728,7 @@ export interface AssignmentPattern extends Span { export interface ObjectPattern extends Span { type: 'ObjectPattern'; properties: Array; + rest: BindingRestElement | null; } export interface BindingProperty extends Span { @@ -739,6 +742,7 @@ export interface BindingProperty extends Span { export interface ArrayPattern extends Span { type: 'ArrayPattern'; elements: Array; + rest: BindingRestElement | null; } export interface BindingRestElement extends Span { diff --git a/tasks/ast_tools/src/derives/estree.rs b/tasks/ast_tools/src/derives/estree.rs index 5ddc9f6ee..8e9043430 100644 --- a/tasks/ast_tools/src/derives/estree.rs +++ b/tasks/ast_tools/src/derives/estree.rs @@ -1,12 +1,13 @@ use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::quote; +use rustc_hash::FxHashMap; use crate::{ markers::ESTreeStructTagMode, schema::{ serialize::{enum_variant_name, get_always_flatten_structs, get_type_tag}, - EnumDef, GetGenerics, GetIdent, Schema, StructDef, TypeDef, + EnumDef, FieldDef, GetGenerics, GetIdent, Schema, StructDef, TypeDef, }, }; @@ -74,10 +75,27 @@ fn serialize_struct(def: &StructDef, schema: &Schema) -> TokenStream { if let Some(ty) = &type_tag { fields.push(quote! { map.serialize_entry("type", #ty)?; }); } + + let mut append_to: FxHashMap = FxHashMap::default(); + + // Scan through to find all append_to fields for field in &def.fields { - if field.markers.derive_attributes.estree.skip { + let Some(parent) = field.markers.derive_attributes.estree.append_to.as_ref() else { + continue; + }; + assert!( + append_to.insert(parent.clone(), field).is_none(), + "Duplicate append_to target (on {ident})" + ); + } + + for field in &def.fields { + if field.markers.derive_attributes.estree.skip + || field.markers.derive_attributes.estree.append_to.is_some() + { continue; } + let ident = field.ident().unwrap(); let name = match &field.markers.derive_attributes.estree.rename { Some(rename) => rename.to_string(), None => field.name.clone().unwrap().to_case(Case::Camel), @@ -93,12 +111,26 @@ fn serialize_struct(def: &StructDef, schema: &Schema) -> TokenStream { None => false, }; + let append_child = append_to.get(&ident.to_string()); + if always_flatten || field.markers.derive_attributes.estree.flatten { + assert!( + append_child.is_none(), + "Cannot flatten and append to the same field (on {ident})" + ); fields.push(quote! { self.#ident.serialize( serde::__private::ser::FlatMapSerializer(&mut map) )?; }); + } else if let Some(append_child) = append_child { + let child_ident = append_child.ident().unwrap(); + fields.push(quote! { + map.serialize_entry( + #name, + &oxc_estree::AppendTo(&self.#ident, &self.#child_ident) + )?; + }); } else { fields.push(quote! { map.serialize_entry(#name, &self.#ident)?; diff --git a/tasks/ast_tools/src/markers.rs b/tasks/ast_tools/src/markers.rs index 3139b8166..e94325f66 100644 --- a/tasks/ast_tools/src/markers.rs +++ b/tasks/ast_tools/src/markers.rs @@ -211,6 +211,7 @@ pub struct ESTreeFieldAttribute { pub skip: bool, pub rename: Option, pub typescript_type: Option, + pub append_to: Option, } impl Parse for ESTreeFieldAttribute { @@ -219,6 +220,7 @@ impl Parse for ESTreeFieldAttribute { let mut skip = false; let mut rename = None; let mut typescript_type = None; + let mut append_to = None; loop { let is_type = input.peek(Token![type]); @@ -257,6 +259,13 @@ impl Parse for ESTreeFieldAttribute { "Duplicate estree(type)" ); } + "append_to" => { + input.parse::()?; + assert!( + append_to.replace(input.parse::()?.value()).is_none(), + "Duplicate estree(append_to)" + ); + } arg => panic!("Unsupported #[estree(...)] argument: {arg}"), } let comma = input.peek(Token![,]); @@ -266,7 +275,7 @@ impl Parse for ESTreeFieldAttribute { break; } } - Ok(Self { flatten, skip, rename, typescript_type }) + Ok(Self { flatten, skip, rename, typescript_type, append_to }) } }