diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 1ed5f93e46e86..4fdfd9e8a5699 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -2196,6 +2196,7 @@ pub struct ImportExpression<'a> { pub struct ImportDeclaration<'a> { pub span: Span, /// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'` + #[estree(with = "OptionVecDefault", type = "Array")] pub specifiers: Option>>, pub source: StringLiteral<'a>, pub phase: Option, diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index 461ee9f5204da..0c9f8a71cfffa 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -1696,7 +1696,7 @@ impl Serialize for ImportDeclaration<'_> { let mut map = serializer.serialize_map(None)?; map.serialize_entry("type", "ImportDeclaration")?; self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.serialize_entry("specifiers", &self.specifiers)?; + map.serialize_entry("specifiers", &crate::serialize::OptionVecDefault(&self.specifiers))?; map.serialize_entry("source", &self.source)?; map.serialize_entry("phase", &self.phase)?; map.serialize_entry("withClause", &self.with_clause)?; diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 378391f049040..4ff80886dac53 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -241,6 +241,18 @@ impl Serialize for ElementsAndRest<'_, E, R> { } } +pub struct OptionVecDefault<'a, 'b, T: Serialize>(pub &'a Option>); + +impl Serialize for OptionVecDefault<'_, '_, T> { + fn serialize(&self, serializer: S) -> Result { + if let Some(vec) = &self.0 { + vec.serialize(serializer) + } else { + [false; 0].serialize(serializer) + } + } +} + /// Serialize `TSModuleBlock` to be ESTree compatible, with `body` and `directives` fields combined, /// and directives output as `StringLiteral` expression statements impl Serialize for TSModuleBlock<'_> { diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index bf5b006945c00..acfb2b674580d 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -911,7 +911,7 @@ export interface ImportExpression extends Span { export interface ImportDeclaration extends Span { type: 'ImportDeclaration'; - specifiers: Array | null; + specifiers: Array; source: StringLiteral; phase: ImportPhase | null; withClause: WithClause | null; diff --git a/tasks/ast_tools/src/derives/estree.rs b/tasks/ast_tools/src/derives/estree.rs index 48de37d199409..36079bbb9282b 100644 --- a/tasks/ast_tools/src/derives/estree.rs +++ b/tasks/ast_tools/src/derives/estree.rs @@ -9,6 +9,7 @@ use crate::{ serialize::{enum_variant_name, get_always_flatten_structs, get_type_tag}, EnumDef, FieldDef, GetGenerics, GetIdent, Schema, StructDef, TypeDef, }, + util::ToIdent, }; use super::{define_derive, Derive}; @@ -141,6 +142,14 @@ fn serialize_struct(def: &StructDef, schema: &Schema) -> TokenStream { } )?; }); + } else if let Some(with) = &field.markers.derive_attributes.estree.with { + let with_ident = with.to_ident(); + fields.push(quote! { + map.serialize_entry( + #name, + &crate::serialize::#with_ident(&self.#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 e58f7a1dcb28a..aa1deaaf181ac 100644 --- a/tasks/ast_tools/src/markers.rs +++ b/tasks/ast_tools/src/markers.rs @@ -227,6 +227,7 @@ pub struct ESTreeFieldAttribute { pub rename: Option, pub typescript_type: Option, pub append_to: Option, + pub with: Option, } impl Parse for ESTreeFieldAttribute { @@ -236,6 +237,7 @@ impl Parse for ESTreeFieldAttribute { let mut rename = None; let mut typescript_type = None; let mut append_to = None; + let mut with = None; loop { let is_type = input.peek(Token![type]); @@ -281,6 +283,13 @@ impl Parse for ESTreeFieldAttribute { "Duplicate estree(append_to)" ); } + "with" => { + input.parse::()?; + assert!( + with.replace(input.parse::()?.value()).is_none(), + "Duplicate estree(with)" + ); + } arg => panic!("Unsupported #[estree(...)] argument: {arg}"), } let comma = input.peek(Token![,]); @@ -290,7 +299,7 @@ impl Parse for ESTreeFieldAttribute { break; } } - Ok(Self { flatten, skip, rename, typescript_type, append_to }) + Ok(Self { flatten, skip, rename, typescript_type, append_to, with }) } }