Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/oxc_ast/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
17 changes: 17 additions & 0 deletions crates/oxc_estree/src/serialize/concat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
2 changes: 1 addition & 1 deletion crates/oxc_estree/src/serialize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_span/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_syntax/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
2 changes: 1 addition & 1 deletion napi/parser/src/generated/derive_estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
32 changes: 25 additions & 7 deletions tasks/ast_tools/src/derives/estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
}
Expand Down Expand Up @@ -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()
Expand All @@ -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) => {
Expand Down Expand Up @@ -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! {
Expand Down
80 changes: 51 additions & 29 deletions tasks/ast_tools/src/generators/raw_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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})");

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}));"
));
}
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});
"));
// 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 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()),
}
_ => 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);
}
Expand Down
76 changes: 38 additions & 38 deletions tasks/ast_tools/src/generators/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions tasks/ast_tools/src/schema/extensions/estree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading