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
This commit is contained in:
ottomated 2024-11-06 21:09:44 +00:00
parent 84ee581980
commit dc0215c906
13 changed files with 140 additions and 95 deletions

3
Cargo.lock generated
View file

@ -1621,6 +1621,9 @@ dependencies = [
[[package]]
name = "oxc_estree"
version = "0.35.0"
dependencies = [
"serde",
]
[[package]]
name = "oxc_index"

View file

@ -42,4 +42,5 @@ serialize = [
"oxc_span/serialize",
"oxc_syntax/serialize",
"oxc_syntax/to_js_string",
"oxc_estree/serialize",
]

View file

@ -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<AssignmentTargetMaybeDefault | AssignmentTargetRest | null>")]
pub elements: Vec<'a, Option<AssignmentTargetMaybeDefault<'a>>>,
#[estree(skip)]
#[estree(append_to = "elements")]
pub rest: Option<AssignmentTargetRest<'a>>,
#[estree(skip)]
pub trailing_comma: Option<Span>,
@ -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<AssignmentTargetProperty | AssignmentTargetRest>")]
pub properties: Vec<'a, AssignmentTargetProperty<'a>>,
#[estree(skip)]
#[estree(append_to = "properties")]
pub rest: Option<AssignmentTargetRest<'a>>,
}
@ -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<BindingProperty | BindingRestElement>")]
pub properties: Vec<'a, BindingProperty<'a>>,
#[estree(skip)]
#[estree(append_to = "properties")]
pub rest: Option<Box<'a, BindingRestElement<'a>>>,
}
@ -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<BindingPattern | BindingRestElement | null>")]
pub elements: Vec<'a, Option<BindingPattern<'a>>>,
#[estree(skip)]
#[estree(append_to = "elements")]
pub rest: Option<Box<'a, BindingRestElement<'a>>>,
}

View file

@ -707,6 +707,26 @@ impl<'a> Serialize for AssignmentTargetPattern<'a> {
}
}
impl<'a> Serialize for ArrayAssignmentTarget<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(None)?;

View file

@ -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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<AssignmentTargetMaybeDefault<'a>>, AssignmentTargetRest<'a>>,
}
impl<'a> Serialize for ObjectAssignmentTarget<'a> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<BindingPattern<'a>>, 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> {

View file

@ -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"]

View file

@ -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<TChild>);
#[cfg(feature = "serialize")]
impl<'b, TVec: Serialize, TChild: Serialize> Serialize for AppendTo<'b, TVec, TChild> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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)
}
}
}

View file

@ -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",
]

View file

@ -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"]

View file

@ -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`.

View file

@ -384,11 +384,13 @@ export type AssignmentTargetPattern = ArrayAssignmentTarget | ObjectAssignmentTa
export interface ArrayAssignmentTarget extends Span {
type: 'ArrayAssignmentTarget';
elements: Array<AssignmentTargetMaybeDefault | AssignmentTargetRest | null>;
rest: AssignmentTargetRest | null;
}
export interface ObjectAssignmentTarget extends Span {
type: 'ObjectAssignmentTarget';
properties: Array<AssignmentTargetProperty | AssignmentTargetRest>;
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<BindingProperty | BindingRestElement>;
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<BindingPattern | BindingRestElement | null>;
rest: BindingRestElement | null;
}
export interface BindingRestElement extends Span {

View file

@ -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<String, &FieldDef> = 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)?;

View file

@ -211,6 +211,7 @@ pub struct ESTreeFieldAttribute {
pub skip: bool,
pub rename: Option<String>,
pub typescript_type: Option<String>,
pub append_to: Option<String>,
}
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::<Token![=]>()?;
assert!(
append_to.replace(input.parse::<LitStr>()?.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 })
}
}