mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(estree): ESTree compatibility for all literals (#7152)
Adds some new estree macro directives: - `#[estree(via = foo::Foo)`: Uses From to convert this struct to foo::Foo before serialization - `#[estree(add_ts = "foo: string")]`: Adds additional fields to the typescript definitions Used these to make all different literals estree-compatible.
This commit is contained in:
parent
dc0215c906
commit
9d6cc9d3af
15 changed files with 206 additions and 118 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
///
|
||||
/// <https://tc39.es/ecma262/#sec-literals-string-literals>
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ const _: () = {
|
|||
assert!(offset_of!(BigIntLiteral, raw) == 8usize);
|
||||
assert!(offset_of!(BigIntLiteral, base) == 24usize);
|
||||
|
||||
assert!(size_of::<RegExpLiteral>() == 40usize);
|
||||
assert!(size_of::<RegExpLiteral>() == 56usize);
|
||||
assert!(align_of::<RegExpLiteral>() == 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::<RegExp>() == 32usize);
|
||||
assert!(align_of::<RegExp>() == 8usize);
|
||||
|
|
@ -45,9 +45,6 @@ const _: () = {
|
|||
assert!(size_of::<RegExpPattern>() == 24usize);
|
||||
assert!(align_of::<RegExpPattern>() == 8usize);
|
||||
|
||||
assert!(size_of::<EmptyObject>() == 0usize);
|
||||
assert!(align_of::<EmptyObject>() == 1usize);
|
||||
|
||||
assert!(size_of::<StringLiteral>() == 24usize);
|
||||
assert!(align_of::<StringLiteral>() == 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::<RegExpLiteral>() == 24usize);
|
||||
assert!(size_of::<RegExpLiteral>() == 32usize);
|
||||
assert!(align_of::<RegExpLiteral>() == 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::<RegExp>() == 16usize);
|
||||
assert!(align_of::<RegExp>() == 4usize);
|
||||
|
|
@ -1605,9 +1602,6 @@ const _: () = {
|
|||
assert!(size_of::<RegExpPattern>() == 12usize);
|
||||
assert!(align_of::<RegExpPattern>() == 4usize);
|
||||
|
||||
assert!(size_of::<EmptyObject>() == 0usize);
|
||||
assert!(align_of::<EmptyObject>() == 1usize);
|
||||
|
||||
assert!(size_of::<StringLiteral>() == 16usize);
|
||||
assert!(align_of::<StringLiteral>() == 4usize);
|
||||
assert!(offset_of!(StringLiteral, span) == 0usize);
|
||||
|
|
|
|||
|
|
@ -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<S>(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<S>(
|
||||
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<S>(
|
||||
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<S>(
|
||||
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`]
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ impl<'a> ContentHash for BigIntLiteral<'a> {
|
|||
|
||||
impl<'a> ContentHash for RegExpLiteral<'a> {
|
||||
fn content_hash<H: Hasher>(&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<H: Hasher>(&self, _: &mut H) {}
|
||||
}
|
||||
|
||||
impl<'a> ContentHash for StringLiteral<'a> {
|
||||
fn content_hash<H: Hasher>(&self, state: &mut H) {
|
||||
ContentHash::content_hash(&self.value, state);
|
||||
|
|
|
|||
|
|
@ -15,52 +15,31 @@ use crate::ast::ts::*;
|
|||
|
||||
impl Serialize for BooleanLiteral {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for StringLiteral<'a> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
regex: Option<SerRegExpValue>,
|
||||
}
|
||||
|
||||
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<EmptyObject>> {
|
||||
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`
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
23
npm/oxc-types/types.d.ts
vendored
23
npm/oxc-types/types.d.ts
vendored
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -101,17 +101,23 @@ fn typescript_struct(def: &StructDef, always_flatten_structs: &FxHashSet<TypeId>
|
|||
|
||||
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}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ESTreeStructTagMode>,
|
||||
pub always_flatten: bool,
|
||||
pub via: Option<String>,
|
||||
pub add_ts: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
|
|
@ -112,6 +115,8 @@ impl Parse for ESTreeStructAttribute {
|
|||
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||
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::<Token![=]>()?;
|
||||
let value = input.parse::<Path>()?.to_token_stream().to_string();
|
||||
assert!(via.replace(value).is_none(), "Duplicate estree(via)");
|
||||
}
|
||||
"add_ts" => {
|
||||
input.parse::<Token![=]>()?;
|
||||
let value = input.parse::<LitStr>()?.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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue