mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
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:
parent
84ee581980
commit
dc0215c906
13 changed files with 140 additions and 95 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -1621,6 +1621,9 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "oxc_estree"
|
||||
version = "0.35.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxc_index"
|
||||
|
|
|
|||
|
|
@ -42,4 +42,5 @@ serialize = [
|
|||
"oxc_span/serialize",
|
||||
"oxc_syntax/serialize",
|
||||
"oxc_syntax/to_js_string",
|
||||
"oxc_estree/serialize",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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>>>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
4
npm/oxc-types/types.d.ts
vendored
4
npm/oxc-types/types.d.ts
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue