mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(ast_tools): support #[estree(prepend_to)] (#10849)
Add an `#[estree(prepend_to = ...)]` attribute to accompany `#[estree(append_to = ...)]`. * `#[estree(prepend_to = target)]` prepends the field to *start* of the target field. * `#[estree(append_to = target)]` appends the field to *end* of the target field. This also allow both prepending *and* appending to the same field. e.g. combining `TSThisParameter`, `FormalParameter`s, and `BindingRestElement` into a single array.
This commit is contained in:
1 parent
b16331ecfa
commit
d066516b6a
10 files changed
+137
-78
No files matched your search
@@ -4,7 +4,7 @@
|
||||
#![allow(unused_imports, clippy::match_same_arms, clippy::semicolon_if_nothing_returned)]
|
||||
|
||||
use oxc_estree::{
|
||||
Concat2, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
Concat2, Concat3, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
};
|
||||
|
||||
use crate::ast::comment::*;
|
||||
|
||||
@@ -26,3 +26,20 @@ impl<C1: ConcatElement, C2: ConcatElement> ESTree for Concat2<'_, C1, C2> {
|
||||
seq.end();
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for concatenating 3 elements into a sequence.
|
||||
pub struct Concat3<'t, C1: ConcatElement, C2: ConcatElement, C3: ConcatElement>(
|
||||
pub &'t C1,
|
||||
pub &'t C2,
|
||||
pub &'t C3,
|
||||
);
|
||||
|
||||
impl<C1: ConcatElement, C2: ConcatElement, C3: ConcatElement> ESTree for Concat3<'_, C1, C2, C3> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) {
|
||||
let mut seq = serializer.serialize_sequence();
|
||||
self.0.push_to_sequence(&mut seq);
|
||||
self.1.push_to_sequence(&mut seq);
|
||||
self.2.push_to_sequence(&mut seq);
|
||||
seq.end();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use formatter::{CompactFormatter, Formatter, PrettyFormatter};
|
||||
use sequences::ESTreeSequenceSerializer;
|
||||
use structs::ESTreeStructSerializer;
|
||||
|
||||
pub use concat::{Concat2, ConcatElement};
|
||||
pub use concat::{Concat2, Concat3, ConcatElement};
|
||||
pub use sequences::SequenceSerializer;
|
||||
pub use strings::{JsonSafeString, LoneSurrogatesString};
|
||||
pub use structs::{FlatStructSerializer, StructSerializer};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#![allow(unused_imports, clippy::match_same_arms, clippy::semicolon_if_nothing_returned)]
|
||||
|
||||
use oxc_estree::{
|
||||
Concat2, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
Concat2, Concat3, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
};
|
||||
|
||||
use crate::source_type::*;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#![allow(unused_imports, clippy::match_same_arms, clippy::semicolon_if_nothing_returned)]
|
||||
|
||||
use oxc_estree::{
|
||||
Concat2, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
Concat2, Concat3, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
};
|
||||
|
||||
use crate::module_record::*;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#![allow(unused_imports, clippy::match_same_arms, clippy::semicolon_if_nothing_returned)]
|
||||
|
||||
use oxc_estree::{
|
||||
Concat2, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
Concat2, Concat3, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
};
|
||||
|
||||
use crate::raw_transfer_types::*;
|
||||
|
||||
@@ -71,7 +71,8 @@ impl Derive for DeriveESTree {
|
||||
|
||||
///@@line_break
|
||||
use oxc_estree::{
|
||||
Concat2, ESTree, FlatStructSerializer, JsonSafeString, Serializer, StructSerializer,
|
||||
Concat2, Concat3, ESTree, FlatStructSerializer,
|
||||
JsonSafeString, Serializer, StructSerializer,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -152,8 +153,8 @@ fn parse_estree_attr(location: AttrLocation, part: AttrPart) -> Result<()> {
|
||||
AttrPart::String("via", value) => {
|
||||
struct_def.fields[field_index].estree.via = Some(value);
|
||||
}
|
||||
AttrPart::String("append_to", value) => {
|
||||
// Find field this field is to be appended to
|
||||
AttrPart::String(attr @ ("prepend_to" | "append_to"), value) => {
|
||||
// Find field this field is to be prepended/appended to
|
||||
let target_field_index = struct_def
|
||||
.fields
|
||||
.iter()
|
||||
@@ -162,15 +163,20 @@ fn parse_estree_attr(location: AttrLocation, part: AttrPart) -> Result<()> {
|
||||
.map(|(field_index, _)| field_index)
|
||||
.ok_or(())?;
|
||||
if target_field_index == field_index {
|
||||
// Can't append field to itself
|
||||
// Can't prepend/append field to itself
|
||||
return Err(());
|
||||
}
|
||||
let target_field = &mut struct_def.fields[target_field_index];
|
||||
if target_field.estree.append_field_index.is_some() {
|
||||
// Can't append twice to same field
|
||||
let other_field_index_mut = if attr == "prepend_to" {
|
||||
&mut target_field.estree.prepend_field_index
|
||||
} else {
|
||||
&mut target_field.estree.append_field_index
|
||||
};
|
||||
if other_field_index_mut.is_some() {
|
||||
// Can't prepend/append twice to same field
|
||||
return Err(());
|
||||
}
|
||||
target_field.estree.append_field_index = Some(field_index);
|
||||
*other_field_index_mut = Some(field_index);
|
||||
struct_def.fields[field_index].estree.skip = true;
|
||||
}
|
||||
AttrPart::String("ts_type", value) => {
|
||||
@@ -441,6 +447,18 @@ impl<'s> StructSerializerGenerator<'s> {
|
||||
let value = if let Some(converter_name) = &field.estree.via {
|
||||
let converter_path = get_converter_path(converter_name, self.krate, self.schema);
|
||||
quote!( #converter_path(#self_path) )
|
||||
} else if let Some(prepend_field_index) = field.estree.prepend_field_index {
|
||||
let prepend_from_ident = struct_def.fields[prepend_field_index].ident();
|
||||
if let Some(append_field_index) = field.estree.append_field_index {
|
||||
let append_from_ident = struct_def.fields[append_field_index].ident();
|
||||
quote! {
|
||||
Concat3(&#self_path.#prepend_from_ident, &#self_path.#field_name_ident, &#self_path.#append_from_ident)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Concat2(&#self_path.#prepend_from_ident, &#self_path.#field_name_ident)
|
||||
}
|
||||
}
|
||||
} else if let Some(append_field_index) = field.estree.append_field_index {
|
||||
let append_from_ident = struct_def.fields[append_field_index].ident();
|
||||
quote! {
|
||||
|
||||
@@ -305,40 +305,62 @@ impl<'s> StructDeserializerGenerator<'s> {
|
||||
return;
|
||||
}
|
||||
|
||||
let value_fn = field_type.deser_name(self.schema);
|
||||
let pos = pos_offset(field_offset);
|
||||
let mut value = format!("{value_fn}({pos})");
|
||||
// Get fields to concatenate
|
||||
// (if fields marked `#[estree(prepend_to)]` or `#[estree(append_to)]` targeting this field)
|
||||
let mut concat_fields = [field; 3];
|
||||
let mut concat_field_count = 1;
|
||||
if let Some(prepend_field_index) = field.estree.prepend_field_index {
|
||||
concat_fields[0] = &struct_def.fields[prepend_field_index];
|
||||
concat_field_count = 2;
|
||||
}
|
||||
if let Some(append_field_index) = field.estree.append_field_index {
|
||||
concat_fields[concat_field_count] = &struct_def.fields[append_field_index];
|
||||
concat_field_count += 1;
|
||||
}
|
||||
|
||||
if let Some(appended_field_index) = field.estree.append_field_index {
|
||||
self.preamble.push(format!("const {field_name} = {value};"));
|
||||
|
||||
let appended_field = &struct_def.fields[appended_field_index];
|
||||
let appended_field_type = appended_field.type_def(self.schema);
|
||||
match appended_field_type {
|
||||
TypeDef::Vec(vec_def) => {
|
||||
let appended_field_fn = vec_def.deser_name(self.schema);
|
||||
let appended_pos = pos_offset(struct_offset + appended_field.offset_64());
|
||||
self.preamble.push(format!(
|
||||
"{field_name}.push(...{appended_field_fn}({appended_pos}));"
|
||||
));
|
||||
let value = if concat_field_count > 1 {
|
||||
// Concatenate fields
|
||||
for (index, &field) in concat_fields[..concat_field_count].iter().enumerate() {
|
||||
let field_pos = pos_offset(struct_offset + field.offset_64());
|
||||
match field.type_def(self.schema) {
|
||||
TypeDef::Vec(vec_def) => {
|
||||
let field_fn = vec_def.deser_name(self.schema);
|
||||
if index == 0 {
|
||||
self.preamble
|
||||
.push(format!("const {field_name} = {field_fn}({field_pos});"));
|
||||
} else {
|
||||
self.preamble
|
||||
.push(format!("{field_name}.push(...{field_fn}({field_pos}));"));
|
||||
}
|
||||
}
|
||||
TypeDef::Option(option_def) => {
|
||||
let option_field_name = get_struct_field_name(field).to_string();
|
||||
let field_fn = option_def.deser_name(self.schema);
|
||||
self.preamble
|
||||
.push(format!("const {option_field_name} = {field_fn}({field_pos});"));
|
||||
if index == 0 {
|
||||
self.preamble.push(format!(
|
||||
"const {field_name} = {option_field_name} === null ? [] : [{option_field_name}];"
|
||||
));
|
||||
} else {
|
||||
self.preamble.push(format!(
|
||||
"if ({option_field_name} !== null) {field_name}.push({option_field_name});"
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => panic!("Cannot append: `{}::{}`", struct_def.name(), field.name()),
|
||||
}
|
||||
TypeDef::Option(option_def) => {
|
||||
let appended_field_name = get_struct_field_name(appended_field).to_string();
|
||||
let appended_field_fn = option_def.deser_name(self.schema);
|
||||
let appended_pos = pos_offset(struct_offset + appended_field.offset_64());
|
||||
self.preamble.push(format!("
|
||||
const {appended_field_name} = {appended_field_fn}({appended_pos});
|
||||
if ({appended_field_name} !== null) {field_name}.push({appended_field_name});
|
||||
"));
|
||||
}
|
||||
_ => panic!("Cannot append: `{}::{}`", struct_def.name(), field.name()),
|
||||
}
|
||||
|
||||
value.clone_from(&field_name);
|
||||
field_name.clone()
|
||||
} else if let Some(converter_name) = &field.estree.via {
|
||||
let converter = self.schema.meta_by_name(converter_name);
|
||||
value = self.apply_converter(converter, struct_def, struct_offset).unwrap();
|
||||
}
|
||||
self.apply_converter(converter, struct_def, struct_offset).unwrap()
|
||||
} else {
|
||||
let value_fn = field_type.deser_name(self.schema);
|
||||
let pos = pos_offset(field_offset);
|
||||
format!("{value_fn}({pos})")
|
||||
};
|
||||
|
||||
self.fields.insert(field_name, value);
|
||||
}
|
||||
|
||||
@@ -192,50 +192,50 @@ fn generate_ts_type_def_for_struct_field_impl<'s>(
|
||||
output_as_type: &mut bool,
|
||||
schema: &'s Schema,
|
||||
) {
|
||||
let field_type_name = if let Some(append_field_index) = field.estree.append_field_index {
|
||||
let appended_field = &struct_def.fields[append_field_index];
|
||||
let appended_type = appended_field.type_def(schema);
|
||||
let appended_type = match appended_type {
|
||||
TypeDef::Option(option_def) => option_def.inner_type(schema),
|
||||
TypeDef::Vec(vec_def) => vec_def.inner_type(schema),
|
||||
_ => panic!(
|
||||
"Appended field must be `Option<T>` or `Vec<T>`: `{}::{}`",
|
||||
struct_def.name(),
|
||||
appended_field.name()
|
||||
),
|
||||
};
|
||||
let appended_type_name = ts_type_name(appended_type, schema);
|
||||
// Get fields to concatenate
|
||||
// (if fields marked `#[estree(prepend_to)]` or `#[estree(append_to)]` targeting this field)
|
||||
let mut concat_fields = [field; 3];
|
||||
let mut concat_field_count = 1;
|
||||
if let Some(prepend_field_index) = field.estree.prepend_field_index {
|
||||
concat_fields[0] = &struct_def.fields[prepend_field_index];
|
||||
concat_field_count = 2;
|
||||
}
|
||||
if let Some(append_field_index) = field.estree.append_field_index {
|
||||
concat_fields[concat_field_count] = &struct_def.fields[append_field_index];
|
||||
concat_field_count += 1;
|
||||
}
|
||||
|
||||
let field_type = field.type_def(schema);
|
||||
let (vec_def, is_option) = match field_type {
|
||||
TypeDef::Vec(vec_def) => (vec_def, false),
|
||||
TypeDef::Option(option_def) => {
|
||||
let vec_def = option_def.inner_type(schema).as_vec().unwrap();
|
||||
(vec_def, true)
|
||||
let field_type_name = if concat_field_count > 1 {
|
||||
// Combine types of concatenated fields
|
||||
let mut field_type_name = "Array<".to_string();
|
||||
let mut include_null = false;
|
||||
for (index, &field) in concat_fields[..concat_field_count].iter().enumerate() {
|
||||
let field_type = match field.type_def(schema) {
|
||||
TypeDef::Option(option_def) => option_def.inner_type(schema),
|
||||
TypeDef::Vec(vec_def) => match vec_def.inner_type(schema) {
|
||||
TypeDef::Option(option_def) => {
|
||||
include_null = true;
|
||||
option_def.inner_type(schema)
|
||||
}
|
||||
field_type => field_type,
|
||||
},
|
||||
_ => panic!(
|
||||
"Appended field must be `Option<T>` or `Vec<T>`: `{}::{}`",
|
||||
struct_def.name(),
|
||||
field.name()
|
||||
),
|
||||
};
|
||||
|
||||
if index > 0 {
|
||||
field_type_name.push_str(" | ");
|
||||
}
|
||||
_ => panic!(
|
||||
"Can only append a field to a `Vec<T>` or `Option<Vec<T>>`: `{}::{}`",
|
||||
struct_def.name(),
|
||||
field.name()
|
||||
),
|
||||
};
|
||||
|
||||
let mut inner_type = vec_def.inner_type(schema);
|
||||
let mut inner_is_option = false;
|
||||
if let TypeDef::Option(option_def) = inner_type {
|
||||
inner_is_option = true;
|
||||
inner_type = option_def.inner_type(schema);
|
||||
field_type_name.push_str(&ts_type_name(field_type, schema));
|
||||
}
|
||||
let inner_type_name = ts_type_name(inner_type, schema);
|
||||
let mut field_type_name = format!("Array<{inner_type_name} | {appended_type_name}");
|
||||
if inner_is_option {
|
||||
|
||||
if include_null {
|
||||
field_type_name.push_str(" | null");
|
||||
}
|
||||
field_type_name.push('>');
|
||||
if is_option {
|
||||
field_type_name.push_str(" | null");
|
||||
}
|
||||
|
||||
Cow::Owned(field_type_name)
|
||||
} else if let Some(converter_name) = &field.estree.via {
|
||||
let Some(ts_type) = get_ts_type_for_converter(converter_name, schema) else {
|
||||
|
||||
@@ -66,6 +66,8 @@ pub struct ESTreeStructField {
|
||||
pub via: Option<String>,
|
||||
/// TS type of this field.
|
||||
pub ts_type: Option<String>,
|
||||
/// Field index of field to prepend to this one
|
||||
pub prepend_field_index: Option<usize>,
|
||||
/// Field index of field to append to this one
|
||||
pub append_field_index: Option<usize>,
|
||||
/// Skip this struct field.
|
||||
|
||||
Reference in new issue
Block a user