diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index 00d1f3f9b5a56..c9735c557112b 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -53,7 +53,9 @@ pub struct NumericLiteral<'a> { /// The value of the number, converted into base 10 pub value: f64, /// The number as it appears in source code - pub raw: &'a str, + /// + /// `None` when this ast node is not constructed from the parser. + pub raw: Option>, /// The base representation used by the literal in source code #[estree(skip)] pub base: NumberBase, @@ -114,7 +116,9 @@ pub struct RegExpLiteral<'a> { #[estree(skip)] pub regex: RegExp<'a>, /// The regular expression as it appears in source code - pub raw: &'a str, + /// + /// `None` when this ast node is not constructed from the parser. + pub raw: Option>, } /// A regular expression diff --git a/crates/oxc_ast/src/ast_builder_impl.rs b/crates/oxc_ast/src/ast_builder_impl.rs index 80aeb1087ba95..9d7a7fb6d6328 100644 --- a/crates/oxc_ast/src/ast_builder_impl.rs +++ b/crates/oxc_ast/src/ast_builder_impl.rs @@ -215,7 +215,7 @@ impl<'a> AstBuilder<'a> { /// `0` #[inline] pub fn number_0(self) -> Expression<'a> { - self.expression_numeric_literal(Span::default(), 0.0, "0", NumberBase::Decimal) + self.expression_numeric_literal(Span::default(), 0.0, None, NumberBase::Decimal) } /// `void 0` diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 31968f3f4d4d4..6baf3170e0aef 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -135,7 +135,7 @@ impl<'a> Expression<'a> { /// Determines whether the given numeral literal's raw value is exactly val pub fn is_specific_raw_number_literal(&self, val: &str) -> bool { - matches!(self, Self::NumericLiteral(lit) if lit.raw == val) + matches!(self, Self::NumericLiteral(lit) if lit.raw.as_ref().is_some_and(|raw| raw == val)) } /// Determines whether the given expr evaluate to `undefined` @@ -502,7 +502,7 @@ impl<'a> ComputedMemberExpression<'a> { { Some(lit.quasis[0].value.raw.clone()) } - Expression::RegExpLiteral(lit) => Some(Atom::from(lit.raw)), + Expression::RegExpLiteral(lit) => lit.raw.clone(), _ => None, } } diff --git a/crates/oxc_ast/src/ast_impl/literal.rs b/crates/oxc_ast/src/ast_impl/literal.rs index f842df38d2014..392bdacab7a2e 100644 --- a/crates/oxc_ast/src/ast_impl/literal.rs +++ b/crates/oxc_ast/src/ast_impl/literal.rs @@ -71,6 +71,15 @@ impl NumericLiteral<'_> { int32bit as i32 } } + + /// Return raw source code for `NumericLiteral`. + /// If `raw` is `None` (node is generated, not parsed from source), fallback to formatting `value`. + pub fn raw_str(&self) -> Cow { + match self.raw.as_ref() { + Some(raw) => Cow::Borrowed(raw), + None => Cow::Owned(format!("{}", self.value)), + } + } } impl ContentHash for NumericLiteral<'_> { @@ -82,7 +91,12 @@ impl ContentHash for NumericLiteral<'_> { impl fmt::Display for NumericLiteral<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.raw.fmt(f) + // We have 2 choices here: + // 1. Only use the `value` field. or + // 2. Use `raw` field if it's `Some`, otherwise fallback to using `value` field. + // For now, we take the 2nd approach, since `NumericLiteral::to_string` is only used in linter, + // where raw does matter. + self.raw_str().fmt(f) } } diff --git a/crates/oxc_ast/src/generated/ast_builder.rs b/crates/oxc_ast/src/generated/ast_builder.rs index 639d54a9d82f6..bd6064dcd80ed 100644 --- a/crates/oxc_ast/src/generated/ast_builder.rs +++ b/crates/oxc_ast/src/generated/ast_builder.rs @@ -80,17 +80,14 @@ impl<'a> AstBuilder<'a> { /// - raw: The number as it appears in source code /// - base: The base representation used by the literal in source code #[inline] - pub fn numeric_literal( + pub fn numeric_literal( self, span: Span, value: f64, - raw: S, + raw: Option>, base: NumberBase, - ) -> NumericLiteral<'a> - where - S: IntoIn<'a, &'a str>, - { - NumericLiteral { span, value, raw: raw.into_in(self.allocator), base } + ) -> NumericLiteral<'a> { + NumericLiteral { span, value, raw, base } } /// Build a [`NumericLiteral`], and store it in the memory arena. @@ -103,16 +100,13 @@ impl<'a> AstBuilder<'a> { /// - raw: The number as it appears in source code /// - base: The base representation used by the literal in source code #[inline] - pub fn alloc_numeric_literal( + pub fn alloc_numeric_literal( self, span: Span, value: f64, - raw: S, + raw: Option>, base: NumberBase, - ) -> Box<'a, NumericLiteral<'a>> - where - S: IntoIn<'a, &'a str>, - { + ) -> Box<'a, NumericLiteral<'a>> { Box::new_in(self.numeric_literal(span, value, raw, base), self.allocator) } @@ -199,11 +193,13 @@ impl<'a> AstBuilder<'a> { /// - 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, regex: RegExp<'a>, raw: S) -> RegExpLiteral<'a> - where - S: IntoIn<'a, &'a str>, - { - RegExpLiteral { span, regex, raw: raw.into_in(self.allocator) } + pub fn reg_exp_literal( + self, + span: Span, + regex: RegExp<'a>, + raw: Option>, + ) -> RegExpLiteral<'a> { + RegExpLiteral { span, regex, raw } } /// Build a [`RegExpLiteral`], and store it in the memory arena. @@ -215,15 +211,12 @@ impl<'a> AstBuilder<'a> { /// - 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, regex: RegExp<'a>, - raw: S, - ) -> Box<'a, RegExpLiteral<'a>> - where - S: IntoIn<'a, &'a str>, - { + raw: Option>, + ) -> Box<'a, RegExpLiteral<'a>> { Box::new_in(self.reg_exp_literal(span, regex, raw), self.allocator) } @@ -413,16 +406,13 @@ impl<'a> AstBuilder<'a> { /// - raw: The number as it appears in source code /// - base: The base representation used by the literal in source code #[inline] - pub fn expression_numeric_literal( + pub fn expression_numeric_literal( self, span: Span, value: f64, - raw: S, + raw: Option>, base: NumberBase, - ) -> Expression<'a> - where - S: IntoIn<'a, &'a str>, - { + ) -> Expression<'a> { Expression::NumericLiteral(self.alloc(self.numeric_literal(span, value, raw, base))) } @@ -456,15 +446,12 @@ impl<'a> AstBuilder<'a> { /// - 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, regex: RegExp<'a>, - raw: S, - ) -> Expression<'a> - where - S: IntoIn<'a, &'a str>, - { + raw: Option>, + ) -> Expression<'a> { Expression::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) } @@ -7911,16 +7898,13 @@ impl<'a> AstBuilder<'a> { /// - raw: The number as it appears in source code /// - base: The base representation used by the literal in source code #[inline] - pub fn ts_literal_numeric_literal( + pub fn ts_literal_numeric_literal( self, span: Span, value: f64, - raw: S, + raw: Option>, base: NumberBase, - ) -> TSLiteral<'a> - where - S: IntoIn<'a, &'a str>, - { + ) -> TSLiteral<'a> { TSLiteral::NumericLiteral(self.alloc(self.numeric_literal(span, value, raw, base))) } @@ -7954,15 +7938,12 @@ impl<'a> AstBuilder<'a> { /// - 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, regex: RegExp<'a>, - raw: S, - ) -> TSLiteral<'a> - where - S: IntoIn<'a, &'a str>, - { + raw: Option>, + ) -> TSLiteral<'a> { TSLiteral::RegExpLiteral(self.alloc(self.reg_exp_literal(span, regex, raw))) } diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index 9e67307e357b8..79afdbc1b550e 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -52,7 +52,7 @@ impl<'a> From<&'a NumericLiteral<'a>> for ESTreeLiteral<'a, f64> { Self { span: value.span, value: value.value, - raw: Some(value.raw), + raw: value.raw.as_ref().map(oxc_span::Atom::as_str), bigint: None, regex: None, } @@ -61,7 +61,13 @@ impl<'a> From<&'a NumericLiteral<'a>> for ESTreeLiteral<'a, f64> { impl<'a> From<&'a StringLiteral<'a>> for ESTreeLiteral<'a, &'a str> { fn from(value: &'a StringLiteral) -> Self { - Self { span: value.span, value: &value.value, raw: None, bigint: None, regex: None } + Self { + span: value.span, + value: &value.value, + raw: value.raw.as_ref().map(oxc_span::Atom::as_str), + bigint: None, + regex: None, + } } } @@ -107,7 +113,7 @@ impl<'a> From<&'a RegExpLiteral<'a>> for ESTreeLiteral<'a, Option> fn from(value: &'a RegExpLiteral) -> Self { Self { span: value.span, - raw: Some(value.raw), + raw: value.raw.as_ref().map(oxc_span::Atom::as_str), value: match &value.regex.pattern { RegExpPattern::Pattern(_) => Some(EmptyObject {}), _ => None, diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 787c1532b51fa..44db97b044b28 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1155,7 +1155,7 @@ impl GenExpr for NumericLiteral<'_> { p.add_source_mapping(self.span); let value = self.value; if ctx.contains(Context::TYPESCRIPT) { - p.print_str(self.raw); + p.print_str(&self.raw_str()); } else if value.is_nan() { p.print_space_before_identifier(); p.print_str("NaN"); diff --git a/crates/oxc_isolated_declarations/src/enum.rs b/crates/oxc_isolated_declarations/src/enum.rs index 70ef76b02354b..d9a844d459bd3 100644 --- a/crates/oxc_isolated_declarations/src/enum.rs +++ b/crates/oxc_isolated_declarations/src/enum.rs @@ -66,7 +66,7 @@ impl<'a> IsolatedDeclarations<'a> { self.ast.expression_numeric_literal( SPAN, value, - value.to_string(), + None, NumberBase::Decimal, ) }; diff --git a/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs b/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs index 1c066c39c6190..62c6160717fd4 100644 --- a/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs +++ b/crates/oxc_linter/src/rules/eslint/no_dupe_keys.rs @@ -101,7 +101,7 @@ fn prop_key_name<'a>(key: &PropertyKey<'a>, ctx: &LintContext<'a>) -> &'a str { PropertyKey::StaticIdentifier(ident) => ident.name.as_str(), PropertyKey::PrivateIdentifier(ident) => ident.name.as_str(), PropertyKey::StringLiteral(lit) => lit.value.as_str(), - PropertyKey::NumericLiteral(lit) => lit.raw, + PropertyKey::NumericLiteral(lit) => lit.raw.as_ref().unwrap().as_str(), _ => ctx.source_range(key.span()), } } diff --git a/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs b/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs index f106db5272066..70cd1a40dfee4 100644 --- a/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs +++ b/crates/oxc_linter/src/rules/eslint/no_loss_of_precision.rs @@ -184,7 +184,7 @@ impl<'a> RawNum<'a> { impl NoLossOfPrecision { fn not_base_ten_loses_precision(node: &'_ NumericLiteral) -> bool { - let raw = node.raw.cow_replace('_', ""); + let raw = node.raw.as_ref().unwrap().as_str().cow_replace('_', ""); let raw = raw.cow_to_uppercase(); #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] // AST always store number as f64, need a cast to format in bin/oct/hex @@ -200,7 +200,7 @@ impl NoLossOfPrecision { } fn base_ten_loses_precision(node: &'_ NumericLiteral) -> bool { - let raw = node.raw.cow_replace('_', ""); + let raw = node.raw.as_ref().unwrap().as_str().cow_replace('_', ""); let Some(raw) = Self::normalize(&raw) else { return true; }; diff --git a/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs b/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs index 042913041ca93..0b0b37dbcda04 100644 --- a/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs +++ b/crates/oxc_linter/src/rules/eslint/no_magic_numbers.rs @@ -247,13 +247,13 @@ impl InternConfig<'_> { InternConfig { node: parent_node, value: NoMagicNumbersNumber::Float(0.0 - numeric.value), - raw: format!("-{}", numeric.raw), + raw: format!("-{}", numeric.raw.as_ref().unwrap()), } } else { InternConfig { node: if is_unary { parent_node } else { node }, value: NoMagicNumbersNumber::Float(numeric.value), - raw: numeric.raw.into(), + raw: numeric.raw.as_ref().unwrap().as_str().into(), } } } diff --git a/crates/oxc_linter/src/rules/eslint/prefer_numeric_literals.rs b/crates/oxc_linter/src/rules/eslint/prefer_numeric_literals.rs index c618f93902ff9..42220705ec3c2 100644 --- a/crates/oxc_linter/src/rules/eslint/prefer_numeric_literals.rs +++ b/crates/oxc_linter/src/rules/eslint/prefer_numeric_literals.rs @@ -136,11 +136,12 @@ fn check_arguments<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) { return; }; - if let Some(name_prefix_set) = RADIX_MAP.get(numeric_lit.raw) { + let raw = numeric_lit.raw.as_ref().unwrap().as_str(); + if let Some(name_prefix_set) = RADIX_MAP.get(raw) { let name = name_prefix_set.index(0).unwrap(); let prefix = name_prefix_set.index(1).unwrap(); - match is_fixable(call_expr, numeric_lit.raw) { + match is_fixable(call_expr, raw) { Ok(argument) => { ctx.diagnostic_with_fix( prefer_numeric_literals_diagnostic(call_expr.span, name), diff --git a/crates/oxc_linter/src/rules/typescript/no_duplicate_enum_values.rs b/crates/oxc_linter/src/rules/typescript/no_duplicate_enum_values.rs index 8b63bd633e4d9..b08105cbc9889 100644 --- a/crates/oxc_linter/src/rules/typescript/no_duplicate_enum_values.rs +++ b/crates/oxc_linter/src/rules/typescript/no_duplicate_enum_values.rs @@ -105,7 +105,7 @@ impl Rule for NoDuplicateEnumValues { ctx.diagnostic(no_duplicate_enum_values_diagnostic( *old_span, enum_member, - num.raw, + num.raw.as_ref().unwrap().as_str(), )); } else { seen_number_values.push((num.value, num.span)); diff --git a/crates/oxc_linter/src/rules/unicorn/consistent_existence_index_check.rs b/crates/oxc_linter/src/rules/unicorn/consistent_existence_index_check.rs index 069d2d8429927..d05d4e6d957f1 100644 --- a/crates/oxc_linter/src/rules/unicorn/consistent_existence_index_check.rs +++ b/crates/oxc_linter/src/rules/unicorn/consistent_existence_index_check.rs @@ -228,7 +228,7 @@ fn is_negative_one(expression: &Expression) -> bool { if let Expression::NumericLiteral(value) = &unary_expression.argument.get_inner_expression() { - return value.raw == "1"; + return value.raw.as_ref().unwrap() == "1"; } } } diff --git a/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs b/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs index cb475322c3b28..4c178d1aad6fb 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_zero_fractions.rs @@ -56,10 +56,11 @@ impl Rule for NoZeroFractions { return; }; - let Some((fmt, is_dangling_dot)) = format_raw(number_literal.raw) else { + let raw = number_literal.raw.as_ref().unwrap().as_str(); + let Some((fmt, is_dangling_dot)) = format_raw(raw) else { return; }; - if fmt == number_literal.raw { + if fmt == raw { return; }; diff --git a/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs b/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs index ca583ca429eee..979b33d27fa35 100644 --- a/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs +++ b/crates/oxc_linter/src/rules/unicorn/number_literal_case.rs @@ -80,7 +80,7 @@ declare_oxc_lint!( impl Rule for NumberLiteralCase { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { let (raw_literal, raw_span) = match node.kind() { - AstKind::NumericLiteral(number) => (number.raw, number.span), + AstKind::NumericLiteral(number) => (number.raw.as_ref().unwrap().as_str(), number.span), AstKind::BigIntLiteral(number) => { let span = number.span; (span.source_text(ctx.source_text()), span) diff --git a/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs b/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs index 7c00a7d426b7b..19a2fc1573032 100644 --- a/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs +++ b/crates/oxc_linter/src/rules/unicorn/numeric_separators_style.rs @@ -86,13 +86,14 @@ impl Rule for NumericSeparatorsStyle { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match node.kind() { AstKind::NumericLiteral(number) => { - if self.only_if_contains_separator && !number.raw.contains('_') { + let raw = number.raw.as_ref().unwrap().as_str(); + if self.only_if_contains_separator && !raw.contains('_') { return; } let formatted = self.format_number(number); - if formatted != number.raw { + if formatted != raw { ctx.diagnostic_with_fix( numeric_separators_style_diagnostic(number.span), |fixer| fixer.replace(number.span, formatted), @@ -151,11 +152,12 @@ impl NumericSeparatorsStyle { fn format_number(&self, number: &NumericLiteral) -> String { use oxc_syntax::number::NumberBase; + let raw = number.raw.as_ref().unwrap(); match number.base { - NumberBase::Binary => self.format_binary(number.raw), - NumberBase::Decimal | NumberBase::Float => self.format_decimal(number.raw), - NumberBase::Hex => self.format_hex(number.raw), - NumberBase::Octal => self.format_octal(number.raw), + NumberBase::Binary => self.format_binary(raw), + NumberBase::Decimal | NumberBase::Float => self.format_decimal(raw), + NumberBase::Hex => self.format_hex(raw), + NumberBase::Octal => self.format_octal(raw), } } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_array_flat_map.rs b/crates/oxc_linter/src/rules/unicorn/prefer_array_flat_map.rs index b2dd0331a31ae..43f4c32059857 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_array_flat_map.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_array_flat_map.rs @@ -63,7 +63,7 @@ impl Rule for PreferArrayFlatMap { if let Some(first_arg) = flat_call_expr.arguments.first() { if let Argument::NumericLiteral(number_lit) = first_arg { - if number_lit.raw != "1" { + if number_lit.raw.as_ref().unwrap() != "1" { return; } } else { diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_array_some.rs b/crates/oxc_linter/src/rules/unicorn/prefer_array_some.rs index 363552d6120ad..03162004c8d28 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_array_some.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_array_some.rs @@ -178,7 +178,7 @@ impl Rule for PreferArraySome { return; }; - if right_num_lit.raw == "0" + if right_num_lit.raw.as_ref().unwrap().as_str() == "0" && is_method_call( left_call_expr, None, @@ -207,7 +207,7 @@ impl Rule for PreferArraySome { return; }; - if right_num_lit.raw != "0" { + if right_num_lit.raw.as_ref().unwrap() != "0" { return; } diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_includes.rs b/crates/oxc_linter/src/rules/unicorn/prefer_includes.rs index 59cacc8f46ccd..54ca5557df0bb 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_includes.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_includes.rs @@ -85,7 +85,7 @@ impl Rule for PreferIncludes { return; }; - if num_lit.raw != "0" { + if num_lit.raw.as_ref().unwrap() != "0" { return; } ctx.diagnostic(prefer_includes_diagnostic( @@ -108,7 +108,7 @@ fn is_negative_one(expr: &Expression) -> bool { return false; }; - num_lit.raw == "1" + num_lit.raw.as_ref().unwrap() == "1" } #[test] diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index b2b770379b5d3..8d48d9e58d776 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -287,7 +287,7 @@ pub fn is_same_member_expression( // x[/regex/] === x['/regex/'] (Expression::StringLiteral(string_lit), Expression::RegExpLiteral(regex_lit)) | (Expression::RegExpLiteral(regex_lit), Expression::StringLiteral(string_lit)) => { - if string_lit.value != regex_lit.raw { + if string_lit.value != regex_lit.raw.as_ref().unwrap() { return false; } } @@ -296,7 +296,7 @@ pub fn is_same_member_expression( (Expression::TemplateLiteral(template_lit), Expression::RegExpLiteral(regex_lit)) | (Expression::RegExpLiteral(regex_lit), Expression::TemplateLiteral(template_lit)) => { if !(template_lit.is_no_substitution_template() - && template_lit.quasi().unwrap() == regex_lit.raw) + && template_lit.quasi().unwrap() == regex_lit.raw.as_ref().unwrap()) { return false; } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 732680df010ae..7213b31fad2fd 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -5,7 +5,7 @@ use oxc_ecmascript::{ }; use oxc_span::{GetSpan, SPAN}; use oxc_syntax::{ - number::{NumberBase, ToJsString}, + number::NumberBase, operator::{BinaryOperator, LogicalOperator}, }; use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx}; @@ -310,7 +310,7 @@ impl<'a, 'b> PeepholeFoldConstants { let number_literal_expr = ctx.ast.expression_numeric_literal( right_expr.span(), num, - num.to_js_string(), + None, if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float }, ); @@ -333,7 +333,7 @@ impl<'a, 'b> PeepholeFoldConstants { let number_literal_expr = ctx.ast.expression_numeric_literal( left_expr.span(), num, - num.to_js_string(), + None, if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float }, ); diff --git a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs index 5da0f59adca51..dde3a40b3f821 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_replace_known_methods.rs @@ -124,12 +124,7 @@ impl PeepholeReplaceKnownMethods { }; #[expect(clippy::cast_precision_loss)] - Some(ctx.ast.expression_numeric_literal( - span, - result as f64, - result.to_string(), - NumberBase::Decimal, - )) + Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) } fn try_fold_string_substring_or_slice<'a>( @@ -219,12 +214,7 @@ impl PeepholeReplaceKnownMethods { let result = string_lit.value.as_str().char_code_at(Some(char_at_index))?; #[expect(clippy::cast_lossless)] - Some(ctx.ast.expression_numeric_literal( - span, - result as f64, - result.to_string(), - NumberBase::Decimal, - )) + Some(ctx.ast.expression_numeric_literal(span, result as f64, None, NumberBase::Decimal)) } fn try_fold_string_replace_or_string_replace_all<'a>( span: Span, diff --git a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs index 701a15c4b3116..5e44eb731a6a6 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_substitute_alternate_syntax.rs @@ -230,7 +230,7 @@ impl<'a, 'b> PeepholeSubstituteAlternateSyntax { let num = ctx.ast.expression_numeric_literal( SPAN, if lit.value ^ no_unary { 0.0 } else { 1.0 }, - if lit.value ^ no_unary { "0" } else { "1" }, + None, NumberBase::Decimal, ); *expr = if no_unary { diff --git a/crates/oxc_minifier/src/node_util/mod.rs b/crates/oxc_minifier/src/node_util/mod.rs index 5653a82238f40..db2c93098cd1a 100644 --- a/crates/oxc_minifier/src/node_util/mod.rs +++ b/crates/oxc_minifier/src/node_util/mod.rs @@ -35,7 +35,7 @@ impl<'a> Ctx<'a, '_> { ConstantValue::Number(n) => { let number_base = if is_exact_int64(n) { NumberBase::Decimal } else { NumberBase::Float }; - self.ast.expression_numeric_literal(span, n, "", number_base) + self.ast.expression_numeric_literal(span, n, None, number_base) } ConstantValue::BigInt(n) => { self.ast.expression_big_int_literal(span, n.to_string() + "n", BigintBase::Decimal) diff --git a/crates/oxc_minifier/tests/ecmascript/array_join.rs b/crates/oxc_minifier/tests/ecmascript/array_join.rs index 4f42f351f6d3e..a8da47a089167 100644 --- a/crates/oxc_minifier/tests/ecmascript/array_join.rs +++ b/crates/oxc_minifier/tests/ecmascript/array_join.rs @@ -13,7 +13,7 @@ fn test() { elements.push(ArrayExpressionElement::NumericLiteral(ast.alloc(ast.numeric_literal( SPAN, 42f64, - "42", + None, NumberBase::Decimal, )))); elements.push(ArrayExpressionElement::StringLiteral( diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 88101e35b9de6..b3e8ab53009ca 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -321,7 +321,7 @@ impl<'a> ParserImpl<'a> { _ => return Err(self.unexpected()), }; self.bump_any(); - Ok(self.ast.numeric_literal(self.end_span(span), value, src, base)) + Ok(self.ast.numeric_literal(self.end_span(span), value, Some(Atom::from(src)), base)) } pub(crate) fn parse_literal_bigint(&mut self) -> Result> { @@ -364,7 +364,11 @@ impl<'a> ParserImpl<'a> { pat.map_or_else(|| RegExpPattern::Invalid(pattern_text), RegExpPattern::Pattern) }, ); - Ok(self.ast.reg_exp_literal(self.end_span(span), RegExp { pattern, flags }, raw)) + Ok(self.ast.reg_exp_literal( + self.end_span(span), + RegExp { pattern, flags }, + Some(Atom::from(raw)), + )) } fn parse_regex_pattern( diff --git a/crates/oxc_prettier/src/format/mod.rs b/crates/oxc_prettier/src/format/mod.rs index efe70237c1032..9f1bdcf63babd 100644 --- a/crates/oxc_prettier/src/format/mod.rs +++ b/crates/oxc_prettier/src/format/mod.rs @@ -2153,7 +2153,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { if need_quote { dynamic_text!( p, - string::print_string(p, literal.raw, p.options.single_quote) + string::print_string(p, &literal.raw_str(), p.options.single_quote) ) } else { literal.format(p) diff --git a/crates/oxc_semantic/src/checker/javascript.rs b/crates/oxc_semantic/src/checker/javascript.rs index c64465fe8f6e6..f121440033d39 100644 --- a/crates/oxc_semantic/src/checker/javascript.rs +++ b/crates/oxc_semantic/src/checker/javascript.rs @@ -221,11 +221,13 @@ pub fn check_number_literal(lit: &NumericLiteral, ctx: &SemanticBuilder<'_>) { // NumericLiteral :: legacy_octalIntegerLiteral // DecimalIntegerLiteral :: NonOctalDecimalIntegerLiteral // * It is a Syntax Error if the source text matched by this production is strict mode code. - fn leading_zero(s: &str) -> bool { - let mut chars = s.bytes(); - if let Some(first) = chars.next() { - if let Some(second) = chars.next() { - return first == b'0' && second.is_ascii_digit(); + fn leading_zero(s: Option<&Atom>) -> bool { + if let Some(s) = s { + let mut chars = s.bytes(); + if let Some(first) = chars.next() { + if let Some(second) = chars.next() { + return first == b'0' && second.is_ascii_digit(); + } } } false @@ -233,10 +235,10 @@ pub fn check_number_literal(lit: &NumericLiteral, ctx: &SemanticBuilder<'_>) { if ctx.strict_mode() { match lit.base { - NumberBase::Octal if leading_zero(lit.raw) => { + NumberBase::Octal if leading_zero(lit.raw.as_ref()) => { ctx.error(legacy_octal(lit.span)); } - NumberBase::Decimal | NumberBase::Float if leading_zero(lit.raw) => { + NumberBase::Decimal | NumberBase::Float if leading_zero(lit.raw.as_ref()) => { ctx.error(leading_zero_decimal(lit.span)); } _ => {} diff --git a/crates/oxc_transformer/src/jsx/jsx_source.rs b/crates/oxc_transformer/src/jsx/jsx_source.rs index 40ad88eca6627..92bed1f3c122d 100644 --- a/crates/oxc_transformer/src/jsx/jsx_source.rs +++ b/crates/oxc_transformer/src/jsx/jsx_source.rs @@ -154,24 +154,16 @@ impl<'a, 'ctx> JsxSource<'a, 'ctx> { let line_number = { let key = ctx.ast.property_key_identifier_name(SPAN, "lineNumber"); - let value = ctx.ast.expression_numeric_literal( - SPAN, - line as f64, - line.to_string(), - NumberBase::Decimal, - ); + let value = + ctx.ast.expression_numeric_literal(SPAN, line as f64, None, NumberBase::Decimal); ctx.ast .object_property_kind_object_property(SPAN, kind, key, value, false, false, false) }; let column_number = { let key = ctx.ast.property_key_identifier_name(SPAN, "columnNumber"); - let value = ctx.ast.expression_numeric_literal( - SPAN, - column as f64, - column.to_string(), - NumberBase::Decimal, - ); + let value = + ctx.ast.expression_numeric_literal(SPAN, column as f64, None, NumberBase::Decimal); ctx.ast .object_property_kind_object_property(SPAN, kind, key, value, false, false, false) }; diff --git a/crates/oxc_transformer/src/typescript/enum.rs b/crates/oxc_transformer/src/typescript/enum.rs index cf32dc363a1a0..7733f22b749d1 100644 --- a/crates/oxc_transformer/src/typescript/enum.rs +++ b/crates/oxc_transformer/src/typescript/enum.rs @@ -306,7 +306,7 @@ impl<'a> TypeScriptEnum<'a> { } fn get_number_literal_expression(value: f64, ctx: &TraverseCtx<'a>) -> Expression<'a> { - ctx.ast.expression_numeric_literal(SPAN, value, value.to_string(), NumberBase::Decimal) + ctx.ast.expression_numeric_literal(SPAN, value, None, NumberBase::Decimal) } fn get_initializer_expr(value: f64, ctx: &TraverseCtx<'a>) -> Expression<'a> { diff --git a/crates/oxc_traverse/src/ast_operations/gather_node_parts.rs b/crates/oxc_traverse/src/ast_operations/gather_node_parts.rs index b7725369ddff4..83036b28a37a2 100644 --- a/crates/oxc_traverse/src/ast_operations/gather_node_parts.rs +++ b/crates/oxc_traverse/src/ast_operations/gather_node_parts.rs @@ -466,7 +466,7 @@ impl<'a> GatherNodeParts<'a> for StringLiteral<'a> { impl<'a> GatherNodeParts<'a> for NumericLiteral<'a> { fn gather(&self, f: &mut F) { - f(self.raw); + f(&self.raw_str()); } } diff --git a/npm/oxc-types/types.d.ts b/npm/oxc-types/types.d.ts index 0cbdcc5eef497..7227b7fc22941 100644 --- a/npm/oxc-types/types.d.ts +++ b/npm/oxc-types/types.d.ts @@ -16,7 +16,7 @@ export interface NullLiteral extends Span { export interface NumericLiteral extends Span { type: 'Literal'; value: number; - raw: string; + raw: string | null; } export interface StringLiteral extends Span { @@ -34,7 +34,7 @@ export interface BigIntLiteral extends Span { export interface RegExpLiteral extends Span { type: 'Literal'; - raw: string; + raw: string | null; value: {} | null; regex: { pattern: string; flags: string }; }