From a6884e4f796fdbfcc7940ae3487f02dbb2ead8c3 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 7 Feb 2025 02:46:32 +0000 Subject: [PATCH] refactor(ast): simplify serializing literal types (#8937) Minimize the amount of hand-written serialization code for literal types (`BooleanLiteral` etc), mostly by using `#[estree(add_fields(...))]`. Also correct the TS type def for `RegExpLiteral`. --- crates/oxc_ast/src/ast/literal.rs | 62 ++++--- crates/oxc_ast/src/generated/derive_estree.rs | 60 +++++-- crates/oxc_ast/src/serialize.rs | 160 ++++++------------ npm/oxc-types/types.d.ts | 29 +--- 4 files changed, 127 insertions(+), 184 deletions(-) diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index e7433f000f00f..a85595527f068 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -20,7 +20,11 @@ use oxc_syntax::number::{BigintBase, NumberBase}; #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] -#[estree(rename = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "raw: string | null")] +#[estree( + rename = "Literal", + add_fields(raw = crate::serialize::boolean_literal_raw(self)), + add_ts = "raw: string | null", +)] pub struct BooleanLiteral { /// Node location in source code pub span: Span, @@ -34,7 +38,14 @@ pub struct BooleanLiteral { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] -#[estree(rename = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, raw: \"null\" | null")] +#[estree( + rename = "Literal", + add_fields( + value = (), + raw = crate::serialize::null_literal_raw(self), + ), + add_ts = "value: null, raw: \"null\" | null", +)] pub struct NullLiteral { /// Node location in source code pub span: Span, @@ -46,7 +57,7 @@ pub struct NullLiteral { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, ContentEq, GetSpan, GetSpanMut, ESTree)] -#[estree(rename = "Literal", via = crate::serialize::ESTreeLiteral)] +#[estree(rename = "Literal")] pub struct NumericLiteral<'a> { /// Node location in source code pub span: Span, @@ -69,7 +80,7 @@ pub struct NumericLiteral<'a> { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, ContentEq, GetSpan, GetSpanMut, ESTree)] -#[estree(rename = "Literal", via = crate::serialize::ESTreeLiteral)] +#[estree(rename = "Literal")] pub struct StringLiteral<'a> { /// Node location in source code pub span: Span, @@ -89,7 +100,14 @@ pub struct StringLiteral<'a> { #[ast(visit)] #[derive(Debug, Clone)] #[generate_derive(CloneIn, ContentEq, GetSpan, GetSpanMut, ESTree)] -#[estree(rename = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, bigint: string")] +#[estree( + rename = "Literal", + add_fields( + value = (), + bigint = crate::serialize::bigint_literal_bigint(self), + ), + add_ts = "value: null, bigint: string", +)] pub struct BigIntLiteral<'a> { /// Node location in source code pub span: Span, @@ -109,16 +127,15 @@ pub struct BigIntLiteral<'a> { #[derive(Debug)] #[generate_derive(CloneIn, ContentEq, GetSpan, GetSpanMut, ESTree)] #[estree( - rename = "Literal", - via = crate::serialize::ESTreeLiteral, - add_ts = "value: {} | null, regex: { pattern: string, flags: string }" + rename = "Literal", + add_fields(value = crate::serialize::EmptyObject), + add_ts = "value: {} | null", )] pub struct RegExpLiteral<'a> { /// Node location in source code pub span: Span, /// 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 /// @@ -136,8 +153,10 @@ pub struct RegExpLiteral<'a> { #[estree(no_type)] pub struct RegExp<'a> { /// The regex pattern between the slashes + #[estree(ts_type = "string")] pub pattern: RegExpPattern<'a>, /// Regex flags after the closing slash + #[estree(ts_type = "string")] pub flags: RegExpFlags, } @@ -147,6 +166,7 @@ pub struct RegExp<'a> { #[ast] #[derive(Debug)] #[generate_derive(CloneIn, ESTree)] +#[estree(custom_serialize, no_ts_def)] pub enum RegExpPattern<'a> { /// Unparsed pattern. Contains string slice of the pattern. /// Pattern was not parsed, so may be valid or invalid. @@ -204,28 +224,6 @@ bitflags! { /// Dummy type to communicate the content of `RegExpFlags` to `oxc_ast_tools`. #[ast(foreign = RegExpFlags)] #[generate_derive(ESTree)] -#[estree( - custom_serialize, - custom_ts_def = " - type RegExpFlags = { - /** Global flag */ - G: 1; - /** Ignore case flag */ - I: 2; - /** Multiline flag */ - M: 4; - /** DotAll flag */ - S: 8; - /** Unicode flag */ - U: 16; - /** Sticky flag */ - Y: 32; - /** Indices flag */ - D: 64; - /** Unicode sets flag */ - V: 128; - } - " -)] +#[estree(custom_serialize, no_ts_def)] #[expect(dead_code)] struct RegExpFlagsAlias(u8); diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index d6e7decda10bd..ad9409273219b 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -1886,37 +1886,75 @@ impl Serialize for ModuleExportName<'_> { impl Serialize for BooleanLiteral { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("value", &self.value)?; + map.serialize_entry("raw", &crate::serialize::boolean_literal_raw(self))?; + map.end() } } impl Serialize for NullLiteral { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("value", &())?; + map.serialize_entry("raw", &crate::serialize::null_literal_raw(self))?; + map.end() } } impl Serialize for NumericLiteral<'_> { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("value", &self.value)?; + map.serialize_entry("raw", &self.raw)?; + map.end() } } impl Serialize for StringLiteral<'_> { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("value", &self.value)?; + map.serialize_entry("raw", &self.raw)?; + map.end() } } impl Serialize for BigIntLiteral<'_> { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("raw", &self.raw)?; + map.serialize_entry("value", &())?; + map.serialize_entry("bigint", &crate::serialize::bigint_literal_bigint(self))?; + map.end() } } impl Serialize for RegExpLiteral<'_> { fn serialize(&self, serializer: S) -> Result { - crate::serialize::ESTreeLiteral::from(self).serialize(serializer) + let mut map = serializer.serialize_map(None)?; + map.serialize_entry("type", "Literal")?; + map.serialize_entry("start", &self.span.start)?; + map.serialize_entry("end", &self.span.end)?; + map.serialize_entry("regex", &self.regex)?; + map.serialize_entry("raw", &self.raw)?; + map.serialize_entry("value", &crate::serialize::EmptyObject)?; + map.end() } } @@ -1929,16 +1967,6 @@ impl Serialize for RegExp<'_> { } } -impl Serialize for RegExpPattern<'_> { - fn serialize(&self, serializer: S) -> Result { - match self { - RegExpPattern::Raw(it) => it.serialize(serializer), - RegExpPattern::Invalid(it) => it.serialize(serializer), - RegExpPattern::Pattern(it) => it.serialize(serializer), - } - } -} - impl Serialize for JSXElement<'_> { 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 8ed74e7bf4ff1..2ed2e63039b1f 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -2,130 +2,77 @@ use cow_utils::CowUtils; use num_bigint::BigInt; use num_traits::Num; use serde::{ - ser::{SerializeSeq, Serializer}, + ser::{SerializeMap, SerializeSeq, Serializer}, Serialize, }; use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec}; -use oxc_span::{Atom, Span}; +use oxc_span::Span; use oxc_syntax::number::BigintBase; -use crate::ast::{ - BigIntLiteral, BindingPatternKind, BooleanLiteral, Directive, Elision, 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: Option<&'a str>, - #[serde(skip_serializing_if = "Option::is_none")] - bigint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - regex: Option, -} - -impl From<&BooleanLiteral> for ESTreeLiteral<'_, bool> { - fn from(lit: &BooleanLiteral) -> Self { - let raw = if lit.span.is_unspanned() { - None - } else { - Some(if lit.value { "true" } else { "false" }) - }; +use crate::ast::*; - Self { span: lit.span, value: lit.value, raw, bigint: None, regex: None } - } -} +// -------------------- +// Literals +// -------------------- -impl From<&NullLiteral> for ESTreeLiteral<'_, ()> { - fn from(lit: &NullLiteral) -> Self { - let raw = if lit.span.is_unspanned() { None } else { Some("null") }; - Self { span: lit.span, value: (), raw, bigint: None, regex: None } +/// Get `raw` field of `BooleanLiteral`. +pub fn boolean_literal_raw(lit: &BooleanLiteral) -> Option<&str> { + if lit.span.is_unspanned() { + None + } else if lit.value { + Some("true") + } else { + Some("false") } } -impl<'a> From<&NumericLiteral<'a>> for ESTreeLiteral<'a, f64> { - fn from(lit: &NumericLiteral<'a>) -> Self { - Self { - span: lit.span, - value: lit.value, - raw: lit.raw.as_ref().map(Atom::as_str), - bigint: None, - regex: None, - } +/// Get `raw` field of `NullLiteral`. +pub fn null_literal_raw(lit: &NullLiteral) -> Option<&str> { + if lit.span.is_unspanned() { + None + } else { + Some("null") } } -impl<'a> From<&StringLiteral<'a>> for ESTreeLiteral<'a, &'a str> { - fn from(lit: &StringLiteral<'a>) -> Self { - Self { - span: lit.span, - value: lit.value.as_str(), - raw: lit.raw.as_ref().map(Atom::as_str), - bigint: None, - regex: None, - } - } +/// Get `bigint` field of `BigIntLiteral`. +pub fn bigint_literal_bigint(lit: &BigIntLiteral) -> String { + let src = &lit.raw.strip_suffix('n').unwrap().cow_replace('_', ""); + let src = match lit.base { + BigintBase::Decimal => src, + BigintBase::Binary | BigintBase::Octal | BigintBase::Hex => &src[2..], + }; + + let radix = match lit.base { + BigintBase::Decimal => 10, + BigintBase::Binary => 2, + BigintBase::Octal => 8, + BigintBase::Hex => 16, + }; + + BigInt::from_str_radix(src, radix).unwrap().to_string() } -impl<'a> From<&BigIntLiteral<'a>> for ESTreeLiteral<'a, ()> { - fn from(lit: &BigIntLiteral<'a>) -> Self { - let src = &lit.raw.strip_suffix('n').unwrap().cow_replace('_', ""); +/// A placeholder for `RegExpLiteral`'s `value` field. +pub struct EmptyObject; - let src = match lit.base { - BigintBase::Decimal => src, - BigintBase::Binary | BigintBase::Octal | BigintBase::Hex => &src[2..], - }; - let radix = match lit.base { - BigintBase::Decimal => 10, - BigintBase::Binary => 2, - BigintBase::Octal => 8, - BigintBase::Hex => 16, - }; - let bigint = BigInt::from_str_radix(src, radix).unwrap(); - - Self { - span: lit.span, - // BigInts can't be serialized to JSON - value: (), - raw: Some(lit.raw.as_str()), - bigint: Some(bigint.to_string()), - regex: None, - } +impl Serialize for EmptyObject { + fn serialize(&self, serializer: S) -> Result { + let map = serializer.serialize_map(None)?; + map.end() } } -#[derive(Serialize)] -pub struct SerRegExpValue { - pattern: String, - flags: String, +impl Serialize for RegExpFlags { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_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<&RegExpLiteral<'a>> for ESTreeLiteral<'a, Option> { - fn from(lit: &RegExpLiteral<'a>) -> Self { - Self { - span: lit.span, - raw: lit.raw.as_ref().map(Atom::as_str), - value: match &lit.regex.pattern { - RegExpPattern::Pattern(_) => Some(EmptyObject {}), - _ => None, - }, - bigint: None, - regex: Some(SerRegExpValue { - pattern: lit.regex.pattern.to_string(), - flags: lit.regex.flags.to_string(), - }), - } +impl Serialize for RegExpPattern<'_> { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) } } @@ -158,15 +105,6 @@ impl Program<'_> { } } -impl Serialize for RegExpFlags { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - /// Serialize `ArrayExpressionElement::Elision` variant as `null` in JSON impl Serialize for Elision { fn serialize(&self, serializer: S) -> Result diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index baf4e011a94f5..34a37341037b9 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -1033,36 +1033,15 @@ export interface BigIntLiteral extends Span { export interface RegExpLiteral extends Span { type: 'Literal'; + regex: RegExp; raw: string | null; value: {} | null; - regex: { pattern: string; flags: string }; } export interface RegExp { - pattern: RegExpPattern; - flags: RegExpFlags; -} - -export type RegExpPattern = string | string | Pattern; - -export type RegExpFlags = { - /** Global flag */ - G: 1; - /** Ignore case flag */ - I: 2; - /** Multiline flag */ - M: 4; - /** DotAll flag */ - S: 8; - /** Unicode flag */ - U: 16; - /** Sticky flag */ - Y: 32; - /** Indices flag */ - D: 64; - /** Unicode sets flag */ - V: 128; -}; + pattern: string; + flags: string; +} export interface JSXElement extends Span { type: 'JSXElement';