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 span: Span,
/// `None` for `import 'foo'`, `Some([])` for `import {} from 'foo'`
#[estree(with = "OptionVecDefault", type = "Array<ImportDeclarationSpecifier>")]
pub specifiers: Option<Vec<'a, ImportDeclarationSpecifier<'a>>>,
pub source: StringLiteral<'a>,
pub phase: Option<ImportPhase>,

View file

@ -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)?;

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,
/// and directives output as `StringLiteral` expression statements
impl Serialize for TSModuleBlock<'_> {

View file

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

View file

@ -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)?;

View file

@ -227,6 +227,7 @@ pub struct ESTreeFieldAttribute {
pub rename: Option<String>,
pub typescript_type: Option<String>,
pub append_to: Option<String>,
pub with: Option<String>,
}
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::<Token![=]>()?;
assert!(
with.replace(input.parse::<LitStr>()?.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 })
}
}