diff --git a/Cargo.lock b/Cargo.lock index 746f6eaef0289..0dab5c61c93ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1470,7 +1470,9 @@ name = "oxc_ast" version = "0.35.0" dependencies = [ "bitflags 2.6.0", + "cow-utils", "num-bigint", + "num-traits", "oxc_allocator", "oxc_ast_macros", "oxc_estree", diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index 84979bc432e5f..15fd4f25cd319 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -27,7 +27,9 @@ oxc_span = { workspace = true } oxc_syntax = { workspace = true } bitflags = { workspace = true } +cow-utils = { workspace = true } num-bigint = { workspace = true } +num-traits = { workspace = true } serde = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true, optional = true } diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index 647684e75261b..0a3339409441a 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -20,6 +20,7 @@ use oxc_syntax::number::{BigintBase, NumberBase}; #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "raw: string")] pub struct BooleanLiteral { /// Node location in source code pub span: Span, @@ -33,6 +34,7 @@ pub struct BooleanLiteral { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, raw: \"null\"")] pub struct NullLiteral { /// Node location in source code pub span: Span, @@ -44,6 +46,7 @@ pub struct NullLiteral { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[estree(type = "Literal", via = crate::serialize::ESTreeLiteral)] pub struct NumericLiteral<'a> { /// Node location in source code pub span: Span, @@ -60,6 +63,7 @@ pub struct NumericLiteral<'a> { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, bigint: string")] pub struct BigIntLiteral<'a> { /// Node location in source code pub span: Span, @@ -76,18 +80,20 @@ pub struct BigIntLiteral<'a> { #[ast(visit)] #[derive(Debug)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[estree( + type = "Literal", + via = crate::serialize::ESTreeLiteral, + add_ts = "value: {} | null, regex: { pattern: string, flags: string }" +)] pub struct RegExpLiteral<'a> { /// Node location in source code pub span: Span, - /// Placeholder for printing. - /// - /// Valid regular expressions are printed as `{}`, while invalid ones are - /// printed as `null`. Note that invalid regular expressions are not yet - /// printed properly. - pub value: EmptyObject, /// The parsed regular expression. See [`oxc_regular_expression`] for more /// details. + #[estree(skip)] pub regex: RegExp<'a>, + /// The regular expression as it appears in source code + pub raw: &'a str, } /// A regular expression @@ -122,13 +128,6 @@ pub enum RegExpPattern<'a> { Pattern(Box<'a, Pattern<'a>>) = 2, } -/// An empty object literal (`{}`) -#[ast] -#[derive(Debug, Clone)] -#[generate_derive(CloneIn, ContentEq, ContentHash, ESTree)] -#[estree(no_type)] -pub struct EmptyObject; - /// String literal /// /// diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index d4f325764edc8..46528ae7f6158 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -31,11 +31,11 @@ const _: () = { assert!(offset_of!(BigIntLiteral, raw) == 8usize); assert!(offset_of!(BigIntLiteral, base) == 24usize); - assert!(size_of::() == 40usize); + assert!(size_of::() == 56usize); assert!(align_of::() == 8usize); assert!(offset_of!(RegExpLiteral, span) == 0usize); - assert!(offset_of!(RegExpLiteral, value) == 8usize); assert!(offset_of!(RegExpLiteral, regex) == 8usize); + assert!(offset_of!(RegExpLiteral, raw) == 40usize); assert!(size_of::() == 32usize); assert!(align_of::() == 8usize); @@ -45,9 +45,6 @@ const _: () = { assert!(size_of::() == 24usize); assert!(align_of::() == 8usize); - assert!(size_of::() == 0usize); - assert!(align_of::() == 1usize); - assert!(size_of::() == 24usize); assert!(align_of::() == 8usize); assert!(offset_of!(StringLiteral, span) == 0usize); @@ -1591,11 +1588,11 @@ const _: () = { assert!(offset_of!(BigIntLiteral, raw) == 8usize); assert!(offset_of!(BigIntLiteral, base) == 16usize); - assert!(size_of::() == 24usize); + assert!(size_of::() == 32usize); assert!(align_of::() == 4usize); assert!(offset_of!(RegExpLiteral, span) == 0usize); - assert!(offset_of!(RegExpLiteral, value) == 8usize); assert!(offset_of!(RegExpLiteral, regex) == 8usize); + assert!(offset_of!(RegExpLiteral, raw) == 24usize); assert!(size_of::() == 16usize); assert!(align_of::() == 4usize); @@ -1605,9 +1602,6 @@ const _: () = { assert!(size_of::() == 12usize); assert!(align_of::() == 4usize); - assert!(size_of::() == 0usize); - assert!(align_of::() == 1usize); - assert!(size_of::() == 16usize); assert!(align_of::() == 4usize); assert!(offset_of!(StringLiteral, span) == 0usize); diff --git a/crates/oxc_ast/src/generated/ast_builder.rs b/crates/oxc_ast/src/generated/ast_builder.rs index 2d26906fb05da..773ea68e86221 100644 --- a/crates/oxc_ast/src/generated/ast_builder.rs +++ b/crates/oxc_ast/src/generated/ast_builder.rs @@ -159,16 +159,14 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: Node location in source code - /// - value: Placeholder for printing. /// - regex: The parsed regular expression. See [`oxc_regular_expression`] for more + /// - raw: The regular expression as it appears in source code #[inline] - pub fn reg_exp_literal( - self, - span: Span, - value: EmptyObject, - regex: RegExp<'a>, - ) -> RegExpLiteral<'a> { - RegExpLiteral { span, value, regex } + pub fn reg_exp_literal(self, span: Span, regex: RegExp<'a>, raw: S) -> RegExpLiteral<'a> + where + S: IntoIn<'a, &'a str>, + { + RegExpLiteral { span, regex, raw: raw.into_in(self.allocator) } } /// Build a [`RegExpLiteral`], and store it in the memory arena. @@ -177,16 +175,19 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: Node location in source code - /// - value: Placeholder for printing. /// - regex: The parsed regular expression. See [`oxc_regular_expression`] for more + /// - raw: The regular expression as it appears in source code #[inline] - pub fn alloc_reg_exp_literal( + pub fn alloc_reg_exp_literal( self, span: Span, - value: EmptyObject, regex: RegExp<'a>, - ) -> Box<'a, RegExpLiteral<'a>> { - Box::new_in(self.reg_exp_literal(span, value, regex), self.allocator) + raw: S, + ) -> Box<'a, RegExpLiteral<'a>> + where + S: IntoIn<'a, &'a str>, + { + Box::new_in(self.reg_exp_literal(span, regex, raw), self.allocator) } /// Build a [`StringLiteral`]. @@ -445,16 +446,19 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: Node location in source code - /// - value: Placeholder for printing. /// - regex: The parsed regular expression. See [`oxc_regular_expression`] for more + /// - raw: The regular expression as it appears in source code #[inline] - pub fn expression_reg_exp_literal( + pub fn expression_reg_exp_literal( self, span: Span, - value: EmptyObject, regex: RegExp<'a>, - ) -> Expression<'a> { - Expression::RegExpLiteral(self.alloc(self.reg_exp_literal(span, value, regex))) + raw: S, + ) -> Expression<'a> + where + S: IntoIn<'a, &'a str>, + { + Expression::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) } /// Build an [`Expression::StringLiteral`] @@ -7959,16 +7963,19 @@ impl<'a> AstBuilder<'a> { /// /// ## Parameters /// - span: Node location in source code - /// - value: Placeholder for printing. /// - regex: The parsed regular expression. See [`oxc_regular_expression`] for more + /// - raw: The regular expression as it appears in source code #[inline] - pub fn ts_literal_reg_exp_literal( + pub fn ts_literal_reg_exp_literal( self, span: Span, - value: EmptyObject, regex: RegExp<'a>, - ) -> TSLiteral<'a> { - TSLiteral::RegExpLiteral(self.alloc(self.reg_exp_literal(span, value, regex))) + raw: S, + ) -> TSLiteral<'a> + where + S: IntoIn<'a, &'a str>, + { + TSLiteral::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) } /// Build a [`TSLiteral::StringLiteral`] diff --git a/crates/oxc_ast/src/generated/derive_clone_in.rs b/crates/oxc_ast/src/generated/derive_clone_in.rs index 2ed1ab60e223c..91082daf172d6 100644 --- a/crates/oxc_ast/src/generated/derive_clone_in.rs +++ b/crates/oxc_ast/src/generated/derive_clone_in.rs @@ -60,8 +60,8 @@ impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for RegExpLiteral<'old_alloc> { fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { RegExpLiteral { span: CloneIn::clone_in(&self.span, allocator), - value: CloneIn::clone_in(&self.value, allocator), regex: CloneIn::clone_in(&self.regex, allocator), + raw: CloneIn::clone_in(&self.raw, allocator), } } } @@ -87,13 +87,6 @@ impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for RegExpPattern<'old_alloc> { } } -impl<'alloc> CloneIn<'alloc> for EmptyObject { - type Cloned = EmptyObject; - fn clone_in(&self, _: &'alloc Allocator) -> Self::Cloned { - EmptyObject - } -} - impl<'old_alloc, 'new_alloc> CloneIn<'new_alloc> for StringLiteral<'old_alloc> { type Cloned = StringLiteral<'new_alloc>; fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { diff --git a/crates/oxc_ast/src/generated/derive_content_eq.rs b/crates/oxc_ast/src/generated/derive_content_eq.rs index f4e4828af3609..c05760395c05d 100644 --- a/crates/oxc_ast/src/generated/derive_content_eq.rs +++ b/crates/oxc_ast/src/generated/derive_content_eq.rs @@ -44,8 +44,8 @@ impl<'a> ContentEq for BigIntLiteral<'a> { impl<'a> ContentEq for RegExpLiteral<'a> { fn content_eq(&self, other: &Self) -> bool { - ContentEq::content_eq(&self.value, &other.value) - && ContentEq::content_eq(&self.regex, &other.regex) + ContentEq::content_eq(&self.regex, &other.regex) + && ContentEq::content_eq(&self.raw, &other.raw) } } @@ -75,12 +75,6 @@ impl<'a> ContentEq for RegExpPattern<'a> { } } -impl ContentEq for EmptyObject { - fn content_eq(&self, _: &Self) -> bool { - true - } -} - impl<'a> ContentEq for StringLiteral<'a> { fn content_eq(&self, other: &Self) -> bool { ContentEq::content_eq(&self.value, &other.value) diff --git a/crates/oxc_ast/src/generated/derive_content_hash.rs b/crates/oxc_ast/src/generated/derive_content_hash.rs index 4bdb3f965ab6b..75f00965d3819 100644 --- a/crates/oxc_ast/src/generated/derive_content_hash.rs +++ b/crates/oxc_ast/src/generated/derive_content_hash.rs @@ -32,8 +32,8 @@ impl<'a> ContentHash for BigIntLiteral<'a> { impl<'a> ContentHash for RegExpLiteral<'a> { fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&self.value, state); ContentHash::content_hash(&self.regex, state); + ContentHash::content_hash(&self.raw, state); } } @@ -55,10 +55,6 @@ impl<'a> ContentHash for RegExpPattern<'a> { } } -impl ContentHash for EmptyObject { - fn content_hash(&self, _: &mut H) {} -} - impl<'a> ContentHash for StringLiteral<'a> { fn content_hash(&self, state: &mut H) { ContentHash::content_hash(&self.value, state); diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index dfdee187269d0..17cd751ba9438 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -15,52 +15,31 @@ use crate::ast::ts::*; impl Serialize for BooleanLiteral { fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("type", "BooleanLiteral")?; - self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.serialize_entry("value", &self.value)?; - map.end() + crate::serialize::ESTreeLiteral::from(self).serialize(serializer) } } impl Serialize for NullLiteral { fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("type", "NullLiteral")?; - self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.end() + crate::serialize::ESTreeLiteral::from(self).serialize(serializer) } } impl<'a> Serialize for NumericLiteral<'a> { fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("type", "NumericLiteral")?; - self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.serialize_entry("value", &self.value)?; - map.serialize_entry("raw", &self.raw)?; - map.end() + crate::serialize::ESTreeLiteral::from(self).serialize(serializer) } } impl<'a> Serialize for BigIntLiteral<'a> { fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("type", "BigIntLiteral")?; - self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.serialize_entry("raw", &self.raw)?; - map.end() + crate::serialize::ESTreeLiteral::from(self).serialize(serializer) } } impl<'a> Serialize for RegExpLiteral<'a> { fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.serialize_entry("type", "RegExpLiteral")?; - self.span.serialize(serde::__private::ser::FlatMapSerializer(&mut map))?; - map.serialize_entry("value", &self.value)?; - map.serialize_entry("regex", &self.regex)?; - map.end() + crate::serialize::ESTreeLiteral::from(self).serialize(serializer) } } @@ -83,13 +62,6 @@ impl<'a> Serialize for RegExpPattern<'a> { } } -impl Serialize for EmptyObject { - fn serialize(&self, serializer: S) -> Result { - let mut map = serializer.serialize_map(None)?; - map.end() - } -} - impl<'a> Serialize for StringLiteral<'a> { fn serialize(&self, serializer: S) -> Result { let mut map = serializer.serialize_map(None)?; diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 58ffd06942b64..66cab18770721 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -1,16 +1,113 @@ +use cow_utils::CowUtils; +use num_bigint::BigInt; +use num_traits::Num; use oxc_allocator::Box; use oxc_span::Span; +use oxc_syntax::number::BigintBase; use serde::{ ser::{SerializeSeq, Serializer}, Serialize, }; use crate::ast::{ - BindingPatternKind, Directive, Elision, FormalParameter, FormalParameterKind, FormalParameters, - JSXElementName, JSXIdentifier, JSXMemberExpressionObject, Program, RegExpFlags, Statement, - StringLiteral, TSModuleBlock, TSTypeAnnotation, + BigIntLiteral, BindingPatternKind, BooleanLiteral, Directive, Elision, FormalParameter, + FormalParameterKind, FormalParameters, JSXElementName, JSXIdentifier, + JSXMemberExpressionObject, NullLiteral, NumericLiteral, Program, RegExpFlags, RegExpLiteral, + RegExpPattern, Statement, StringLiteral, TSModuleBlock, TSTypeAnnotation, }; +#[derive(Serialize)] +#[serde(tag = "type", rename = "Literal")] +pub struct ESTreeLiteral<'a, T> { + #[serde(flatten)] + span: Span, + value: T, + raw: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + bigint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + regex: Option, +} + +impl<'a> From<&BooleanLiteral> for ESTreeLiteral<'a, bool> { + fn from(value: &BooleanLiteral) -> Self { + Self { + span: value.span, + value: value.value, + raw: if value.value { "true" } else { "false" }, + bigint: None, + regex: None, + } + } +} + +impl<'a> From<&NullLiteral> for ESTreeLiteral<'a, ()> { + fn from(value: &NullLiteral) -> Self { + Self { span: value.span, value: (), raw: "null", bigint: None, regex: None } + } +} + +impl<'a> From<&'a NumericLiteral<'a>> for ESTreeLiteral<'a, f64> { + fn from(value: &'a NumericLiteral) -> Self { + Self { span: value.span, value: value.value, raw: value.raw, bigint: None, regex: None } + } +} + +impl<'a> From<&'a BigIntLiteral<'a>> for ESTreeLiteral<'a, ()> { + fn from(value: &'a BigIntLiteral) -> Self { + let src = &value.raw.strip_suffix('n').unwrap().cow_replace('_', ""); + + let src = match value.base { + BigintBase::Decimal => src, + BigintBase::Binary | BigintBase::Octal | BigintBase::Hex => &src[2..], + }; + let radix = match value.base { + BigintBase::Decimal => 10, + BigintBase::Binary => 2, + BigintBase::Octal => 8, + BigintBase::Hex => 16, + }; + let bigint = BigInt::from_str_radix(src, radix).unwrap(); + + Self { + span: value.span, + // BigInts can't be serialized to JSON + value: (), + raw: value.raw.as_str(), + bigint: Some(bigint.to_string()), + regex: None, + } + } +} +#[derive(Serialize)] +pub struct SerRegExpValue { + pattern: String, + flags: String, +} + +/// A placeholder for regexp literals that can't be serialized to JSON +#[derive(Serialize)] +#[allow(clippy::empty_structs_with_brackets)] +pub struct EmptyObject {} + +impl<'a> From<&'a RegExpLiteral<'a>> for ESTreeLiteral<'a, Option> { + fn from(value: &'a RegExpLiteral) -> Self { + Self { + span: value.span, + raw: value.raw, + value: match &value.regex.pattern { + RegExpPattern::Pattern(_) => Some(EmptyObject {}), + _ => None, + }, + bigint: None, + regex: Some(SerRegExpValue { + pattern: value.regex.pattern.to_string(), + flags: value.regex.flags.to_string(), + }), + } + } +} + pub struct EcmaFormatter; /// Serialize f64 with `ryu_js` diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 99e8ed7d46fbc..4664cf10acd1a 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -350,6 +350,7 @@ impl<'a> ParserImpl<'a> { let pattern_text = &self.source_text[pattern_start as usize..pattern_end as usize]; let flags_start = pattern_end + 1; // +1 to include right `/` let flags_text = &self.source_text[flags_start as usize..self.cur_token().end as usize]; + let raw = self.cur_src(); self.bump_any(); // Parse pattern if options is enabled and also flags are valid let pattern = (self.options.parse_regular_expression && !flags_error) @@ -363,7 +364,7 @@ impl<'a> ParserImpl<'a> { pat.map_or_else(|| RegExpPattern::Invalid(pattern_text), RegExpPattern::Pattern) }, ); - Ok(self.ast.reg_exp_literal(self.end_span(span), EmptyObject, RegExp { pattern, flags })) + Ok(self.ast.reg_exp_literal(self.end_span(span), RegExp { pattern, flags }, raw)) } fn parse_regex_pattern( diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index d681add25f34d..1caa9915c5077 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -2,29 +2,35 @@ // To edit this generated file you have to edit `tasks/ast_tools/src/generators/typescript.rs` export interface BooleanLiteral extends Span { - type: 'BooleanLiteral'; + type: 'Literal'; value: boolean; + raw: string; } export interface NullLiteral extends Span { - type: 'NullLiteral'; + type: 'Literal'; + value: null; + raw: 'null'; } export interface NumericLiteral extends Span { - type: 'NumericLiteral'; + type: 'Literal'; value: number; raw: string; } export interface BigIntLiteral extends Span { - type: 'BigIntLiteral'; + type: 'Literal'; raw: string; + value: null; + bigint: string; } export interface RegExpLiteral extends Span { - type: 'RegExpLiteral'; - value: EmptyObject; - regex: RegExp; + type: 'Literal'; + raw: string; + value: {} | null; + regex: { pattern: string; flags: string }; } export interface RegExp { @@ -34,9 +40,6 @@ export interface RegExp { export type RegExpPattern = string | string | Pattern; -export interface EmptyObject { -} - export interface StringLiteral extends Span { type: 'StringLiteral'; value: string; diff --git a/tasks/ast_tools/src/derives/estree.rs b/tasks/ast_tools/src/derives/estree.rs index 8e9043430bc5b..a92b724bb8b9d 100644 --- a/tasks/ast_tools/src/derives/estree.rs +++ b/tasks/ast_tools/src/derives/estree.rs @@ -66,6 +66,13 @@ impl Derive for DeriveESTree { } fn serialize_struct(def: &StructDef, schema: &Schema) -> TokenStream { + if let Some(via) = &def.markers.estree.as_ref().and_then(|e| e.via.as_ref()) { + let via: TokenStream = via.parse().unwrap(); + return quote! { + #via::from(self).serialize(serializer) + }; + } + let ident = def.ident(); // If type_tag is Some, we serialize it manually. If None, either one of // the fields is named r#type, or the struct does not need a "type" field. diff --git a/tasks/ast_tools/src/generators/typescript.rs b/tasks/ast_tools/src/generators/typescript.rs index 944541bf5a983..53ad654d8a18a 100644 --- a/tasks/ast_tools/src/generators/typescript.rs +++ b/tasks/ast_tools/src/generators/typescript.rs @@ -101,17 +101,23 @@ fn typescript_struct(def: &StructDef, always_flatten_structs: &FxHashSet let extends_union = extends.iter().any(|it| it.contains('|')); + let body = if let Some(extra_ts) = def.markers.estree.as_ref().and_then(|e| e.add_ts.as_ref()) { + format!("{{{fields}\n\t{extra_ts}\n}}") + } else { + format!("{{{fields}\n}}") + }; + if extends_union { let extends = if extends.is_empty() { String::new() } else { format!(" & {}", extends.join(" & ")) }; - format!("export type {ident} = ({{{fields}\n}}){extends};") + format!("export type {ident} = ({body}){extends};") } else { let extends = if extends.is_empty() { String::new() } else { format!(" extends {}", extends.join(", ")) }; - format!("export interface {ident}{extends} {{{fields}\n}}") + format!("export interface {ident}{extends} {body}") } } diff --git a/tasks/ast_tools/src/markers.rs b/tasks/ast_tools/src/markers.rs index e94325f665399..e58f7a1dcb28a 100644 --- a/tasks/ast_tools/src/markers.rs +++ b/tasks/ast_tools/src/markers.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use quote::ToTokens; use serde::Serialize; use syn::{ ext::IdentExt, @@ -7,7 +8,7 @@ use syn::{ parse2, punctuated::{self, Punctuated}, spanned::Spanned, - token, Attribute, Expr, Ident, LitStr, Meta, MetaNameValue, Token, + token, Attribute, Expr, Ident, LitStr, Meta, MetaNameValue, Path, Token, }; use crate::util::NormalizeError; @@ -99,6 +100,8 @@ impl From<&Ident> for CloneInAttribute { pub struct ESTreeStructAttribute { pub tag_mode: Option, pub always_flatten: bool, + pub via: Option, + pub add_ts: Option, } #[derive(Debug, Serialize, PartialEq, Eq)] @@ -112,6 +115,8 @@ impl Parse for ESTreeStructAttribute { fn parse(input: ParseStream) -> Result { let mut tag_mode = None; let mut always_flatten = false; + let mut via = None; + let mut add_ts = None; loop { let is_type = input.peek(Token![type]); @@ -149,6 +154,16 @@ impl Parse for ESTreeStructAttribute { "Duplicate tag mode in #[estree(...)]" ); } + "via" => { + input.parse::()?; + let value = input.parse::()?.to_token_stream().to_string(); + assert!(via.replace(value).is_none(), "Duplicate estree(via)"); + } + "add_ts" => { + input.parse::()?; + let value = input.parse::()?.value(); + assert!(add_ts.replace(value).is_none(), "Duplicate estree(add_ts)"); + } arg => panic!("Unsupported #[estree(...)] argument: {arg}"), } let comma = input.peek(Token![,]); @@ -158,7 +173,7 @@ impl Parse for ESTreeStructAttribute { break; } } - Ok(Self { tag_mode, always_flatten }) + Ok(Self { tag_mode, always_flatten, via, add_ts }) } }