From a1b2d83ea9c1157cf71a4b88857918d90074f0e6 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sat, 22 Jun 2024 16:58:24 +0800 Subject: [PATCH] chore(ast): move ast implementations to ast_impl (#3821) Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com> --- crates/oxc_ast/src/ast/js.rs | 1534 +----------------------- crates/oxc_ast/src/ast/literal.rs | 177 +-- crates/oxc_ast/src/ast_impl/js.rs | 1464 ++++++++++++++++++++++ crates/oxc_ast/src/ast_impl/literal.rs | 165 +++ crates/oxc_ast/src/ast_impl/mod.rs | 2 + crates/oxc_ast/src/lib.rs | 1 + crates/oxc_traverse/src/traverse.rs | 12 +- crates/oxc_traverse/src/walk.rs | 60 +- 8 files changed, 1721 insertions(+), 1694 deletions(-) create mode 100644 crates/oxc_ast/src/ast_impl/js.rs create mode 100644 crates/oxc_ast/src/ast_impl/literal.rs create mode 100644 crates/oxc_ast/src/ast_impl/mod.rs diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 4e79697f33900..e7ad53a3460f7 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1,46 +1,28 @@ -// NB: `#[visited_node]` attribute on AST nodes does not do anything to the code in this file. -// It is purely a marker for codegen used in `oxc_traverse`. See docs in that crate. - // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] -use std::{cell::Cell, fmt, hash::Hash}; +use crate::ast::*; +use std::cell::Cell; use oxc_allocator::{Box, Vec}; use oxc_ast_macros::visited_node; -use oxc_span::{Atom, CompactStr, SourceType, Span}; +use oxc_span::{Atom, SourceType, Span}; use oxc_syntax::{ operator::{ AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator, }, reference::{ReferenceFlag, ReferenceId}, - scope::{ScopeFlags, ScopeId}, + scope::ScopeId, symbol::SymbolId, }; + +use super::macros::inherit_variants; + #[cfg(feature = "serialize")] use serde::Serialize; #[cfg(feature = "serialize")] use tsify::Tsify; -use super::{inherit_variants, jsx::*, literal::*, ts::*}; - -#[cfg(feature = "serialize")] -#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] -const TS_APPEND_CONTENT: &'static str = r#" -export interface BindingIdentifier extends Span { type: "Identifier", name: Atom } -export interface IdentifierReference extends Span { type: "Identifier", name: Atom } -export interface IdentifierName extends Span { type: "Identifier", name: Atom } -export interface LabelIdentifier extends Span { type: "Identifier", name: Atom } -export interface AssignmentTargetRest extends Span { type: "RestElement", argument: AssignmentTarget } -export interface BindingRestElement extends Span { type: "RestElement", argument: BindingPattern } -export interface FormalParameterRest extends Span { - type: "RestElement", - argument: BindingPatternKind, - typeAnnotation?: TSTypeAnnotation, - optional: boolean, -} -"#; - #[visited_node( scope(ScopeFlags::Top), strict_if(self.source_type.is_strict() || self.directives.iter().any(Directive::is_use_strict)) @@ -58,37 +40,6 @@ pub struct Program<'a> { pub scope_id: Cell>, } -impl<'a> Program<'a> { - pub fn new( - span: Span, - source_type: SourceType, - directives: Vec<'a, Directive<'a>>, - hashbang: Option>, - body: Vec<'a, Statement<'a>>, - ) -> Self { - Self { span, source_type, directives, hashbang, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for Program<'a> { - fn hash(&self, state: &mut H) { - self.source_type.hash(state); - self.directives.hash(state); - self.hashbang.hash(state); - self.body.hash(state); - } -} - -impl<'a> Program<'a> { - pub fn is_empty(&self) -> bool { - self.body.is_empty() && self.directives.is_empty() - } - - pub fn is_strict(&self) -> bool { - self.source_type.is_strict() || self.directives.iter().any(Directive::is_use_strict) - } -} - inherit_variants! { /// Expression /// @@ -202,223 +153,6 @@ macro_rules! match_expression { } pub use match_expression; -impl<'a> Expression<'a> { - pub fn is_typescript_syntax(&self) -> bool { - matches!( - self, - Self::TSAsExpression(_) - | Self::TSSatisfiesExpression(_) - | Self::TSTypeAssertion(_) - | Self::TSNonNullExpression(_) - | Self::TSInstantiationExpression(_) - ) - } - - pub fn is_primary_expression(&self) -> bool { - self.is_literal() - || matches!( - self, - Self::Identifier(_) - | Self::ThisExpression(_) - | Self::FunctionExpression(_) - | Self::ClassExpression(_) - | Self::ParenthesizedExpression(_) - | Self::ArrayExpression(_) - | Self::ObjectExpression(_) - ) - } - - pub fn is_literal(&self) -> bool { - // Note: TemplateLiteral is not `Literal` - matches!( - self, - Self::BooleanLiteral(_) - | Self::NullLiteral(_) - | Self::NumericLiteral(_) - | Self::BigintLiteral(_) - | Self::RegExpLiteral(_) - | Self::StringLiteral(_) - ) - } - - pub fn is_string_literal(&self) -> bool { - matches!(self, Self::StringLiteral(_) | Self::TemplateLiteral(_)) - } - - pub fn is_specific_string_literal(&self, string: &str) -> bool { - match self { - Self::StringLiteral(s) => s.value == string, - _ => false, - } - } - - /// Determines whether the given expr is a `null` literal - pub fn is_null(&self) -> bool { - matches!(self, Expression::NullLiteral(_)) - } - - /// Determines whether the given expr is a `undefined` literal - pub fn is_undefined(&self) -> bool { - matches!(self, Self::Identifier(ident) if ident.name == "undefined") - } - - /// Determines whether the given expr is a `void expr` - pub fn is_void(&self) -> bool { - matches!(self, Self::UnaryExpression(expr) if expr.operator == UnaryOperator::Void) - } - - /// Determines whether the given expr is a `void 0` - pub fn is_void_0(&self) -> bool { - match self { - Self::UnaryExpression(expr) if expr.operator == UnaryOperator::Void => { - matches!(&expr.argument, Self::NumericLiteral(lit) if lit.value == 0.0) - } - _ => false, - } - } - - /// Determines whether the given expr is a `0` - pub fn is_number_0(&self) -> bool { - matches!(self, Self::NumericLiteral(lit) if lit.value == 0.0) - } - - pub fn is_number(&self, val: f64) -> bool { - matches!(self, Self::NumericLiteral(lit) if (lit.value - val).abs() < f64::EPSILON) - } - - /// 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) - } - - /// Determines whether the given expr evaluate to `undefined` - pub fn evaluate_to_undefined(&self) -> bool { - self.is_undefined() || self.is_void() - } - - /// Determines whether the given expr is a `null` or `undefined` or `void 0` - pub fn is_null_or_undefined(&self) -> bool { - self.is_null() || self.evaluate_to_undefined() - } - - /// Determines whether the given expr is a `NaN` literal - pub fn is_nan(&self) -> bool { - matches!(self, Self::Identifier(ident) if ident.name == "NaN") - } - - /// Remove nested parentheses from this expression. - pub fn without_parenthesized(&self) -> &Self { - match self { - Expression::ParenthesizedExpression(expr) => expr.expression.without_parenthesized(), - _ => self, - } - } - - pub fn is_specific_id(&self, name: &str) -> bool { - match self.get_inner_expression() { - Expression::Identifier(ident) => ident.name == name, - _ => false, - } - } - - pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool { - match self.get_inner_expression() { - expr if expr.is_member_expression() => { - expr.to_member_expression().is_specific_member_access(object, property) - } - Expression::ChainExpression(chain) => { - let Some(expr) = chain.expression.as_member_expression() else { - return false; - }; - expr.is_specific_member_access(object, property) - } - _ => false, - } - } - - pub fn get_inner_expression(&self) -> &Expression<'a> { - match self { - Expression::ParenthesizedExpression(expr) => expr.expression.get_inner_expression(), - Expression::TSAsExpression(expr) => expr.expression.get_inner_expression(), - Expression::TSSatisfiesExpression(expr) => expr.expression.get_inner_expression(), - Expression::TSInstantiationExpression(expr) => expr.expression.get_inner_expression(), - Expression::TSNonNullExpression(expr) => expr.expression.get_inner_expression(), - Expression::TSTypeAssertion(expr) => expr.expression.get_inner_expression(), - _ => self, - } - } - - pub fn is_identifier_reference(&self) -> bool { - matches!(self, Expression::Identifier(_)) - } - - pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> { - match self.get_inner_expression() { - Expression::Identifier(ident) => Some(ident), - _ => None, - } - } - - pub fn is_function(&self) -> bool { - matches!(self, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_)) - } - - pub fn is_call_expression(&self) -> bool { - matches!(self, Expression::CallExpression(_)) - } - - pub fn is_super_call_expression(&self) -> bool { - matches!(self, Expression::CallExpression(expr) if matches!(&expr.callee, Expression::Super(_))) - } - - pub fn is_call_like_expression(&self) -> bool { - self.is_call_expression() - && matches!(self, Expression::NewExpression(_) | Expression::ImportExpression(_)) - } - - pub fn is_binaryish(&self) -> bool { - matches!(self, Expression::BinaryExpression(_) | Expression::LogicalExpression(_)) - } - - /// Returns literal's value converted to the Boolean type - /// returns `true` when node is truthy, `false` when node is falsy, `None` when it cannot be determined. - pub fn get_boolean_value(&self) -> Option { - match self { - Self::BooleanLiteral(lit) => Some(lit.value), - Self::NullLiteral(_) => Some(false), - Self::NumericLiteral(lit) => Some(lit.value != 0.0), - Self::BigintLiteral(lit) => Some(!lit.is_zero()), - Self::RegExpLiteral(_) => Some(true), - Self::StringLiteral(lit) => Some(!lit.value.is_empty()), - _ => None, - } - } - - pub fn get_member_expr(&self) -> Option<&MemberExpression<'a>> { - match self.get_inner_expression() { - Expression::ChainExpression(chain_expr) => chain_expr.expression.as_member_expression(), - expr => expr.as_member_expression(), - } - } - - pub fn is_immutable_value(&self) -> bool { - match self { - Self::BooleanLiteral(_) - | Self::NullLiteral(_) - | Self::NumericLiteral(_) - | Self::BigintLiteral(_) - | Self::RegExpLiteral(_) - | Self::StringLiteral(_) => true, - Self::TemplateLiteral(lit) if lit.is_no_substitution_template() => true, - Self::UnaryExpression(unary_expr) => unary_expr.argument.is_immutable_value(), - Self::Identifier(ident) => { - matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN") - } - _ => false, - } - } -} - /// Identifier Name #[visited_node] #[derive(Debug, Clone, Hash)] @@ -430,12 +164,6 @@ pub struct IdentifierName<'a> { pub name: Atom<'a>, } -impl<'a> IdentifierName<'a> { - pub fn new(span: Span, name: Atom<'a>) -> Self { - Self { span, name } - } -} - /// Identifier Reference #[visited_node] #[derive(Debug, Clone)] @@ -451,27 +179,6 @@ pub struct IdentifierReference<'a> { pub reference_flag: ReferenceFlag, } -impl<'a> Hash for IdentifierReference<'a> { - fn hash(&self, state: &mut H) { - self.name.hash(state); - } -} - -impl<'a> IdentifierReference<'a> { - pub fn new(span: Span, name: Atom<'a>) -> Self { - Self { span, name, reference_id: Cell::default(), reference_flag: ReferenceFlag::default() } - } - - pub fn new_read(span: Span, name: Atom<'a>, reference_id: Option) -> Self { - Self { - span, - name, - reference_id: Cell::new(reference_id), - reference_flag: ReferenceFlag::Read, - } - } -} - /// Binding Identifier #[visited_node] #[derive(Debug, Clone)] @@ -485,18 +192,6 @@ pub struct BindingIdentifier<'a> { pub symbol_id: Cell>, } -impl<'a> Hash for BindingIdentifier<'a> { - fn hash(&self, state: &mut H) { - self.name.hash(state); - } -} - -impl<'a> BindingIdentifier<'a> { - pub fn new(span: Span, name: Atom<'a>) -> Self { - Self { span, name, symbol_id: Cell::default() } - } -} - /// Label Identifier #[visited_node] #[derive(Debug, Clone, Hash)] @@ -555,12 +250,6 @@ pub enum ArrayExpressionElement<'a> { } } -impl<'a> ArrayExpressionElement<'a> { - pub fn is_elision(&self) -> bool { - matches!(self, Self::Elision(_)) - } -} - /// Array Expression Elision Element /// Serialized as `null` in JSON AST. See `serialize.rs`. #[visited_node] @@ -582,13 +271,6 @@ pub struct ObjectExpression<'a> { pub trailing_comma: Option, } -impl<'a> ObjectExpression<'a> { - pub fn has_proto(&self) -> bool { - use crate::syntax_directed_operations::PropName; - self.properties.iter().any(|p| p.prop_name().is_some_and(|name| name.0 == "__proto__")) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -633,64 +315,6 @@ pub enum PropertyKey<'a> { } } -impl<'a> PropertyKey<'a> { - pub fn static_name(&self) -> Option { - match self { - Self::StaticIdentifier(ident) => Some(ident.name.to_compact_str()), - Self::StringLiteral(lit) => Some(lit.value.to_compact_str()), - Self::RegExpLiteral(lit) => Some(lit.regex.to_string().into()), - Self::NumericLiteral(lit) => Some(lit.value.to_string().into()), - Self::BigintLiteral(lit) => Some(lit.raw.to_compact_str()), - Self::NullLiteral(_) => Some("null".into()), - Self::TemplateLiteral(lit) => lit - .expressions - .is_empty() - .then(|| lit.quasi()) - .flatten() - .map(|quasi| quasi.to_compact_str()), - _ => None, - } - } - - pub fn is_specific_static_name(&self, name: &str) -> bool { - self.static_name().is_some_and(|n| n == name) - } - - pub fn is_identifier(&self) -> bool { - matches!(self, Self::PrivateIdentifier(_) | Self::StaticIdentifier(_)) - } - - pub fn is_private_identifier(&self) -> bool { - matches!(self, Self::PrivateIdentifier(_)) - } - - pub fn private_name(&self) -> Option> { - match self { - Self::PrivateIdentifier(ident) => Some(ident.name.clone()), - _ => None, - } - } - - pub fn name(&self) -> Option { - if self.is_private_identifier() { - self.private_name().map(|name| name.to_compact_str()) - } else { - self.static_name() - } - } - - pub fn is_specific_id(&self, name: &str) -> bool { - match self { - PropertyKey::StaticIdentifier(ident) => ident.name == name, - _ => false, - } - } - - pub fn is_specific_string_literal(&self, string: &str) -> bool { - matches!(self, Self::StringLiteral(s) if s.value == string) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[cfg_attr(feature = "serialize", serde(rename_all = "lowercase"))] @@ -714,17 +338,6 @@ pub struct TemplateLiteral<'a> { pub expressions: Vec<'a, Expression<'a>>, } -impl<'a> TemplateLiteral<'a> { - pub fn is_no_substitution_template(&self) -> bool { - self.expressions.is_empty() && self.quasis.len() == 1 - } - - /// Get single quasi from `template` - pub fn quasi(&self) -> Option> { - self.quasis.first().and_then(|quasi| quasi.value.cooked.clone()) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -789,81 +402,6 @@ macro_rules! match_member_expression { } pub use match_member_expression; -impl<'a> MemberExpression<'a> { - pub fn is_computed(&self) -> bool { - matches!(self, MemberExpression::ComputedMemberExpression(_)) - } - - pub fn optional(&self) -> bool { - match self { - MemberExpression::ComputedMemberExpression(expr) => expr.optional, - MemberExpression::StaticMemberExpression(expr) => expr.optional, - MemberExpression::PrivateFieldExpression(expr) => expr.optional, - } - } - - pub fn object(&self) -> &Expression<'a> { - match self { - MemberExpression::ComputedMemberExpression(expr) => &expr.object, - MemberExpression::StaticMemberExpression(expr) => &expr.object, - MemberExpression::PrivateFieldExpression(expr) => &expr.object, - } - } - - pub fn static_property_name(&self) -> Option<&str> { - match self { - MemberExpression::ComputedMemberExpression(expr) => { - expr.static_property_name().map(|name| name.as_str()) - } - MemberExpression::StaticMemberExpression(expr) => Some(expr.property.name.as_str()), - MemberExpression::PrivateFieldExpression(_) => None, - } - } - - pub fn static_property_info(&self) -> Option<(Span, &str)> { - match self { - MemberExpression::ComputedMemberExpression(expr) => match &expr.expression { - Expression::StringLiteral(lit) => Some((lit.span, &lit.value)), - Expression::TemplateLiteral(lit) => { - if lit.expressions.is_empty() && lit.quasis.len() == 1 { - Some((lit.span, &lit.quasis[0].value.raw)) - } else { - None - } - } - _ => None, - }, - MemberExpression::StaticMemberExpression(expr) => { - Some((expr.property.span, &expr.property.name)) - } - MemberExpression::PrivateFieldExpression(_) => None, - } - } - - pub fn through_optional_is_specific_member_access(&self, object: &str, property: &str) -> bool { - let object_matches = match self.object().without_parenthesized() { - Expression::ChainExpression(x) => match &x.expression { - ChainElement::CallExpression(_) => false, - match_member_expression!(ChainElement) => { - let member_expr = x.expression.to_member_expression(); - member_expr.object().without_parenthesized().is_specific_id(object) - } - }, - x => x.is_specific_id(object), - }; - - let property_matches = self.static_property_name().is_some_and(|p| p == property); - - object_matches && property_matches - } - - /// Whether it is a static member access `object.property` - pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool { - self.object().is_specific_id(object) - && self.static_property_name().is_some_and(|p| p == property) - } -} - /// `MemberExpression[?Yield, ?Await] [ Expression[+In, ?Yield, ?Await] ]` #[visited_node] #[derive(Debug, Hash)] @@ -877,20 +415,6 @@ pub struct ComputedMemberExpression<'a> { pub optional: bool, // for optional chaining } -impl<'a> ComputedMemberExpression<'a> { - pub fn static_property_name(&self) -> Option> { - match &self.expression { - Expression::StringLiteral(lit) => Some(lit.value.clone()), - Expression::TemplateLiteral(lit) - if lit.expressions.is_empty() && lit.quasis.len() == 1 => - { - Some(lit.quasis[0].value.raw.clone()) - } - _ => None, - } - } -} - /// `MemberExpression[?Yield, ?Await] . IdentifierName` #[visited_node] #[derive(Debug, Hash)] @@ -904,28 +428,6 @@ pub struct StaticMemberExpression<'a> { pub optional: bool, // for optional chaining } -impl<'a> StaticMemberExpression<'a> { - pub fn get_first_object(&self) -> &Expression<'a> { - match &self.object { - Expression::StaticMemberExpression(member) => { - if let Expression::StaticMemberExpression(expr) = &member.object { - expr.get_first_object() - } else { - &self.object - } - } - Expression::ChainExpression(chain) => { - if let ChainElement::StaticMemberExpression(expr) = &chain.expression { - expr.get_first_object() - } else { - &self.object - } - } - _ => &self.object, - } - } -} - /// `MemberExpression[?Yield, ?Await] . PrivateIdentifier` #[visited_node] #[derive(Debug, Hash)] @@ -953,54 +455,6 @@ pub struct CallExpression<'a> { pub type_parameters: Option>>, } -impl<'a> CallExpression<'a> { - pub fn callee_name(&self) -> Option<&str> { - match &self.callee { - Expression::Identifier(ident) => Some(ident.name.as_str()), - expr => expr.as_member_expression().and_then(MemberExpression::static_property_name), - } - } - - pub fn is_require_call(&self) -> bool { - if self.arguments.len() != 1 { - return false; - } - if let Expression::Identifier(id) = &self.callee { - id.name == "require" - && matches!( - self.arguments.first(), - Some(Argument::StringLiteral(_) | Argument::TemplateLiteral(_)), - ) - } else { - false - } - } - - pub fn is_symbol_or_symbol_for_call(&self) -> bool { - // TODO: is 'Symbol' reference to global object - match &self.callee { - Expression::Identifier(id) => id.name == "Symbol", - expr => match expr.as_member_expression() { - Some(member) => { - matches!(member.object(), Expression::Identifier(id) if id.name == "Symbol") - && member.static_property_name() == Some("for") - } - None => false, - }, - } - } - - pub fn common_js_require(&self) -> Option<&StringLiteral> { - if !(self.callee.is_specific_id("require") && self.arguments.len() == 1) { - return None; - } - match &self.arguments[0] { - Argument::StringLiteral(str_literal) => Some(str_literal), - _ => None, - } - } -} - /// New Expression #[visited_node] #[derive(Debug, Hash)] @@ -1055,12 +509,6 @@ pub enum Argument<'a> { } } -impl Argument<'_> { - pub fn is_spread(&self) -> bool { - matches!(self, Self::SpreadElement(_)) - } -} - /// Update Expression #[visited_node] #[derive(Debug, Hash)] @@ -1171,36 +619,6 @@ pub enum AssignmentTarget<'a> { } } -impl<'a> AssignmentTarget<'a> { - pub fn get_identifier(&self) -> Option<&str> { - self.as_simple_assignment_target().and_then(|it| it.get_identifier()) - } - - pub fn get_expression(&self) -> Option<&Expression<'a>> { - self.as_simple_assignment_target().and_then(|it| it.get_expression()) - } -} - -/// Macro for matching `AssignmentTarget`'s variants. -/// Includes `SimpleAssignmentTarget`'s and `AssignmentTargetPattern`'s variants. -#[macro_export] -macro_rules! match_assignment_target { - ($ty:ident) => { - $ty::AssignmentTargetIdentifier(_) - | $ty::ComputedMemberExpression(_) - | $ty::StaticMemberExpression(_) - | $ty::PrivateFieldExpression(_) - | $ty::TSAsExpression(_) - | $ty::TSSatisfiesExpression(_) - | $ty::TSNonNullExpression(_) - | $ty::TSTypeAssertion(_) - | $ty::TSInstantiationExpression(_) - | $ty::ArrayAssignmentTarget(_) - | $ty::ObjectAssignmentTarget(_) - }; -} -pub use match_assignment_target; - inherit_variants! { /// Simple Assignment Target /// @@ -1224,6 +642,26 @@ pub enum SimpleAssignmentTarget<'a> { } } +/// Macro for matching `AssignmentTarget`'s variants. +/// Includes `SimpleAssignmentTarget`'s and `AssignmentTargetPattern`'s variants. +#[macro_export] +macro_rules! match_assignment_target { + ($ty:ident) => { + $ty::AssignmentTargetIdentifier(_) + | $ty::ComputedMemberExpression(_) + | $ty::StaticMemberExpression(_) + | $ty::PrivateFieldExpression(_) + | $ty::TSAsExpression(_) + | $ty::TSSatisfiesExpression(_) + | $ty::TSNonNullExpression(_) + | $ty::TSTypeAssertion(_) + | $ty::TSInstantiationExpression(_) + | $ty::ArrayAssignmentTarget(_) + | $ty::ObjectAssignmentTarget(_) + }; +} +pub use match_assignment_target; + /// Macro for matching `SimpleAssignmentTarget`'s variants. /// Includes `MemberExpression`'s variants #[macro_export] @@ -1242,26 +680,6 @@ macro_rules! match_simple_assignment_target { } pub use match_simple_assignment_target; -impl<'a> SimpleAssignmentTarget<'a> { - pub fn get_identifier(&self) -> Option<&str> { - match self { - Self::AssignmentTargetIdentifier(ident) => Some(ident.name.as_str()), - match_member_expression!(Self) => self.to_member_expression().static_property_name(), - _ => None, - } - } - - pub fn get_expression(&self) -> Option<&Expression<'a>> { - match self { - Self::TSAsExpression(expr) => Some(&expr.expression), - Self::TSSatisfiesExpression(expr) => Some(&expr.expression), - Self::TSNonNullExpression(expr) => Some(&expr.expression), - Self::TSTypeAssertion(expr) => Some(&expr.expression), - _ => None, - } - } -} - #[visited_node] #[repr(C, u8)] #[derive(Debug, Hash)] @@ -1300,15 +718,6 @@ pub struct ArrayAssignmentTarget<'a> { pub trailing_comma: Option, } -impl<'a> ArrayAssignmentTarget<'a> { - pub fn new_with_elements( - span: Span, - elements: Vec<'a, Option>>, - ) -> Self { - Self { span, elements, rest: None, trailing_comma: None } - } -} - // See serializer in serialize.rs #[visited_node] #[derive(Debug, Hash)] @@ -1326,23 +735,6 @@ pub struct ObjectAssignmentTarget<'a> { pub rest: Option>, } -impl<'a> ObjectAssignmentTarget<'a> { - pub fn new_with_properties( - span: Span, - properties: Vec<'a, AssignmentTargetProperty<'a>>, - ) -> Self { - Self { span, properties, rest: None } - } - - pub fn is_empty(&self) -> bool { - self.properties.is_empty() && self.rest.is_none() - } - - pub fn len(&self) -> usize { - self.properties.len() + usize::from(self.rest.is_some()) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] @@ -1372,22 +764,6 @@ pub enum AssignmentTargetMaybeDefault<'a> { } } -impl<'a> AssignmentTargetMaybeDefault<'a> { - pub fn name(&self) -> Option { - match self { - AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(id) => Some(id.name.clone()), - Self::AssignmentTargetWithDefault(target) => { - if let AssignmentTarget::AssignmentTargetIdentifier(id) = &target.binding { - Some(id.name.clone()) - } else { - None - } - } - _ => None, - } - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -1541,31 +917,6 @@ pub enum Statement<'a> { } } -impl<'a> Statement<'a> { - pub fn is_typescript_syntax(&self) -> bool { - match self { - match_declaration!(Self) => { - self.as_declaration().is_some_and(Declaration::is_typescript_syntax) - } - match_module_declaration!(Self) => { - self.as_module_declaration().is_some_and(ModuleDeclaration::is_typescript_syntax) - } - _ => false, - } - } - - pub fn is_iteration_statement(&self) -> bool { - matches!( - self, - Statement::DoWhileStatement(_) - | Statement::ForInStatement(_) - | Statement::ForOfStatement(_) - | Statement::ForStatement(_) - | Statement::WhileStatement(_) - ) - } -} - /// Directive Prologue #[visited_node] #[derive(Debug, Hash)] @@ -1580,15 +931,6 @@ pub struct Directive<'a> { pub directive: Atom<'a>, } -impl<'a> Directive<'a> { - /// A Use Strict Directive is an ExpressionStatement in a Directive Prologue whose StringLiteral is either of the exact code point sequences "use strict" or 'use strict'. - /// A Use Strict Directive may not contain an EscapeSequence or LineContinuation. - /// - pub fn is_use_strict(&self) -> bool { - self.directive == "use strict" - } -} - /// Hashbang #[visited_node] #[derive(Debug, Hash)] @@ -1612,18 +954,6 @@ pub struct BlockStatement<'a> { pub scope_id: Cell>, } -impl<'a> BlockStatement<'a> { - pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { - Self { span, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for BlockStatement<'a> { - fn hash(&self, state: &mut H) { - self.body.hash(state); - } -} - /// Declarations and the Variable Statement #[visited_node] #[repr(C, u8)] @@ -1660,43 +990,6 @@ macro_rules! match_declaration { } pub use match_declaration; -impl<'a> Declaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - match self { - Self::VariableDeclaration(decl) => decl.is_typescript_syntax(), - Self::FunctionDeclaration(func) => func.is_typescript_syntax(), - Self::ClassDeclaration(class) => class.is_typescript_syntax(), - Self::UsingDeclaration(_) => false, - _ => true, - } - } - - pub fn id(&self) -> Option<&BindingIdentifier<'a>> { - match self { - Declaration::FunctionDeclaration(decl) => decl.id.as_ref(), - Declaration::ClassDeclaration(decl) => decl.id.as_ref(), - Declaration::TSTypeAliasDeclaration(decl) => Some(&decl.id), - Declaration::TSInterfaceDeclaration(decl) => Some(&decl.id), - Declaration::TSEnumDeclaration(decl) => Some(&decl.id), - Declaration::TSImportEqualsDeclaration(decl) => Some(&decl.id), - _ => None, - } - } - - pub fn modifiers(&self) -> Option<&Modifiers<'a>> { - match self { - Declaration::VariableDeclaration(decl) => Some(&decl.modifiers), - Declaration::FunctionDeclaration(decl) => Some(&decl.modifiers), - Declaration::ClassDeclaration(decl) => Some(&decl.modifiers), - Declaration::TSEnumDeclaration(decl) => Some(&decl.modifiers), - Declaration::TSTypeAliasDeclaration(decl) => Some(&decl.modifiers), - Declaration::TSModuleDeclaration(decl) => Some(&decl.modifiers), - Declaration::TSInterfaceDeclaration(decl) => Some(&decl.modifiers), - _ => None, - } - } -} - /// Variable Declaration #[visited_node] #[derive(Debug, Hash)] @@ -1711,16 +1004,6 @@ pub struct VariableDeclaration<'a> { pub modifiers: Modifiers<'a>, } -impl<'a> VariableDeclaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - self.modifiers.contains(ModifierKind::Declare) - } - - pub fn has_init(&self) -> bool { - self.declarations.iter().any(|decl| decl.init.is_some()) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] #[cfg_attr(feature = "serialize", serde(rename_all = "lowercase"))] @@ -1730,35 +1013,6 @@ pub enum VariableDeclarationKind { Let, } -impl VariableDeclarationKind { - pub fn is_var(&self) -> bool { - matches!(self, Self::Var) - } - - pub fn is_const(&self) -> bool { - matches!(self, Self::Const) - } - - pub fn is_lexical(&self) -> bool { - matches!(self, Self::Const | Self::Let) - } - - pub fn as_str(&self) -> &'static str { - match self { - Self::Var => "var", - Self::Const => "const", - Self::Let => "let", - } - } -} - -impl fmt::Display for VariableDeclarationKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = self.as_str(); - write!(f, "{s}") - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -1863,27 +1117,6 @@ pub struct ForStatement<'a> { pub scope_id: Cell>, } -impl<'a> ForStatement<'a> { - pub fn new( - span: Span, - init: Option>, - test: Option>, - update: Option>, - body: Statement<'a>, - ) -> Self { - Self { span, init, test, update, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for ForStatement<'a> { - fn hash(&self, state: &mut H) { - self.init.hash(state); - self.test.hash(state); - self.update.hash(state); - self.body.hash(state); - } -} - inherit_variants! { /// For Statement Init /// @@ -1903,14 +1136,6 @@ pub enum ForStatementInit<'a> { } } -impl<'a> ForStatementInit<'a> { - /// LexicalDeclaration[In, Yield, Await] : - /// LetOrConst BindingList[?In, ?Yield, ?Await] ; - pub fn is_lexical_declaration(&self) -> bool { - matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical()) - } -} - /// For-In Statement #[visited_node(scope(ScopeFlags::empty()), scope_if(self.left.is_lexical_declaration()))] #[derive(Debug)] @@ -1925,61 +1150,6 @@ pub struct ForInStatement<'a> { pub scope_id: Cell>, } -impl<'a> ForInStatement<'a> { - pub fn new( - span: Span, - left: ForStatementLeft<'a>, - right: Expression<'a>, - body: Statement<'a>, - ) -> Self { - Self { span, left, right, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for ForInStatement<'a> { - fn hash(&self, state: &mut H) { - self.left.hash(state); - self.right.hash(state); - self.body.hash(state); - } -} - -/// For-Of Statement -#[visited_node(scope(ScopeFlags::empty()), scope_if(self.left.is_lexical_declaration()))] -#[derive(Debug)] -#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] -#[cfg_attr(feature = "serialize", serde(tag = "type"))] -pub struct ForOfStatement<'a> { - #[cfg_attr(feature = "serialize", serde(flatten))] - pub span: Span, - pub r#await: bool, - pub left: ForStatementLeft<'a>, - pub right: Expression<'a>, - pub body: Statement<'a>, - pub scope_id: Cell>, -} - -impl<'a> ForOfStatement<'a> { - pub fn new( - span: Span, - r#await: bool, - left: ForStatementLeft<'a>, - right: Expression<'a>, - body: Statement<'a>, - ) -> Self { - Self { span, r#await, left, right, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for ForOfStatement<'a> { - fn hash(&self, state: &mut H) { - self.r#await.hash(state); - self.left.hash(state); - self.right.hash(state); - self.body.hash(state); - } -} - inherit_variants! { /// For Statement Left /// @@ -1998,13 +1168,19 @@ pub enum ForStatementLeft<'a> { @inherit AssignmentTarget } } - -impl<'a> ForStatementLeft<'a> { - /// LexicalDeclaration[In, Yield, Await] : - /// LetOrConst BindingList[?In, ?Yield, ?Await] ; - pub fn is_lexical_declaration(&self) -> bool { - matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical()) - } +/// For-Of Statement +#[visited_node(scope(ScopeFlags::empty()), scope_if(self.left.is_lexical_declaration()))] +#[derive(Debug)] +#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] +#[cfg_attr(feature = "serialize", serde(tag = "type"))] +pub struct ForOfStatement<'a> { + #[cfg_attr(feature = "serialize", serde(flatten))] + pub span: Span, + pub r#await: bool, + pub left: ForStatementLeft<'a>, + pub right: Expression<'a>, + pub body: Statement<'a>, + pub scope_id: Cell>, } /// Continue Statement @@ -2065,19 +1241,6 @@ pub struct SwitchStatement<'a> { pub scope_id: Cell>, } -impl<'a> SwitchStatement<'a> { - pub fn new(span: Span, discriminant: Expression<'a>, cases: Vec<'a, SwitchCase<'a>>) -> Self { - Self { span, discriminant, cases, scope_id: Cell::default() } - } -} - -impl<'a> Hash for SwitchStatement<'a> { - fn hash(&self, state: &mut H) { - self.discriminant.hash(state); - self.cases.hash(state); - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2089,12 +1252,6 @@ pub struct SwitchCase<'a> { pub consequent: Vec<'a, Statement<'a>>, } -impl<'a> SwitchCase<'a> { - pub fn is_default_case(&self) -> bool { - self.test.is_none() - } -} - /// Labelled Statement #[visited_node] #[derive(Debug, Hash)] @@ -2143,23 +1300,6 @@ pub struct CatchClause<'a> { pub scope_id: Cell>, } -impl<'a> CatchClause<'a> { - pub fn new( - span: Span, - param: Option>, - body: Box<'a, BlockStatement<'a>>, - ) -> Self { - Self { span, param, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for CatchClause<'a> { - fn hash(&self, state: &mut H) { - self.param.hash(state); - self.body.hash(state); - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2198,20 +1338,6 @@ pub struct BindingPattern<'a> { pub optional: bool, } -impl<'a> BindingPattern<'a> { - pub fn new_with_kind(kind: BindingPatternKind<'a>) -> Self { - Self { kind, type_annotation: None, optional: false } - } - - pub fn get_identifier(&self) -> Option> { - self.kind.get_identifier() - } - - pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { - self.kind.get_binding_identifier() - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2230,40 +1356,6 @@ pub enum BindingPatternKind<'a> { AssignmentPattern(Box<'a, AssignmentPattern<'a>>), } -impl<'a> BindingPatternKind<'a> { - pub fn get_identifier(&self) -> Option> { - match self { - Self::BindingIdentifier(ident) => Some(ident.name.clone()), - Self::AssignmentPattern(assign) => assign.left.get_identifier(), - _ => None, - } - } - - pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { - match self { - Self::BindingIdentifier(ident) => Some(ident), - Self::AssignmentPattern(assign) => assign.left.get_binding_identifier(), - _ => None, - } - } - - pub fn is_destructuring_pattern(&self) -> bool { - match self { - Self::ObjectPattern(_) | Self::ArrayPattern(_) => true, - Self::AssignmentPattern(pattern) => pattern.left.kind.is_destructuring_pattern(), - Self::BindingIdentifier(_) => false, - } - } - - pub fn is_binding_identifier(&self) -> bool { - matches!(self, Self::BindingIdentifier(_)) - } - - pub fn is_assignment_pattern(&self) -> bool { - matches!(self, Self::AssignmentPattern(_)) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2289,16 +1381,6 @@ pub struct ObjectPattern<'a> { pub rest: Option>>, } -impl<'a> ObjectPattern<'a> { - pub fn is_empty(&self) -> bool { - self.properties.is_empty() && self.rest.is_none() - } - - pub fn len(&self) -> usize { - self.properties.len() + usize::from(self.rest.is_some()) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2329,16 +1411,6 @@ pub struct ArrayPattern<'a> { pub rest: Option>>, } -impl<'a> ArrayPattern<'a> { - pub fn is_empty(&self) -> bool { - self.elements.is_empty() && self.rest.is_none() - } - - pub fn len(&self) -> usize { - self.elements.len() + usize::from(self.rest.is_some()) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize))] @@ -2390,81 +1462,6 @@ pub struct Function<'a> { pub scope_id: Cell>, } -impl<'a> Function<'a> { - #![allow(clippy::too_many_arguments)] - pub fn new( - r#type: FunctionType, - span: Span, - id: Option>, - generator: bool, - r#async: bool, - this_param: Option>, - params: Box<'a, FormalParameters<'a>>, - body: Option>>, - type_parameters: Option>>, - return_type: Option>>, - modifiers: Modifiers<'a>, - ) -> Self { - Self { - r#type, - span, - id, - generator, - r#async, - this_param, - params, - body, - type_parameters, - return_type, - modifiers, - scope_id: Cell::default(), - } - } - - pub fn is_typescript_syntax(&self) -> bool { - matches!( - self.r#type, - FunctionType::TSDeclareFunction | FunctionType::TSEmptyBodyFunctionExpression - ) || self.body.is_none() - || self.modifiers.contains(ModifierKind::Declare) - } - - pub fn is_expression(&self) -> bool { - self.r#type == FunctionType::FunctionExpression - } - - pub fn is_function_declaration(&self) -> bool { - matches!(self.r#type, FunctionType::FunctionDeclaration) - } - - pub fn is_ts_declare_function(&self) -> bool { - matches!(self.r#type, FunctionType::TSDeclareFunction) - } - - pub fn is_declaration(&self) -> bool { - matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction) - } - - pub fn is_strict(&self) -> bool { - self.body.as_ref().is_some_and(|body| body.has_use_strict_directive()) - } -} - -impl<'a> Hash for Function<'a> { - fn hash(&self, state: &mut H) { - self.r#type.hash(state); - self.id.hash(state); - self.generator.hash(state); - self.r#async.hash(state); - self.this_param.hash(state); - self.params.hash(state); - self.body.hash(state); - self.type_parameters.hash(state); - self.return_type.hash(state); - self.modifiers.hash(state); - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] pub enum FunctionType { @@ -2494,20 +1491,6 @@ pub struct FormalParameters<'a> { pub rest: Option>>, } -impl<'a> FormalParameters<'a> { - pub fn parameters_count(&self) -> usize { - self.items.len() + self.rest.as_ref().map_or(0, |_| 1) - } - - /// Iterates over all bound parameters, including rest parameters. - pub fn iter_bindings(&self) -> impl Iterator> + '_ { - self.items - .iter() - .map(|param| ¶m.pattern) - .chain(self.rest.iter().map(|rest| &rest.argument)) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2522,12 +1505,6 @@ pub struct FormalParameter<'a> { pub decorators: Vec<'a, Decorator<'a>>, } -impl<'a> FormalParameter<'a> { - pub fn is_public(&self) -> bool { - matches!(self.accessibility, Some(TSAccessibility::Public)) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] pub enum FormalParameterKind { @@ -2541,18 +1518,6 @@ pub enum FormalParameterKind { Signature, } -impl FormalParameterKind { - pub fn is_signature(&self) -> bool { - matches!(self, Self::Signature) - } -} - -impl<'a> FormalParameters<'a> { - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } -} - /// #[visited_node] #[derive(Debug, Hash)] @@ -2565,16 +1530,6 @@ pub struct FunctionBody<'a> { pub statements: Vec<'a, Statement<'a>>, } -impl<'a> FunctionBody<'a> { - pub fn is_empty(&self) -> bool { - self.directives.is_empty() && self.statements.is_empty() - } - - pub fn has_use_strict_directive(&self) -> bool { - self.directives.iter().any(Directive::is_use_strict) - } -} - /// Arrow Function Definitions #[visited_node( scope(ScopeFlags::Function | ScopeFlags::Arrow), @@ -2598,50 +1553,6 @@ pub struct ArrowFunctionExpression<'a> { pub scope_id: Cell>, } -impl<'a> ArrowFunctionExpression<'a> { - pub fn new( - span: Span, - expression: bool, - r#async: bool, - params: Box<'a, FormalParameters<'a>>, - body: Box<'a, FunctionBody<'a>>, - type_parameters: Option>>, - return_type: Option>>, - ) -> Self { - Self { - span, - expression, - r#async, - params, - body, - type_parameters, - return_type, - scope_id: Cell::default(), - } - } - - /// Get expression part of `ArrowFunctionExpression`: `() => expression_part`. - pub fn get_expression(&self) -> Option<&Expression<'a>> { - if self.expression { - if let Statement::ExpressionStatement(expr_stmt) = &self.body.statements[0] { - return Some(&expr_stmt.expression); - } - } - None - } -} - -impl<'a> Hash for ArrowFunctionExpression<'a> { - fn hash(&self, state: &mut H) { - self.expression.hash(state); - self.r#async.hash(state); - self.params.hash(state); - self.body.hash(state); - self.type_parameters.hash(state); - self.return_type.hash(state); - } -} - /// Generator Function Definitions #[visited_node] #[derive(Debug, Hash)] @@ -2675,94 +1586,6 @@ pub struct Class<'a> { pub scope_id: Cell>, } -impl<'a> Class<'a> { - #[allow(clippy::too_many_arguments)] - pub fn new( - r#type: ClassType, - span: Span, - decorators: Vec<'a, Decorator<'a>>, - id: Option>, - super_class: Option>, - body: Box<'a, ClassBody<'a>>, - type_parameters: Option>>, - super_type_parameters: Option>>, - implements: Option>>, - modifiers: Modifiers<'a>, - ) -> Self { - Self { - r#type, - span, - decorators, - id, - super_class, - body, - type_parameters, - super_type_parameters, - implements, - modifiers, - scope_id: Cell::default(), - } - } - - /// `true` if this [`Class`] is an expression. - /// - /// For example, - /// ```ts - /// var Foo = class { /* ... */ } - /// ``` - pub fn is_expression(&self) -> bool { - self.r#type == ClassType::ClassExpression - } - - /// `true` if this [`Class`] is a declaration statement. - /// - /// For example, - /// ```ts - /// class Foo { - /// // ... - /// } - /// ``` - /// - /// Not to be confused with [`Class::is_declare`]. - pub fn is_declaration(&self) -> bool { - self.r#type == ClassType::ClassDeclaration - } - - /// `true` if this [`Class`] is being within a typescript declaration file - /// or `declare` statement. - /// - /// For example, - /// ```ts - /// declare global { - /// declare class Foo { - /// // ... - /// } - /// } - /// - /// Not to be confused with [`Class::is_declaration`]. - pub fn is_declare(&self) -> bool { - self.modifiers.contains(ModifierKind::Declare) - } - - pub fn is_typescript_syntax(&self) -> bool { - self.is_declare() - } -} - -impl<'a> Hash for Class<'a> { - fn hash(&self, state: &mut H) { - self.r#type.hash(state); - self.decorators.hash(state); - self.id.hash(state); - self.super_class.hash(state); - self.body.hash(state); - self.type_parameters.hash(state); - self.super_type_parameters.hash(state); - self.implements.hash(state); - self.modifiers.hash(state); - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] pub enum ClassType { @@ -2792,97 +1615,6 @@ pub enum ClassElement<'a> { TSIndexSignature(Box<'a, TSIndexSignature<'a>>), } -impl<'a> ClassElement<'a> { - pub fn r#static(&self) -> bool { - match self { - Self::TSIndexSignature(_) | Self::StaticBlock(_) => false, - Self::MethodDefinition(def) => def.r#static, - Self::PropertyDefinition(def) => def.r#static, - Self::AccessorProperty(def) => def.r#static, - } - } - - pub fn computed(&self) -> bool { - match self { - Self::TSIndexSignature(_) | Self::StaticBlock(_) => false, - Self::MethodDefinition(def) => def.computed, - Self::PropertyDefinition(def) => def.computed, - Self::AccessorProperty(def) => def.computed, - } - } - - pub fn accessibility(&self) -> Option { - match self { - Self::StaticBlock(_) | Self::TSIndexSignature(_) | Self::AccessorProperty(_) => None, - Self::MethodDefinition(def) => def.accessibility, - Self::PropertyDefinition(def) => def.accessibility, - } - } - - pub fn method_definition_kind(&self) -> Option { - match self { - Self::TSIndexSignature(_) - | Self::StaticBlock(_) - | Self::PropertyDefinition(_) - | Self::AccessorProperty(_) => None, - Self::MethodDefinition(def) => Some(def.kind), - } - } - - pub fn property_key(&self) -> Option<&PropertyKey<'a>> { - match self { - Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, - Self::MethodDefinition(def) => Some(&def.key), - Self::PropertyDefinition(def) => Some(&def.key), - Self::AccessorProperty(def) => Some(&def.key), - } - } - - pub fn static_name(&self) -> Option { - match self { - Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, - Self::MethodDefinition(def) => def.key.static_name(), - Self::PropertyDefinition(def) => def.key.static_name(), - Self::AccessorProperty(def) => def.key.static_name(), - } - } - - pub fn is_property(&self) -> bool { - matches!(self, Self::PropertyDefinition(_) | Self::AccessorProperty(_)) - } - - pub fn is_ts_empty_body_function(&self) -> bool { - match self { - Self::PropertyDefinition(_) - | Self::StaticBlock(_) - | Self::AccessorProperty(_) - | Self::TSIndexSignature(_) => false, - Self::MethodDefinition(method) => method.value.body.is_none(), - } - } - - pub fn is_typescript_syntax(&self) -> bool { - match self { - Self::TSIndexSignature(_) => true, - Self::MethodDefinition(method) => method.value.is_typescript_syntax(), - Self::PropertyDefinition(property) => { - property.r#type == PropertyDefinitionType::TSAbstractPropertyDefinition - } - Self::AccessorProperty(property) => property.r#type.is_abstract(), - Self::StaticBlock(_) => false, - } - } - - pub fn has_decorator(&self) -> bool { - match self { - Self::MethodDefinition(method) => !method.decorators.is_empty(), - Self::PropertyDefinition(property) => !property.decorators.is_empty(), - Self::AccessorProperty(property) => !property.decorators.is_empty(), - Self::StaticBlock(_) | Self::TSIndexSignature(_) => false, - } - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2948,33 +1680,6 @@ pub enum MethodDefinitionKind { Set, } -impl MethodDefinitionKind { - pub fn is_constructor(&self) -> bool { - matches!(self, Self::Constructor) - } - - pub fn is_method(&self) -> bool { - matches!(self, Self::Method) - } - - pub fn is_set(&self) -> bool { - matches!(self, Self::Set) - } - - pub fn is_get(&self) -> bool { - matches!(self, Self::Get) - } - - pub fn scope_flags(self) -> ScopeFlags { - match self { - Self::Constructor => ScopeFlags::Constructor | ScopeFlags::Function, - Self::Method => ScopeFlags::Function, - Self::Get => ScopeFlags::GetAccessor | ScopeFlags::Function, - Self::Set => ScopeFlags::SetAccessor | ScopeFlags::Function, - } - } -} - #[visited_node] #[derive(Debug, Clone, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -2985,12 +1690,6 @@ pub struct PrivateIdentifier<'a> { pub name: Atom<'a>, } -impl<'a> PrivateIdentifier<'a> { - pub fn new(span: Span, name: Atom<'a>) -> Self { - Self { span, name } - } -} - #[visited_node(scope(ScopeFlags::ClassStaticBlock))] #[derive(Debug)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -3002,18 +1701,6 @@ pub struct StaticBlock<'a> { pub scope_id: Cell>, } -impl<'a> StaticBlock<'a> { - pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { - Self { span, body, scope_id: Cell::default() } - } -} - -impl<'a> Hash for StaticBlock<'a> { - fn hash(&self, state: &mut H) { - self.body.hash(state); - } -} - #[visited_node] #[repr(C, u8)] #[derive(Debug, Hash)] @@ -3051,60 +1738,6 @@ macro_rules! match_module_declaration { } pub use match_module_declaration; -impl<'a> ModuleDeclaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - match self { - ModuleDeclaration::ImportDeclaration(_) => false, - ModuleDeclaration::ExportDefaultDeclaration(decl) => decl.is_typescript_syntax(), - ModuleDeclaration::ExportNamedDeclaration(decl) => decl.is_typescript_syntax(), - ModuleDeclaration::ExportAllDeclaration(decl) => decl.is_typescript_syntax(), - ModuleDeclaration::TSNamespaceExportDeclaration(_) - | ModuleDeclaration::TSExportAssignment(_) => true, - } - } - - pub fn is_import(&self) -> bool { - matches!(self, Self::ImportDeclaration(_)) - } - - pub fn is_export(&self) -> bool { - matches!( - self, - Self::ExportAllDeclaration(_) - | Self::ExportDefaultDeclaration(_) - | Self::ExportNamedDeclaration(_) - | Self::TSExportAssignment(_) - | Self::TSNamespaceExportDeclaration(_) - ) - } - - pub fn is_default_export(&self) -> bool { - matches!(self, Self::ExportDefaultDeclaration(_)) - } - - pub fn source(&self) -> Option<&StringLiteral<'a>> { - match self { - Self::ImportDeclaration(decl) => Some(&decl.source), - Self::ExportAllDeclaration(decl) => Some(&decl.source), - Self::ExportNamedDeclaration(decl) => decl.source.as_ref(), - Self::ExportDefaultDeclaration(_) - | Self::TSExportAssignment(_) - | Self::TSNamespaceExportDeclaration(_) => None, - } - } - - pub fn with_clause(&self) -> Option<&WithClause<'a>> { - match self { - Self::ImportDeclaration(decl) => decl.with_clause.as_ref(), - Self::ExportAllDeclaration(decl) => decl.with_clause.as_ref(), - Self::ExportNamedDeclaration(decl) => decl.with_clause.as_ref(), - Self::ExportDefaultDeclaration(_) - | Self::TSExportAssignment(_) - | Self::TSNamespaceExportDeclaration(_) => None, - } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] pub enum AccessorPropertyType { @@ -3112,12 +1745,6 @@ pub enum AccessorPropertyType { TSAbstractAccessorProperty, } -impl AccessorPropertyType { - pub fn is_abstract(&self) -> bool { - matches!(self, Self::TSAbstractAccessorProperty) - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -3173,22 +1800,6 @@ pub enum ImportDeclarationSpecifier<'a> { ImportNamespaceSpecifier(Box<'a, ImportNamespaceSpecifier<'a>>), } -impl<'a> ImportDeclarationSpecifier<'a> { - pub fn name(&self) -> CompactStr { - match self { - ImportDeclarationSpecifier::ImportSpecifier(specifier) => { - specifier.local.name.to_compact_str() - } - ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { - specifier.local.name.to_compact_str() - } - ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { - specifier.local.name.to_compact_str() - } - } - } -} - // import {imported} from "source" // import {imported as local} from "source" #[visited_node] @@ -3256,15 +1867,6 @@ pub enum ImportAttributeKey<'a> { StringLiteral(StringLiteral<'a>), } -impl<'a> ImportAttributeKey<'a> { - pub fn as_atom(&self) -> Atom<'a> { - match self { - Self::Identifier(identifier) => identifier.name.clone(), - Self::StringLiteral(literal) => literal.value.clone(), - } - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -3281,13 +1883,6 @@ pub struct ExportNamedDeclaration<'a> { pub with_clause: Option>, } -impl<'a> ExportNamedDeclaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - self.export_kind == ImportOrExportKind::Type - || self.declaration.as_ref().map_or(false, Declaration::is_typescript_syntax) - } -} - /// Export Default Declaration /// export default HoistableDeclaration /// export default ClassDeclaration @@ -3303,12 +1898,6 @@ pub struct ExportDefaultDeclaration<'a> { pub exported: ModuleExportName<'a>, // `default` } -impl<'a> ExportDefaultDeclaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - self.declaration.is_typescript_syntax() - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -3322,12 +1911,6 @@ pub struct ExportAllDeclaration<'a> { pub export_kind: ImportOrExportKind, // `export type *` } -impl<'a> ExportAllDeclaration<'a> { - pub fn is_typescript_syntax(&self) -> bool { - self.export_kind.is_type() - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -3340,12 +1923,6 @@ pub struct ExportSpecifier<'a> { pub export_kind: ImportOrExportKind, // `export type *` } -impl<'a> ExportSpecifier<'a> { - pub fn new(span: Span, local: ModuleExportName<'a>, exported: ModuleExportName<'a>) -> Self { - Self { span, local, exported, export_kind: ImportOrExportKind::Value } - } -} - inherit_variants! { /// Export Default Declaration Kind /// @@ -3368,18 +1945,6 @@ pub enum ExportDefaultDeclarationKind<'a> { } } -impl<'a> ExportDefaultDeclarationKind<'a> { - #[inline] - pub fn is_typescript_syntax(&self) -> bool { - match self { - Self::FunctionDeclaration(func) => func.is_typescript_syntax(), - Self::ClassDeclaration(class) => class.is_typescript_syntax(), - Self::TSInterfaceDeclaration(_) => true, - _ => false, - } - } -} - /// Support: /// * `import {"\0 any unicode" as foo} from ""` /// * `export {foo as "\0 any unicode"}` @@ -3393,22 +1958,3 @@ pub enum ModuleExportName<'a> { Identifier(IdentifierName<'a>), StringLiteral(StringLiteral<'a>), } - -impl<'a> fmt::Display for ModuleExportName<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let s = match self { - Self::Identifier(identifier) => identifier.name.to_string(), - Self::StringLiteral(literal) => format!(r#""{}""#, literal.value), - }; - write!(f, "{s}") - } -} - -impl<'a> ModuleExportName<'a> { - pub fn name(&self) -> Atom<'a> { - match self { - Self::Identifier(identifier) => identifier.name.clone(), - Self::StringLiteral(literal) => literal.value.clone(), - } - } -} diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index 9d756ebdfcc5f..cc52455afa46e 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -6,10 +6,7 @@ // Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` #![allow(non_snake_case)] -use std::{ - fmt, - hash::{Hash, Hasher}, -}; +use std::hash::Hash; use bitflags::bitflags; use oxc_ast_macros::visited_node; @@ -30,20 +27,6 @@ pub struct BooleanLiteral { pub value: bool, } -impl BooleanLiteral { - pub fn new(span: Span, value: bool) -> Self { - Self { span, value } - } - - pub fn as_str(&self) -> &'static str { - if self.value { - "true" - } else { - "false" - } - } -} - #[visited_node] #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -53,18 +36,6 @@ pub struct NullLiteral { pub span: Span, } -impl Hash for NullLiteral { - fn hash(&self, state: &mut H) { - None::.hash(state); - } -} - -impl NullLiteral { - pub fn new(span: Span) -> Self { - Self { span } - } -} - #[visited_node] #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -78,45 +49,6 @@ pub struct NumericLiteral<'a> { pub base: NumberBase, } -impl<'a> NumericLiteral<'a> { - pub fn new(span: Span, value: f64, raw: &'a str, base: NumberBase) -> Self { - Self { span, value, raw, base } - } - - /// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/base/JSCompDoubles.java#L113) - /// - #[allow(clippy::cast_possible_truncation)] // for `as i32` - pub fn ecmascript_to_int32(num: f64) -> i32 { - // Fast path for most common case. Also covers -0.0 - let int32_value = num as i32; - if (f64::from(int32_value) - num).abs() < f64::EPSILON { - return int32_value; - } - - // NaN, Infinity if not included in our NumericLiteral, so we just serde(skip) step 2. - - // step 3 - let pos_int = num.signum() * num.abs().floor(); - - // step 4 - let int32bit = pos_int % 2f64.powi(32); - - // step5 - if int32bit >= 2f64.powi(31) { - (int32bit - 2f64.powi(32)) as i32 - } else { - int32bit as i32 - } - } -} - -impl<'a> Hash for NumericLiteral<'a> { - fn hash(&self, state: &mut H) { - self.base.hash(state); - self.raw.hash(state); - } -} - #[visited_node] #[derive(Debug, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -129,12 +61,6 @@ pub struct BigIntLiteral<'a> { pub base: BigintBase, } -impl<'a> BigIntLiteral<'a> { - pub fn is_zero(&self) -> bool { - self.raw == "0n" - } -} - #[visited_node] #[derive(Debug, Clone, Hash)] #[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] @@ -155,10 +81,18 @@ pub struct RegExp<'a> { pub flags: RegExpFlags, } -impl<'a> fmt::Display for RegExp<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "/{}/{}", self.pattern, self.flags) - } +#[derive(Debug, Clone, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] +pub struct EmptyObject; + +#[visited_node] +#[derive(Debug, Clone, Hash)] +#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] +#[cfg_attr(feature = "serialize", serde(tag = "type"))] +pub struct StringLiteral<'a> { + #[cfg_attr(feature = "serialize", serde(flatten))] + pub span: Span, + pub value: Atom<'a>, } bitflags! { @@ -190,88 +124,3 @@ export type RegExpFlags = { V: 128 }; "#; - -impl TryFrom for RegExpFlags { - type Error = char; - - fn try_from(value: char) -> Result { - match value { - 'g' => Ok(Self::G), - 'i' => Ok(Self::I), - 'm' => Ok(Self::M), - 's' => Ok(Self::S), - 'u' => Ok(Self::U), - 'y' => Ok(Self::Y), - 'd' => Ok(Self::D), - 'v' => Ok(Self::V), - _ => Err(value), - } - } -} - -impl fmt::Display for RegExpFlags { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.contains(Self::G) { - write!(f, "g")?; - } - if self.contains(Self::I) { - write!(f, "i")?; - } - if self.contains(Self::M) { - write!(f, "m")?; - } - if self.contains(Self::S) { - write!(f, "s")?; - } - if self.contains(Self::U) { - write!(f, "u")?; - } - if self.contains(Self::Y) { - write!(f, "y")?; - } - if self.contains(Self::D) { - write!(f, "d")?; - } - if self.contains(Self::V) { - write!(f, "v")?; - } - Ok(()) - } -} - -#[derive(Debug, Clone, Hash)] -#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] -pub struct EmptyObject; - -#[visited_node] -#[derive(Debug, Clone, Hash)] -#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))] -#[cfg_attr(feature = "serialize", serde(tag = "type"))] -pub struct StringLiteral<'a> { - #[cfg_attr(feature = "serialize", serde(flatten))] - pub span: Span, - pub value: Atom<'a>, -} - -impl<'a> StringLiteral<'a> { - pub fn new(span: Span, value: Atom<'a>) -> Self { - Self { span, value } - } - - /// Static Semantics: `IsStringWellFormedUnicode` - /// test for \uD800-\uDFFF - pub fn is_string_well_formed_unicode(&self) -> bool { - let mut chars = self.value.chars(); - while let Some(c) = chars.next() { - if c == '\\' && chars.next() == Some('u') { - let hex = &chars.as_str()[..4]; - if let Ok(hex) = u32::from_str_radix(hex, 16) { - if (0xd800..=0xdfff).contains(&hex) { - return false; - } - }; - } - } - true - } -} diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs new file mode 100644 index 0000000000000..8ca8c38a66d6d --- /dev/null +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -0,0 +1,1464 @@ +// NB: `#[visited_node]` attribute on AST nodes does not do anything to the code in this file. +// It is purely a marker for codegen used in `oxc_traverse`. See docs in that crate. + +use crate::ast::*; + +use std::{cell::Cell, fmt, hash::Hash}; + +use oxc_allocator::{Box, Vec}; +use oxc_span::{Atom, CompactStr, SourceType, Span}; +use oxc_syntax::{ + operator::UnaryOperator, + reference::{ReferenceFlag, ReferenceId}, + scope::ScopeFlags, +}; + +#[cfg(feature = "serialize")] +#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] +const TS_APPEND_CONTENT: &'static str = r#" +export interface BindingIdentifier extends Span { type: "Identifier", name: Atom } +export interface IdentifierReference extends Span { type: "Identifier", name: Atom } +export interface IdentifierName extends Span { type: "Identifier", name: Atom } +export interface LabelIdentifier extends Span { type: "Identifier", name: Atom } +export interface AssignmentTargetRest extends Span { type: "RestElement", argument: AssignmentTarget } +export interface BindingRestElement extends Span { type: "RestElement", argument: BindingPattern } +export interface FormalParameterRest extends Span { + type: "RestElement", + argument: BindingPatternKind, + typeAnnotation?: TSTypeAnnotation, + optional: boolean, +} +"#; + +impl<'a> Program<'a> { + pub fn new( + span: Span, + source_type: SourceType, + directives: Vec<'a, Directive<'a>>, + hashbang: Option>, + body: Vec<'a, Statement<'a>>, + ) -> Self { + Self { span, source_type, directives, hashbang, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for Program<'a> { + fn hash(&self, state: &mut H) { + self.source_type.hash(state); + self.directives.hash(state); + self.hashbang.hash(state); + self.body.hash(state); + } +} + +impl<'a> Program<'a> { + pub fn is_empty(&self) -> bool { + self.body.is_empty() && self.directives.is_empty() + } + + pub fn is_strict(&self) -> bool { + self.source_type.is_strict() || self.directives.iter().any(Directive::is_use_strict) + } +} + +impl<'a> Expression<'a> { + pub fn is_typescript_syntax(&self) -> bool { + matches!( + self, + Self::TSAsExpression(_) + | Self::TSSatisfiesExpression(_) + | Self::TSTypeAssertion(_) + | Self::TSNonNullExpression(_) + | Self::TSInstantiationExpression(_) + ) + } + + pub fn is_primary_expression(&self) -> bool { + self.is_literal() + || matches!( + self, + Self::Identifier(_) + | Self::ThisExpression(_) + | Self::FunctionExpression(_) + | Self::ClassExpression(_) + | Self::ParenthesizedExpression(_) + | Self::ArrayExpression(_) + | Self::ObjectExpression(_) + ) + } + + pub fn is_literal(&self) -> bool { + // Note: TemplateLiteral is not `Literal` + matches!( + self, + Self::BooleanLiteral(_) + | Self::NullLiteral(_) + | Self::NumericLiteral(_) + | Self::BigintLiteral(_) + | Self::RegExpLiteral(_) + | Self::StringLiteral(_) + ) + } + + pub fn is_string_literal(&self) -> bool { + matches!(self, Self::StringLiteral(_) | Self::TemplateLiteral(_)) + } + + pub fn is_specific_string_literal(&self, string: &str) -> bool { + match self { + Self::StringLiteral(s) => s.value == string, + _ => false, + } + } + + /// Determines whether the given expr is a `null` literal + pub fn is_null(&self) -> bool { + matches!(self, Expression::NullLiteral(_)) + } + + /// Determines whether the given expr is a `undefined` literal + pub fn is_undefined(&self) -> bool { + matches!(self, Self::Identifier(ident) if ident.name == "undefined") + } + + /// Determines whether the given expr is a `void expr` + pub fn is_void(&self) -> bool { + matches!(self, Self::UnaryExpression(expr) if expr.operator == UnaryOperator::Void) + } + + /// Determines whether the given expr is a `void 0` + pub fn is_void_0(&self) -> bool { + match self { + Self::UnaryExpression(expr) if expr.operator == UnaryOperator::Void => { + matches!(&expr.argument, Self::NumericLiteral(lit) if lit.value == 0.0) + } + _ => false, + } + } + + /// Determines whether the given expr is a `0` + pub fn is_number_0(&self) -> bool { + matches!(self, Self::NumericLiteral(lit) if lit.value == 0.0) + } + + pub fn is_number(&self, val: f64) -> bool { + matches!(self, Self::NumericLiteral(lit) if (lit.value - val).abs() < f64::EPSILON) + } + + /// 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) + } + + /// Determines whether the given expr evaluate to `undefined` + pub fn evaluate_to_undefined(&self) -> bool { + self.is_undefined() || self.is_void() + } + + /// Determines whether the given expr is a `null` or `undefined` or `void 0` + pub fn is_null_or_undefined(&self) -> bool { + self.is_null() || self.evaluate_to_undefined() + } + + /// Determines whether the given expr is a `NaN` literal + pub fn is_nan(&self) -> bool { + matches!(self, Self::Identifier(ident) if ident.name == "NaN") + } + + /// Remove nested parentheses from this expression. + pub fn without_parenthesized(&self) -> &Self { + match self { + Expression::ParenthesizedExpression(expr) => expr.expression.without_parenthesized(), + _ => self, + } + } + + pub fn is_specific_id(&self, name: &str) -> bool { + match self.get_inner_expression() { + Expression::Identifier(ident) => ident.name == name, + _ => false, + } + } + + pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool { + match self.get_inner_expression() { + expr if expr.is_member_expression() => { + expr.to_member_expression().is_specific_member_access(object, property) + } + Expression::ChainExpression(chain) => { + let Some(expr) = chain.expression.as_member_expression() else { + return false; + }; + expr.is_specific_member_access(object, property) + } + _ => false, + } + } + + pub fn get_inner_expression(&self) -> &Expression<'a> { + match self { + Expression::ParenthesizedExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSAsExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSSatisfiesExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSInstantiationExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSNonNullExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSTypeAssertion(expr) => expr.expression.get_inner_expression(), + _ => self, + } + } + + pub fn is_identifier_reference(&self) -> bool { + matches!(self, Expression::Identifier(_)) + } + + pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> { + match self.get_inner_expression() { + Expression::Identifier(ident) => Some(ident), + _ => None, + } + } + + pub fn is_function(&self) -> bool { + matches!(self, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_)) + } + + pub fn is_call_expression(&self) -> bool { + matches!(self, Expression::CallExpression(_)) + } + + pub fn is_super_call_expression(&self) -> bool { + matches!(self, Expression::CallExpression(expr) if matches!(&expr.callee, Expression::Super(_))) + } + + pub fn is_call_like_expression(&self) -> bool { + self.is_call_expression() + && matches!(self, Expression::NewExpression(_) | Expression::ImportExpression(_)) + } + + pub fn is_binaryish(&self) -> bool { + matches!(self, Expression::BinaryExpression(_) | Expression::LogicalExpression(_)) + } + + /// Returns literal's value converted to the Boolean type + /// returns `true` when node is truthy, `false` when node is falsy, `None` when it cannot be determined. + pub fn get_boolean_value(&self) -> Option { + match self { + Self::BooleanLiteral(lit) => Some(lit.value), + Self::NullLiteral(_) => Some(false), + Self::NumericLiteral(lit) => Some(lit.value != 0.0), + Self::BigintLiteral(lit) => Some(!lit.is_zero()), + Self::RegExpLiteral(_) => Some(true), + Self::StringLiteral(lit) => Some(!lit.value.is_empty()), + _ => None, + } + } + + pub fn get_member_expr(&self) -> Option<&MemberExpression<'a>> { + match self.get_inner_expression() { + Expression::ChainExpression(chain_expr) => chain_expr.expression.as_member_expression(), + expr => expr.as_member_expression(), + } + } + + pub fn is_immutable_value(&self) -> bool { + match self { + Self::BooleanLiteral(_) + | Self::NullLiteral(_) + | Self::NumericLiteral(_) + | Self::BigintLiteral(_) + | Self::RegExpLiteral(_) + | Self::StringLiteral(_) => true, + Self::TemplateLiteral(lit) if lit.is_no_substitution_template() => true, + Self::UnaryExpression(unary_expr) => unary_expr.argument.is_immutable_value(), + Self::Identifier(ident) => { + matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN") + } + _ => false, + } + } +} + +impl<'a> IdentifierName<'a> { + pub fn new(span: Span, name: Atom<'a>) -> Self { + Self { span, name } + } +} + +impl<'a> Hash for IdentifierReference<'a> { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl<'a> IdentifierReference<'a> { + pub fn new(span: Span, name: Atom<'a>) -> Self { + Self { span, name, reference_id: Cell::default(), reference_flag: ReferenceFlag::default() } + } + + pub fn new_read(span: Span, name: Atom<'a>, reference_id: Option) -> Self { + Self { + span, + name, + reference_id: Cell::new(reference_id), + reference_flag: ReferenceFlag::Read, + } + } +} + +impl<'a> Hash for BindingIdentifier<'a> { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl<'a> BindingIdentifier<'a> { + pub fn new(span: Span, name: Atom<'a>) -> Self { + Self { span, name, symbol_id: Cell::default() } + } +} + +impl<'a> ArrayExpressionElement<'a> { + pub fn is_elision(&self) -> bool { + matches!(self, Self::Elision(_)) + } +} + +impl<'a> ObjectExpression<'a> { + pub fn has_proto(&self) -> bool { + use crate::syntax_directed_operations::PropName; + self.properties.iter().any(|p| p.prop_name().is_some_and(|name| name.0 == "__proto__")) + } +} + +impl<'a> PropertyKey<'a> { + pub fn static_name(&self) -> Option { + match self { + Self::StaticIdentifier(ident) => Some(ident.name.to_compact_str()), + Self::StringLiteral(lit) => Some(lit.value.to_compact_str()), + Self::RegExpLiteral(lit) => Some(lit.regex.to_string().into()), + Self::NumericLiteral(lit) => Some(lit.value.to_string().into()), + Self::BigintLiteral(lit) => Some(lit.raw.to_compact_str()), + Self::NullLiteral(_) => Some("null".into()), + Self::TemplateLiteral(lit) => lit + .expressions + .is_empty() + .then(|| lit.quasi()) + .flatten() + .map(|quasi| quasi.to_compact_str()), + _ => None, + } + } + + pub fn is_specific_static_name(&self, name: &str) -> bool { + self.static_name().is_some_and(|n| n == name) + } + + pub fn is_identifier(&self) -> bool { + matches!(self, Self::PrivateIdentifier(_) | Self::StaticIdentifier(_)) + } + + pub fn is_private_identifier(&self) -> bool { + matches!(self, Self::PrivateIdentifier(_)) + } + + pub fn private_name(&self) -> Option> { + match self { + Self::PrivateIdentifier(ident) => Some(ident.name.clone()), + _ => None, + } + } + + pub fn name(&self) -> Option { + if self.is_private_identifier() { + self.private_name().map(|name| name.to_compact_str()) + } else { + self.static_name() + } + } + + pub fn is_specific_id(&self, name: &str) -> bool { + match self { + PropertyKey::StaticIdentifier(ident) => ident.name == name, + _ => false, + } + } + + pub fn is_specific_string_literal(&self, string: &str) -> bool { + matches!(self, Self::StringLiteral(s) if s.value == string) + } +} + +impl<'a> TemplateLiteral<'a> { + pub fn is_no_substitution_template(&self) -> bool { + self.expressions.is_empty() && self.quasis.len() == 1 + } + + /// Get single quasi from `template` + pub fn quasi(&self) -> Option> { + self.quasis.first().and_then(|quasi| quasi.value.cooked.clone()) + } +} + +impl<'a> MemberExpression<'a> { + pub fn is_computed(&self) -> bool { + matches!(self, MemberExpression::ComputedMemberExpression(_)) + } + + pub fn optional(&self) -> bool { + match self { + MemberExpression::ComputedMemberExpression(expr) => expr.optional, + MemberExpression::StaticMemberExpression(expr) => expr.optional, + MemberExpression::PrivateFieldExpression(expr) => expr.optional, + } + } + + pub fn object(&self) -> &Expression<'a> { + match self { + MemberExpression::ComputedMemberExpression(expr) => &expr.object, + MemberExpression::StaticMemberExpression(expr) => &expr.object, + MemberExpression::PrivateFieldExpression(expr) => &expr.object, + } + } + + pub fn static_property_name(&self) -> Option<&str> { + match self { + MemberExpression::ComputedMemberExpression(expr) => { + expr.static_property_name().map(|name| name.as_str()) + } + MemberExpression::StaticMemberExpression(expr) => Some(expr.property.name.as_str()), + MemberExpression::PrivateFieldExpression(_) => None, + } + } + + pub fn static_property_info(&self) -> Option<(Span, &str)> { + match self { + MemberExpression::ComputedMemberExpression(expr) => match &expr.expression { + Expression::StringLiteral(lit) => Some((lit.span, &lit.value)), + Expression::TemplateLiteral(lit) => { + if lit.expressions.is_empty() && lit.quasis.len() == 1 { + Some((lit.span, &lit.quasis[0].value.raw)) + } else { + None + } + } + _ => None, + }, + MemberExpression::StaticMemberExpression(expr) => { + Some((expr.property.span, &expr.property.name)) + } + MemberExpression::PrivateFieldExpression(_) => None, + } + } + + pub fn through_optional_is_specific_member_access(&self, object: &str, property: &str) -> bool { + let object_matches = match self.object().without_parenthesized() { + Expression::ChainExpression(x) => match &x.expression { + ChainElement::CallExpression(_) => false, + match_member_expression!(ChainElement) => { + let member_expr = x.expression.to_member_expression(); + member_expr.object().without_parenthesized().is_specific_id(object) + } + }, + x => x.is_specific_id(object), + }; + + let property_matches = self.static_property_name().is_some_and(|p| p == property); + + object_matches && property_matches + } + + /// Whether it is a static member access `object.property` + pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool { + self.object().is_specific_id(object) + && self.static_property_name().is_some_and(|p| p == property) + } +} + +impl<'a> ComputedMemberExpression<'a> { + pub fn static_property_name(&self) -> Option> { + match &self.expression { + Expression::StringLiteral(lit) => Some(lit.value.clone()), + Expression::TemplateLiteral(lit) + if lit.expressions.is_empty() && lit.quasis.len() == 1 => + { + Some(lit.quasis[0].value.raw.clone()) + } + _ => None, + } + } +} + +impl<'a> StaticMemberExpression<'a> { + pub fn get_first_object(&self) -> &Expression<'a> { + match &self.object { + Expression::StaticMemberExpression(member) => { + if let Expression::StaticMemberExpression(expr) = &member.object { + expr.get_first_object() + } else { + &self.object + } + } + Expression::ChainExpression(chain) => { + if let ChainElement::StaticMemberExpression(expr) = &chain.expression { + expr.get_first_object() + } else { + &self.object + } + } + _ => &self.object, + } + } +} + +impl<'a> CallExpression<'a> { + pub fn callee_name(&self) -> Option<&str> { + match &self.callee { + Expression::Identifier(ident) => Some(ident.name.as_str()), + expr => expr.as_member_expression().and_then(MemberExpression::static_property_name), + } + } + + pub fn is_require_call(&self) -> bool { + if self.arguments.len() != 1 { + return false; + } + if let Expression::Identifier(id) = &self.callee { + id.name == "require" + && matches!( + self.arguments.first(), + Some(Argument::StringLiteral(_) | Argument::TemplateLiteral(_)), + ) + } else { + false + } + } + + pub fn is_symbol_or_symbol_for_call(&self) -> bool { + // TODO: is 'Symbol' reference to global object + match &self.callee { + Expression::Identifier(id) => id.name == "Symbol", + expr => match expr.as_member_expression() { + Some(member) => { + matches!(member.object(), Expression::Identifier(id) if id.name == "Symbol") + && member.static_property_name() == Some("for") + } + None => false, + }, + } + } + + pub fn common_js_require(&self) -> Option<&StringLiteral> { + if !(self.callee.is_specific_id("require") && self.arguments.len() == 1) { + return None; + } + match &self.arguments[0] { + Argument::StringLiteral(str_literal) => Some(str_literal), + _ => None, + } + } +} + +impl Argument<'_> { + pub fn is_spread(&self) -> bool { + matches!(self, Self::SpreadElement(_)) + } +} + +impl<'a> AssignmentTarget<'a> { + pub fn get_identifier(&self) -> Option<&str> { + self.as_simple_assignment_target().and_then(|it| it.get_identifier()) + } + + pub fn get_expression(&self) -> Option<&Expression<'a>> { + self.as_simple_assignment_target().and_then(|it| it.get_expression()) + } +} + +impl<'a> SimpleAssignmentTarget<'a> { + pub fn get_identifier(&self) -> Option<&str> { + match self { + Self::AssignmentTargetIdentifier(ident) => Some(ident.name.as_str()), + match_member_expression!(Self) => self.to_member_expression().static_property_name(), + _ => None, + } + } + + pub fn get_expression(&self) -> Option<&Expression<'a>> { + match self { + Self::TSAsExpression(expr) => Some(&expr.expression), + Self::TSSatisfiesExpression(expr) => Some(&expr.expression), + Self::TSNonNullExpression(expr) => Some(&expr.expression), + Self::TSTypeAssertion(expr) => Some(&expr.expression), + _ => None, + } + } +} + +impl<'a> ArrayAssignmentTarget<'a> { + pub fn new_with_elements( + span: Span, + elements: Vec<'a, Option>>, + ) -> Self { + Self { span, elements, rest: None, trailing_comma: None } + } +} + +impl<'a> ObjectAssignmentTarget<'a> { + pub fn new_with_properties( + span: Span, + properties: Vec<'a, AssignmentTargetProperty<'a>>, + ) -> Self { + Self { span, properties, rest: None } + } + + pub fn is_empty(&self) -> bool { + self.properties.is_empty() && self.rest.is_none() + } + + pub fn len(&self) -> usize { + self.properties.len() + usize::from(self.rest.is_some()) + } +} + +impl<'a> AssignmentTargetMaybeDefault<'a> { + pub fn name(&self) -> Option { + match self { + AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(id) => Some(id.name.clone()), + Self::AssignmentTargetWithDefault(target) => { + if let AssignmentTarget::AssignmentTargetIdentifier(id) = &target.binding { + Some(id.name.clone()) + } else { + None + } + } + _ => None, + } + } +} + +impl<'a> Statement<'a> { + pub fn is_typescript_syntax(&self) -> bool { + match self { + match_declaration!(Self) => { + self.as_declaration().is_some_and(Declaration::is_typescript_syntax) + } + match_module_declaration!(Self) => { + self.as_module_declaration().is_some_and(ModuleDeclaration::is_typescript_syntax) + } + _ => false, + } + } + + pub fn is_iteration_statement(&self) -> bool { + matches!( + self, + Statement::DoWhileStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) + | Statement::ForStatement(_) + | Statement::WhileStatement(_) + ) + } +} + +impl<'a> Directive<'a> { + /// A Use Strict Directive is an ExpressionStatement in a Directive Prologue whose StringLiteral is either of the exact code point sequences "use strict" or 'use strict'. + /// A Use Strict Directive may not contain an EscapeSequence or LineContinuation. + /// + pub fn is_use_strict(&self) -> bool { + self.directive == "use strict" + } +} + +impl<'a> BlockStatement<'a> { + pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { + Self { span, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for BlockStatement<'a> { + fn hash(&self, state: &mut H) { + self.body.hash(state); + } +} + +impl<'a> Declaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + match self { + Self::VariableDeclaration(decl) => decl.is_typescript_syntax(), + Self::FunctionDeclaration(func) => func.is_typescript_syntax(), + Self::ClassDeclaration(class) => class.is_typescript_syntax(), + Self::UsingDeclaration(_) => false, + _ => true, + } + } + + pub fn id(&self) -> Option<&BindingIdentifier<'a>> { + match self { + Declaration::FunctionDeclaration(decl) => decl.id.as_ref(), + Declaration::ClassDeclaration(decl) => decl.id.as_ref(), + Declaration::TSTypeAliasDeclaration(decl) => Some(&decl.id), + Declaration::TSInterfaceDeclaration(decl) => Some(&decl.id), + Declaration::TSEnumDeclaration(decl) => Some(&decl.id), + Declaration::TSImportEqualsDeclaration(decl) => Some(&decl.id), + _ => None, + } + } + + pub fn modifiers(&self) -> Option<&Modifiers<'a>> { + match self { + Declaration::VariableDeclaration(decl) => Some(&decl.modifiers), + Declaration::FunctionDeclaration(decl) => Some(&decl.modifiers), + Declaration::ClassDeclaration(decl) => Some(&decl.modifiers), + Declaration::TSEnumDeclaration(decl) => Some(&decl.modifiers), + Declaration::TSTypeAliasDeclaration(decl) => Some(&decl.modifiers), + Declaration::TSModuleDeclaration(decl) => Some(&decl.modifiers), + Declaration::TSInterfaceDeclaration(decl) => Some(&decl.modifiers), + _ => None, + } + } +} + +impl<'a> VariableDeclaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + self.modifiers.contains(ModifierKind::Declare) + } + + pub fn has_init(&self) -> bool { + self.declarations.iter().any(|decl| decl.init.is_some()) + } +} + +impl VariableDeclarationKind { + pub fn is_var(&self) -> bool { + matches!(self, Self::Var) + } + + pub fn is_const(&self) -> bool { + matches!(self, Self::Const) + } + + pub fn is_lexical(&self) -> bool { + matches!(self, Self::Const | Self::Let) + } + + pub fn as_str(&self) -> &'static str { + match self { + Self::Var => "var", + Self::Const => "const", + Self::Let => "let", + } + } +} + +impl fmt::Display for VariableDeclarationKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self.as_str(); + write!(f, "{s}") + } +} + +impl<'a> ForStatement<'a> { + pub fn new( + span: Span, + init: Option>, + test: Option>, + update: Option>, + body: Statement<'a>, + ) -> Self { + Self { span, init, test, update, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for ForStatement<'a> { + fn hash(&self, state: &mut H) { + self.init.hash(state); + self.test.hash(state); + self.update.hash(state); + self.body.hash(state); + } +} + +impl<'a> ForStatementInit<'a> { + /// LexicalDeclaration[In, Yield, Await] : + /// LetOrConst BindingList[?In, ?Yield, ?Await] ; + pub fn is_lexical_declaration(&self) -> bool { + matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical()) + } +} + +impl<'a> ForInStatement<'a> { + pub fn new( + span: Span, + left: ForStatementLeft<'a>, + right: Expression<'a>, + body: Statement<'a>, + ) -> Self { + Self { span, left, right, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for ForInStatement<'a> { + fn hash(&self, state: &mut H) { + self.left.hash(state); + self.right.hash(state); + self.body.hash(state); + } +} + +impl<'a> ForOfStatement<'a> { + pub fn new( + span: Span, + r#await: bool, + left: ForStatementLeft<'a>, + right: Expression<'a>, + body: Statement<'a>, + ) -> Self { + Self { span, r#await, left, right, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for ForOfStatement<'a> { + fn hash(&self, state: &mut H) { + self.r#await.hash(state); + self.left.hash(state); + self.right.hash(state); + self.body.hash(state); + } +} + +impl<'a> ForStatementLeft<'a> { + /// LexicalDeclaration[In, Yield, Await] : + /// LetOrConst BindingList[?In, ?Yield, ?Await] ; + pub fn is_lexical_declaration(&self) -> bool { + matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical()) + } +} + +impl<'a> SwitchStatement<'a> { + pub fn new(span: Span, discriminant: Expression<'a>, cases: Vec<'a, SwitchCase<'a>>) -> Self { + Self { span, discriminant, cases, scope_id: Cell::default() } + } +} + +impl<'a> Hash for SwitchStatement<'a> { + fn hash(&self, state: &mut H) { + self.discriminant.hash(state); + self.cases.hash(state); + } +} + +impl<'a> SwitchCase<'a> { + pub fn is_default_case(&self) -> bool { + self.test.is_none() + } +} +impl<'a> CatchClause<'a> { + pub fn new( + span: Span, + param: Option>, + body: Box<'a, BlockStatement<'a>>, + ) -> Self { + Self { span, param, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for CatchClause<'a> { + fn hash(&self, state: &mut H) { + self.param.hash(state); + self.body.hash(state); + } +} + +impl<'a> BindingPattern<'a> { + pub fn new_with_kind(kind: BindingPatternKind<'a>) -> Self { + Self { kind, type_annotation: None, optional: false } + } + + pub fn get_identifier(&self) -> Option> { + self.kind.get_identifier() + } + + pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { + self.kind.get_binding_identifier() + } +} + +impl<'a> BindingPatternKind<'a> { + pub fn get_identifier(&self) -> Option> { + match self { + Self::BindingIdentifier(ident) => Some(ident.name.clone()), + Self::AssignmentPattern(assign) => assign.left.get_identifier(), + _ => None, + } + } + + pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { + match self { + Self::BindingIdentifier(ident) => Some(ident), + Self::AssignmentPattern(assign) => assign.left.get_binding_identifier(), + _ => None, + } + } + + pub fn is_destructuring_pattern(&self) -> bool { + match self { + Self::ObjectPattern(_) | Self::ArrayPattern(_) => true, + Self::AssignmentPattern(pattern) => pattern.left.kind.is_destructuring_pattern(), + Self::BindingIdentifier(_) => false, + } + } + + pub fn is_binding_identifier(&self) -> bool { + matches!(self, Self::BindingIdentifier(_)) + } + + pub fn is_assignment_pattern(&self) -> bool { + matches!(self, Self::AssignmentPattern(_)) + } +} + +impl<'a> ObjectPattern<'a> { + pub fn is_empty(&self) -> bool { + self.properties.is_empty() && self.rest.is_none() + } + + pub fn len(&self) -> usize { + self.properties.len() + usize::from(self.rest.is_some()) + } +} + +impl<'a> ArrayPattern<'a> { + pub fn is_empty(&self) -> bool { + self.elements.is_empty() && self.rest.is_none() + } + + pub fn len(&self) -> usize { + self.elements.len() + usize::from(self.rest.is_some()) + } +} + +impl<'a> Function<'a> { + #![allow(clippy::too_many_arguments)] + pub fn new( + r#type: FunctionType, + span: Span, + id: Option>, + generator: bool, + r#async: bool, + this_param: Option>, + params: Box<'a, FormalParameters<'a>>, + body: Option>>, + type_parameters: Option>>, + return_type: Option>>, + modifiers: Modifiers<'a>, + ) -> Self { + Self { + r#type, + span, + id, + generator, + r#async, + this_param, + params, + body, + type_parameters, + return_type, + modifiers, + scope_id: Cell::default(), + } + } + + pub fn is_typescript_syntax(&self) -> bool { + matches!( + self.r#type, + FunctionType::TSDeclareFunction | FunctionType::TSEmptyBodyFunctionExpression + ) || self.body.is_none() + || self.modifiers.contains(ModifierKind::Declare) + } + + pub fn is_expression(&self) -> bool { + self.r#type == FunctionType::FunctionExpression + } + + pub fn is_function_declaration(&self) -> bool { + matches!(self.r#type, FunctionType::FunctionDeclaration) + } + + pub fn is_ts_declare_function(&self) -> bool { + matches!(self.r#type, FunctionType::TSDeclareFunction) + } + + pub fn is_declaration(&self) -> bool { + matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction) + } + + pub fn is_strict(&self) -> bool { + self.body.as_ref().is_some_and(|body| body.has_use_strict_directive()) + } +} + +impl<'a> Hash for Function<'a> { + fn hash(&self, state: &mut H) { + self.r#type.hash(state); + self.id.hash(state); + self.generator.hash(state); + self.r#async.hash(state); + self.this_param.hash(state); + self.params.hash(state); + self.body.hash(state); + self.type_parameters.hash(state); + self.return_type.hash(state); + self.modifiers.hash(state); + } +} + +impl<'a> FormalParameters<'a> { + pub fn parameters_count(&self) -> usize { + self.items.len() + self.rest.as_ref().map_or(0, |_| 1) + } + + /// Iterates over all bound parameters, including rest parameters. + pub fn iter_bindings(&self) -> impl Iterator> + '_ { + self.items + .iter() + .map(|param| ¶m.pattern) + .chain(self.rest.iter().map(|rest| &rest.argument)) + } +} + +impl<'a> FormalParameter<'a> { + pub fn is_public(&self) -> bool { + matches!(self.accessibility, Some(TSAccessibility::Public)) + } +} + +impl FormalParameterKind { + pub fn is_signature(&self) -> bool { + matches!(self, Self::Signature) + } +} + +impl<'a> FormalParameters<'a> { + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} +impl<'a> FunctionBody<'a> { + pub fn is_empty(&self) -> bool { + self.directives.is_empty() && self.statements.is_empty() + } + + pub fn has_use_strict_directive(&self) -> bool { + self.directives.iter().any(Directive::is_use_strict) + } +} + +impl<'a> ArrowFunctionExpression<'a> { + pub fn new( + span: Span, + expression: bool, + r#async: bool, + params: Box<'a, FormalParameters<'a>>, + body: Box<'a, FunctionBody<'a>>, + type_parameters: Option>>, + return_type: Option>>, + ) -> Self { + Self { + span, + expression, + r#async, + params, + body, + type_parameters, + return_type, + scope_id: Cell::default(), + } + } + + /// Get expression part of `ArrowFunctionExpression`: `() => expression_part`. + pub fn get_expression(&self) -> Option<&Expression<'a>> { + if self.expression { + if let Statement::ExpressionStatement(expr_stmt) = &self.body.statements[0] { + return Some(&expr_stmt.expression); + } + } + None + } +} + +impl<'a> Hash for ArrowFunctionExpression<'a> { + fn hash(&self, state: &mut H) { + self.expression.hash(state); + self.r#async.hash(state); + self.params.hash(state); + self.body.hash(state); + self.type_parameters.hash(state); + self.return_type.hash(state); + } +} + +impl<'a> Class<'a> { + #[allow(clippy::too_many_arguments)] + pub fn new( + r#type: ClassType, + span: Span, + decorators: Vec<'a, Decorator<'a>>, + id: Option>, + super_class: Option>, + body: Box<'a, ClassBody<'a>>, + type_parameters: Option>>, + super_type_parameters: Option>>, + implements: Option>>, + modifiers: Modifiers<'a>, + ) -> Self { + Self { + r#type, + span, + decorators, + id, + super_class, + body, + type_parameters, + super_type_parameters, + implements, + modifiers, + scope_id: Cell::default(), + } + } + + /// `true` if this [`Class`] is an expression. + /// + /// For example, + /// ```ts + /// var Foo = class { /* ... */ } + /// ``` + pub fn is_expression(&self) -> bool { + self.r#type == ClassType::ClassExpression + } + + /// `true` if this [`Class`] is a declaration statement. + /// + /// For example, + /// ```ts + /// class Foo { + /// // ... + /// } + /// ``` + /// + /// Not to be confused with [`Class::is_declare`]. + pub fn is_declaration(&self) -> bool { + self.r#type == ClassType::ClassDeclaration + } + + /// `true` if this [`Class`] is being within a typescript declaration file + /// or `declare` statement. + /// + /// For example, + /// ```ts + /// declare global { + /// declare class Foo { + /// // ... + /// } + /// } + /// + /// Not to be confused with [`Class::is_declaration`]. + pub fn is_declare(&self) -> bool { + self.modifiers.contains(ModifierKind::Declare) + } + + pub fn is_typescript_syntax(&self) -> bool { + self.is_declare() + } +} + +impl<'a> Hash for Class<'a> { + fn hash(&self, state: &mut H) { + self.r#type.hash(state); + self.decorators.hash(state); + self.id.hash(state); + self.super_class.hash(state); + self.body.hash(state); + self.type_parameters.hash(state); + self.super_type_parameters.hash(state); + self.implements.hash(state); + self.modifiers.hash(state); + } +} + +impl<'a> ClassElement<'a> { + pub fn r#static(&self) -> bool { + match self { + Self::TSIndexSignature(_) | Self::StaticBlock(_) => false, + Self::MethodDefinition(def) => def.r#static, + Self::PropertyDefinition(def) => def.r#static, + Self::AccessorProperty(def) => def.r#static, + } + } + + pub fn computed(&self) -> bool { + match self { + Self::TSIndexSignature(_) | Self::StaticBlock(_) => false, + Self::MethodDefinition(def) => def.computed, + Self::PropertyDefinition(def) => def.computed, + Self::AccessorProperty(def) => def.computed, + } + } + + pub fn accessibility(&self) -> Option { + match self { + Self::StaticBlock(_) | Self::TSIndexSignature(_) | Self::AccessorProperty(_) => None, + Self::MethodDefinition(def) => def.accessibility, + Self::PropertyDefinition(def) => def.accessibility, + } + } + + pub fn method_definition_kind(&self) -> Option { + match self { + Self::TSIndexSignature(_) + | Self::StaticBlock(_) + | Self::PropertyDefinition(_) + | Self::AccessorProperty(_) => None, + Self::MethodDefinition(def) => Some(def.kind), + } + } + + pub fn property_key(&self) -> Option<&PropertyKey<'a>> { + match self { + Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, + Self::MethodDefinition(def) => Some(&def.key), + Self::PropertyDefinition(def) => Some(&def.key), + Self::AccessorProperty(def) => Some(&def.key), + } + } + + pub fn static_name(&self) -> Option { + match self { + Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, + Self::MethodDefinition(def) => def.key.static_name(), + Self::PropertyDefinition(def) => def.key.static_name(), + Self::AccessorProperty(def) => def.key.static_name(), + } + } + + pub fn is_property(&self) -> bool { + matches!(self, Self::PropertyDefinition(_) | Self::AccessorProperty(_)) + } + + pub fn is_ts_empty_body_function(&self) -> bool { + match self { + Self::PropertyDefinition(_) + | Self::StaticBlock(_) + | Self::AccessorProperty(_) + | Self::TSIndexSignature(_) => false, + Self::MethodDefinition(method) => method.value.body.is_none(), + } + } + + pub fn is_typescript_syntax(&self) -> bool { + match self { + Self::TSIndexSignature(_) => true, + Self::MethodDefinition(method) => method.value.is_typescript_syntax(), + Self::PropertyDefinition(property) => { + property.r#type == PropertyDefinitionType::TSAbstractPropertyDefinition + } + Self::AccessorProperty(property) => property.r#type.is_abstract(), + Self::StaticBlock(_) => false, + } + } + + pub fn has_decorator(&self) -> bool { + match self { + Self::MethodDefinition(method) => !method.decorators.is_empty(), + Self::PropertyDefinition(property) => !property.decorators.is_empty(), + Self::AccessorProperty(property) => !property.decorators.is_empty(), + Self::StaticBlock(_) | Self::TSIndexSignature(_) => false, + } + } +} + +impl MethodDefinitionKind { + pub fn is_constructor(&self) -> bool { + matches!(self, Self::Constructor) + } + + pub fn is_method(&self) -> bool { + matches!(self, Self::Method) + } + + pub fn is_set(&self) -> bool { + matches!(self, Self::Set) + } + + pub fn is_get(&self) -> bool { + matches!(self, Self::Get) + } + + pub fn scope_flags(self) -> ScopeFlags { + match self { + Self::Constructor => ScopeFlags::Constructor | ScopeFlags::Function, + Self::Method => ScopeFlags::Function, + Self::Get => ScopeFlags::GetAccessor | ScopeFlags::Function, + Self::Set => ScopeFlags::SetAccessor | ScopeFlags::Function, + } + } +} + +impl<'a> PrivateIdentifier<'a> { + pub fn new(span: Span, name: Atom<'a>) -> Self { + Self { span, name } + } +} + +impl<'a> StaticBlock<'a> { + pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { + Self { span, body, scope_id: Cell::default() } + } +} + +impl<'a> Hash for StaticBlock<'a> { + fn hash(&self, state: &mut H) { + self.body.hash(state); + } +} + +impl<'a> ModuleDeclaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + match self { + ModuleDeclaration::ImportDeclaration(_) => false, + ModuleDeclaration::ExportDefaultDeclaration(decl) => decl.is_typescript_syntax(), + ModuleDeclaration::ExportNamedDeclaration(decl) => decl.is_typescript_syntax(), + ModuleDeclaration::ExportAllDeclaration(decl) => decl.is_typescript_syntax(), + ModuleDeclaration::TSNamespaceExportDeclaration(_) + | ModuleDeclaration::TSExportAssignment(_) => true, + } + } + + pub fn is_import(&self) -> bool { + matches!(self, Self::ImportDeclaration(_)) + } + + pub fn is_export(&self) -> bool { + matches!( + self, + Self::ExportAllDeclaration(_) + | Self::ExportDefaultDeclaration(_) + | Self::ExportNamedDeclaration(_) + | Self::TSExportAssignment(_) + | Self::TSNamespaceExportDeclaration(_) + ) + } + + pub fn is_default_export(&self) -> bool { + matches!(self, Self::ExportDefaultDeclaration(_)) + } + + pub fn source(&self) -> Option<&StringLiteral<'a>> { + match self { + Self::ImportDeclaration(decl) => Some(&decl.source), + Self::ExportAllDeclaration(decl) => Some(&decl.source), + Self::ExportNamedDeclaration(decl) => decl.source.as_ref(), + Self::ExportDefaultDeclaration(_) + | Self::TSExportAssignment(_) + | Self::TSNamespaceExportDeclaration(_) => None, + } + } + + pub fn with_clause(&self) -> Option<&WithClause<'a>> { + match self { + Self::ImportDeclaration(decl) => decl.with_clause.as_ref(), + Self::ExportAllDeclaration(decl) => decl.with_clause.as_ref(), + Self::ExportNamedDeclaration(decl) => decl.with_clause.as_ref(), + Self::ExportDefaultDeclaration(_) + | Self::TSExportAssignment(_) + | Self::TSNamespaceExportDeclaration(_) => None, + } + } +} + +impl AccessorPropertyType { + pub fn is_abstract(&self) -> bool { + matches!(self, Self::TSAbstractAccessorProperty) + } +} + +impl<'a> ImportDeclarationSpecifier<'a> { + pub fn name(&self) -> CompactStr { + match self { + ImportDeclarationSpecifier::ImportSpecifier(specifier) => { + specifier.local.name.to_compact_str() + } + ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { + specifier.local.name.to_compact_str() + } + ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { + specifier.local.name.to_compact_str() + } + } + } +} + +impl<'a> ImportAttributeKey<'a> { + pub fn as_atom(&self) -> Atom<'a> { + match self { + Self::Identifier(identifier) => identifier.name.clone(), + Self::StringLiteral(literal) => literal.value.clone(), + } + } +} + +impl<'a> ExportNamedDeclaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + self.export_kind == ImportOrExportKind::Type + || self.declaration.as_ref().map_or(false, Declaration::is_typescript_syntax) + } +} + +impl<'a> ExportDefaultDeclaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + self.declaration.is_typescript_syntax() + } +} + +impl<'a> ExportAllDeclaration<'a> { + pub fn is_typescript_syntax(&self) -> bool { + self.export_kind.is_type() + } +} + +impl<'a> ExportSpecifier<'a> { + pub fn new(span: Span, local: ModuleExportName<'a>, exported: ModuleExportName<'a>) -> Self { + Self { span, local, exported, export_kind: ImportOrExportKind::Value } + } +} + +impl<'a> ExportDefaultDeclarationKind<'a> { + #[inline] + pub fn is_typescript_syntax(&self) -> bool { + match self { + Self::FunctionDeclaration(func) => func.is_typescript_syntax(), + Self::ClassDeclaration(class) => class.is_typescript_syntax(), + Self::TSInterfaceDeclaration(_) => true, + _ => false, + } + } +} + +impl<'a> fmt::Display for ModuleExportName<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + Self::Identifier(identifier) => identifier.name.to_string(), + Self::StringLiteral(literal) => format!(r#""{}""#, literal.value), + }; + write!(f, "{s}") + } +} + +impl<'a> ModuleExportName<'a> { + pub fn name(&self) -> Atom<'a> { + match self { + Self::Identifier(identifier) => identifier.name.clone(), + Self::StringLiteral(literal) => literal.value.clone(), + } + } +} diff --git a/crates/oxc_ast/src/ast_impl/literal.rs b/crates/oxc_ast/src/ast_impl/literal.rs new file mode 100644 index 0000000000000..25e99ae1b299b --- /dev/null +++ b/crates/oxc_ast/src/ast_impl/literal.rs @@ -0,0 +1,165 @@ +//! Literals + +// NB: `#[visited_node]` attribute on AST nodes does not do anything to the code in this file. +// It is purely a marker for codegen used in `oxc_traverse`. See docs in that crate. + +// Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]` +#![allow(non_snake_case)] + +use crate::ast::*; + +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use oxc_span::{Atom, Span}; +use oxc_syntax::number::NumberBase; + +impl BooleanLiteral { + pub fn new(span: Span, value: bool) -> Self { + Self { span, value } + } + + pub fn as_str(&self) -> &'static str { + if self.value { + "true" + } else { + "false" + } + } +} + +impl Hash for NullLiteral { + fn hash(&self, state: &mut H) { + None::.hash(state); + } +} + +impl NullLiteral { + pub fn new(span: Span) -> Self { + Self { span } + } +} + +impl<'a> NumericLiteral<'a> { + pub fn new(span: Span, value: f64, raw: &'a str, base: NumberBase) -> Self { + Self { span, value, raw, base } + } + + /// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/base/JSCompDoubles.java#L113) + /// + #[allow(clippy::cast_possible_truncation)] // for `as i32` + pub fn ecmascript_to_int32(num: f64) -> i32 { + // Fast path for most common case. Also covers -0.0 + let int32_value = num as i32; + if (f64::from(int32_value) - num).abs() < f64::EPSILON { + return int32_value; + } + + // NaN, Infinity if not included in our NumericLiteral, so we just serde(skip) step 2. + + // step 3 + let pos_int = num.signum() * num.abs().floor(); + + // step 4 + let int32bit = pos_int % 2f64.powi(32); + + // step5 + if int32bit >= 2f64.powi(31) { + (int32bit - 2f64.powi(32)) as i32 + } else { + int32bit as i32 + } + } +} + +impl<'a> Hash for NumericLiteral<'a> { + fn hash(&self, state: &mut H) { + self.base.hash(state); + self.raw.hash(state); + } +} + +impl<'a> BigIntLiteral<'a> { + pub fn is_zero(&self) -> bool { + self.raw == "0n" + } +} + +impl<'a> fmt::Display for RegExp<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "/{}/{}", self.pattern, self.flags) + } +} + +impl TryFrom for RegExpFlags { + type Error = char; + + fn try_from(value: char) -> Result { + match value { + 'g' => Ok(Self::G), + 'i' => Ok(Self::I), + 'm' => Ok(Self::M), + 's' => Ok(Self::S), + 'u' => Ok(Self::U), + 'y' => Ok(Self::Y), + 'd' => Ok(Self::D), + 'v' => Ok(Self::V), + _ => Err(value), + } + } +} + +impl fmt::Display for RegExpFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.contains(Self::G) { + write!(f, "g")?; + } + if self.contains(Self::I) { + write!(f, "i")?; + } + if self.contains(Self::M) { + write!(f, "m")?; + } + if self.contains(Self::S) { + write!(f, "s")?; + } + if self.contains(Self::U) { + write!(f, "u")?; + } + if self.contains(Self::Y) { + write!(f, "y")?; + } + if self.contains(Self::D) { + write!(f, "d")?; + } + if self.contains(Self::V) { + write!(f, "v")?; + } + Ok(()) + } +} + +impl<'a> StringLiteral<'a> { + pub fn new(span: Span, value: Atom<'a>) -> Self { + Self { span, value } + } + + /// Static Semantics: `IsStringWellFormedUnicode` + /// test for \uD800-\uDFFF + pub fn is_string_well_formed_unicode(&self) -> bool { + let mut chars = self.value.chars(); + while let Some(c) = chars.next() { + if c == '\\' && chars.next() == Some('u') { + let hex = &chars.as_str()[..4]; + if let Ok(hex) = u32::from_str_radix(hex, 16) { + if (0xd800..=0xdfff).contains(&hex) { + return false; + } + }; + } + } + true + } +} diff --git a/crates/oxc_ast/src/ast_impl/mod.rs b/crates/oxc_ast/src/ast_impl/mod.rs new file mode 100644 index 0000000000000..0008512a63a27 --- /dev/null +++ b/crates/oxc_ast/src/ast_impl/mod.rs @@ -0,0 +1,2 @@ +mod js; +mod literal; diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index 5e79653d9059e..dc77219b1eeac 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -14,6 +14,7 @@ mod serialize; pub mod ast; mod ast_builder; +mod ast_impl; mod ast_kind; pub mod precedence; mod span; diff --git a/crates/oxc_traverse/src/traverse.rs b/crates/oxc_traverse/src/traverse.rs index 1819d1ebcb913..c76d5d52f8944 100644 --- a/crates/oxc_traverse/src/traverse.rs +++ b/crates/oxc_traverse/src/traverse.rs @@ -731,12 +731,6 @@ pub trait Traverse<'a> { #[inline] fn exit_for_in_statement(&mut self, node: &mut ForInStatement<'a>, ctx: &mut TraverseCtx<'a>) {} - #[inline] - fn enter_for_of_statement(&mut self, node: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) { - } - #[inline] - fn exit_for_of_statement(&mut self, node: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) {} - #[inline] fn enter_for_statement_left( &mut self, @@ -752,6 +746,12 @@ pub trait Traverse<'a> { ) { } + #[inline] + fn enter_for_of_statement(&mut self, node: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) { + } + #[inline] + fn exit_for_of_statement(&mut self, node: &mut ForOfStatement<'a>, ctx: &mut TraverseCtx<'a>) {} + #[inline] fn enter_continue_statement( &mut self, diff --git a/crates/oxc_traverse/src/walk.rs b/crates/oxc_traverse/src/walk.rs index d77d8a68aa387..12ce0e1455938 100644 --- a/crates/oxc_traverse/src/walk.rs +++ b/crates/oxc_traverse/src/walk.rs @@ -1763,6 +1763,36 @@ pub(crate) unsafe fn walk_for_in_statement<'a, Tr: Traverse<'a>>( } } +pub(crate) unsafe fn walk_for_statement_left<'a, Tr: Traverse<'a>>( + traverser: &mut Tr, + node: *mut ForStatementLeft<'a>, + ctx: &mut TraverseCtx<'a>, +) { + traverser.enter_for_statement_left(&mut *node, ctx); + match &mut *node { + ForStatementLeft::VariableDeclaration(node) => { + walk_variable_declaration(traverser, (&mut **node) as *mut _, ctx) + } + ForStatementLeft::UsingDeclaration(node) => { + walk_using_declaration(traverser, (&mut **node) as *mut _, ctx) + } + ForStatementLeft::AssignmentTargetIdentifier(_) + | ForStatementLeft::TSAsExpression(_) + | ForStatementLeft::TSSatisfiesExpression(_) + | ForStatementLeft::TSNonNullExpression(_) + | ForStatementLeft::TSTypeAssertion(_) + | ForStatementLeft::TSInstantiationExpression(_) + | ForStatementLeft::ArrayAssignmentTarget(_) + | ForStatementLeft::ObjectAssignmentTarget(_) + | ForStatementLeft::ComputedMemberExpression(_) + | ForStatementLeft::StaticMemberExpression(_) + | ForStatementLeft::PrivateFieldExpression(_) => { + walk_assignment_target(traverser, node as *mut _, ctx) + } + } + traverser.exit_for_statement_left(&mut *node, ctx); +} + pub(crate) unsafe fn walk_for_of_statement<'a, Tr: Traverse<'a>>( traverser: &mut Tr, node: *mut ForOfStatement<'a>, @@ -1802,36 +1832,6 @@ pub(crate) unsafe fn walk_for_of_statement<'a, Tr: Traverse<'a>>( } } -pub(crate) unsafe fn walk_for_statement_left<'a, Tr: Traverse<'a>>( - traverser: &mut Tr, - node: *mut ForStatementLeft<'a>, - ctx: &mut TraverseCtx<'a>, -) { - traverser.enter_for_statement_left(&mut *node, ctx); - match &mut *node { - ForStatementLeft::VariableDeclaration(node) => { - walk_variable_declaration(traverser, (&mut **node) as *mut _, ctx) - } - ForStatementLeft::UsingDeclaration(node) => { - walk_using_declaration(traverser, (&mut **node) as *mut _, ctx) - } - ForStatementLeft::AssignmentTargetIdentifier(_) - | ForStatementLeft::TSAsExpression(_) - | ForStatementLeft::TSSatisfiesExpression(_) - | ForStatementLeft::TSNonNullExpression(_) - | ForStatementLeft::TSTypeAssertion(_) - | ForStatementLeft::TSInstantiationExpression(_) - | ForStatementLeft::ArrayAssignmentTarget(_) - | ForStatementLeft::ObjectAssignmentTarget(_) - | ForStatementLeft::ComputedMemberExpression(_) - | ForStatementLeft::StaticMemberExpression(_) - | ForStatementLeft::PrivateFieldExpression(_) => { - walk_assignment_target(traverser, node as *mut _, ctx) - } - } - traverser.exit_for_statement_left(&mut *node, ctx); -} - pub(crate) unsafe fn walk_continue_statement<'a, Tr: Traverse<'a>>( traverser: &mut Tr, node: *mut ContinueStatement<'a>,