fix(ast)!: always return Array<ImportDeclarationSpecifier> for ImportDeclaration.specifiers (#8560)

refs https://github.com/oxc-project/oxc/issues/2854#issuecomment-2595115817
This commit is contained in:
sapphi-red 2025-01-17 13:04:59 +00:00
parent 869bc73e67
commit 19d36771af
No known key found for this signature in database
GPG key ID: 67631A259A77AC6C
6 changed files with 34 additions and 3 deletions

View file

@ -2196,6 +2196,7 @@ pub struct ImportExpression<'a> {
pub struct ImportDeclaration<'a> { pub struct ImportDeclaration<'a> {
pub span: Span, pub span: Span,
/// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'` /// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'`
#[estree(with = "OptionVecDefault", type = "Array<ImportDeclarationSpecifier>")]
pub specifiers: Option<Vec<'a, ImportDeclarationSpecifier<'a>>>, pub specifiers: Option<Vec<'a, ImportDeclarationSpecifier<'a>>>,
pub source: StringLiteral<'a>, pub source: StringLiteral<'a>,
pub phase: Option<ImportPhase>, pub phase: Option<ImportPhase>,

View file

@ -1696,7 +1696,7 @@ impl Serialize for ImportDeclaration<'_> {
let mut map = serializer.serialize_map(None)?; let mut map = serializer.serialize_map(None)?;
map.serialize_entry("type", "ImportDeclaration")?; map.serialize_entry("type", "ImportDeclaration")?;
self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; 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("source", &self.source)?;
map.serialize_entry("phase", &self.phase)?; map.serialize_entry("phase", &self.phase)?;
map.serialize_entry("withClause", &self.with_clause)?; map.serialize_entry("withClause", &self.with_clause)?;

View file

@ -241,6 +241,18 @@ impl<E: Serialize, R: Serialize> Serialize for ElementsAndRest<'_, E, R> {
} }
} }
pub struct OptionVecDefault<'a, 'b, T: Serialize>(pub &'a Option<oxc_allocator::Vec<'b, T>>);
impl<T: Serialize> Serialize for OptionVecDefault<'_, '_, T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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, /// Serialize `TSModuleBlock` to be ESTree compatible, with `body` and `directives` fields combined,
/// and directives output as `StringLiteral` expression statements /// and directives output as `StringLiteral` expression statements
impl Serialize for TSModuleBlock<'_> { impl Serialize for TSModuleBlock<'_> {

View file

@ -911,7 +911,7 @@ export interface ImportExpression extends Span {
export interface ImportDeclaration extends Span { export interface ImportDeclaration extends Span {
type: 'ImportDeclaration'; type: 'ImportDeclaration';
specifiers: Array<ImportDeclarationSpecifier> | null; specifiers: Array<ImportDeclarationSpecifier>;
source: StringLiteral; source: StringLiteral;
phase: ImportPhase | null; phase: ImportPhase | null;
withClause: WithClause | null; withClause: WithClause | null;

View file

@ -9,6 +9,7 @@ use crate::{
serialize::{enum_variant_name, get_always_flatten_structs, get_type_tag}, serialize::{enum_variant_name, get_always_flatten_structs, get_type_tag},
EnumDef, FieldDef, GetGenerics, GetIdent, Schema, StructDef, TypeDef, EnumDef, FieldDef, GetGenerics, GetIdent, Schema, StructDef, TypeDef,
}, },
util::ToIdent,
}; };
use super::{define_derive, Derive}; 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 { } else {
fields.push(quote! { fields.push(quote! {
map.serialize_entry(#name, &self.#ident)?; map.serialize_entry(#name, &self.#ident)?;

View file

@ -227,6 +227,7 @@ pub struct ESTreeFieldAttribute {
pub rename: Option<String>, pub rename: Option<String>,
pub typescript_type: Option<String>, pub typescript_type: Option<String>,
pub append_to: Option<String>, pub append_to: Option<String>,
pub with: Option<String>,
} }
impl Parse for ESTreeFieldAttribute { impl Parse for ESTreeFieldAttribute {
@ -236,6 +237,7 @@ impl Parse for ESTreeFieldAttribute {
let mut rename = None; let mut rename = None;
let mut typescript_type = None; let mut typescript_type = None;
let mut append_to = None; let mut append_to = None;
let mut with = None;
loop { loop {
let is_type = input.peek(Token![type]); let is_type = input.peek(Token![type]);
@ -281,6 +283,13 @@ impl Parse for ESTreeFieldAttribute {
"Duplicate estree(append_to)" "Duplicate estree(append_to)"
); );
} }
"with" => {
input.parse::<Token![=]>()?;
assert!(
with.replace(input.parse::<LitStr>()?.value()).is_none(),
"Duplicate estree(with)"
);
}
arg => panic!("Unsupported #[estree(...)] argument: {arg}"), arg => panic!("Unsupported #[estree(...)] argument: {arg}"),
} }
let comma = input.peek(Token![,]); let comma = input.peek(Token![,]);
@ -290,7 +299,7 @@ impl Parse for ESTreeFieldAttribute {
break; break;
} }
} }
Ok(Self { flatten, skip, rename, typescript_type, append_to }) Ok(Self { flatten, skip, rename, typescript_type, append_to, with })
} }
} }