diff --git a/Cargo.toml b/Cargo.toml index e3f854961fc26..a362cab4816af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ repository = "https://github.com/Boshen/oxc" version = "0.0.0" [workspace.dependencies] +bitflags = "1.3.2" bumpalo = "3.12.0" compact_str = "0.6.1" miette = "5.5.0" diff --git a/crates/oxc_ast/Cargo.toml b/crates/oxc_ast/Cargo.toml index 28217a9465ffe..7a591a9ceacbe 100644 --- a/crates/oxc_ast/Cargo.toml +++ b/crates/oxc_ast/Cargo.toml @@ -10,5 +10,14 @@ repository.workspace = true version.workspace = true [dependencies] -thiserror = { workspace = true } +oxc_allocator = { path = "../oxc_allocator" } + +bitflags = { workspace = true } compact_str = { workspace = true, features = ["serde"] } +thiserror = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, optional = true } + +num-bigint = "0.4.3" +ryu-js = "0.2.2" +ordered-float = { version = "3.4.0", features = ["serde"] } diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs new file mode 100644 index 0000000000000..946b6908c1bd8 --- /dev/null +++ b/crates/oxc_ast/src/ast/js.rs @@ -0,0 +1,1761 @@ +use std::fmt::Display; + +use oxc_allocator::{Box, Vec}; +use serde::Serialize; + +use super::{ + AssignmentOperator, BigintLiteral, BinaryOperator, BooleanLiteral, Decorator, + ImportOrExportKind, JSXElement, JSXFragment, LogicalOperator, NullLiteral, NumberLiteral, + RegExpLiteral, StringLiteral, TSAbstractMethodDefinition, TSAbstractPropertyDefinition, + TSAccessibility, TSAsExpression, TSClassImplements, TSEnumDeclaration, TSExportAssignment, + TSImportEqualsDeclaration, TSIndexSignature, TSInstantiationExpression, TSInterfaceDeclaration, + TSModuleDeclaration, TSNamespaceExportDeclaration, TSNonNullExpression, TSTypeAliasDeclaration, + TSTypeAnnotation, TSTypeAssertion, TSTypeParameterDeclaration, TSTypeParameterInstantiation, + UnaryOperator, UpdateOperator, +}; +use crate::{Atom, Node, SourceType}; + +#[derive(Debug, PartialEq, Hash)] +pub struct Program<'a> { + pub node: Node, + pub directives: Vec<'a, Directive<'a>>, + pub body: Vec<'a, Statement<'a>>, + pub source_type: SourceType, +} + +// SAFETY: The AST is part of the bump allocator, +// it is our responsibility to never simultaneously mutate across threads. +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl<'a> Send for Program<'a> {} +unsafe impl<'a> Sync for Program<'a> {} + +impl<'a> Program<'a> { + #[must_use] + pub fn is_empty(&self) -> bool { + self.body.is_empty() + } +} + +/// Section 13 Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum Expression<'a> { + BooleanLiteral(Box<'a, BooleanLiteral>), + NullLiteral(Box<'a, NullLiteral>), + NumberLiteral(Box<'a, NumberLiteral<'a>>), + BigintLiteral(Box<'a, BigintLiteral>), + RegExpLiteral(Box<'a, RegExpLiteral>), + StringLiteral(Box<'a, StringLiteral>), + TemplateLiteral(Box<'a, TemplateLiteral<'a>>), + + Identifier(Box<'a, IdentifierReference>), + + MetaProperty(Box<'a, MetaProperty>), + Super(Box<'a, Super>), + + ArrayExpression(Box<'a, ArrayExpression<'a>>), + ArrowFunctionExpression(Box<'a, ArrowExpression<'a>>), + AssignmentExpression(Box<'a, AssignmentExpression<'a>>), + AwaitExpression(Box<'a, AwaitExpression<'a>>), + BinaryExpression(Box<'a, BinaryExpression<'a>>), + CallExpression(Box<'a, CallExpression<'a>>), + ChainExpression(Box<'a, ChainExpression<'a>>), + ClassExpression(Box<'a, Class<'a>>), + ConditionalExpression(Box<'a, ConditionalExpression<'a>>), + FunctionExpression(Box<'a, Function<'a>>), + ImportExpression(Box<'a, ImportExpression<'a>>), + LogicalExpression(Box<'a, LogicalExpression<'a>>), + MemberExpression(Box<'a, MemberExpression<'a>>), + NewExpression(Box<'a, NewExpression<'a>>), + ObjectExpression(Box<'a, ObjectExpression<'a>>), + ParenthesizedExpression(Box<'a, ParenthesizedExpression<'a>>), + SequenceExpression(Box<'a, SequenceExpression<'a>>), + TaggedTemplateExpression(Box<'a, TaggedTemplateExpression<'a>>), + ThisExpression(Box<'a, ThisExpression>), + UnaryExpression(Box<'a, UnaryExpression<'a>>), + UpdateExpression(Box<'a, UpdateExpression<'a>>), + YieldExpression(Box<'a, YieldExpression<'a>>), + PrivateInExpression(Box<'a, PrivateInExpression<'a>>), + + JSXElement(Box<'a, JSXElement<'a>>), + JSXFragment(Box<'a, JSXFragment<'a>>), + + TSAsExpression(Box<'a, TSAsExpression<'a>>), + TSTypeAssertion(Box<'a, TSTypeAssertion<'a>>), + TSNonNullExpression(Box<'a, TSNonNullExpression<'a>>), + TSInstantiationExpression(Box<'a, TSInstantiationExpression<'a>>), +} + +impl<'a> Expression<'a> { + /// `PrimaryExpression` + /// [tc39/ecma262#prod-PrimaryExpression](https://tc39.es/ecma262/#prod-PrimaryExpression) + #[must_use] + pub const fn is_primary_expression(&self) -> bool { + self.is_literal_expression() + || matches!( + self, + Self::Identifier(_) + | Self::ThisExpression(_) + | Self::FunctionExpression(_) + | Self::ClassExpression(_) + | Self::ParenthesizedExpression(_) + | Self::ArrayExpression(_) + | Self::ObjectExpression(_) + ) + } + + #[must_use] + pub const fn is_literal_expression(&self) -> bool { + matches!( + self, + Self::BooleanLiteral(_) + | Self::NullLiteral(_) + | Self::NumberLiteral(_) + | Self::BigintLiteral(_) + | Self::RegExpLiteral(_) + | Self::StringLiteral(_) // TemplateLiteral is not `Literal` type per oxc_ast + ) + } + + #[must_use] + pub const fn is_string_literal(&self) -> bool { + matches!(self, Self::StringLiteral(_) | Self::TemplateLiteral(_)) + } + + /// Determines whether the given expr is a `null` literal + #[must_use] + pub const fn is_null(&self) -> bool { + matches!(self, Expression::NullLiteral(_)) + } + + /// Determines whether the given expr is a `undefined` literal + #[must_use] + pub fn is_undefined(&self) -> bool { + matches!(self, Self::Identifier(ident) if ident.name == "undefined") + } + + /// Remove nested parentheses from this expression. + #[must_use] + pub fn without_parenthesized(&self) -> &Self { + match self { + Expression::ParenthesizedExpression(Box(expr)) => { + expr.expression.without_parenthesized() + } + _ => self, + } + } + + #[must_use] + pub fn is_specific_id(&self, name: &str) -> bool { + match self.get_inner_expression() { + Expression::Identifier(ident) => ident.name == name, + _ => false, + } + } + + #[must_use] + 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::TSInstantiationExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSNonNullExpression(expr) => expr.expression.get_inner_expression(), + Expression::TSTypeAssertion(expr) => expr.expression.get_inner_expression(), + _ => self, + } + } + + #[must_use] + pub fn get_identifier_reference(&self) -> Option<&IdentifierReference> { + match self.get_inner_expression() { + Expression::Identifier(ident) => Some(ident), + _ => None, + } + } +} + +/// Section 12.6 `IdentifierName` +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +#[cfg_attr(feature = "acorn", serde(rename = "Identifier"))] +pub struct IdentifierName { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +/// Section 13.1 `IdentifierReference` +#[derive(Debug, Clone, Serialize, PartialEq, Hash, Eq)] +#[serde(tag = "type")] +#[cfg_attr(feature = "acorn", serde(rename = "Identifier"))] +pub struct IdentifierReference { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +/// Section 13.1 `BindingIdentifier` +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +#[cfg_attr(feature = "acorn", serde(rename = "Identifier"))] +pub struct BindingIdentifier { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +/// Section 13.1 `LabelIdentifier` +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +#[cfg_attr(feature = "acorn", serde(rename = "Identifier"))] +pub struct LabelIdentifier { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +/// Section 13.2.2 This Expression +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ThisExpression { + #[serde(flatten)] + pub node: Node, +} + +/// Section 13.2.5 Array Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ArrayExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub elements: Vec<'a, Option>>, + #[cfg_attr(feature = "acorn", serde(skip))] + pub trailing_comma: Option, +} + +/// Section 13.2.6 Object Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ObjectExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub properties: Vec<'a, ObjectProperty<'a>>, + #[cfg_attr(feature = "acorn", serde(skip))] + pub trailing_comma: Option, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ObjectProperty<'a> { + Property(Box<'a, Property<'a>>), + SpreadProperty(Box<'a, SpreadElement<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct Property<'a> { + #[serde(flatten)] + pub node: Node, + pub kind: PropertyKind, + pub key: PropertyKey<'a>, + pub value: PropertyValue<'a>, + pub method: bool, + pub shorthand: bool, + pub computed: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum PropertyKey<'a> { + Identifier(IdentifierName), + PrivateIdentifier(PrivateIdentifier), + Expression(Expression<'a>), +} + +impl<'a> PropertyKey<'a> { + #[must_use] + pub fn static_name(&self) -> Option { + match self { + Self::Identifier(ident) => Some(ident.name.clone()), + Self::PrivateIdentifier(_) => None, + Self::Expression(expr) => match expr { + Expression::StringLiteral(lit) => Some(lit.value.clone()), + Expression::RegExpLiteral(lit) => Some(Atom::from(lit.regex.to_string())), + Expression::NumberLiteral(lit) => Some(Atom::from(lit.value.to_string())), + Expression::BigintLiteral(lit) => Some(Atom::from(lit.value.to_string())), + Expression::NullLiteral(_) => Some("null".into()), + Expression::TemplateLiteral(lit) => { + lit.expressions.is_empty().then(|| lit.quasi()).flatten().cloned() + } + _ => None, + }, + } + } + + #[must_use] + pub const fn is_private_identifier(&self) -> bool { + matches!(self, Self::PrivateIdentifier(_)) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum PropertyValue<'a> { + // for AssignmentProperty in ObjectPattern https://github.com/oxc_ast/oxc_ast/blob/master/es2015.md#objectpattern + Pattern(BindingPattern<'a>), + Expression(Expression<'a>), +} + +#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum PropertyKind { + Init, + Get, + Set, +} + +/// Section 13.2.9 Template Literal +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TemplateLiteral<'a> { + #[serde(flatten)] + pub node: Node, + pub quasis: Vec<'a, TemplateElement>, + pub expressions: Vec<'a, Expression<'a>>, +} + +impl<'a> TemplateLiteral<'a> { + #[must_use] + pub fn is_no_substitution_template(&self) -> bool { + self.expressions.is_empty() && self.quasis.len() == 1 + } + + /// Get single quasi from `template` + #[must_use] + pub fn quasi(&self) -> Option<&Atom> { + self.quasis.first().and_then(|quasi| quasi.value.cooked.as_ref()) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TaggedTemplateExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub tag: Expression<'a>, + pub quasi: TemplateLiteral<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TemplateElement { + #[serde(flatten)] + pub node: Node, + pub tail: bool, + pub value: TemplateElementValue, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +pub struct TemplateElementValue { + pub raw: Atom, + pub cooked: Option, +} + +/// Section 13.3 Member Expression +#[derive(Debug, PartialEq, Hash)] +pub enum MemberExpression<'a> { + ComputedMemberExpression(ComputedMemberExpression<'a>), + StaticMemberExpression(StaticMemberExpression<'a>), + PrivateFieldExpression(PrivateFieldExpression<'a>), +} + +impl<'a> MemberExpression<'a> { + #[must_use] + pub const fn optional(&self) -> bool { + match self { + MemberExpression::ComputedMemberExpression(expr) => expr.optional, + MemberExpression::StaticMemberExpression(expr) => expr.optional, + MemberExpression::PrivateFieldExpression(expr) => expr.optional, + } + } + + #[must_use] + pub const fn object(&self) -> &Expression<'a> { + match self { + MemberExpression::ComputedMemberExpression(expr) => &expr.object, + MemberExpression::StaticMemberExpression(expr) => &expr.object, + MemberExpression::PrivateFieldExpression(expr) => &expr.object, + } + } + + #[must_use] + pub fn static_property_name(&'a self) -> Option<&'a str> { + match self { + MemberExpression::ComputedMemberExpression(expr) => match &expr.expression { + Expression::StringLiteral(lit) => Some(&lit.value), + Expression::TemplateLiteral(lit) => { + if lit.expressions.is_empty() && lit.quasis.len() == 1 { + Some(&lit.quasis[0].value.raw) + } else { + None + } + } + _ => None, + }, + MemberExpression::StaticMemberExpression(expr) => Some(&expr.property.name), + MemberExpression::PrivateFieldExpression(_) => None, + } + } +} + +#[derive(Debug, PartialEq, Hash)] +pub struct ComputedMemberExpression<'a> { + pub node: Node, + pub object: Expression<'a>, + pub expression: Expression<'a>, + pub optional: bool, // for optional chaining +} + +#[derive(Debug, PartialEq, Hash)] +pub struct StaticMemberExpression<'a> { + pub node: Node, + pub object: Expression<'a>, + pub property: IdentifierName, + pub optional: bool, // for optional chaining +} + +#[derive(Debug, PartialEq, Hash)] +pub struct PrivateFieldExpression<'a> { + pub node: Node, + pub object: Expression<'a>, + pub field: PrivateIdentifier, + pub optional: bool, // for optional chaining +} + +/// Section 13.3 Call Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct CallExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub callee: Expression<'a>, + pub arguments: Vec<'a, Argument<'a>>, + pub optional: bool, // for optional chaining + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +impl<'a> CallExpression<'a> { + #[must_use] + 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::Expression( + Expression::StringLiteral(_) | Expression::TemplateLiteral(_), + )), + ) + } else { + false + } + } + + #[must_use] + pub fn is_symbol_or_symbol_for_call(&'a self) -> bool { + // TODO: is 'Symbol' reference to global object + match &self.callee { + Expression::Identifier(id) => id.name == "Symbol", + Expression::MemberExpression(member) => { + matches!(member.object(), Expression::Identifier(id) if id.name == "Symbol") + && member.static_property_name() == Some("for") + } + _ => false, + } + } + + #[must_use] + pub fn common_js_require(&self) -> Option<&StringLiteral> { + if let Expression::Identifier(ident) = &self.callee + && ident.name =="require" + && self.arguments.len() == 1 + && let Argument::Expression(Expression::StringLiteral(str_literal)) = &self.arguments[0] { + Some(str_literal) + } else { + None + } + } +} + +/// Section 13.3 New Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct NewExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub callee: Expression<'a>, + pub arguments: Vec<'a, Argument<'a>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +/// Section 13.3 Meta Property +/// `new.target` | `import.meta` +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct MetaProperty { + #[serde(flatten)] + pub node: Node, + pub meta: IdentifierName, + pub property: IdentifierName, +} + +/// Section 13.3 Spread Element +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct SpreadElement<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: Expression<'a>, +} + +/// Section 13.3 Argument +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum Argument<'a> { + SpreadElement(Box<'a, SpreadElement<'a>>), + Expression(Expression<'a>), +} + +/// Section 13.4 Update Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct UpdateExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub operator: UpdateOperator, + pub prefix: bool, + pub argument: SimpleAssignmentTarget<'a>, +} + +/// Section 13.5 Unary Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct UnaryExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub operator: UnaryOperator, + pub prefix: bool, + pub argument: Expression<'a>, +} + +/// Section 13.6 - 13.13 Binary Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct BinaryExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub left: Expression<'a>, + pub operator: BinaryOperator, + pub right: Expression<'a>, +} + +/// `RelationalExpression`[In, Yield, Await] : +/// [+In] `PrivateIdentifier` in `ShiftExpression`[?Yield, ?Await] +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +#[cfg_attr(feature = "acorn", serde(rename = "BinaryExpression"))] +pub struct PrivateInExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub left: PrivateIdentifier, + pub operator: BinaryOperator, // BinaryOperator::In + pub right: Expression<'a>, +} + +/// Section 13.13 Binary Logical Operators +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct LogicalExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub left: Expression<'a>, + pub operator: LogicalOperator, + pub right: Expression<'a>, +} + +/// Section 13.14 Conditional Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ConditionalExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub test: Expression<'a>, + pub consequent: Expression<'a>, + pub alternate: Expression<'a>, +} + +/// Section 13.15 Assignment Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AssignmentExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub operator: AssignmentOperator, + pub left: AssignmentTarget<'a>, + pub right: Expression<'a>, +} + +/// 13.15.5 Destructuring Assignment +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum AssignmentTarget<'a> { + SimpleAssignmentTarget(SimpleAssignmentTarget<'a>), + AssignmentTargetPattern(AssignmentTargetPattern<'a>), +} + +impl<'a> AssignmentTarget<'a> { + #[must_use] + pub const fn is_destructuring_pattern(&self) -> bool { + matches!(self, Self::AssignmentTargetPattern(_)) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum SimpleAssignmentTarget<'a> { + AssignmentTargetIdentifier(Box<'a, IdentifierReference>), + MemberAssignmentTarget(Box<'a, MemberExpression<'a>>), + TSAsExpression(Box<'a, TSAsExpression<'a>>), + TSNonNullExpression(Box<'a, TSNonNullExpression<'a>>), + TSTypeAssertion(Box<'a, TSTypeAssertion<'a>>), +} + +impl<'a> SimpleAssignmentTarget<'a> { + #[must_use] + pub fn get_expression(&self) -> Option<&Expression<'a>> { + match self { + Self::TSAsExpression(expr) => Some(&expr.expression), + Self::TSNonNullExpression(expr) => Some(&expr.expression), + Self::TSTypeAssertion(expr) => Some(&expr.expression), + _ => None, + } + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum AssignmentTargetPattern<'a> { + ArrayAssignmentTarget(Box<'a, ArrayAssignmentTarget<'a>>), + ObjectAssignmentTarget(Box<'a, ObjectAssignmentTarget<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ArrayAssignmentTarget<'a> { + #[serde(flatten)] + pub node: Node, + pub elements: Vec<'a, Option>>, + pub rest: Option>, + pub trailing_comma: Option, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ObjectAssignmentTarget<'a> { + #[serde(flatten)] + pub node: Node, + pub properties: Vec<'a, AssignmentTargetProperty<'a>>, + pub rest: Option>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum AssignmentTargetMaybeDefault<'a> { + AssignmentTarget(Box<'a, AssignmentTarget<'a>>), + AssignmentTargetWithDefault(Box<'a, AssignmentTargetWithDefault<'a>>), +} + +impl<'a> AssignmentTargetMaybeDefault<'a> { + #[must_use] + pub fn name(&self) -> Option { + let target = match self { + Self::AssignmentTarget(target) => target, + Self::AssignmentTargetWithDefault(target) => &target.binding, + }; + + if let AssignmentTarget::SimpleAssignmentTarget( + SimpleAssignmentTarget::AssignmentTargetIdentifier(id), + ) = target + { + Some(id.name.clone()) + } else { + None + } + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AssignmentTargetWithDefault<'a> { + #[serde(flatten)] + pub node: Node, + pub binding: AssignmentTarget<'a>, + pub init: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum AssignmentTargetProperty<'a> { + AssignmentTargetPropertyIdentifier(Box<'a, AssignmentTargetPropertyIdentifier<'a>>), + AssignmentTargetPropertyProperty(Box<'a, AssignmentTargetPropertyProperty<'a>>), +} + +/// `AssignmentProperty`[Yield, Await] : +/// `IdentifierReference`[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AssignmentTargetPropertyIdentifier<'a> { + #[serde(flatten)] + pub node: Node, + pub binding: IdentifierReference, + pub init: Option>, +} + +/// `AssignmentProperty`[Yield, Await] : +/// `PropertyName`[?Yield, ?Await] : `AssignmentElement`[?Yield, ?Await] +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AssignmentTargetPropertyProperty<'a> { + #[serde(flatten)] + pub node: Node, + pub name: PropertyKey<'a>, + pub binding: AssignmentTargetMaybeDefault<'a>, +} + +/// Section 13.16 Sequence Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct SequenceExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expressions: Vec<'a, Expression<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct Super { + #[serde(flatten)] + pub node: Node, +} + +/// Section 15.8 Await Expression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AwaitExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ChainExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: ChainElement<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ChainElement<'a> { + CallExpression(Box<'a, CallExpression<'a>>), + MemberExpression(Box<'a, MemberExpression<'a>>), +} + +// Section 13.2 ParenthesizedExpression +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ParenthesizedExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +/// Section 14 Statements +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum Statement<'a> { + // Statements + BlockStatement(Box<'a, BlockStatement<'a>>), + BreakStatement(Box<'a, BreakStatement>), + ContinueStatement(Box<'a, ContinueStatement>), + DebuggerStatement(Box<'a, DebuggerStatement>), + DoWhileStatement(Box<'a, DoWhileStatement<'a>>), + EmptyStatement(Box<'a, EmptyStatement>), + ExpressionStatement(Box<'a, ExpressionStatement<'a>>), + ForInStatement(Box<'a, ForInStatement<'a>>), + ForOfStatement(Box<'a, ForOfStatement<'a>>), + ForStatement(Box<'a, ForStatement<'a>>), + IfStatement(Box<'a, IfStatement<'a>>), + LabeledStatement(Box<'a, LabeledStatement<'a>>), + ReturnStatement(Box<'a, ReturnStatement<'a>>), + SwitchStatement(Box<'a, SwitchStatement<'a>>), + ThrowStatement(Box<'a, ThrowStatement<'a>>), + TryStatement(Box<'a, TryStatement<'a>>), + WhileStatement(Box<'a, WhileStatement<'a>>), + WithStatement(Box<'a, WithStatement<'a>>), + + ModuleDeclaration(Box<'a, ModuleDeclaration<'a>>), + Declaration(Declaration<'a>), +} + +/// Section 11.2.1 Directive Prologue +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename = "ExpressionStatement")] +pub struct Directive<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: StringLiteral, + // directives should always use the unescaped raw string + pub directive: &'a str, +} + +/// Section 14.2 Block Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct BlockStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Vec<'a, Statement<'a>>, +} + +/// Section 14.3 Declarations and the Variable Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum Declaration<'a> { + VariableDeclaration(Box<'a, VariableDeclaration<'a>>), + FunctionDeclaration(Box<'a, Function<'a>>), + ClassDeclaration(Box<'a, Class<'a>>), + + TSTypeAliasDeclaration(Box<'a, TSTypeAliasDeclaration<'a>>), + TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>), + TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>), + TSModuleDeclaration(Box<'a, TSModuleDeclaration<'a>>), + TSImportEqualsDeclaration(Box<'a, TSImportEqualsDeclaration<'a>>), +} + +impl<'a> Declaration<'a> { + #[must_use] + pub fn is_typescript_syntax(&self) -> bool { + match self { + Self::VariableDeclaration(_) => false, + Self::FunctionDeclaration(func) => func.is_typescript_syntax(), + Self::ClassDeclaration(class) => class.declare, + _ => true, + } + } +} + +/// Section 14.3.2 Variable Declaration +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct VariableDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub kind: VariableDeclarationKind, + pub declarations: Vec<'a, VariableDeclarator<'a>>, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum VariableDeclarationKind { + Var, + Const, + Let, +} + +impl VariableDeclarationKind { + #[must_use] + pub const fn is_const(&self) -> bool { + matches!(self, Self::Const) + } + + #[must_use] + pub const fn is_lexical(&self) -> bool { + matches!(self, Self::Const | Self::Let) + } +} + +impl Display for VariableDeclarationKind { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = match self { + Self::Var => "var", + Self::Const => "const", + Self::Let => "let", + }; + write!(f, "{s}") + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct VariableDeclarator<'a> { + #[serde(flatten)] + pub node: Node, + #[serde(skip)] + pub kind: VariableDeclarationKind, + pub id: BindingPattern<'a>, + pub init: Option>, + #[serde(skip_serializing_if = "crate::is_false")] + pub definite: bool, +} + +/// Section 14.4 Empty Statement +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct EmptyStatement { + #[serde(flatten)] + pub node: Node, +} + +/// Section 14.5 Expression Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ExpressionStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +/// Section 14.6 If Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct IfStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub test: Expression<'a>, + pub consequent: Statement<'a>, + pub alternate: Option>, +} + +/// Section 14.7.2 Do-While Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct DoWhileStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Statement<'a>, + pub test: Expression<'a>, +} + +/// Section 14.7.3 While Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct WhileStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub test: Expression<'a>, + pub body: Statement<'a>, +} + +/// Section 14.7.4 For Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ForStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub init: Option>, + pub test: Option>, + pub update: Option>, + pub body: Statement<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ForStatementInit<'a> { + VariableDeclaration(Box<'a, VariableDeclaration<'a>>), + Expression(Expression<'a>), +} + +/// Section 14.7.5 For-In Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ForInStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub left: ForStatementLeft<'a>, + pub right: Expression<'a>, + pub body: Statement<'a>, +} + +/// Section 14.7.5 For-Of Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ForOfStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub r#await: bool, + pub left: ForStatementLeft<'a>, + pub right: Expression<'a>, + pub body: Statement<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ForStatementLeft<'a> { + VariableDeclaration(Box<'a, VariableDeclaration<'a>>), + AssignmentTarget(AssignmentTarget<'a>), +} + +/// Section 14.8 Continue Statement +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ContinueStatement { + #[serde(flatten)] + pub node: Node, + pub label: Option, +} + +/// Section 14.9 Break Statement +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct BreakStatement { + #[serde(flatten)] + pub node: Node, + pub label: Option, +} + +/// Section 14.10 Return Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ReturnStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: Option>, +} + +/// Section 14.11 With Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct WithStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub object: Expression<'a>, + pub body: Statement<'a>, +} + +/// Section 14.12 Switch Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct SwitchStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub discriminant: Expression<'a>, + pub cases: Vec<'a, SwitchCase<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct SwitchCase<'a> { + #[serde(flatten)] + pub node: Node, + pub test: Option>, + pub consequent: Vec<'a, Statement<'a>>, +} + +/// Section 14.13 Labelled Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct LabeledStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub label: LabelIdentifier, + pub body: Statement<'a>, +} + +/// Section 14.14 Throw Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ThrowStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: Expression<'a>, +} + +/// Section 14.15 Try Statement +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TryStatement<'a> { + #[serde(flatten)] + pub node: Node, + pub block: Box<'a, BlockStatement<'a>>, + pub handler: Option>>, + pub finalizer: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct CatchClause<'a> { + #[serde(flatten)] + pub node: Node, + pub param: Option>, + pub body: Box<'a, BlockStatement<'a>>, +} + +/// Section 14.16 Debugger Statement +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct DebuggerStatement { + #[serde(flatten)] + pub node: Node, +} + +/// Section 14.3.3 Destructuring Binding Patterns +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct BindingPattern<'a> { + #[serde(flatten)] + pub kind: BindingPatternKind<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_annotation: Option>, + #[serde(skip_serializing_if = "crate::is_false")] + pub optional: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum BindingPatternKind<'a> { + BindingIdentifier(Box<'a, BindingIdentifier>), + ObjectPattern(Box<'a, ObjectPattern<'a>>), + ArrayPattern(Box<'a, ArrayPattern<'a>>), + RestElement(Box<'a, RestElement<'a>>), + AssignmentPattern(Box<'a, AssignmentPattern<'a>>), +} + +impl<'a> BindingPatternKind<'a> { + #[must_use] + pub const fn is_destructuring_pattern(&self) -> bool { + matches!(self, Self::ObjectPattern(_) | Self::ArrayPattern(_)) + } + + #[must_use] + pub const fn is_rest_element(&self) -> bool { + matches!(self, Self::RestElement(_)) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AssignmentPattern<'a> { + #[serde(flatten)] + pub node: Node, + pub left: BindingPattern<'a>, + pub right: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ObjectPattern<'a> { + #[serde(flatten)] + pub node: Node, + pub properties: Vec<'a, ObjectPatternProperty<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ObjectPatternProperty<'a> { + Property(Box<'a, Property<'a>>), + RestElement(Box<'a, RestElement<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ArrayPattern<'a> { + #[serde(flatten)] + pub node: Node, + pub elements: Vec<'a, Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct RestElement<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: BindingPattern<'a>, +} + +/// Section 15.2 Function Definitions +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(rename_all = "camelCase")] +#[allow(clippy::struct_excessive_bools)] +pub struct Function<'a> { + pub r#type: FunctionType, + #[serde(flatten)] + pub node: Node, + pub id: Option, + pub expression: bool, + pub generator: bool, + pub r#async: bool, + pub params: FormalParameters<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option>>, + #[serde(skip_serializing_if = "crate::is_false")] + pub declare: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub return_type: Option>, +} + +impl<'a> Function<'a> { + #[must_use] + pub const fn is_typescript_syntax(&self) -> bool { + self.declare || self.body.is_none() + } + + #[must_use] + pub fn is_expression(&self) -> bool { + self.r#type == FunctionType::FunctionExpression + } + + #[must_use] + pub const fn is_function_declaration(&self) -> bool { + matches!(self.r#type, FunctionType::FunctionDeclaration) + } + + #[must_use] + pub const fn is_ts_declare_function(&self) -> bool { + matches!(self.r#type, FunctionType::TSDeclareFunction) + } + + #[must_use] + pub const fn is_declaration(&self) -> bool { + matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction) + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum FunctionType { + FunctionDeclaration, + FunctionExpression, + TSDeclareFunction, +} + +#[derive(Debug, PartialEq, Hash)] +pub struct FormalParameters<'a> { + pub node: Node, + pub kind: FormalParameterKind, + pub items: Vec<'a, FormalParameter<'a>>, +} + +#[derive(Debug, PartialEq, Hash, Serialize)] +pub struct FormalParameter<'a> { + #[serde(flatten)] + pub node: Node, + #[serde(flatten)] + pub pattern: BindingPattern<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub accessibility: Option, + #[serde(skip_serializing_if = "crate::is_false")] + pub readonly: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub decorators: Option>>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FormalParameterKind { + /// https://tc39.es/ecma262/#prod-FormalParameters + FormalParameter, + /// https://tc39.es/ecma262/#prod-UniqueFormalParameters + UniqueFormalParameters, + /// https://tc39.es/ecma262/#prod-ArrowFormalParameters + ArrowFormalParameters, + /// Part of TypeScript type signatures + Signature, +} + +impl<'a> FormalParameters<'a> { + #[must_use] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +#[derive(Debug, PartialEq, Hash)] +pub struct FunctionBody<'a> { + pub node: Node, + pub directives: Vec<'a, Directive<'a>>, + pub statements: Vec<'a, Statement<'a>>, +} + +impl<'a> FunctionBody<'a> { + #[must_use] + pub fn is_empty(&self) -> bool { + self.directives.is_empty() && self.statements.is_empty() + } +} + +/// Section 15.3 Arrow Function Definitions +#[derive(Debug, PartialEq, Hash)] +pub struct ArrowExpression<'a> { + pub node: Node, + pub expression: bool, + pub generator: bool, + pub r#async: bool, + pub params: FormalParameters<'a>, // UniqueFormalParameters in spec + pub body: Box<'a, FunctionBody<'a>>, + + pub type_parameters: Option>>, + pub return_type: Option>, +} + +/// Section 15.5 Generator Function Definitions +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct YieldExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub delegate: bool, + pub argument: Option>, +} + +/// Section 15.7 Class Definitions +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct Class<'a> { + pub r#type: ClassType, + #[serde(flatten)] + pub node: Node, + pub id: Option, + pub super_class: Option>, + pub body: ClassBody<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub super_type_parameters: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub implements: Option>>>, + #[serde(skip_serializing_if = "crate::is_false")] + pub r#abstract: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub decorators: Option>>, + #[serde(skip_serializing_if = "crate::is_false")] + pub declare: bool, +} + +impl<'a> Class<'a> { + #[must_use] + pub fn is_expression(&self) -> bool { + self.r#type == ClassType::ClassExpression + } + + #[must_use] + pub fn is_declaration(&self) -> bool { + self.r#type == ClassType::ClassDeclaration + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum ClassType { + ClassDeclaration, + ClassExpression, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ClassBody<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Vec<'a, ClassElement<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ClassElement<'a> { + StaticBlock(Box<'a, StaticBlock<'a>>), + MethodDefinition(Box<'a, MethodDefinition<'a>>), + PropertyDefinition(Box<'a, PropertyDefinition<'a>>), + AccessorProperty(Box<'a, AccessorProperty<'a>>), + TSAbstractMethodDefinition(Box<'a, TSAbstractMethodDefinition<'a>>), + TSAbstractPropertyDefinition(Box<'a, TSAbstractPropertyDefinition<'a>>), + TSIndexSignature(Box<'a, TSIndexSignature<'a>>), +} + +impl<'a> ClassElement<'a> { + #[must_use] + 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, + Self::TSAbstractMethodDefinition(def) => def.method_definition.r#static, + Self::TSAbstractPropertyDefinition(def) => def.property_definition.r#static, + } + } + + #[must_use] + 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, + Self::TSAbstractMethodDefinition(def) => def.method_definition.computed, + Self::TSAbstractPropertyDefinition(def) => def.property_definition.computed, + } + } + + #[must_use] + pub fn method_definition_kind(&self) -> Option { + match self { + Self::TSIndexSignature(_) + | Self::StaticBlock(_) + | Self::PropertyDefinition(_) + | Self::AccessorProperty(_) => None, + Self::MethodDefinition(def) => Some(def.kind), + Self::TSAbstractMethodDefinition(def) => Some(def.method_definition.kind), + Self::TSAbstractPropertyDefinition(_def) => None, + } + } + + #[must_use] + 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), + Self::TSAbstractMethodDefinition(def) => Some(&def.method_definition.key), + Self::TSAbstractPropertyDefinition(def) => Some(&def.property_definition.key), + } + } + + #[must_use] + 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(), + Self::TSAbstractMethodDefinition(_def) => None, + Self::TSAbstractPropertyDefinition(_def) => None, + } + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +#[allow(clippy::struct_excessive_bools)] +pub struct MethodDefinition<'a> { + #[serde(flatten)] + pub node: Node, + pub key: PropertyKey<'a>, + pub value: Box<'a, Function<'a>>, // FunctionExpression + pub kind: MethodDefinitionKind, + pub computed: bool, + pub r#static: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub r#override: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub optional: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub accessibility: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub decorators: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +#[allow(clippy::struct_excessive_bools)] +pub struct PropertyDefinition<'a> { + #[serde(flatten)] + pub node: Node, + pub key: PropertyKey<'a>, + pub value: Option>, + pub computed: bool, + pub r#static: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub declare: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub r#override: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub optional: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub definite: bool, + #[serde(skip_serializing_if = "crate::is_false")] + pub readonly: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_annotation: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub accessibility: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub decorators: Option>>, +} + +#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum MethodDefinitionKind { + Constructor, + Method, + Get, + Set, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct PrivateIdentifier { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct StaticBlock<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Vec<'a, Statement<'a>>, +} + +/// Section 16.2.2 Imports +#[derive(Debug, Serialize, PartialEq, Hash)] +pub struct ModuleDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + #[serde(flatten)] + pub kind: ModuleDeclarationKind<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ModuleDeclarationKind<'a> { + ImportDeclaration(Box<'a, ImportDeclaration<'a>>), + ExportAllDeclaration(Box<'a, ExportAllDeclaration<'a>>), + ExportDefaultDeclaration(Box<'a, ExportDefaultDeclaration<'a>>), + ExportNamedDeclaration(Box<'a, ExportNamedDeclaration<'a>>), + + TSExportAssignment(Box<'a, TSExportAssignment<'a>>), + TSNamespaceExportDeclaration(Box<'a, TSNamespaceExportDeclaration>), +} + +impl<'a> ModuleDeclarationKind<'a> { + #[must_use] + pub const fn is_export(&self) -> bool { + matches!( + self, + Self::ExportAllDeclaration(_) + | Self::ExportDefaultDeclaration(_) + | Self::ExportNamedDeclaration(_) + | Self::TSExportAssignment(_) + | Self::TSNamespaceExportDeclaration(_) + ) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct AccessorProperty<'a> { + #[serde(flatten)] + pub node: Node, + pub key: PropertyKey<'a>, + pub value: Option>, + pub computed: bool, + pub r#static: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ImportExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub source: Expression<'a>, + #[cfg_attr(feature = "acorn", serde(skip_serializing_if = "Vec::is_empty"))] + pub arguments: Vec<'a, Expression<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct ImportDeclaration<'a> { + pub specifiers: Vec<'a, ImportDeclarationSpecifier>, + pub source: StringLiteral, + #[cfg_attr(feature = "acorn", serde(skip_serializing_if = "Option::is_none"))] + pub assertions: Option>, // Some(vec![]) for empty assertion + #[cfg_attr(feature = "acorn", serde(skip_serializing_if = "Option::is_none"))] + pub import_kind: Option, // `import type { foo } from 'bar'` +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum ImportDeclarationSpecifier { + ImportSpecifier(ImportSpecifier), + ImportDefaultSpecifier(ImportDefaultSpecifier), + ImportNamespaceSpecifier(ImportNamespaceSpecifier), +} + +// import {imported} from "source" +// import {imported as local} from "source" +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ImportSpecifier { + #[serde(flatten)] + pub node: Node, + pub imported: ModuleExportName, + pub local: BindingIdentifier, +} + +// import local from "source" +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ImportDefaultSpecifier { + #[serde(flatten)] + pub node: Node, + pub local: BindingIdentifier, +} + +// import * as local from "source" +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ImportNamespaceSpecifier { + #[serde(flatten)] + pub node: Node, + pub local: BindingIdentifier, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ImportAttribute { + #[serde(flatten)] + pub node: Node, + pub key: ImportAttributeKey, + pub value: StringLiteral, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum ImportAttributeKey { + Identifier(IdentifierName), + StringLiteral(StringLiteral), +} + +impl ImportAttributeKey { + #[must_use] + pub fn as_atom(&self) -> Atom { + match self { + Self::Identifier(identifier) => identifier.name.clone(), + Self::StringLiteral(literal) => literal.value.clone(), + } + } +} + +/// Exports +/// [tc39/ecma262#sec-exports](https://tc39.es/ecma262/#sec-exports) +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ExportNamedDeclaration<'a> { + pub declaration: Option>, + pub specifiers: Vec<'a, ExportSpecifier>, + pub source: Option, + #[cfg_attr(feature = "acorn", serde(skip_serializing_if = "Option::is_none"))] + pub export_kind: Option, // `export type { foo }` +} + +impl<'a> ExportNamedDeclaration<'a> { + #[must_use] + pub fn is_typescript_syntax(&self) -> bool { + self.export_kind == Some(ImportOrExportKind::Type) + || self.declaration.as_ref().map_or(false, Declaration::is_typescript_syntax) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct ExportDefaultDeclaration<'a> { + pub declaration: ExportDefaultDeclarationKind<'a>, + #[cfg_attr(feature = "acorn", serde(skip))] + pub exported: ModuleExportName, // `default` +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ExportAllDeclaration<'a> { + pub exported: Option, + pub source: StringLiteral, + #[cfg_attr(feature = "acorn", serde(skip_serializing_if = "Option::is_none"))] + pub assertions: Option>, // Some(vec![]) for empty assertion +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct ExportSpecifier { + #[serde(flatten)] + pub node: Node, + pub local: ModuleExportName, + pub exported: ModuleExportName, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum ExportDefaultDeclarationKind<'a> { + Expression(Expression<'a>), + FunctionDeclaration(Box<'a, Function<'a>>), + ClassDeclaration(Box<'a, Class<'a>>), + + TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>), + TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>), +} + +impl<'a> ExportDefaultDeclarationKind<'a> { + #[inline] + #[must_use] + pub fn is_typescript_syntax(&self) -> bool { + match self { + ExportDefaultDeclarationKind::FunctionDeclaration(func) + if func.is_typescript_syntax() => + { + true + } + ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) + | ExportDefaultDeclarationKind::TSEnumDeclaration(_) => true, + _ => false, + } + } +} + +// es2022: https://github.com/oxc_ast/oxc_ast/blob/master/es2022.md#modules +// https://github.com/tc39/ecma262/pull/2154 +// support: +// import {"\0 any unicode" as foo} from ""; +// export {foo as "\0 any unicode"}; +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum ModuleExportName { + Identifier(IdentifierName), + StringLiteral(StringLiteral), +} + +impl Display for ModuleExportName { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = match self { + Self::Identifier(identifier) => identifier.name.to_string(), + Self::StringLiteral(literal) => literal.value.to_string(), + }; + write!(f, "{s}") + } +} + +impl ModuleExportName { + #[must_use] + pub const fn name(&self) -> &Atom { + match self { + Self::Identifier(identifier) => &identifier.name, + Self::StringLiteral(literal) => &literal.value, + } + } +} diff --git a/crates/oxc_ast/src/ast/jsdoc.rs b/crates/oxc_ast/src/ast/jsdoc.rs new file mode 100644 index 0000000000000..726be7494597a --- /dev/null +++ b/crates/oxc_ast/src/ast/jsdoc.rs @@ -0,0 +1,21 @@ +//! [`JSDoc`](https://github.com/microsoft/TypeScript/blob/54a554d8af2657630307cbfa8a3e4f3946e36507/src/compiler/types.ts#L393) + +use serde::Serialize; + +use crate::{Node, TSType}; + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct JSDocNullableType<'a> { + #[serde(flatten)] + pub node: Node, + pub type_annotation: TSType<'a>, + pub postfix: bool, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct JSDocUnknownType { + #[serde(flatten)] + pub node: Node, +} diff --git a/crates/oxc_ast/src/ast/jsx.rs b/crates/oxc_ast/src/ast/jsx.rs new file mode 100644 index 0000000000000..86eff93f0302a --- /dev/null +++ b/crates/oxc_ast/src/ast/jsx.rs @@ -0,0 +1,248 @@ +//! [JSX](https://facebook.github.io/jsx) + +use oxc_allocator::{Box, Vec}; +use serde::Serialize; + +use crate::{Atom, Expression, Node, StringLiteral, TSTypeParameterInstantiation}; + +// 1.2 JSX Elements + +/// `JSXElement` : +/// `JSXSelfClosingElement` +/// `JSXOpeningElement` `JSXChildren_opt` `JSXClosingElement` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct JSXElement<'a> { + #[serde(flatten)] + pub node: Node, + pub opening_element: Box<'a, JSXOpeningElement<'a>>, + pub closing_element: Option>>, + pub children: Vec<'a, JSXChild<'a>>, +} + +/// `JSXOpeningElement` : +/// < `JSXElementName` `JSXAttributes_opt` > +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct JSXOpeningElement<'a> { + #[serde(flatten)] + pub node: Node, + pub self_closing: bool, + pub name: JSXElementName<'a>, + pub attributes: Vec<'a, JSXAttributeItem<'a>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +/// `JSXClosingElement` : +/// < / `JSXElementName` > +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXClosingElement<'a> { + #[serde(flatten)] + pub node: Node, + pub name: JSXElementName<'a>, +} + +/// `JSXFragment` : +/// < > `JSXChildren_opt` < / > +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct JSXFragment<'a> { + #[serde(flatten)] + pub node: Node, + pub opening_fragment: JSXOpeningFragment, + pub closing_fragment: JSXClosingFragment, + pub children: Vec<'a, JSXChild<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXOpeningFragment { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXClosingFragment { + #[serde(flatten)] + pub node: Node, +} + +/// `JSXElementName` : +/// `JSXIdentifier` +/// `JSXNamespacedName` +/// `JSXMemberExpression` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXElementName<'a> { + Identifier(JSXIdentifier), + NamespacedName(Box<'a, JSXNamespacedName>), + MemberExpression(Box<'a, JSXMemberExpression<'a>>), +} + +/// `JSXNamespacedName` : +/// `JSXIdentifier` : `JSXIdentifier` +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXNamespacedName { + #[serde(flatten)] + pub node: Node, + pub namespace: JSXIdentifier, + pub property: JSXIdentifier, +} + +/// `JSXMemberExpression` : +/// `JSXIdentifier` . `JSXIdentifier` +/// `JSXMemberExpression` . `JSXIdentifier` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXMemberExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub object: JSXMemberExpressionObject<'a>, + pub property: JSXIdentifier, +} + +impl<'a> JSXMemberExpression<'a> { + #[must_use] + pub fn get_object_identifier(&self) -> &JSXIdentifier { + match &self.object { + JSXMemberExpressionObject::Identifier(ident) => ident, + JSXMemberExpressionObject::MemberExpression(expr) => expr.get_object_identifier(), + } + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXMemberExpressionObject<'a> { + Identifier(JSXIdentifier), + MemberExpression(Box<'a, JSXMemberExpression<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXExpressionContainer<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: JSXExpression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXExpression<'a> { + Expression(Expression<'a>), + EmptyExpression(JSXEmptyExpression), +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXEmptyExpression { + #[serde(flatten)] + pub node: Node, +} + +// 1.3 JSX Attributes + +/// `JSXAttributes` : +/// `JSXSpreadAttribute` `JSXAttributes_opt` +/// `JSXAttribute` `JSXAttributes_opt` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXAttributeItem<'a> { + Attribute(Box<'a, JSXAttribute<'a>>), + SpreadAttribute(Box<'a, JSXSpreadAttribute<'a>>), +} + +/// `JSXAttribute` : +/// `JSXAttributeName` `JSXAttributeInitializer_opt` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXAttribute<'a> { + #[serde(flatten)] + pub node: Node, + pub name: JSXAttributeName<'a>, + pub value: Option>, +} + +/// `JSXSpreadAttribute` : +/// { ... `AssignmentExpression` } +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXSpreadAttribute<'a> { + #[serde(flatten)] + pub node: Node, + pub argument: Expression<'a>, +} + +/// `JSXAttributeName` : +/// `JSXIdentifier` +/// `JSXNamespacedName` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXAttributeName<'a> { + Identifier(JSXIdentifier), + NamespacedName(Box<'a, JSXNamespacedName>), +} + +/// `JSXAttributeValue` : +/// " `JSXDoubleStringCharacters_opt` " +/// ' `JSXSingleStringCharacters_opt` ' +/// { `AssignmentExpression` } +/// `JSXElement` +/// `JSXFragment` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXAttributeValue<'a> { + StringLiteral(StringLiteral), + ExpressionContainer(JSXExpressionContainer<'a>), + Element(Box<'a, JSXElement<'a>>), + Fragment(Box<'a, JSXFragment<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXIdentifier { + #[serde(flatten)] + pub node: Node, + pub name: Atom, +} + +// 1.4 JSX Children + +/// `JSXChild` : +/// `JSXText` +/// `JSXElement` +/// `JSXFragment` +/// { `JSXChildExpression_opt` } +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum JSXChild<'a> { + Text(JSXText), + Element(Box<'a, JSXElement<'a>>), + Fragment(Box<'a, JSXFragment<'a>>), + ExpressionContainer(JSXExpressionContainer<'a>), + Spread(JSXSpreadChild<'a>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct JSXSpreadChild<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +/// `JSXText` :: +/// `JSXTextCharacter` `JSXTextopt` +/// `JSXTextCharacter` :: +/// `JSXStringCharacter` but not one of { or < or > or } +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct JSXText { + #[serde(flatten)] + pub node: Node, + pub value: Atom, +} diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs new file mode 100644 index 0000000000000..39310b5b5ede2 --- /dev/null +++ b/crates/oxc_ast/src/ast/literal.rs @@ -0,0 +1,364 @@ +//! Literals + +use std::{ + fmt, + hash::{Hash, Hasher}, +}; + +use num_bigint::BigUint; +use ordered_float::NotNan; +use serde::{ + ser::{SerializeStruct, Serializer}, + Serialize, +}; + +use crate::{Atom, Node}; + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename = "Literal")] +pub struct BooleanLiteral { + #[serde(flatten)] + pub node: Node, + pub value: bool, +} + +impl BooleanLiteral { + #[must_use] + pub const fn as_str(&self) -> &'static str { + if self.value { "true" } else { "false" } + } +} + +#[derive(Debug, Clone, Eq)] +pub struct NullLiteral { + pub node: Node, +} + +impl Hash for NullLiteral { + fn hash(&self, state: &mut H) { + None::.hash(state); + } +} + +impl PartialEq for NullLiteral { + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Serialize for NullLiteral { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("NullLiteral", 4)?; + state.serialize_field("type", &"Literal")?; + state.serialize_field("start", &self.node.start)?; + state.serialize_field("end", &self.node.end)?; + state.serialize_field("value", &())?; + state.end() + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[serde(tag = "type", rename = "Literal")] +pub struct NumberLiteral<'a> { + #[serde(flatten)] + pub node: Node, + pub value: NotNan, // using NotNan for `Hash` + #[serde(skip)] + pub raw: &'a str, + #[serde(skip)] + pub base: NumberBase, +} + +impl<'a> NumberLiteral<'a> { + #[must_use] + pub const fn new(node: Node, value: f64, raw: &'a str, base: NumberBase) -> Self { + let value = unsafe { NotNan::new_unchecked(value) }; + Self { node, value, raw, base } + } +} + +impl<'a> Hash for NumberLiteral<'a> { + fn hash(&self, state: &mut H) { + self.base.hash(state); + self.value.hash(state); + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename = "Literal")] +pub struct BigintLiteral { + #[serde(flatten)] + pub node: Node, + #[serde(serialize_with = "crate::serialize::serialize_bigint")] + pub value: BigUint, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename = "Literal")] +pub struct RegExpLiteral { + #[serde(flatten)] + pub node: Node, + // valid regex is printed as {} + // invalid regex is printed as null, which we can't implement yet + pub value: EmptyObject, + pub regex: RegExp, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +pub struct RegExp { + pub pattern: Atom, + pub flags: Atom, +} + +impl fmt::Display for RegExp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "/{}/{}", self.pattern, self.flags) + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +pub struct EmptyObject {} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename = "Literal")] +pub struct StringLiteral { + #[serde(flatten)] + pub node: Node, + pub value: Atom, +} + +impl StringLiteral { + /// Static Semantics: `IsStringWellFormedUnicode` + /// test for \uD800-\uDFFF + #[must_use] + 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 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum NumberBase { + Decimal, + Binary, + Octal, + Hex, +} + +impl<'a> NumberLiteral<'a> { + #[allow(clippy::inherent_to_string)] + fn to_string(&self) -> String { + let mut buffer = ryu_js::Buffer::new(); + buffer.format(*self.value).to_string() + } + + /// From [boa](https://github.com/boa-dev/boa/blob/52bc15bc2320cd6cbc661a138ae955ceb0c9597a/boa_engine/src/builtins/number/mod.rs#L417) + /// [spec]: `https://tc39.es/ecma262/#sec-number.prototype.toprecision` + #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + #[must_use] + pub fn to_precision(&self, precision: Option) -> Option { + // 1 & 6 + let mut this_num = self.value; + // 2 + let Some(precision) = precision else { + return Some(self.to_string()); + }; + + // 3 + // let precision = precision.to_integer_or_infinity(context)?; + + // 4 + if !this_num.is_finite() { + return Some(self.to_string()); + } + + // let precision = match precision { + // IntegerOrInfinity::Integer(x) if (1..=100).contains(&x) => x as usize, + // _ => { + // // 5 + // return context.throw_range_error( + // "precision must be an integer at least 1 and no greater than 100", + // ); + // } + // }; + if !(1..=100).contains(&precision) { + return None; + } + let precision_i32 = precision as i32; + + // 7 + let mut prefix = String::new(); // spec: 's' + let mut suffix: String; // spec: 'm' + let mut exponent: i32; // spec: 'e' + + // 8 + if *this_num < 0.0 { + prefix.push('-'); + this_num = -this_num; + } + + // 9 + if this_num == 0.0 { + suffix = "0".repeat(precision); + exponent = 0; + // 10 + } else { + // Due to f64 limitations, this part differs a bit from the spec, + // but has the same effect. It manipulates the string constructed + // by `format`: digits with an optional dot between two of them. + suffix = format!("{this_num:.100}"); + + // a: getting an exponent + exponent = Self::flt_str_to_exp(&suffix); + // b: getting relevant digits only + if exponent < 0 { + suffix = suffix.split_off((1 - exponent) as usize); + } else if let Some(n) = suffix.find('.') { + suffix.remove(n); + } + // impl: having exactly `precision` digits in `suffix` + if Self::round_to_precision(&mut suffix, precision) { + exponent += 1; + } + + // c: switching to scientific notation + let great_exp = exponent >= precision_i32; + if exponent < -6 || great_exp { + // ii + if precision > 1 { + suffix.insert(1, '.'); + } + // vi + suffix.push('e'); + // iii + if great_exp { + suffix.push('+'); + } + // iv, v + suffix.push_str(&exponent.to_string()); + + return Some(format!("{prefix}{suffix}")); + } + } + + // 11 + let e_inc = exponent + 1; + if e_inc == precision_i32 { + return Some(format!("{prefix}{suffix}")); + } + + // 12 + if exponent >= 0 { + suffix.insert(e_inc as usize, '.'); + // 13 + } else { + prefix.push('0'); + prefix.push('.'); + prefix.push_str(&"0".repeat(-e_inc as usize)); + } + + // 14 + Some(format!("{prefix}{suffix}")) + } + + /// `round_to_precision` - used in `to_precision` + /// + /// This procedure has two roles: + /// - If there are enough or more than enough digits in the + /// string to show the required precision, the number + /// represented by these digits is rounded using string + /// manipulation. + /// - Else, zeroes are appended to the string. + /// - Additionally, sometimes the exponent was wrongly computed and + /// while up-rounding we find that we need an extra digit. When this + /// happens, we return true so that the calling context can adjust + /// the exponent. The string is kept at an exact length of `precision`. + /// + /// When this procedure returns, `digits` is exactly `precision` long. + /// + fn round_to_precision(digits: &mut String, precision: usize) -> bool { + if digits.len() > precision { + let to_round = digits.split_off(precision); + let mut digit = + digits.pop().expect("already checked that length is bigger than precision") as u8; + if let Some(first) = to_round.chars().next() { + if first > '4' { + digit += 1; + } + } + + if digit as char == ':' { + // ':' is '9' + 1 + // need to propagate the increment backward + let mut replacement = String::from("0"); + let mut propagated = false; + for c in digits.chars().rev() { + let d = match (c, propagated) { + ('0'..='8', false) => (c as u8 + 1) as char, + (_, false) => '0', + (_, true) => c, + }; + replacement.push(d); + if d != '0' { + propagated = true; + } + } + digits.clear(); + let replacement = if propagated { + replacement.as_str() + } else { + digits.push('1'); + &replacement.as_str()[1..] + }; + for c in replacement.chars().rev() { + digits.push(c); + } + !propagated + } else { + digits.push(digit as char); + false + } + } else { + digits.push_str(&"0".repeat(precision - digits.len())); + false + } + } + + /// `flt_str_to_exp` - used in `to_precision` + /// + /// This function traverses a string representing a number, + /// returning the floored log10 of this number. + /// + #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)] + fn flt_str_to_exp(flt: &str) -> i32 { + let mut non_zero_encountered = false; + let mut dot_encountered = false; + for (i, c) in flt.chars().enumerate() { + if c == '.' { + if non_zero_encountered { + return (i as i32) - 1; + } + dot_encountered = true; + } else if c != '0' { + if dot_encountered { + return 1 - (i as i32); + } + non_zero_encountered = true; + } + } + (flt.len() as i32) - 1 + } +} diff --git a/crates/oxc_ast/src/ast/mod.rs b/crates/oxc_ast/src/ast/mod.rs new file mode 100644 index 0000000000000..d59dc41966712 --- /dev/null +++ b/crates/oxc_ast/src/ast/mod.rs @@ -0,0 +1,13 @@ +mod js; +mod jsdoc; +mod jsx; +mod literal; +mod operator; +mod ts; + +pub use self::js::*; +pub use self::jsdoc::*; +pub use self::jsx::*; +pub use self::literal::*; +pub use self::operator::*; +pub use self::ts::*; diff --git a/crates/oxc_ast/src/ast/operator.rs b/crates/oxc_ast/src/ast/operator.rs new file mode 100644 index 0000000000000..691e283b372fc --- /dev/null +++ b/crates/oxc_ast/src/ast/operator.rs @@ -0,0 +1,387 @@ +use std::fmt::{Display, Formatter, Result}; + +use serde::Serialize; + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum Operator { + AssignmentOperator(AssignmentOperator), + BinaryOperator(BinaryOperator), + LogicalOperator(LogicalOperator), + UnaryOperator(UnaryOperator), + UpdateOperator(UpdateOperator), +} + +impl From for Operator { + fn from(op: AssignmentOperator) -> Self { + Self::AssignmentOperator(op) + } +} + +impl From for Operator { + fn from(op: BinaryOperator) -> Self { + Self::BinaryOperator(op) + } +} + +impl From for Operator { + fn from(op: LogicalOperator) -> Self { + Self::LogicalOperator(op) + } +} + +impl From for Operator { + fn from(op: UnaryOperator) -> Self { + Self::UnaryOperator(op) + } +} + +impl From for Operator { + fn from(op: UpdateOperator) -> Self { + Self::UpdateOperator(op) + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum AssignmentOperator { + #[serde(rename = "=")] + Assign, + #[serde(rename = "+=")] + Addition, + #[serde(rename = "-=")] + Subtraction, + #[serde(rename = "*=")] + Multiplication, + #[serde(rename = "/=")] + Division, + #[serde(rename = "%=")] + Remainder, + #[serde(rename = "<<=")] + ShiftLeft, + #[serde(rename = ">>=")] + ShiftRight, + #[serde(rename = ">>>=")] + ShiftRightZeroFill, + #[serde(rename = "|=")] + BitwiseOR, + #[serde(rename = "^=")] + BitwiseXOR, + #[serde(rename = "&=")] + BitwiseAnd, + #[serde(rename = "&&=")] + LogicalAnd, + #[serde(rename = "||=")] + LogicalOr, + #[serde(rename = "??=")] + LogicalNullish, + #[serde(rename = "**=")] + Exponential, +} + +impl AssignmentOperator { + #[must_use] + pub const fn is_logical_operator(self) -> bool { + matches!(self, Self::LogicalAnd | Self::LogicalOr | Self::LogicalNullish) + } + + #[must_use] + pub const fn is_arithmetic(self) -> bool { + matches!( + self, + Self::Addition + | Self::Subtraction + | Self::Multiplication + | Self::Division + | Self::Remainder + | Self::Exponential + ) + } + + #[must_use] + pub const fn is_bitwise(self) -> bool { + matches!(self, Self::BitwiseOR | Self::BitwiseXOR | Self::BitwiseAnd) + } + + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::Assign => "=", + Self::Addition => "+=", + Self::Subtraction => "-=", + Self::Multiplication => "*=", + Self::Division => "/=", + Self::Remainder => "%=", + Self::ShiftLeft => "<<=", + Self::ShiftRight => ">>=", + Self::ShiftRightZeroFill => ">>>=", + Self::BitwiseOR => "|=", + Self::BitwiseXOR => "^=", + Self::BitwiseAnd => "&=", + Self::LogicalAnd => "&&=", + Self::LogicalOr => "||=", + Self::LogicalNullish => "??=", + Self::Exponential => "**=", + } + } +} + +impl Display for AssignmentOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let operator = self.as_str(); + write!(f, "{operator}") + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum BinaryOperator { + #[serde(rename = "==")] + Equality, + #[serde(rename = "!=")] + Inequality, + #[serde(rename = "===")] + StrictEquality, + #[serde(rename = "!==")] + StrictInequality, + #[serde(rename = "<")] + LessThan, + #[serde(rename = "<=")] + LessEqualThan, + #[serde(rename = ">")] + GreaterThan, + #[serde(rename = ">=")] + GreaterEqualThan, + #[serde(rename = "<<")] + ShiftLeft, + #[serde(rename = ">>")] + ShiftRight, + #[serde(rename = ">>>")] + ShiftRightZeroFill, + #[serde(rename = "+")] + Addition, + #[serde(rename = "-")] + Subtraction, + #[serde(rename = "*")] + Multiplication, + #[serde(rename = "/")] + Division, + #[serde(rename = "%")] + Remainder, + #[serde(rename = "|")] + BitwiseOR, + #[serde(rename = "^")] + BitwiseXOR, + #[serde(rename = "&")] + BitwiseAnd, + #[serde(rename = "in")] + In, + #[serde(rename = "instanceof")] + Instanceof, + #[serde(rename = "**")] + Exponential, +} + +impl BinaryOperator { + #[must_use] + pub const fn is_equality(self) -> bool { + matches!( + self, + Self::Equality | Self::Inequality | Self::StrictEquality | Self::StrictInequality + ) + } + + #[must_use] + pub const fn is_compare(self) -> bool { + matches!( + self, + Self::LessThan | Self::LessEqualThan | Self::GreaterThan | Self::GreaterEqualThan + ) + } + + #[must_use] + pub const fn is_arithmetic(self) -> bool { + matches!( + self, + Self::Addition + | Self::Subtraction + | Self::Multiplication + | Self::Division + | Self::Remainder + | Self::Exponential + ) + } + + #[must_use] + pub const fn is_relational(self) -> bool { + matches!(self, Self::In | Self::Instanceof) + } + + #[must_use] + pub const fn is_bitwise(self) -> bool { + matches!( + self, + Self::BitwiseOR + | Self::BitwiseXOR + | Self::BitwiseAnd + | Self::ShiftLeft + | Self::ShiftRight + | Self::ShiftRightZeroFill, + ) + } + + #[must_use] + pub const fn is_numeric_or_string_binary_operator(self) -> bool { + self.is_arithmetic() || self.is_bitwise() + } + + #[must_use] + pub const fn is_keyword(self) -> bool { + matches!(self, Self::In | Self::Instanceof) + } + + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::Equality => "==", + Self::Inequality => "!=", + Self::StrictEquality => "===", + Self::StrictInequality => "!==", + Self::LessThan => "<", + Self::LessEqualThan => "<=", + Self::GreaterThan => ">", + Self::GreaterEqualThan => ">=", + Self::ShiftLeft => "<<", + Self::ShiftRight => ">>", + Self::ShiftRightZeroFill => ">>>", + Self::Addition => "+", + Self::Subtraction => "-", + Self::Multiplication => "*", + Self::Division => "/", + Self::Remainder => "%", + Self::BitwiseOR => "|", + Self::BitwiseXOR => "^", + Self::BitwiseAnd => "&", + Self::In => "in", + Self::Instanceof => "instanceof", + Self::Exponential => "**", + } + } +} + +impl Display for BinaryOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let operator = self.as_str(); + write!(f, "{operator}") + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum LogicalOperator { + #[serde(rename = "||")] + Or, + #[serde(rename = "&&")] + And, + #[serde(rename = "??")] + Coalesce, +} + +impl LogicalOperator { + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::Or => "||", + Self::And => "&&", + Self::Coalesce => "??", + } + } +} + +impl Display for LogicalOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let operator = self.as_str(); + write!(f, "{operator}") + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum UnaryOperator { + #[serde(rename = "-")] + UnaryNegation, + #[serde(rename = "+")] + UnaryPlus, + #[serde(rename = "!")] + LogicalNot, + #[serde(rename = "~")] + BitwiseNot, + #[serde(rename = "typeof")] + Typeof, + #[serde(rename = "void")] + Void, + #[serde(rename = "delete")] + Delete, +} + +impl UnaryOperator { + #[must_use] + pub const fn operator(&self) -> Operator { + Operator::UnaryOperator(*self) + } + + #[must_use] + pub const fn is_arithmetic(self) -> bool { + matches!(self, Self::UnaryNegation | Self::UnaryPlus) + } + + #[must_use] + pub const fn is_bitwise(self) -> bool { + matches!(self, Self::BitwiseNot) + } + + #[must_use] + pub const fn is_keyword(self) -> bool { + matches!(self, Self::Typeof | Self::Void | Self::Delete) + } + + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::UnaryNegation => "-", + Self::UnaryPlus => "+", + Self::LogicalNot => "!", + Self::BitwiseNot => "~", + Self::Typeof => "typeof", + Self::Void => "void", + Self::Delete => "delete", + } + } +} + +impl Display for UnaryOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let operator = self.as_str(); + write!(f, "{operator}") + } +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +pub enum UpdateOperator { + #[serde(rename = "++")] + Increment, + #[serde(rename = "--")] + Decrement, +} + +impl UpdateOperator { + #[must_use] + pub const fn as_str(&self) -> &'static str { + match self { + Self::Increment => "++", + Self::Decrement => "--", + } + } +} + +impl Display for UpdateOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let operator = self.as_str(); + write!(f, "{operator}") + } +} diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs new file mode 100644 index 0000000000000..b2ed62f575724 --- /dev/null +++ b/crates/oxc_ast/src/ast/ts.rs @@ -0,0 +1,875 @@ +//! [AST Spec](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/ast-spec) + +use oxc_allocator::{Box, Vec}; +use serde::Serialize; + +use crate::{ + Atom, BigintLiteral, BindingIdentifier, BooleanLiteral, Expression, FormalParameters, + IdentifierName, JSDocNullableType, JSDocUnknownType, MethodDefinition, Node, NullLiteral, + NumberLiteral, PropertyDefinition, PropertyKey, RegExpLiteral, Statement, StringLiteral, + TemplateElement, TemplateLiteral, UnaryExpression, +}; + +#[allow(clippy::trivially_copy_pass_by_ref)] +#[must_use] +pub fn is_false(val: &bool) -> bool { + !val +} + +/// `EnumDeclaration`: +/// `const_opt` enum `BindingIdentifier` { `EnumBody_opt` } +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TSEnumDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub id: BindingIdentifier, + pub members: Vec<'a, TSEnumMember<'a>>, + #[serde(skip_serializing_if = "is_false")] + pub declare: bool, + #[serde(skip_serializing_if = "is_false")] + pub r#const: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TSEnumMember<'a> { + #[serde(flatten)] + pub node: Node, + pub id: TSEnumMemberName<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub initializer: Option>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum TSEnumMemberName<'a> { + Identifier(IdentifierName), + StringLiteral(StringLiteral), + // Invalid Grammar `enum E { [computed] }` + ComputedPropertyName(Expression<'a>), + // Invalid Grammar `enum E { 1 }` + NumberLiteral(NumberLiteral<'a>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeAnnotation<'a> { + #[serde(flatten)] + pub node: Node, + pub type_annotation: TSType<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSLiteralType<'a> { + #[serde(flatten)] + pub node: Node, + pub literal: TSLiteral<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSLiteral<'a> { + BooleanLiteral(Box<'a, BooleanLiteral>), + NullLiteral(Box<'a, NullLiteral>), + NumberLiteral(Box<'a, NumberLiteral<'a>>), + BigintLiteral(Box<'a, BigintLiteral>), + RegExpLiteral(Box<'a, RegExpLiteral>), + StringLiteral(Box<'a, StringLiteral>), + TemplateLiteral(Box<'a, TemplateLiteral<'a>>), + UnaryExpression(Box<'a, UnaryExpression<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSType<'a> { + // Keyword + TSAnyKeyword(Box<'a, TSAnyKeyword>), + TSBigIntKeyword(Box<'a, TSBigIntKeyword>), + TSBooleanKeyword(Box<'a, TSBooleanKeyword>), + TSNeverKeyword(Box<'a, TSNeverKeyword>), + TSNullKeyword(Box<'a, TSNullKeyword>), + TSNumberKeyword(Box<'a, TSNumberKeyword>), + TSObjectKeyword(Box<'a, TSObjectKeyword>), + TSStringKeyword(Box<'a, TSStringKeyword>), + TSSymbolKeyword(Box<'a, TSSymbolKeyword>), + TSThisKeyword(Box<'a, TSThisKeyword>), + TSUndefinedKeyword(Box<'a, TSUndefinedKeyword>), + TSUnknownKeyword(Box<'a, TSUnknownKeyword>), + TSVoidKeyword(Box<'a, TSVoidKeyword>), + // Compound + TSArrayType(Box<'a, TSArrayType<'a>>), + TSConditionalType(Box<'a, TSConditionalType<'a>>), + TSConstructorType(Box<'a, TSConstructorType<'a>>), + TSFunctionType(Box<'a, TSFunctionType<'a>>), + TSImportType(Box<'a, TSImportType<'a>>), + TSIndexedAccessType(Box<'a, TSIndexedAccessType<'a>>), + TSInferType(Box<'a, TSInferType<'a>>), + TSIntersectionType(Box<'a, TSIntersectionType<'a>>), + TSLiteralType(Box<'a, TSLiteralType<'a>>), + TSMappedType(Box<'a, TSMappedType<'a>>), + TSQualifiedName(Box<'a, TSQualifiedName<'a>>), + TSTemplateLiteralType(Box<'a, TSTemplateLiteralType<'a>>), + TSTupleType(Box<'a, TSTupleType<'a>>), + TSTypeLiteral(Box<'a, TSTypeLiteral<'a>>), + TSTypeOperatorType(Box<'a, TSTypeOperatorType<'a>>), + TSTypePredicate(Box<'a, TSTypePredicate<'a>>), + TSTypeQuery(Box<'a, TSTypeQuery<'a>>), + TSTypeReference(Box<'a, TSTypeReference<'a>>), + TSUnionType(Box<'a, TSUnionType<'a>>), + // JSDoc + JSDocNullableType(Box<'a, JSDocNullableType<'a>>), + JSDocUnknownType(Box<'a, JSDocUnknownType>), +} + +impl<'a> TSType<'a> { + #[must_use] + pub fn is_const_type_reference(&self) -> bool { + matches!(self, TSType::TSTypeReference(reference) if reference.type_name.is_const()) + } +} + +/// +/// `SomeType` extends `OtherType` ? `TrueType` : `FalseType`; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSConditionalType<'a> { + #[serde(flatten)] + pub node: Node, + pub check_type: TSType<'a>, + pub extends_type: TSType<'a>, + pub true_type: TSType<'a>, + pub false_type: TSType<'a>, +} + +/// +/// string | string[] | (() => string) | { s: string } +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TSUnionType<'a> { + #[serde(flatten)] + pub node: Node, + pub types: Vec<'a, TSType<'a>>, +} + +/// +/// type `ColorfulCircle` = Colorful & Circle; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type")] +pub struct TSIntersectionType<'a> { + #[serde(flatten)] + pub node: Node, + pub types: Vec<'a, TSType<'a>>, +} + +/// +/// keyof unique readonly +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename = "TSTypeOperator")] +pub struct TSTypeOperatorType<'a> { + #[serde(flatten)] + pub node: Node, + pub operator: TSTypeOperator, + pub type_annotation: TSType<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum TSTypeOperator { + #[serde(rename = "keyof")] + Keyof, + #[serde(rename = "unique")] + Unique, + #[serde(rename = "readonly")] + Readonly, +} + +impl TSTypeOperator { + #[must_use] + pub fn from_src(src: &Atom) -> Option { + match src.as_str() { + "keyof" => Some(Self::Keyof), + "unique" => Some(Self::Unique), + "readonly" => Some(Self::Readonly), + _ => None, + } + } +} + +/// +/// let myArray: string[] = ["hello", "world"]; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSArrayType<'a> { + #[serde(flatten)] + pub node: Node, + pub element_type: TSType<'a>, +} + +/// +/// type I1 = Person["age" | "name"]; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSIndexedAccessType<'a> { + #[serde(flatten)] + pub node: Node, + pub object_type: TSType<'a>, + pub index_type: TSType<'a>, +} + +/// +/// type `StringNumberPair` = [string, number]; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTupleType<'a> { + #[serde(flatten)] + pub node: Node, + pub element_types: Vec<'a, TSTupleElement<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSNamedTupleMember<'a> { + #[serde(flatten)] + pub node: Node, + pub element_type: TSType<'a>, + pub label: IdentifierName, + pub optional: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSOptionalType<'a> { + #[serde(flatten)] + pub node: Node, + pub type_annotation: TSType<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSRestType<'a> { + #[serde(flatten)] + pub node: Node, + pub type_annotation: TSType<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSTupleElement<'a> { + TSType(TSType<'a>), + TSOptionalType(Box<'a, TSOptionalType<'a>>), + TSRestType(Box<'a, TSRestType<'a>>), + TSNamedTupleMember(Box<'a, TSNamedTupleMember<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSAnyKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSStringKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSBooleanKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSNumberKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSNeverKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSUnknownKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSNullKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSUndefinedKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSVoidKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSSymbolKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSThisKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSObjectKeyword { + #[serde(flatten)] + pub node: Node, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type")] +pub struct TSBigIntKeyword { + #[serde(flatten)] + pub node: Node, +} + +/// type C = A; +/// type D = B.a; +/// type E = D.c.b.a; +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeReference<'a> { + #[serde(flatten)] + pub node: Node, + pub type_name: TSTypeName<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum TSTypeName<'a> { + IdentifierName(Box<'a, IdentifierName>), + QualifiedName(Box<'a, TSQualifiedName<'a>>), +} + +impl<'a> TSTypeName<'a> { + #[must_use] + pub fn get_first_name(name: &TSTypeName) -> IdentifierName { + match name { + TSTypeName::IdentifierName(name) => (*name).clone(), + TSTypeName::QualifiedName(name) => TSTypeName::get_first_name(&name.left), + } + } + + #[must_use] + pub fn is_const(&self) -> bool { + if let TSTypeName::IdentifierName(ident) = self { + if ident.name == "const" { + return true; + } + } + false + } + + #[must_use] + pub const fn is_identifier(&self) -> bool { + matches!(self, Self::IdentifierName(_)) + } + + #[must_use] + pub const fn is_qualified_name(&self) -> bool { + matches!(self, Self::QualifiedName(_)) + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSQualifiedName<'a> { + #[serde(flatten)] + pub node: Node, + pub left: TSTypeName<'a>, + pub right: IdentifierName, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeParameterInstantiation<'a> { + #[serde(flatten)] + pub node: Node, + pub params: Vec<'a, TSType<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeParameter<'a> { + #[serde(flatten)] + pub node: Node, + pub name: BindingIdentifier, + #[serde(skip_serializing_if = "Option::is_none")] + pub constraint: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub default: Option>, + #[serde(skip_serializing_if = "is_false")] + pub r#in: bool, + #[serde(skip_serializing_if = "is_false")] + pub out: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeParameterDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub params: Vec<'a, Box<'a, TSTypeParameter<'a>>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeAliasDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub id: BindingIdentifier, + pub type_annotation: TSType<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, + #[serde(skip_serializing_if = "is_false")] + pub declare: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSAbstractMethodDefinition<'a> { + #[serde(flatten)] + pub method_definition: MethodDefinition<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSAbstractPropertyDefinition<'a> { + #[serde(flatten)] + pub property_definition: PropertyDefinition<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum TSAccessibility { + Private, + Protected, + Public, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSClassImplements<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: TSTypeName<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +/// `InterfaceDeclaration`: +/// interface `BindingIdentifier` `TypeParameters_opt` `InterfaceExtendsClause_opt` `ObjectType` +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSInterfaceDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub id: BindingIdentifier, + pub body: Box<'a, TSInterfaceBody<'a>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub extends: Option>>>, + #[serde(skip_serializing_if = "is_false")] + pub declare: bool, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSInterfaceBody<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Vec<'a, TSSignature<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSPropertySignature<'a> { + #[serde(flatten)] + pub node: Node, + pub computed: bool, + #[serde(skip_serializing_if = "is_false")] + pub optional: bool, + #[serde(skip_serializing_if = "is_false")] + pub readonly: bool, + pub key: PropertyKey<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_annotation: Option>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSSignature<'a> { + TSIndexSignature(Box<'a, TSIndexSignature<'a>>), + TSPropertySignature(Box<'a, TSPropertySignature<'a>>), + TSCallSignatureDeclaration(Box<'a, TSCallSignatureDeclaration<'a>>), + TSConstructSignatureDeclaration(Box<'a, TSConstructSignatureDeclaration<'a>>), + TSMethodSignature(Box<'a, TSMethodSignature<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSIndexSignature<'a> { + #[serde(flatten)] + pub node: Node, + pub parameters: Vec<'a, Box<'a, TSIndexSignatureName<'a>>>, + pub type_annotation: TSTypeAnnotation<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSCallSignatureDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub params: FormalParameters<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub return_type: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum TSMethodSignatureKind { + Method, + Get, + Set, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSMethodSignature<'a> { + #[serde(flatten)] + pub node: Node, + pub key: PropertyKey<'a>, + pub computed: bool, + #[serde(skip_serializing_if = "is_false")] + pub optional: bool, + pub kind: TSMethodSignatureKind, + pub params: FormalParameters<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub return_type: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSConstructSignatureDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub params: FormalParameters<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub return_type: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase", rename = "Identifier")] +pub struct TSIndexSignatureName<'a> { + #[serde(flatten)] + pub node: Node, + pub name: Atom, + pub type_annotation: TSTypeAnnotation<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSInterfaceHeritage<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypePredicate<'a> { + #[serde(flatten)] + pub node: Node, + pub parameter_name: TSTypePredicateName, + pub asserts: bool, + pub type_annotation: Option>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSTypePredicateName { + Identifier(IdentifierName), + This(TSThisKeyword), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSModuleDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub id: TSModuleDeclarationName, + pub body: TSModuleDeclarationBody<'a>, + pub declare: bool, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum TSModuleDeclarationName { + Identifier(IdentifierName), + StringLiteral(StringLiteral), +} + +impl TSModuleDeclarationName { + #[must_use] + pub const fn name(&self) -> &Atom { + match self { + Self::Identifier(ident) => &ident.name, + Self::StringLiteral(lit) => &lit.value, + } + } +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged)] +pub enum TSModuleDeclarationBody<'a> { + TSModuleDeclaration(Box<'a, TSModuleDeclaration<'a>>), + TSModuleBlock(Box<'a, TSModuleBlock<'a>>), +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSModuleBlock<'a> { + #[serde(flatten)] + pub node: Node, + pub body: Vec<'a, Statement<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeLiteral<'a> { + #[serde(flatten)] + pub node: Node, + pub members: Vec<'a, TSSignature<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSInferType<'a> { + #[serde(flatten)] + pub node: Node, + pub type_parameter: Box<'a, TSTypeParameter<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeQuery<'a> { + #[serde(flatten)] + pub node: Node, + pub expr_name: TSTypeName<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSImportType<'a> { + #[serde(flatten)] + pub node: Node, + pub is_type_of: bool, + pub parameter: TSType<'a>, + pub qualifier: Option>, + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSFunctionType<'a> { + #[serde(flatten)] + pub node: Node, + pub params: FormalParameters<'a>, + pub return_type: TSTypeAnnotation<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSConstructorType<'a> { + #[serde(flatten)] + pub node: Node, + pub r#abstract: bool, + pub params: FormalParameters<'a>, + pub return_type: TSTypeAnnotation<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSMappedType<'a> { + #[serde(flatten)] + pub node: Node, + pub type_parameter: Box<'a, TSTypeParameter<'a>>, + pub name_type: Option>, + pub type_annotation: TSType<'a>, + pub optional: TSMappedTypeModifierOperator, + pub readonly: TSMappedTypeModifierOperator, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSMappedTypeModifierOperator { + // #[serde(rename = true)] + True, + #[serde(rename = "+")] + Plus, + #[serde(rename = "-")] + Minus, + // #[serde(skip_serializing)] + None, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTemplateLiteralType<'a> { + #[serde(flatten)] + pub node: Node, + pub quasis: Vec<'a, TemplateElement>, + pub types: Vec<'a, TSType<'a>>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSAsExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, + pub type_annotation: TSType<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSTypeAssertion<'a> { + #[serde(flatten)] + pub node: Node, + pub type_annotation: TSType<'a>, + pub expression: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSImportEqualsDeclaration<'a> { + #[serde(flatten)] + pub node: Node, + pub id: BindingIdentifier, + pub module_reference: Box<'a, TSModuleReference<'a>>, + pub is_export: bool, + pub import_kind: ImportOrExportKind, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum TSModuleReference<'a> { + TypeName(TSTypeName<'a>), + ExternalModuleReference(TSExternalModuleReference), +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSExternalModuleReference { + #[serde(flatten)] + pub node: Node, + pub expression: StringLiteral, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSNonNullExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct Decorator<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSExportAssignment<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSNamespaceExportDeclaration { + #[serde(flatten)] + pub node: Node, + pub id: IdentifierName, +} + +#[derive(Debug, Serialize, PartialEq, Hash)] +#[serde(tag = "type", rename_all = "camelCase")] +pub struct TSInstantiationExpression<'a> { + #[serde(flatten)] + pub node: Node, + pub expression: Expression<'a>, + pub type_parameters: Box<'a, TSTypeParameterInstantiation<'a>>, +} + +#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq, Hash)] +#[serde(untagged, rename_all = "camelCase")] +pub enum ImportOrExportKind { + Value, + Type, +} + +impl ImportOrExportKind { + #[must_use] + pub const fn is_value(&self) -> bool { + matches!(self, Self::Value) + } + + #[must_use] + pub const fn is_type(&self) -> bool { + matches!(self, Self::Type) + } +} diff --git a/crates/oxc_ast/src/ast_builder.rs b/crates/oxc_ast/src/ast_builder.rs new file mode 100644 index 0000000000000..c7119691ebf4d --- /dev/null +++ b/crates/oxc_ast/src/ast_builder.rs @@ -0,0 +1,1744 @@ +//! AST builder for creating AST nodes + +#![allow(clippy::unused_self, clippy::missing_const_for_fn, clippy::too_many_arguments)] + +use oxc_allocator::{Allocator, Box, String, Vec}; + +#[allow(clippy::wildcard_imports)] +use crate::{ast::*, Atom, Node, SourceType}; + +pub struct AstBuilder<'a> { + pub allocator: &'a Allocator, +} + +impl<'a> AstBuilder<'a> { + pub fn new(allocator: &'a Allocator) -> Self { + Self { allocator } + } + + pub fn alloc(&self, value: T) -> Box<'a, T> { + Box(self.allocator.alloc(value)) + } + + #[must_use] + #[inline] + pub fn new_vec(&self) -> Vec<'a, T> { + Vec::new_in(self.allocator) + } + + #[must_use] + pub fn new_vec_with_capacity(&self, capacity: usize) -> Vec<'a, T> { + Vec::with_capacity_in(capacity, self.allocator) + } + + #[must_use] + pub fn new_vec_single(&self, value: T) -> Vec<'a, T> { + let mut vec = self.new_vec_with_capacity(1); + vec.push(value); + vec + } + + #[must_use] + #[inline] + pub fn new_str(&self, value: &str) -> &'a str { + String::from_str_in(value, self.allocator).into_bump_str() + } + + #[must_use] + #[inline] + pub fn program( + &self, + node: Node, + directives: Vec<'a, Directive>, + body: Vec<'a, Statement<'a>>, + source_type: SourceType, + ) -> Program<'a> { + Program { node, directives, body, source_type } + } + + /* ---------- Literals ---------- */ + + #[must_use] + #[inline] + pub fn literal_string_expression(&self, literal: StringLiteral) -> Expression<'a> { + Expression::StringLiteral(self.alloc(literal)) + } + + #[must_use] + #[inline] + pub fn literal_boolean_expression(&self, literal: BooleanLiteral) -> Expression<'a> { + Expression::BooleanLiteral(self.alloc(literal)) + } + + #[must_use] + #[inline] + pub fn literal_null_expression(&self, literal: NullLiteral) -> Expression<'a> { + Expression::NullLiteral(self.alloc(literal)) + } + + #[must_use] + #[inline] + pub fn literal_regexp_expression(&self, literal: RegExpLiteral) -> Expression<'a> { + Expression::RegExpLiteral(self.alloc(literal)) + } + + #[must_use] + #[inline] + pub fn literal_number_expression(&self, literal: NumberLiteral<'a>) -> Expression<'a> { + Expression::NumberLiteral(self.alloc(literal)) + } + + #[must_use] + #[inline] + pub fn literal_bigint_expression(&self, literal: BigintLiteral) -> Expression<'a> { + Expression::BigintLiteral(self.alloc(literal)) + } + + /* ---------- Identifiers ---------- */ + + #[must_use] + #[inline] + pub fn identifier_expression(&self, identifier: IdentifierReference) -> Expression<'a> { + Expression::Identifier(self.alloc(identifier)) + } + + /* ---------- Statements ---------- */ + + #[must_use] + #[inline] + pub fn directive( + &self, + node: Node, + expression: StringLiteral, + directive: &'a str, + ) -> Directive<'a> { + Directive { node, expression, directive } + } + + #[must_use] + #[inline] + pub fn block(&self, node: Node, body: Vec<'a, Statement<'a>>) -> Box<'a, BlockStatement<'a>> { + self.alloc(BlockStatement { node, body }) + } + + #[must_use] + #[inline] + pub fn block_statement(&self, block: Box<'a, BlockStatement<'a>>) -> Statement<'a> { + Statement::BlockStatement( + self.alloc(BlockStatement { node: block.node, body: block.unbox().body }), + ) + } + + #[must_use] + #[inline] + pub fn break_statement(&self, node: Node, label: Option) -> Statement<'a> { + Statement::BreakStatement(self.alloc(BreakStatement { node, label })) + } + + #[must_use] + #[inline] + pub fn continue_statement(&self, node: Node, label: Option) -> Statement<'a> { + Statement::ContinueStatement(self.alloc(ContinueStatement { node, label })) + } + + #[must_use] + #[inline] + pub fn debugger_statement(&self, node: Node) -> Statement<'a> { + Statement::DebuggerStatement(self.alloc(DebuggerStatement { node })) + } + + #[must_use] + #[inline] + pub fn do_while_statement( + &self, + node: Node, + body: Statement<'a>, + test: Expression<'a>, + ) -> Statement<'a> { + Statement::DoWhileStatement(self.alloc(DoWhileStatement { node, body, test })) + } + + #[must_use] + #[inline] + pub fn empty_statement(&self, node: Node) -> Statement<'a> { + Statement::EmptyStatement(self.alloc(EmptyStatement { node })) + } + + #[must_use] + #[inline] + pub fn expression_statement(&self, node: Node, expression: Expression<'a>) -> Statement<'a> { + Statement::ExpressionStatement(self.alloc(ExpressionStatement { node, expression })) + } + + #[must_use] + #[inline] + pub fn for_in_statement( + &self, + node: Node, + left: ForStatementLeft<'a>, + right: Expression<'a>, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::ForInStatement(self.alloc(ForInStatement { node, left, right, body })) + } + + #[must_use] + #[inline] + pub fn for_of_statement( + &self, + node: Node, + r#await: bool, + left: ForStatementLeft<'a>, + right: Expression<'a>, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::ForOfStatement(self.alloc(ForOfStatement { node, r#await, left, right, body })) + } + + #[must_use] + #[inline] + pub fn for_statement( + &self, + node: Node, + init: Option>, + test: Option>, + update: Option>, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::ForStatement(self.alloc(ForStatement { node, init, test, update, body })) + } + + #[must_use] + #[inline] + pub fn if_statement( + &self, + node: Node, + test: Expression<'a>, + consequent: Statement<'a>, + alternate: Option>, + ) -> Statement<'a> { + Statement::IfStatement(self.alloc(IfStatement { node, test, consequent, alternate })) + } + + #[must_use] + #[inline] + pub fn labeled_statement( + &self, + node: Node, + label: LabelIdentifier, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::LabeledStatement(self.alloc(LabeledStatement { node, label, body })) + } + + #[must_use] + #[inline] + pub fn return_statement(&self, node: Node, argument: Option>) -> Statement<'a> { + Statement::ReturnStatement(self.alloc(ReturnStatement { node, argument })) + } + + #[must_use] + #[inline] + pub fn switch_statement( + &self, + node: Node, + discriminant: Expression<'a>, + cases: Vec<'a, SwitchCase<'a>>, + ) -> Statement<'a> { + Statement::SwitchStatement(self.alloc(SwitchStatement { node, discriminant, cases })) + } + + #[must_use] + #[inline] + pub fn switch_case( + &self, + node: Node, + test: Option>, + consequent: Vec<'a, Statement<'a>>, + ) -> SwitchCase<'a> { + SwitchCase { node, test, consequent } + } + + #[must_use] + #[inline] + pub fn throw_statement(&self, node: Node, argument: Expression<'a>) -> Statement<'a> { + Statement::ThrowStatement(self.alloc(ThrowStatement { node, argument })) + } + + #[must_use] + #[inline] + pub fn try_statement( + &self, + node: Node, + block: Box<'a, BlockStatement<'a>>, + handler: Option>>, + finalizer: Option>>, + ) -> Statement<'a> { + Statement::TryStatement(self.alloc(TryStatement { node, block, handler, finalizer })) + } + + #[must_use] + #[inline] + pub fn catch_clause( + &self, + node: Node, + param: Option>, + body: Box<'a, BlockStatement<'a>>, + ) -> Box<'a, CatchClause<'a>> { + self.alloc(CatchClause { node, param, body }) + } + + #[must_use] + #[inline] + pub fn while_statement( + &self, + node: Node, + test: Expression<'a>, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::WhileStatement(self.alloc(WhileStatement { node, test, body })) + } + + #[must_use] + #[inline] + pub fn with_statement( + &self, + node: Node, + object: Expression<'a>, + body: Statement<'a>, + ) -> Statement<'a> { + Statement::WithStatement(self.alloc(WithStatement { node, object, body })) + } + + /* ---------- Expressions ---------- */ + + #[must_use] + #[inline] + pub fn super_(&self, node: Node) -> Expression<'a> { + Expression::Super(self.alloc(Super { node })) + } + + #[must_use] + #[inline] + pub fn meta_property( + &self, + node: Node, + meta: IdentifierName, + property: IdentifierName, + ) -> Expression<'a> { + Expression::MetaProperty(self.alloc(MetaProperty { node, meta, property })) + } + + #[must_use] + #[inline] + pub fn array_expression( + &self, + node: Node, + elements: Vec<'a, Option>>, + trailing_comma: Option, + ) -> Expression<'a> { + Expression::ArrayExpression(self.alloc(ArrayExpression { node, elements, trailing_comma })) + } + + #[must_use] + #[inline] + pub fn arrow_expression( + &self, + node: Node, + expression: bool, + generator: bool, + r#async: bool, + params: FormalParameters<'a>, + body: Box<'a, FunctionBody<'a>>, + type_parameters: Option>>, + return_type: Option>, + ) -> Expression<'a> { + Expression::ArrowFunctionExpression(self.alloc(ArrowExpression { + node, + expression, + generator, + r#async, + params, + body, + type_parameters, + return_type, + })) + } + + #[must_use] + #[inline] + pub fn assignment_expression( + &self, + node: Node, + operator: AssignmentOperator, + left: AssignmentTarget<'a>, + right: Expression<'a>, + ) -> Expression<'a> { + Expression::AssignmentExpression(self.alloc(AssignmentExpression { + node, + operator, + left, + right, + })) + } + + #[must_use] + #[inline] + pub fn await_expression(&self, node: Node, argument: Expression<'a>) -> Expression<'a> { + Expression::AwaitExpression(self.alloc(AwaitExpression { node, argument })) + } + + #[must_use] + #[inline] + pub fn binary_expression( + &self, + node: Node, + left: Expression<'a>, + operator: BinaryOperator, + right: Expression<'a>, + ) -> Expression<'a> { + Expression::BinaryExpression(self.alloc(BinaryExpression { node, left, operator, right })) + } + + #[must_use] + #[inline] + pub fn call_expression( + &self, + node: Node, + callee: Expression<'a>, + arguments: Vec<'a, Argument<'a>>, + optional: bool, // for optional chaining + type_parameters: Option>>, + ) -> Expression<'a> { + Expression::CallExpression(self.alloc(CallExpression { + node, + callee, + arguments, + optional, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn chain_expression(&self, node: Node, expression: ChainElement<'a>) -> Expression<'a> { + Expression::ChainExpression(self.alloc(ChainExpression { node, expression })) + } + + #[must_use] + #[inline] + pub fn class_expression(&self, class: Box<'a, Class<'a>>) -> Expression<'a> { + Expression::ClassExpression(class) + } + + #[must_use] + #[inline] + pub fn conditional_expression( + &self, + node: Node, + test: Expression<'a>, + consequent: Expression<'a>, + alternate: Expression<'a>, + ) -> Expression<'a> { + Expression::ConditionalExpression(self.alloc(ConditionalExpression { + node, + test, + consequent, + alternate, + })) + } + + #[must_use] + #[inline] + pub fn function_expression(&self, function: Box<'a, Function<'a>>) -> Expression<'a> { + Expression::FunctionExpression(function) + } + + #[must_use] + #[inline] + pub fn import_expression( + &self, + node: Node, + source: Expression<'a>, + arguments: Vec<'a, Expression<'a>>, + ) -> Expression<'a> { + Expression::ImportExpression(self.alloc(ImportExpression { node, source, arguments })) + } + + #[must_use] + #[inline] + pub fn logical_expression( + &self, + node: Node, + left: Expression<'a>, + operator: LogicalOperator, + right: Expression<'a>, + ) -> Expression<'a> { + Expression::LogicalExpression(self.alloc(LogicalExpression { node, left, operator, right })) + } + + #[must_use] + #[inline] + pub fn computed_member_expression( + &self, + node: Node, + object: Expression<'a>, + expression: Expression<'a>, + optional: bool, // for optional chaining + ) -> Expression<'a> { + Expression::MemberExpression(self.alloc({ + MemberExpression::ComputedMemberExpression(ComputedMemberExpression { + node, + object, + expression, + optional, + }) + })) + } + + #[must_use] + #[inline] + pub fn static_member_expression( + &self, + node: Node, + object: Expression<'a>, + property: IdentifierName, + optional: bool, // for optional chaining + ) -> Expression<'a> { + Expression::MemberExpression(self.alloc({ + MemberExpression::StaticMemberExpression(StaticMemberExpression { + node, + object, + property, + optional, + }) + })) + } + + #[must_use] + #[inline] + pub fn private_field_expression( + &self, + node: Node, + object: Expression<'a>, + field: PrivateIdentifier, + optional: bool, + ) -> Expression<'a> { + Expression::MemberExpression(self.alloc({ + MemberExpression::PrivateFieldExpression(PrivateFieldExpression { + node, + object, + field, + optional, + }) + })) + } + + #[must_use] + #[inline] + pub fn new_expression( + &self, + node: Node, + callee: Expression<'a>, + arguments: Vec<'a, Argument<'a>>, + type_parameters: Option>>, + ) -> Expression<'a> { + Expression::NewExpression(self.alloc(NewExpression { + node, + callee, + arguments, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn object_expression( + &self, + node: Node, + properties: Vec<'a, ObjectProperty<'a>>, + trailing_comma: Option, + ) -> Expression<'a> { + Expression::ObjectExpression(self.alloc(ObjectExpression { + node, + properties, + trailing_comma, + })) + } + + #[must_use] + #[inline] + pub fn parenthesized_expression( + &self, + node: Node, + expression: Expression<'a>, + ) -> Expression<'a> { + Expression::ParenthesizedExpression( + self.alloc(ParenthesizedExpression { node, expression }), + ) + } + + #[must_use] + #[inline] + pub fn sequence_expression( + &self, + node: Node, + expressions: Vec<'a, Expression<'a>>, + ) -> Expression<'a> { + Expression::SequenceExpression(self.alloc(SequenceExpression { node, expressions })) + } + + #[must_use] + #[inline] + pub fn tagged_template_expression( + &self, + node: Node, + tag: Expression<'a>, + quasi: TemplateLiteral<'a>, + type_parameters: Option>>, + ) -> Expression<'a> { + Expression::TaggedTemplateExpression(self.alloc(TaggedTemplateExpression { + node, + tag, + quasi, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn template_literal_expression( + &self, + template_literal: TemplateLiteral<'a>, + ) -> Expression<'a> { + Expression::TemplateLiteral(self.alloc(template_literal)) + } + + #[must_use] + #[inline] + pub fn this_expression(&self, node: Node) -> Expression<'a> { + Expression::ThisExpression(self.alloc(ThisExpression { node })) + } + + #[must_use] + #[inline] + pub fn unary_expression( + &self, + node: Node, + operator: UnaryOperator, + prefix: bool, + argument: Expression<'a>, + ) -> Expression<'a> { + Expression::UnaryExpression(self.alloc(UnaryExpression { + node, + operator, + prefix, + argument, + })) + } + + #[must_use] + #[inline] + pub fn update_expression( + &self, + node: Node, + operator: UpdateOperator, + prefix: bool, + argument: SimpleAssignmentTarget<'a>, + ) -> Expression<'a> { + Expression::UpdateExpression(self.alloc(UpdateExpression { + node, + operator, + prefix, + argument, + })) + } + + #[must_use] + #[inline] + pub fn yield_expression( + &self, + node: Node, + delegate: bool, + argument: Option>, + ) -> Expression<'a> { + Expression::YieldExpression(self.alloc(YieldExpression { node, delegate, argument })) + } + + /* ---------- Functions ---------- */ + #[must_use] + #[inline] + pub fn function_declaration(&self, func: Box<'a, Function<'a>>) -> Statement<'a> { + Statement::Declaration(Declaration::FunctionDeclaration(func)) + } + + #[must_use] + #[inline] + pub fn formal_parameters( + &self, + node: Node, + kind: FormalParameterKind, + items: Vec<'a, FormalParameter<'a>>, + ) -> FormalParameters<'a> { + FormalParameters { node, kind, items } + } + + #[must_use] + #[inline] + pub fn formal_parameter( + &self, + node: Node, + pattern: BindingPattern<'a>, + accessibility: Option, + readonly: bool, + decorators: Option>>, + ) -> FormalParameter<'a> { + FormalParameter { node, pattern, accessibility, readonly, decorators } + } + + #[must_use] + #[inline] + #[allow(clippy::fn_params_excessive_bools)] + pub fn function( + &self, + r#type: FunctionType, + node: Node, + id: Option, + expression: bool, + generator: bool, + r#async: bool, + params: FormalParameters<'a>, + body: Option>>, + type_parameters: Option>>, + return_type: Option>, + declare: bool, + ) -> Box<'a, Function<'a>> { + self.alloc(Function { + r#type, + node, + id, + expression, + generator, + r#async, + params, + body, + declare, + type_parameters, + return_type, + }) + } + + #[must_use] + #[inline] + pub fn function_body( + &self, + node: Node, + directives: Vec<'a, Directive>, + statements: Vec<'a, Statement<'a>>, + ) -> Box<'a, FunctionBody<'a>> { + self.alloc(FunctionBody { node, directives, statements }) + } + + /* ---------- Class ---------- */ + + #[must_use] + #[inline] + pub fn class( + &self, + r#type: ClassType, + node: Node, + id: Option, + super_class: Option>, + body: ClassBody<'a>, + type_parameters: Option>>, + super_type_parameters: Option>>, + implements: Option>>>, + r#abstract: bool, + decorators: Option>>, + declare: bool, + ) -> Box<'a, Class<'a>> { + self.alloc(Class { + r#type, + node, + id, + super_class, + body, + type_parameters, + super_type_parameters, + implements, + r#abstract, + decorators, + declare, + }) + } + + #[must_use] + #[inline] + pub fn class_declaration(&self, class: Box<'a, Class<'a>>) -> Statement<'a> { + Statement::Declaration(Declaration::ClassDeclaration(class)) + } + + #[must_use] + #[inline] + pub fn static_block(&self, node: Node, body: Vec<'a, Statement<'a>>) -> ClassElement<'a> { + ClassElement::StaticBlock(self.alloc(StaticBlock { node, body })) + } + + #[must_use] + #[inline] + pub fn accessor_property( + &self, + node: Node, + key: PropertyKey<'a>, + value: Option>, + computed: bool, + r#static: bool, + ) -> ClassElement<'a> { + ClassElement::AccessorProperty(self.alloc(AccessorProperty { + node, + key, + value, + computed, + r#static, + })) + } + + /* ---------- Declarations ---------- */ + + #[must_use] + #[inline] + pub fn variable_declaration( + &self, + node: Node, + kind: VariableDeclarationKind, + declarations: Vec<'a, VariableDeclarator<'a>>, + ) -> Box<'a, VariableDeclaration<'a>> { + self.alloc(VariableDeclaration { node, kind, declarations }) + } + + #[must_use] + #[inline] + pub fn variable_declarator( + &self, + node: Node, + kind: VariableDeclarationKind, + id: BindingPattern<'a>, + init: Option>, + definite: bool, + ) -> VariableDeclarator<'a> { + VariableDeclarator { node, kind, id, init, definite } + } + + /* ---------- Patterns ---------- */ + + #[must_use] + #[inline] + pub fn binding_pattern( + &self, + kind: BindingPatternKind<'a>, + type_annotation: Option>, + optional: bool, + ) -> BindingPattern<'a> { + BindingPattern { kind, type_annotation, optional } + } + + #[must_use] + #[inline] + pub fn binding_identifier(&self, identifier: BindingIdentifier) -> BindingPatternKind<'a> { + BindingPatternKind::BindingIdentifier(self.alloc(identifier)) + } + + #[must_use] + #[inline] + pub fn object_pattern( + &self, + node: Node, + properties: Vec<'a, ObjectPatternProperty<'a>>, + ) -> BindingPatternKind<'a> { + BindingPatternKind::ObjectPattern(self.alloc(ObjectPattern { node, properties })) + } + + #[must_use] + #[inline] + pub fn spread_element( + &self, + node: Node, + argument: Expression<'a>, + ) -> Box<'a, SpreadElement<'a>> { + self.alloc(SpreadElement { node, argument }) + } + + #[must_use] + #[inline] + pub fn property( + &self, + node: Node, + kind: PropertyKind, + key: PropertyKey<'a>, + value: PropertyValue<'a>, + method: bool, + shorthand: bool, + computed: bool, + ) -> Box<'a, Property<'a>> { + self.alloc(Property { node, kind, key, value, method, shorthand, computed }) + } + + #[must_use] + #[inline] + pub fn array_pattern( + &self, + node: Node, + elements: Vec<'a, Option>>, + ) -> BindingPatternKind<'a> { + BindingPatternKind::ArrayPattern(self.alloc(ArrayPattern { node, elements })) + } + + #[must_use] + #[inline] + pub fn assignment_pattern( + &self, + node: Node, + left: BindingPattern<'a>, + right: Expression<'a>, + ) -> BindingPattern<'a> { + let pattern = self.alloc(AssignmentPattern { node, left, right }); + BindingPattern { + kind: BindingPatternKind::AssignmentPattern(pattern), + type_annotation: None, + optional: false, + } + } + + #[must_use] + #[inline] + pub fn rest_element( + &self, + node: Node, + argument: BindingPattern<'a>, + ) -> Box<'a, RestElement<'a>> { + self.alloc(RestElement { node, argument }) + } + + #[must_use] + #[inline] + pub fn rest_element_pattern(&self, elem: Box<'a, RestElement<'a>>) -> BindingPattern<'a> { + BindingPattern { + kind: BindingPatternKind::RestElement(elem), + type_annotation: None, + optional: false, + } + } + + /* ---------- Modules ---------- */ + + #[must_use] + #[inline] + pub fn module_declaration(&self, node: Node, kind: ModuleDeclarationKind<'a>) -> Statement<'a> { + Statement::ModuleDeclaration(self.alloc(ModuleDeclaration { node, kind })) + } + + #[must_use] + #[inline] + pub fn import_declaration( + &self, + specifiers: Vec<'a, ImportDeclarationSpecifier>, + source: StringLiteral, + assertions: Option>, + import_kind: Option, + ) -> Box<'a, ImportDeclaration<'a>> { + self.alloc(ImportDeclaration { specifiers, source, assertions, import_kind }) + } + + #[must_use] + #[inline] + pub fn export_all_declaration( + &self, + exported: Option, + source: StringLiteral, + assertions: Option>, // Some(vec![]) for empty assertion + ) -> Box<'a, ExportAllDeclaration<'a>> { + self.alloc(ExportAllDeclaration { exported, source, assertions }) + } + + #[must_use] + #[inline] + pub fn export_default_declaration( + &self, + declaration: ExportDefaultDeclarationKind<'a>, + exported: ModuleExportName, + ) -> Box<'a, ExportDefaultDeclaration<'a>> { + self.alloc(ExportDefaultDeclaration { declaration, exported }) + } + + #[must_use] + #[inline] + pub fn export_named_declaration( + &self, + declaration: Option>, + specifiers: Vec<'a, ExportSpecifier>, + source: Option, + export_kind: Option, // `export type { foo }` + ) -> Box<'a, ExportNamedDeclaration<'a>> { + self.alloc(ExportNamedDeclaration { declaration, specifiers, source, export_kind }) + } + + /* ---------- JSX ----------------- */ + #[must_use] + #[inline] + pub fn jsx_element( + &self, + node: Node, + opening_element: Box<'a, JSXOpeningElement<'a>>, + closing_element: Option>>, + children: Vec<'a, JSXChild<'a>>, + ) -> Box<'a, JSXElement<'a>> { + self.alloc(JSXElement { node, opening_element, closing_element, children }) + } + + #[must_use] + #[inline] + pub fn jsx_opening_element( + &self, + node: Node, + self_closing: bool, + name: JSXElementName<'a>, + attributes: Vec<'a, JSXAttributeItem<'a>>, + type_parameters: Option>>, + ) -> Box<'a, JSXOpeningElement<'a>> { + self.alloc(JSXOpeningElement { node, self_closing, name, attributes, type_parameters }) + } + + #[must_use] + #[inline] + pub fn jsx_closing_element( + &self, + node: Node, + name: JSXElementName<'a>, + ) -> Box<'a, JSXClosingElement<'a>> { + self.alloc(JSXClosingElement { node, name }) + } + + #[must_use] + #[inline] + pub fn jsx_fragment( + &self, + node: Node, + opening_fragment: JSXOpeningFragment, + closing_fragment: JSXClosingFragment, + children: Vec<'a, JSXChild<'a>>, + ) -> Box<'a, JSXFragment<'a>> { + self.alloc(JSXFragment { node, opening_fragment, closing_fragment, children }) + } + + #[must_use] + #[inline] + pub fn jsx_opening_fragment(&self, node: Node) -> JSXOpeningFragment { + JSXOpeningFragment { node } + } + + #[must_use] + #[inline] + pub fn jsx_closing_fragment(&self, node: Node) -> JSXClosingFragment { + JSXClosingFragment { node } + } + + #[must_use] + #[inline] + pub fn jsx_namespaced_name( + &self, + node: Node, + namespace: JSXIdentifier, + property: JSXIdentifier, + ) -> Box<'a, JSXNamespacedName> { + self.alloc(JSXNamespacedName { node, namespace, property }) + } + + #[must_use] + #[inline] + pub fn jsx_member_expression( + &self, + node: Node, + object: JSXMemberExpressionObject<'a>, + property: JSXIdentifier, + ) -> Box<'a, JSXMemberExpression<'a>> { + self.alloc(JSXMemberExpression { node, object, property }) + } + + #[must_use] + #[inline] + pub fn jsx_expression_container( + &self, + node: Node, + expression: JSXExpression<'a>, + ) -> JSXExpressionContainer<'a> { + JSXExpressionContainer { node, expression } + } + + #[must_use] + #[inline] + pub fn jsx_spread_child(&self, node: Node, expression: Expression<'a>) -> JSXSpreadChild<'a> { + JSXSpreadChild { node, expression } + } + + #[must_use] + #[inline] + pub fn jsx_empty_expression(&self, node: Node) -> JSXEmptyExpression { + JSXEmptyExpression { node } + } + + #[must_use] + #[inline] + pub fn jsx_attribute( + &self, + node: Node, + name: JSXAttributeName<'a>, + value: Option>, + ) -> Box<'a, JSXAttribute<'a>> { + self.alloc(JSXAttribute { node, name, value }) + } + + #[must_use] + #[inline] + pub fn jsx_spread_attribute( + &self, + node: Node, + argument: Expression<'a>, + ) -> Box<'a, JSXSpreadAttribute<'a>> { + self.alloc(JSXSpreadAttribute { node, argument }) + } + + #[must_use] + #[inline] + pub fn jsx_identifier(&self, node: Node, name: Atom) -> JSXIdentifier { + JSXIdentifier { node, name } + } + + #[must_use] + #[inline] + pub fn jsx_text(&self, node: Node, value: Atom) -> JSXText { + JSXText { node, value } + } + + /* ---------- TypeScript ---------- */ + #[must_use] + #[inline] + pub fn ts_module_declaration( + &self, + node: Node, + id: TSModuleDeclarationName, + body: TSModuleDeclarationBody<'a>, + declare: bool, + ) -> Box<'a, TSModuleDeclaration<'a>> { + self.alloc(TSModuleDeclaration { node, id, body, declare }) + } + + #[must_use] + #[inline] + pub fn ts_type_annotation( + &self, + node: Node, + type_annotation: TSType<'a>, + ) -> TSTypeAnnotation<'a> { + TSTypeAnnotation { node, type_annotation } + } + + #[must_use] + #[inline] + pub fn ts_literal_type(&self, node: Node, literal: TSLiteral<'a>) -> TSType<'a> { + TSType::TSLiteralType(self.alloc(TSLiteralType { node, literal })) + } + + #[must_use] + #[inline] + pub fn ts_union_type(&self, node: Node, types: Vec<'a, TSType<'a>>) -> TSType<'a> { + TSType::TSUnionType(self.alloc(TSUnionType { node, types })) + } + + #[must_use] + #[inline] + pub fn ts_intersection_type(&self, node: Node, types: Vec<'a, TSType<'a>>) -> TSType<'a> { + TSType::TSIntersectionType(self.alloc(TSIntersectionType { node, types })) + } + + #[must_use] + #[inline] + pub fn ts_type_operator_type( + &self, + node: Node, + operator: TSTypeOperator, + type_annotation: TSType<'a>, + ) -> TSType<'a> { + TSType::TSTypeOperatorType(self.alloc(TSTypeOperatorType { + node, + operator, + type_annotation, + })) + } + + #[must_use] + #[inline] + pub fn ts_array_type(&self, node: Node, element_type: TSType<'a>) -> TSType<'a> { + TSType::TSArrayType(self.alloc(TSArrayType { node, element_type })) + } + + #[must_use] + #[inline] + pub fn ts_indexed_access_type( + &self, + node: Node, + object_type: TSType<'a>, + index_type: TSType<'a>, + ) -> TSType<'a> { + TSType::TSIndexedAccessType(self.alloc(TSIndexedAccessType { + node, + object_type, + index_type, + })) + } + + #[must_use] + #[inline] + pub fn ts_tuple_type( + &self, + node: Node, + element_types: Vec<'a, TSTupleElement<'a>>, + ) -> TSType<'a> { + TSType::TSTupleType(self.alloc(TSTupleType { node, element_types })) + } + + #[must_use] + #[inline] + pub fn ts_type_reference( + &self, + node: Node, + type_name: TSTypeName<'a>, + type_parameters: Option>>, + ) -> TSType<'a> { + TSType::TSTypeReference(self.alloc(TSTypeReference { node, type_name, type_parameters })) + } + + #[must_use] + #[inline] + pub fn ts_type_literal(&self, node: Node, members: Vec<'a, TSSignature<'a>>) -> TSType<'a> { + TSType::TSTypeLiteral(self.alloc(TSTypeLiteral { node, members })) + } + + #[must_use] + #[inline] + pub fn ts_type_implement( + &self, + node: Node, + expression: TSTypeName<'a>, + type_parameters: Option>>, + ) -> Box<'a, TSClassImplements<'a>> { + self.alloc(TSClassImplements { node, expression, type_parameters }) + } + + #[must_use] + #[inline] + pub fn ts_type_parameter( + &self, + node: Node, + name: BindingIdentifier, + constraint: Option>, + default: Option>, + r#in: bool, + out: bool, + ) -> Box<'a, TSTypeParameter<'a>> { + self.alloc(TSTypeParameter { node, name, constraint, default, r#in, out }) + } + + #[must_use] + #[inline] + pub fn ts_type_parameters( + &self, + node: Node, + params: Vec<'a, Box<'a, TSTypeParameter<'a>>>, + ) -> Box<'a, TSTypeParameterDeclaration<'a>> { + self.alloc(TSTypeParameterDeclaration { node, params }) + } + + #[must_use] + #[inline] + pub fn ts_interface_heritages( + &self, + extends: Vec<'a, (Expression<'a>, Option>>, Node)>, + ) -> Vec<'a, Box<'a, TSInterfaceHeritage<'a>>> { + Vec::from_iter_in( + extends.into_iter().map(|(expression, type_parameters, node)| { + self.alloc(TSInterfaceHeritage { node, expression, type_parameters }) + }), + self.allocator, + ) + } + + #[must_use] + #[inline] + pub fn ts_interface_body( + &self, + node: Node, + body: Vec<'a, TSSignature<'a>>, + ) -> Box<'a, TSInterfaceBody<'a>> { + self.alloc(TSInterfaceBody { node, body }) + } + + #[must_use] + #[inline] + pub fn ts_index_signature( + &self, + node: Node, + parameters: Vec<'a, Box<'a, TSIndexSignatureName<'a>>>, + type_annotation: TSTypeAnnotation<'a>, + ) -> TSSignature<'a> { + TSSignature::TSIndexSignature(self.alloc(TSIndexSignature { + node, + parameters, + type_annotation, + })) + } + + #[must_use] + #[inline] + pub fn ts_property_signature( + &self, + node: Node, + computed: bool, + optional: bool, + readonly: bool, + key: PropertyKey<'a>, + type_annotation: Option>, + ) -> TSSignature<'a> { + TSSignature::TSPropertySignature(self.alloc(TSPropertySignature { + node, + computed, + optional, + readonly, + key, + type_annotation, + })) + } + + #[must_use] + #[inline] + pub fn ts_call_signature_declaration( + &self, + node: Node, + params: FormalParameters<'a>, + return_type: Option>, + type_parameters: Option>>, + ) -> TSSignature<'a> { + TSSignature::TSCallSignatureDeclaration(self.alloc(TSCallSignatureDeclaration { + node, + params, + return_type, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_construct_signature_declaration( + &self, + node: Node, + params: FormalParameters<'a>, + return_type: Option>, + type_parameters: Option>>, + ) -> TSSignature<'a> { + TSSignature::TSConstructSignatureDeclaration(self.alloc(TSConstructSignatureDeclaration { + node, + params, + return_type, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_method_signature( + &self, + node: Node, + key: PropertyKey<'a>, + computed: bool, + optional: bool, + kind: TSMethodSignatureKind, + params: FormalParameters<'a>, + return_type: Option>, + type_parameters: Option>>, + ) -> TSSignature<'a> { + TSSignature::TSMethodSignature(self.alloc(TSMethodSignature { + node, + key, + computed, + optional, + kind, + params, + return_type, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_module_block( + &self, + node: Node, + body: Vec<'a, Statement<'a>>, + ) -> Box<'a, TSModuleBlock<'a>> { + self.alloc(TSModuleBlock { node, body }) + } + + #[must_use] + #[inline] + pub fn ts_type_arguments( + &self, + node: Node, + params: Vec<'a, TSType<'a>>, + ) -> Box<'a, TSTypeParameterInstantiation<'a>> { + self.alloc(TSTypeParameterInstantiation { node, params }) + } + + #[must_use] + #[inline] + pub fn ts_non_null_expression(&self, node: Node, expression: Expression<'a>) -> Expression<'a> { + Expression::TSNonNullExpression(self.alloc(TSNonNullExpression { node, expression })) + } + + #[must_use] + #[inline] + pub fn ts_type_assertion( + &self, + node: Node, + type_annotation: TSType<'a>, + expression: Expression<'a>, + ) -> Expression<'a> { + Expression::TSTypeAssertion(self.alloc(TSTypeAssertion { + node, + type_annotation, + expression, + })) + } + + #[must_use] + #[inline] + pub fn ts_import_equals_declaration( + &self, + node: Node, + id: BindingIdentifier, + module_reference: TSModuleReference<'a>, + is_export: bool, + import_kind: ImportOrExportKind, + ) -> Declaration<'a> { + Declaration::TSImportEqualsDeclaration(self.alloc(TSImportEqualsDeclaration { + node, + id, + module_reference: self.alloc(module_reference), + is_export, + import_kind, + })) + } + + #[must_use] + #[inline] + pub fn ts_interface_declaration( + &self, + node: Node, + id: BindingIdentifier, + body: Box<'a, TSInterfaceBody<'a>>, + type_parameters: Option>>, + extends: Option>>>, + declare: bool, + ) -> Declaration<'a> { + Declaration::TSInterfaceDeclaration(self.alloc(TSInterfaceDeclaration { + node, + id, + body, + type_parameters, + extends, + declare, + })) + } + + #[must_use] + #[inline] + pub fn ts_type_alias_declaration( + &self, + node: Node, + id: BindingIdentifier, + type_annotation: TSType<'a>, + type_parameters: Option>>, + declare: bool, + ) -> Declaration<'a> { + Declaration::TSTypeAliasDeclaration(self.alloc(TSTypeAliasDeclaration { + node, + id, + type_annotation, + type_parameters, + declare, + })) + } + + #[must_use] + #[inline] + pub fn ts_enum_declaration( + &self, + node: Node, + id: BindingIdentifier, + members: Vec<'a, TSEnumMember<'a>>, + declare: bool, + r#const: bool, + ) -> Declaration<'a> { + Declaration::TSEnumDeclaration(self.alloc(TSEnumDeclaration { + node, + id, + members, + declare, + r#const, + })) + } + + #[must_use] + #[inline] + pub fn decorator(&self, node: Node, expression: Expression<'a>) -> Decorator<'a> { + Decorator { node, expression } + } + + #[must_use] + #[inline] + pub fn ts_void_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSVoidKeyword(self.alloc(TSVoidKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_this_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSThisKeyword(self.alloc(TSThisKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_any_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSAnyKeyword(self.alloc(TSAnyKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_unknown_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSUnknownKeyword(self.alloc(TSUnknownKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_number_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSNumberKeyword(self.alloc(TSNumberKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_boolean_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSBooleanKeyword(self.alloc(TSBooleanKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_object_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSObjectKeyword(self.alloc(TSObjectKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_string_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSStringKeyword(self.alloc(TSStringKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_bigint_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSBigIntKeyword(self.alloc(TSBigIntKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_symbol_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSSymbolKeyword(self.alloc(TSSymbolKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_null_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSNullKeyword(self.alloc(TSNullKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_undefined_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSUndefinedKeyword(self.alloc(TSUndefinedKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_never_keyword(&self, node: Node) -> TSType<'a> { + TSType::TSNeverKeyword(self.alloc(TSNeverKeyword { node })) + } + + #[must_use] + #[inline] + pub fn ts_template_literal_type( + &self, + node: Node, + quasis: Vec<'a, TemplateElement>, + types: Vec<'a, TSType<'a>>, + ) -> TSType<'a> { + TSType::TSTemplateLiteralType(self.alloc(TSTemplateLiteralType { node, quasis, types })) + } + + #[must_use] + #[inline] + pub fn ts_type_query_type( + &self, + node: Node, + expr_name: TSTypeName<'a>, + type_parameters: Option>>, + ) -> TSType<'a> { + TSType::TSTypeQuery(self.alloc(TSTypeQuery { node, expr_name, type_parameters })) + } + + #[must_use] + #[inline] + pub fn ts_conditional_type( + &self, + node: Node, + check_type: TSType<'a>, + extends_type: TSType<'a>, + true_type: TSType<'a>, + false_type: TSType<'a>, + ) -> TSType<'a> { + TSType::TSConditionalType(self.alloc(TSConditionalType { + node, + check_type, + extends_type, + true_type, + false_type, + })) + } + + #[must_use] + #[inline] + pub fn ts_mapped_type( + &self, + node: Node, + type_parameter: Box<'a, TSTypeParameter<'a>>, + name_type: Option>, + type_annotation: TSType<'a>, + optional: TSMappedTypeModifierOperator, + readonly: TSMappedTypeModifierOperator, + ) -> TSType<'a> { + TSType::TSMappedType(self.alloc(TSMappedType { + node, + type_parameter, + name_type, + type_annotation, + optional, + readonly, + })) + } + + #[must_use] + #[inline] + pub fn ts_import_type( + &self, + node: Node, + is_type_of: bool, + parameter: TSType<'a>, + qualifier: Option>, + type_parameters: Option>>, + ) -> TSType<'a> { + TSType::TSImportType(self.alloc(TSImportType { + node, + is_type_of, + parameter, + qualifier, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_constructor_type( + &self, + node: Node, + r#abstract: bool, + params: FormalParameters<'a>, + return_type: TSTypeAnnotation<'a>, + type_parameters: Option>>, + ) -> TSType<'a> { + TSType::TSConstructorType(self.alloc(TSConstructorType { + node, + r#abstract, + params, + return_type, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_function_type( + &self, + node: Node, + params: FormalParameters<'a>, + return_type: TSTypeAnnotation<'a>, + type_parameters: Option>>, + ) -> TSType<'a> { + TSType::TSFunctionType(self.alloc(TSFunctionType { + node, + params, + return_type, + type_parameters, + })) + } + + #[must_use] + #[inline] + pub fn ts_infer_type( + &self, + node: Node, + type_parameter: Box<'a, TSTypeParameter<'a>>, + ) -> TSType<'a> { + TSType::TSInferType(self.alloc(TSInferType { node, type_parameter })) + } + + #[must_use] + #[inline] + pub fn ts_type_predicate( + &self, + node: Node, + parameter_name: TSTypePredicateName, + asserts: bool, + type_annotation: Option>, + ) -> TSType<'a> { + TSType::TSTypePredicate(self.alloc(TSTypePredicate { + node, + parameter_name, + asserts, + type_annotation, + })) + } + + /* JSDoc */ + #[must_use] + #[inline] + pub fn js_doc_nullable_type( + &self, + node: Node, + type_annotation: TSType<'a>, + postfix: bool, + ) -> TSType<'a> { + TSType::JSDocNullableType(self.alloc(JSDocNullableType { node, type_annotation, postfix })) + } + + #[must_use] + #[inline] + pub fn js_doc_unknown_type(&self, node: Node) -> TSType<'a> { + TSType::JSDocUnknownType(self.alloc(JSDocUnknownType { node })) + } +} diff --git a/crates/oxc_ast/src/context.rs b/crates/oxc_ast/src/context.rs new file mode 100644 index 0000000000000..7d5e6cde916d0 --- /dev/null +++ b/crates/oxc_ast/src/context.rs @@ -0,0 +1,185 @@ +//! ECMAScript Grammar Contexts: [In] [Yield] [Await] +#![allow(non_upper_case_globals)] + +use bitflags::bitflags; + +bitflags! { + /// 5.1.5 Grammar Notation + /// A production may be parameterized by a subscripted annotation of the form “[parameters]”, + /// which may appear as a suffix to the nonterminal symbol defined by the production. + /// “parameters” may be either a single name or a comma separated list of names. + /// A parameterized production is shorthand for a set of productions defining all combinations of the parameter names, + /// preceded by an underscore, appended to the parameterized nonterminal symbol. + pub struct Context: u8 { + /// [In] Flag, i.e. the [In] part in RelationalExpression[In, Yield, Await] + /// Section 13.10 Relational Operators Note 2: + /// The [In] grammar parameter is needed to avoid confusing the in operator + /// in a relational expression with the in operator in a for statement. + const In = 1 << 0; + + /// [Yield] Flag + const Yield = 1 << 1; + + /// [Await] Flag + /// Section 15.8 Async Function Definitions Note 1: + /// await is parsed as an AwaitExpression when the [Await] parameter is present + const Await = 1 << 2; + + /// [Return] Flag + /// i.e. the [Return] in Statement[Yield, Await, Return] + const Return = 1<< 3; + + /// Typescript should parse extends clause as conditional type instead of type constrains. + /// Used in infer clause + /// + /// type X = T extends infer U extends number ? U : T; + /// The "infer U extends number" is type constrains. + /// + /// type X = T extends (infer U extends number ? U : T) ? U : T; + /// The "(infer U extends number ? U : T)" is conditional type. + const DisallowConditionalTypes = 1 << 4; + + /// A declaration file, or inside something with the `declare` modifier. + /// Declarations that don't define an implementation is "ambient": + /// * ambient variable declaration => `declare var $: any` + /// * ambient class declaration => `declare class C { foo(); } , etc..` + const Ambient = 1 << 5; + + /// Decorator context does not parse computed member expressions, e.g. + /// `class C { @dec() ["method"]() {} }` + const Decorator = 1 << 6; + } +} + +impl Default for Context { + fn default() -> Self { + Self::In + } +} + +impl Context { + #[must_use] + #[inline] + pub const fn has_in(self) -> bool { + self.contains(Self::In) + } + + #[must_use] + #[inline] + pub const fn has_yield(self) -> bool { + self.contains(Self::Yield) + } + + #[must_use] + #[inline] + pub const fn has_await(self) -> bool { + self.contains(Self::Await) + } + + #[must_use] + #[inline] + pub const fn has_return(self) -> bool { + self.contains(Self::Return) + } + + #[must_use] + #[inline] + pub const fn has_disallow_conditional_types(self) -> bool { + self.contains(Self::DisallowConditionalTypes) + } + + #[must_use] + #[inline] + pub const fn has_ambient(self) -> bool { + self.contains(Self::Ambient) + } + + #[must_use] + #[inline] + pub const fn has_decorator(self) -> bool { + self.contains(Self::Decorator) + } + + #[must_use] + pub const fn union_await_if(self, include: bool) -> Self { + self.union_if(Self::Await, include) + } + + #[must_use] + pub const fn union_yield_if(self, include: bool) -> Self { + self.union_if(Self::Yield, include) + } + + #[must_use] + const fn union_if(self, other: Self, include: bool) -> Self { + if include { self.union(other) } else { self } + } + + #[must_use] + pub fn and_in(self, include: bool) -> Self { + self.and(Self::In, include) + } + + #[must_use] + pub fn and_yield(self, include: bool) -> Self { + self.and(Self::Yield, include) + } + + #[must_use] + pub fn and_await(self, include: bool) -> Self { + self.and(Self::Await, include) + } + + #[must_use] + pub fn and_return(self, include: bool) -> Self { + self.and(Self::Return, include) + } + + #[must_use] + pub fn and_disallow_conditional_types(self, include: bool) -> Self { + self.and(Self::DisallowConditionalTypes, include) + } + + #[must_use] + pub fn and_ambient(self, include: bool) -> Self { + self.and(Self::Ambient, include) + } + + #[must_use] + pub fn and_decorator(self, include: bool) -> Self { + self.and(Self::Decorator, include) + } + + #[must_use] + fn and(self, flag: Self, set: bool) -> Self { + if set { self | flag } else { self - flag } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum StatementContext { + If, + Label, + Do, + While, + With, + For, + StatementList, +} + +impl StatementContext { + #[must_use] + pub fn is_single_statement(self) -> bool { + self != Self::StatementList + } + + #[must_use] + pub fn is_statement_list(self) -> bool { + self == Self::StatementList + } + + #[must_use] + pub const fn is_iteration(self) -> bool { + matches!(self, Self::For | Self::While | Self::Do) + } +} diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index dcb70fd9b3144..875679db87732 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -1,9 +1,59 @@ //! AST +//! NOTE: This is not compatible with estree. -mod source_type; +#![feature(let_chains)] +#![feature(is_some_and)] -pub use source_type::SourceType; +mod serialize; + +pub mod ast; +pub mod ast_builder; +pub mod context; +pub mod node; +pub mod source_type; + +pub use num_bigint::BigUint; + +pub use self::ast::*; +pub use self::ast_builder::*; +pub use self::node::*; +pub use self::source_type::*; pub type Atom = compact_str::CompactString; -pub type Span = std::ops::Range; +// After experimenting with two types of boxed enum variants: +// 1. +// ``` +// enum Expression { +// Variant(Box) +// } +// struct Struct { +// expression: Expression +// } +// ``` +// 2. +// ``` +// enum Expression { +// Variant(Struct) +// } +// struct Struct { +// expression: Box +// } +// ``` +// I have concluded that the first options is more performant and more ergonomic to use. +// The following test make sure all enum variants are boxed, resulting 16 bytes for each enum. +// Read `https://nnethercote.github.io/perf-book/type-sizes.html` for more details. +#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] +#[test] +fn no_bloat_enum_sizes() { + use std::mem::size_of; + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 16); +} diff --git a/crates/oxc_ast/src/node.rs b/crates/oxc_ast/src/node.rs new file mode 100644 index 0000000000000..2cde33c64d5b3 --- /dev/null +++ b/crates/oxc_ast/src/node.rs @@ -0,0 +1,374 @@ +use std::{ + hash::{Hash, Hasher}, + ops::Range, +}; + +use serde::Serialize; + +use crate::{context::Context, Argument, AssignmentTarget, AssignmentTargetPattern, BindingPattern, BindingPatternKind, ClassElement, Declaration, ExportDefaultDeclarationKind, Expression, ForStatementInit, ImportAttributeKey, JSXElementName, MemberExpression, ModuleExportName, ObjectPatternProperty, ObjectProperty, PropertyKey, PropertyValue, SimpleAssignmentTarget, Statement, TSModuleDeclarationName, TSSignature, TSType}; + +pub type Span = Range; + +#[derive(Debug, Default, Clone, Copy, Serialize, PartialEq, Eq)] +pub struct Node { + pub start: usize, + pub end: usize, + #[serde(skip)] + pub ctx: Context, +} + +impl Node { + #[must_use] + #[inline] + pub const fn new(start: usize, end: usize, ctx: Context) -> Self { + Self { start, end, ctx } + } + + #[must_use] + pub const fn range(&self) -> Span { + self.start..self.end + } +} + +// #[allow(clippy::derive_hash_xor_eq)] +impl Hash for Node { + fn hash(&self, _state: &mut H) { + // hash to nothing so all ast nodes can be comparible with hash + } +} + +pub trait GetNode { + #[must_use] + fn node(&self) -> Node; +} + +impl<'a> GetNode for Statement<'a> { + fn node(&self) -> Node { + match self { + Self::BlockStatement(stmt) => stmt.node, + Self::BreakStatement(stmt) => stmt.node, + Self::ContinueStatement(stmt) => stmt.node, + Self::DebuggerStatement(stmt) => stmt.node, + Self::DoWhileStatement(stmt) => stmt.node, + Self::EmptyStatement(stmt) => stmt.node, + Self::ExpressionStatement(stmt) => stmt.node, + Self::ForInStatement(stmt) => stmt.node, + Self::ForOfStatement(stmt) => stmt.node, + Self::ForStatement(stmt) => stmt.node, + Self::IfStatement(stmt) => stmt.node, + Self::LabeledStatement(stmt) => stmt.node, + Self::ReturnStatement(stmt) => stmt.node, + Self::SwitchStatement(stmt) => stmt.node, + Self::ThrowStatement(stmt) => stmt.node, + Self::TryStatement(stmt) => stmt.node, + Self::WhileStatement(stmt) => stmt.node, + Self::WithStatement(stmt) => stmt.node, + Self::ModuleDeclaration(decl) => decl.node, + Self::Declaration(decl) => decl.node(), + } + } +} + +impl<'a> GetNode for Expression<'a> { + fn node(&self) -> Node { + match self { + Self::BooleanLiteral(e) => e.node, + Self::NullLiteral(e) => e.node, + Self::NumberLiteral(e) => e.node, + Self::BigintLiteral(e) => e.node, + Self::RegExpLiteral(e) => e.node, + Self::StringLiteral(e) => e.node, + Self::TemplateLiteral(e) => e.node, + Self::Identifier(e) => e.node, + Self::MetaProperty(e) => e.node, + Self::Super(e) => e.node, + Self::ArrayExpression(e) => e.node, + Self::ArrowFunctionExpression(e) => e.node, + Self::AssignmentExpression(e) => e.node, + Self::AwaitExpression(e) => e.node, + Self::BinaryExpression(e) => e.node, + Self::PrivateInExpression(e) => e.node, + Self::CallExpression(e) => e.node, + Self::ChainExpression(e) => e.node, + Self::ClassExpression(e) => e.node, + Self::ConditionalExpression(e) => e.node, + Self::FunctionExpression(e) => e.node, + Self::ImportExpression(e) => e.node, + Self::LogicalExpression(e) => e.node, + Self::MemberExpression(e) => e.node(), + Self::NewExpression(e) => e.node, + Self::ObjectExpression(e) => e.node, + Self::ParenthesizedExpression(e) => e.node, + Self::SequenceExpression(e) => e.node, + Self::TaggedTemplateExpression(e) => e.node, + Self::ThisExpression(e) => e.node, + Self::UnaryExpression(e) => e.node, + Self::UpdateExpression(e) => e.node, + Self::YieldExpression(e) => e.node, + Self::JSXElement(e) => e.node, + Self::JSXFragment(e) => e.node, + Self::TSAsExpression(e) => e.node, + Self::TSTypeAssertion(e) => e.node, + Self::TSNonNullExpression(e) => e.node, + Self::TSInstantiationExpression(e) => e.node, + } + } +} + +impl<'a> GetNode for BindingPatternKind<'a> { + fn node(&self) -> Node { + match self { + Self::BindingIdentifier(ident) => ident.node, + Self::ObjectPattern(pat) => pat.node, + Self::ArrayPattern(pat) => pat.node, + Self::RestElement(elem) => elem.node, + Self::AssignmentPattern(pat) => pat.node, + } + } +} + +impl<'a> GetNode for BindingPattern<'a> { + fn node(&self) -> Node { + match &self.kind { + BindingPatternKind::BindingIdentifier(ident) => ident.node, + BindingPatternKind::ObjectPattern(pat) => pat.node, + BindingPatternKind::ArrayPattern(pat) => pat.node, + BindingPatternKind::RestElement(pat) => pat.node, + BindingPatternKind::AssignmentPattern(pat) => pat.node, + } + } +} + +impl<'a> GetNode for ClassElement<'a> { + fn node(&self) -> Node { + match self { + Self::StaticBlock(block) => block.node, + Self::MethodDefinition(def) => def.node, + Self::PropertyDefinition(def) => def.node, + Self::AccessorProperty(def) => def.node, + Self::TSAbstractMethodDefinition(def) => def.method_definition.node, + Self::TSAbstractPropertyDefinition(def) => def.property_definition.node, + Self::TSIndexSignature(sig) => sig.node, + } + } +} + +impl<'a> GetNode for PropertyKey<'a> { + fn node(&self) -> Node { + match self { + Self::Identifier(ident) => ident.node, + Self::PrivateIdentifier(ident) => ident.node, + Self::Expression(expr) => expr.node(), + } + } +} + +impl<'a> GetNode for MemberExpression<'a> { + fn node(&self) -> Node { + match self { + Self::ComputedMemberExpression(expr) => expr.node, + Self::StaticMemberExpression(expr) => expr.node, + Self::PrivateFieldExpression(expr) => expr.node, + } + } +} + +impl GetNode for ImportAttributeKey { + fn node(&self) -> Node { + match self { + Self::Identifier(identifier) => identifier.node, + Self::StringLiteral(literal) => literal.node, + } + } +} + +impl GetNode for ModuleExportName { + fn node(&self) -> Node { + match self { + Self::Identifier(identifier) => identifier.node, + Self::StringLiteral(literal) => literal.node, + } + } +} + +impl<'a> GetNode for Declaration<'a> { + fn node(&self) -> Node { + match self { + Self::VariableDeclaration(decl) => decl.node, + Self::FunctionDeclaration(decl) => decl.node, + Self::ClassDeclaration(decl) => decl.node, + Self::TSTypeAliasDeclaration(decl) => decl.node, + Self::TSInterfaceDeclaration(decl) => decl.node, + Self::TSEnumDeclaration(decl) => decl.node, + Self::TSModuleDeclaration(decl) => decl.node, + Self::TSImportEqualsDeclaration(decl) => decl.node, + } + } +} + +impl GetNode for TSModuleDeclarationName { + fn node(&self) -> Node { + match self { + Self::Identifier(ident) => ident.node, + Self::StringLiteral(lit) => lit.node, + } + } +} + +impl<'a> GetNode for ObjectProperty<'a> { + fn node(&self) -> Node { + match self { + Self::Property(p) => p.node, + Self::SpreadProperty(p) => p.node, + } + } +} + +impl<'a> GetNode for ObjectPatternProperty<'a> { + fn node(&self) -> Node { + match self { + Self::Property(p) => p.node, + Self::RestElement(e) => e.node, + } + } +} + +impl<'a> GetNode for AssignmentTarget<'a> { + fn node(&self) -> Node { + match self { + Self::SimpleAssignmentTarget(SimpleAssignmentTarget::AssignmentTargetIdentifier( + ident, + )) => ident.node, + Self::SimpleAssignmentTarget(SimpleAssignmentTarget::MemberAssignmentTarget(expr)) => { + expr.node() + } + Self::SimpleAssignmentTarget(SimpleAssignmentTarget::TSAsExpression(expr)) => expr.node, + Self::SimpleAssignmentTarget(SimpleAssignmentTarget::TSNonNullExpression(expr)) => { + expr.node + } + Self::SimpleAssignmentTarget(SimpleAssignmentTarget::TSTypeAssertion(expr)) => { + expr.node + } + Self::AssignmentTargetPattern(AssignmentTargetPattern::ArrayAssignmentTarget(pat)) => { + pat.node + } + Self::AssignmentTargetPattern(AssignmentTargetPattern::ObjectAssignmentTarget(pat)) => { + pat.node + } + } + } +} + +impl<'a> GetNode for PropertyValue<'a> { + fn node(&self) -> Node { + match self { + Self::Pattern(pat) => pat.node(), + Self::Expression(expr) => expr.node(), + } + } +} + +impl<'a> GetNode for Argument<'a> { + fn node(&self) -> Node { + match self { + Self::SpreadElement(e) => e.node, + Self::Expression(expr) => expr.node(), + } + } +} + +impl<'a> GetNode for ForStatementInit<'a> { + fn node(&self) -> Node { + match self { + Self::VariableDeclaration(x) => x.node, + Self::Expression(x) => x.node(), + } + } +} + +impl<'a> GetNode for SimpleAssignmentTarget<'a> { + fn node(&self) -> Node { + match self { + Self::AssignmentTargetIdentifier(ident) => ident.node, + Self::MemberAssignmentTarget(expr) => expr.node(), + Self::TSAsExpression(expr) => expr.node, + Self::TSNonNullExpression(expr) => expr.node, + Self::TSTypeAssertion(expr) => expr.node, + } + } +} + +impl<'a> GetNode for JSXElementName<'a> { + fn node(&self) -> Node { + match self { + Self::Identifier(ident) => ident.node, + Self::NamespacedName(name) => name.node, + Self::MemberExpression(expr) => expr.node, + } + } +} + +impl<'a> GetNode for TSSignature<'a> { + fn node(&self) -> Node { + match self { + Self::TSIndexSignature(sig) => sig.node, + Self::TSPropertySignature(sig) => sig.node, + Self::TSCallSignatureDeclaration(decl) => decl.node, + Self::TSConstructSignatureDeclaration(decl) => decl.node, + Self::TSMethodSignature(sig) => sig.node, + } + } +} + +impl<'a> GetNode for TSType<'a> { + fn node(&self) -> Node { + match self { + Self::TSConditionalType(t) => t.node, + Self::TSFunctionType(t) => t.node, + Self::TSLiteralType(t) => t.node, + Self::TSTypeReference(t) => t.node, + Self::TSTypeQuery(t) => t.node, + Self::TSUnionType(t) => t.node, + Self::TSTupleType(t) => t.node, + Self::TSArrayType(t) => t.node, + Self::TSIntersectionType(t) => t.node, + Self::TSMappedType(t) => t.node, + Self::TSInferType(t) => t.node, + Self::TSConstructorType(t) => t.node, + Self::TSIndexedAccessType(t) => t.node, + Self::TSTypeOperatorType(t) => t.node, + Self::TSImportType(t) => t.node, + Self::TSQualifiedName(t) => t.node, + Self::TSTypePredicate(t) => t.node, + Self::TSTypeLiteral(t) => t.node, + Self::TSTemplateLiteralType(t) => t.node, + Self::TSAnyKeyword(t) => t.node, + Self::TSUnknownKeyword(t) => t.node, + Self::TSUndefinedKeyword(t) => t.node, + Self::TSNullKeyword(t) => t.node, + Self::TSNumberKeyword(t) => t.node, + Self::TSStringKeyword(t) => t.node, + Self::TSNeverKeyword(t) => t.node, + Self::TSBooleanKeyword(t) => t.node, + Self::TSSymbolKeyword(t) => t.node, + Self::TSBigIntKeyword(t) => t.node, + Self::TSThisKeyword(t) => t.node, + Self::TSVoidKeyword(t) => t.node, + Self::TSObjectKeyword(t) => t.node, + Self::JSDocNullableType(t) => t.node, + Self::JSDocUnknownType(t) => t.node, + } + } +} + +impl<'a> GetNode for ExportDefaultDeclarationKind<'a> { + fn node(&self) -> Node { + match self { + Self::ClassDeclaration(x) => x.node, + Self::Expression(x) => x.node(), + Self::FunctionDeclaration(x) => x.node, + Self::TSEnumDeclaration(x) => x.node, + Self::TSInterfaceDeclaration(x) => x.node, + } + } +} diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs new file mode 100644 index 0000000000000..cd04fcefe3dba --- /dev/null +++ b/crates/oxc_ast/src/serialize.rs @@ -0,0 +1,192 @@ +use oxc_allocator::Vec; +use serde::{ + ser::{SerializeSeq, SerializeStruct, Serializer}, + Serialize, +}; + +use crate::{ + ast::{ + ArrowExpression, Directive, FormalParameters, FunctionBody, MemberExpression, Program, + Statement, + }, + ModuleKind, +}; + +#[cfg(feature = "serde_json")] +pub struct EcmaFormatter; + +/// Serialize f64 with `ryu_js` +#[cfg(feature = "serde_json")] +impl serde_json::ser::Formatter for EcmaFormatter { + fn write_f64(&mut self, writer: &mut W, value: f64) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + let mut buffer = ryu_js::Buffer::new(); + let s = buffer.format(value); + writer.write_all(s.as_bytes()) + } +} + +impl<'a> Program<'a> { + /// # Panics + #[must_use] + #[cfg(feature = "serde_json")] + pub fn to_json(&self) -> String { + let buf = std::vec::Vec::new(); + let mut ser = serde_json::Serializer::with_formatter(buf, crate::serialize::EcmaFormatter); + self.serialize(&mut ser).unwrap(); + String::from_utf8(ser.into_inner()).unwrap() + } +} + +impl<'a> Serialize for Program<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("Program", 5)?; + state.serialize_field("type", &"Program")?; + state.serialize_field("start", &self.node.start)?; + state.serialize_field("end", &self.node.end)?; + let source_type = match self.source_type.module_kind() { + ModuleKind::Script => "script", + ModuleKind::Module => "module", + }; + state.serialize_field("sourceType", &source_type)?; + let body = BlockWrapper { directives: &self.directives, body: &self.body }; + state.serialize_field("body", &body)?; + + state.end() + } +} + +pub fn serialize_bigint(value: &T, s: S) -> Result +where + T: std::fmt::Display, + S: serde::Serializer, +{ + s.collect_str(&format_args!("{value}n")) +} + +/// Helper struct for serializing `Program` and `FunctionBody` +#[derive(Debug, PartialEq)] +pub struct BlockWrapper<'a, 'b> { + pub directives: &'b Vec<'a, Directive<'a>>, + pub body: &'b Vec<'a, Statement<'a>>, +} + +impl<'a, 'b> Serialize for BlockWrapper<'a, 'b> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.directives.len() + self.body.len()))?; + for e in self.directives { + seq.serialize_element(e)?; + } + for e in self.body { + seq.serialize_element(e)?; + } + seq.end() + } +} + +impl<'a> Serialize for MemberExpression<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("MemberExpression", 7)?; + state.serialize_field("type", &"MemberExpression")?; + match &self { + MemberExpression::ComputedMemberExpression(expr) => { + state.serialize_field("start", &expr.node.start)?; + state.serialize_field("end", &expr.node.end)?; + state.serialize_field("object", &expr.object)?; + state.serialize_field("property", &expr.expression)?; + state.serialize_field("computed", &true)?; + state.serialize_field("optional", &expr.optional)?; + } + MemberExpression::StaticMemberExpression(expr) => { + state.serialize_field("start", &expr.node.start)?; + state.serialize_field("end", &expr.node.end)?; + state.serialize_field("object", &expr.object)?; + state.serialize_field("property", &expr.property)?; + state.serialize_field("computed", &false)?; + state.serialize_field("optional", &expr.optional)?; + } + MemberExpression::PrivateFieldExpression(expr) => { + state.serialize_field("start", &expr.node.start)?; + state.serialize_field("end", &expr.node.end)?; + state.serialize_field("object", &expr.object)?; + state.serialize_field("property", &expr.field)?; + state.serialize_field("computed", &false)?; + state.serialize_field("optional", &expr.optional)?; + } + } + state.end() + } +} + +impl<'a> Serialize for FormalParameters<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.items.len()))?; + + for e in &self.items { + seq.serialize_element(e)?; + } + + seq.end() + } +} + +impl<'a> Serialize for FunctionBody<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("FunctionBody", 4)?; + state.serialize_field("type", &"BlockStatement")?; + state.serialize_field("start", &self.node.start)?; + state.serialize_field("end", &self.node.end)?; + let body = BlockWrapper { directives: &self.directives, body: &self.statements }; + state.serialize_field("body", &body)?; + state.end() + } +} + +impl<'a> Serialize for ArrowExpression<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut len = 9; + if self.type_parameters.is_some() { + len += 1; + } + if self.return_type.is_some() { + len += 1; + } + let mut state = serializer.serialize_struct("ArrowExpression", len)?; + state.serialize_field("type", &"ArrowFunctionExpression")?; + state.serialize_field("start", &self.node.start)?; + state.serialize_field("end", &self.node.end)?; + state.serialize_field("id", &None as &Option<()>)?; // Always none in oxc_ast + state.serialize_field("expression", &self.expression)?; + state.serialize_field("generator", &self.generator)?; + state.serialize_field("async", &self.r#async)?; + state.serialize_field("params", &self.params)?; + state.serialize_field("body", &self.body)?; + if self.type_parameters.is_some() { + state.serialize_field("typeParameters", &self.type_parameters)?; + } + if self.return_type.is_some() { + state.serialize_field("returnType", &self.return_type)?; + } + state.end() + } +} diff --git a/tasks/coverage/src/suite.rs b/tasks/coverage/src/suite.rs index 56664e1be72e4..5da253c59b81d 100644 --- a/tasks/coverage/src/suite.rs +++ b/tasks/coverage/src/suite.rs @@ -238,7 +238,7 @@ pub trait Case: Sized + Sync + Send + UnwindSafe { fn run(&mut self); /// Execute the parser once and get the test result - fn execute(&mut self, _source_type: &SourceType) -> TestResult { + fn execute(&mut self, _source_type: SourceType) -> TestResult { self.parser_return_to_test_result(Err(String::new())) } diff --git a/tasks/coverage/src/test262.rs b/tasks/coverage/src/test262.rs index 40a026af262de..a0d6d6556e703 100644 --- a/tasks/coverage/src/test262.rs +++ b/tasks/coverage/src/test262.rs @@ -172,18 +172,18 @@ impl Case for Test262Case { self.result = if flags.contains(&TestFlag::OnlyStrict) { // always_strict = true; - self.execute(&source_type) + self.execute(source_type) } else if flags.contains(&TestFlag::Module) { source_type.set_module(); - self.execute(&source_type) + self.execute(source_type) } else if flags.contains(&TestFlag::NoStrict) || flags.contains(&TestFlag::Raw) { - self.execute(&source_type) + self.execute(source_type) } else { // always_strict = true; - let res = self.execute(&source_type); + let res = self.execute(source_type); if matches!(res, TestResult::Passed) { // always_strict = false; - self.execute(&source_type) + self.execute(source_type) } else { res }