diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 7f98292ed..c7368b612 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -1,3 +1,5 @@ +// FIXME: lots of methods are missing docs. If you have time, it would be a huge help to add some :) +#![warn(missing_docs)] use std::{borrow::Cow, cell::Cell, fmt}; use oxc_allocator::{Box, FromIn, Vec}; @@ -12,16 +14,20 @@ use oxc_syntax::{ use crate::ast::*; impl<'a> Program<'a> { + /// Returns `true` if this program has no statements or directives. pub fn is_empty(&self) -> bool { self.body.is_empty() && self.directives.is_empty() } + /// Returns `true` if this program uses strict mode semantics. Both source + /// type and `"use strict"` directives are considered. pub fn is_strict(&self) -> bool { self.source_type.is_strict() || self.directives.iter().any(Directive::is_use_strict) } } impl<'a> Expression<'a> { + /// Returns `true` if this expression is TypeScript-specific syntax. pub fn is_typescript_syntax(&self) -> bool { matches!( self, @@ -33,6 +39,7 @@ impl<'a> Expression<'a> { ) } + /// Returns `true` if this is a [primary expression](https://tc39.es/ecma262/#sec-primary-expression). pub fn is_primary_expression(&self) -> bool { self.is_literal() || matches!( @@ -66,18 +73,24 @@ impl<'a> Expression<'a> { ) } + /// Returns `true` for [string](StringLiteral) and [template](TemplateLiteral) literals. pub fn is_string_literal(&self) -> bool { matches!(self, Self::StringLiteral(_) | Self::TemplateLiteral(_)) } + /// Returns `true` for [numeric](NumericLiteral) and [big int](BigIntLiteral) literals. pub fn is_number_literal(&self) -> bool { matches!(self, Self::NumericLiteral(_) | Self::BigIntLiteral(_)) } + /// Returns `true` for [bigint literals](BigIntLiteral). pub fn is_big_int_literal(&self) -> bool { matches!(self, Self::BigIntLiteral(_)) } + /// Returns `true` for [string literals](StringLiteral) matching the + /// expected value. Note that [non-substitution template + /// literals](TemplateLiteral) are not considered. pub fn is_specific_string_literal(&self, string: &str) -> bool { match self { Self::StringLiteral(s) => s.value == string, @@ -110,6 +123,7 @@ impl<'a> Expression<'a> { } } + /// Returns `true` for [numeric literals](NumericLiteral) pub fn is_number(&self) -> bool { matches!(self, Self::NumericLiteral(_)) } @@ -119,6 +133,7 @@ impl<'a> Expression<'a> { matches!(self, Self::NumericLiteral(lit) if lit.value == 0.0) } + /// Determines whether the given expr is a specific [number](NumericLiteral) literal. pub fn is_number_value(&self, val: f64) -> bool { matches!(self, Self::NumericLiteral(lit) if (lit.value - val).abs() < f64::EPSILON) } @@ -154,6 +169,7 @@ impl<'a> Expression<'a> { expr } + #[allow(missing_docs)] pub fn is_specific_id(&self, name: &str) -> bool { match self.get_inner_expression() { Expression::Identifier(ident) => ident.name == name, @@ -161,6 +177,7 @@ impl<'a> Expression<'a> { } } + #[allow(missing_docs)] pub fn is_specific_member_access(&self, object: &str, property: &str) -> bool { match self.get_inner_expression() { expr if expr.is_member_expression() => { @@ -176,6 +193,7 @@ impl<'a> Expression<'a> { } } + #[allow(missing_docs)] pub fn get_inner_expression(&self) -> &Expression<'a> { let mut expr = self; loop { @@ -192,6 +210,7 @@ impl<'a> Expression<'a> { expr } + #[allow(missing_docs)] pub fn get_inner_expression_mut(&mut self) -> &mut Expression<'a> { let mut expr = self; loop { @@ -208,10 +227,12 @@ impl<'a> Expression<'a> { expr } + #[allow(missing_docs)] pub fn is_identifier_reference(&self) -> bool { matches!(self, Expression::Identifier(_)) } + #[allow(missing_docs)] pub fn get_identifier_reference(&self) -> Option<&IdentifierReference<'a>> { match self.get_inner_expression() { Expression::Identifier(ident) => Some(ident), @@ -219,27 +240,33 @@ impl<'a> Expression<'a> { } } + #[allow(missing_docs)] pub fn is_function(&self) -> bool { matches!(self, Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_)) } + #[allow(missing_docs)] pub fn is_call_expression(&self) -> bool { matches!(self, Expression::CallExpression(_)) } + #[allow(missing_docs)] pub fn is_super_call_expression(&self) -> bool { matches!(self, Expression::CallExpression(expr) if matches!(&expr.callee, Expression::Super(_))) } + #[allow(missing_docs)] pub fn is_call_like_expression(&self) -> bool { self.is_call_expression() && matches!(self, Expression::NewExpression(_) | Expression::ImportExpression(_)) } + #[allow(missing_docs)] pub fn is_binaryish(&self) -> bool { matches!(self, Expression::BinaryExpression(_) | Expression::LogicalExpression(_)) } + #[allow(missing_docs)] pub fn get_member_expr(&self) -> Option<&MemberExpression<'a>> { match self.get_inner_expression() { Expression::ChainExpression(chain_expr) => chain_expr.expression.as_member_expression(), @@ -247,6 +274,7 @@ impl<'a> Expression<'a> { } } + #[allow(missing_docs)] pub fn is_immutable_value(&self) -> bool { match self { Self::BooleanLiteral(_) @@ -264,6 +292,7 @@ impl<'a> Expression<'a> { } } + #[allow(missing_docs)] pub fn is_require_call(&self) -> bool { if let Self::CallExpression(call_expr) = self { call_expr.is_require_call() @@ -274,6 +303,7 @@ impl<'a> Expression<'a> { } impl<'a> IdentifierName<'a> { + #[allow(missing_docs)] pub fn new(span: Span, name: Atom<'a>) -> Self { Self { span, name } } @@ -287,12 +317,14 @@ impl<'a> fmt::Display for IdentifierName<'a> { } impl<'a> IdentifierReference<'a> { + #[allow(missing_docs)] #[inline] pub fn new(span: Span, name: Atom<'a>) -> Self { Self { span, name, reference_id: Cell::default() } } #[inline] + #[allow(missing_docs)] pub fn new_with_reference_id( span: Span, name: Atom<'a>, @@ -302,6 +334,7 @@ impl<'a> IdentifierReference<'a> { } #[inline] + #[allow(missing_docs)] pub fn reference_id(&self) -> Option { self.reference_id.get() } @@ -314,10 +347,12 @@ impl<'a> fmt::Display for IdentifierReference<'a> { } impl<'a> BindingIdentifier<'a> { + #[allow(missing_docs)] pub fn new(span: Span, name: Atom<'a>) -> Self { Self { span, name, symbol_id: Cell::default() } } + #[allow(missing_docs)] pub fn new_with_symbol_id(span: Span, name: Atom<'a>, symbol_id: SymbolId) -> Self { Self { span, name, symbol_id: Cell::new(Some(symbol_id)) } } @@ -331,12 +366,14 @@ impl<'a> fmt::Display for BindingIdentifier<'a> { } impl<'a> ArrayExpressionElement<'a> { + #[allow(missing_docs)] pub fn is_elision(&self) -> bool { matches!(self, Self::Elision(_)) } } impl<'a> PropertyKey<'a> { + #[allow(missing_docs)] pub fn static_name(&self) -> Option> { match self { Self::StaticIdentifier(ident) => Some(Cow::Borrowed(ident.name.as_str())), @@ -352,18 +389,22 @@ impl<'a> PropertyKey<'a> { } } + #[allow(missing_docs)] pub fn is_specific_static_name(&self, name: &str) -> bool { self.static_name().is_some_and(|n| n == name) } + #[allow(missing_docs)] pub fn is_identifier(&self) -> bool { matches!(self, Self::PrivateIdentifier(_) | Self::StaticIdentifier(_)) } + #[allow(missing_docs)] pub fn is_private_identifier(&self) -> bool { matches!(self, Self::PrivateIdentifier(_)) } + #[allow(missing_docs)] pub fn private_name(&self) -> Option> { match self { Self::PrivateIdentifier(ident) => Some(ident.name.clone()), @@ -371,6 +412,7 @@ impl<'a> PropertyKey<'a> { } } + #[allow(missing_docs)] pub fn name(&self) -> Option> { if self.is_private_identifier() { self.private_name().map(|name| Cow::Borrowed(name.as_str())) @@ -379,6 +421,7 @@ impl<'a> PropertyKey<'a> { } } + #[allow(missing_docs)] pub fn is_specific_id(&self, name: &str) -> bool { match self { PropertyKey::StaticIdentifier(ident) => ident.name == name, @@ -386,6 +429,7 @@ impl<'a> PropertyKey<'a> { } } + #[allow(missing_docs)] pub fn is_specific_string_literal(&self, string: &str) -> bool { matches!(self, Self::StringLiteral(s) if s.value == string) } @@ -401,6 +445,7 @@ impl PropertyKind { } impl<'a> TemplateLiteral<'a> { + #[allow(missing_docs)] pub fn is_no_substitution_template(&self) -> bool { self.expressions.is_empty() && self.quasis.len() == 1 } @@ -412,10 +457,12 @@ impl<'a> TemplateLiteral<'a> { } impl<'a> MemberExpression<'a> { + #[allow(missing_docs)] pub fn is_computed(&self) -> bool { matches!(self, MemberExpression::ComputedMemberExpression(_)) } + #[allow(missing_docs)] pub fn optional(&self) -> bool { match self { MemberExpression::ComputedMemberExpression(expr) => expr.optional, @@ -424,6 +471,7 @@ impl<'a> MemberExpression<'a> { } } + #[allow(missing_docs)] pub fn object(&self) -> &Expression<'a> { match self { MemberExpression::ComputedMemberExpression(expr) => &expr.object, @@ -432,6 +480,7 @@ impl<'a> MemberExpression<'a> { } } + #[allow(missing_docs)] pub fn static_property_name(&self) -> Option<&'a str> { match self { MemberExpression::ComputedMemberExpression(expr) => { @@ -442,6 +491,7 @@ impl<'a> MemberExpression<'a> { } } + #[allow(missing_docs)] pub fn static_property_info(&self) -> Option<(Span, &'a str)> { match self { MemberExpression::ComputedMemberExpression(expr) => match &expr.expression { @@ -462,6 +512,7 @@ impl<'a> MemberExpression<'a> { } } + #[allow(missing_docs)] pub fn through_optional_is_specific_member_access(&self, object: &str, property: &str) -> bool { let object_matches = match self.object().without_parentheses() { Expression::ChainExpression(x) => match &x.expression { @@ -487,6 +538,7 @@ impl<'a> MemberExpression<'a> { } impl<'a> ComputedMemberExpression<'a> { + #[allow(missing_docs)] pub fn static_property_name(&self) -> Option> { match &self.expression { Expression::StringLiteral(lit) => Some(lit.value.clone()), @@ -501,6 +553,7 @@ impl<'a> ComputedMemberExpression<'a> { } impl<'a> StaticMemberExpression<'a> { + #[allow(missing_docs)] pub fn get_first_object(&self) -> &Expression<'a> { match &self.object { Expression::StaticMemberExpression(member) => { @@ -523,6 +576,7 @@ impl<'a> StaticMemberExpression<'a> { } impl<'a> CallExpression<'a> { + #[allow(missing_docs)] pub fn callee_name(&self) -> Option<&str> { match &self.callee { Expression::Identifier(ident) => Some(ident.name.as_str()), @@ -530,6 +584,7 @@ impl<'a> CallExpression<'a> { } } + #[allow(missing_docs)] pub fn is_require_call(&self) -> bool { if self.arguments.len() != 1 { return false; @@ -545,6 +600,7 @@ impl<'a> CallExpression<'a> { } } + #[allow(missing_docs)] pub fn is_symbol_or_symbol_for_call(&self) -> bool { // TODO: is 'Symbol' reference to global object match &self.callee { @@ -559,6 +615,7 @@ impl<'a> CallExpression<'a> { } } + #[allow(missing_docs)] pub fn common_js_require(&self) -> Option<&StringLiteral> { if !(self.callee.is_specific_id("require") && self.arguments.len() == 1) { return None; @@ -571,26 +628,31 @@ impl<'a> CallExpression<'a> { } impl Argument<'_> { + #[allow(missing_docs)] pub fn is_spread(&self) -> bool { matches!(self, Self::SpreadElement(_)) } } impl<'a> AssignmentTarget<'a> { + #[allow(missing_docs)] pub fn get_identifier(&self) -> Option<&'a str> { self.as_simple_assignment_target().and_then(SimpleAssignmentTarget::get_identifier) } + #[allow(missing_docs)] pub fn get_expression(&self) -> Option<&Expression<'a>> { self.as_simple_assignment_target().and_then(SimpleAssignmentTarget::get_expression) } + #[allow(missing_docs)] pub fn get_expression_mut(&mut self) -> Option<&mut Expression<'a>> { self.as_simple_assignment_target_mut().and_then(SimpleAssignmentTarget::get_expression_mut) } } impl<'a> SimpleAssignmentTarget<'a> { + #[allow(missing_docs)] pub fn get_identifier(&self) -> Option<&'a str> { match self { Self::AssignmentTargetIdentifier(ident) => Some(ident.name.as_str()), @@ -599,6 +661,7 @@ impl<'a> SimpleAssignmentTarget<'a> { } } + #[allow(missing_docs)] pub fn get_expression(&self) -> Option<&Expression<'a>> { match self { Self::TSAsExpression(expr) => Some(&expr.expression), @@ -609,6 +672,7 @@ impl<'a> SimpleAssignmentTarget<'a> { } } + #[allow(missing_docs)] pub fn get_expression_mut(&mut self) -> Option<&mut Expression<'a>> { match self { Self::TSAsExpression(expr) => Some(&mut expr.expression), @@ -622,6 +686,7 @@ impl<'a> SimpleAssignmentTarget<'a> { } impl<'a> ArrayAssignmentTarget<'a> { + #[allow(missing_docs)] pub fn new_with_elements( span: Span, elements: Vec<'a, Option>>, @@ -631,6 +696,7 @@ impl<'a> ArrayAssignmentTarget<'a> { } impl<'a> ObjectAssignmentTarget<'a> { + #[allow(missing_docs)] pub fn new_with_properties( span: Span, properties: Vec<'a, AssignmentTargetProperty<'a>>, @@ -638,16 +704,19 @@ impl<'a> ObjectAssignmentTarget<'a> { Self { span, properties, rest: None } } + #[allow(missing_docs)] pub fn is_empty(&self) -> bool { self.properties.is_empty() && self.rest.is_none() } + #[allow(missing_docs)] pub fn len(&self) -> usize { self.properties.len() + usize::from(self.rest.is_some()) } } impl<'a> AssignmentTargetMaybeDefault<'a> { + #[allow(missing_docs)] pub fn name(&self) -> Option { match self { AssignmentTargetMaybeDefault::AssignmentTargetIdentifier(id) => Some(id.name.clone()), @@ -664,6 +733,7 @@ impl<'a> AssignmentTargetMaybeDefault<'a> { } impl<'a> Statement<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { match self { match_declaration!(Self) => { @@ -676,6 +746,7 @@ impl<'a> Statement<'a> { } } + #[allow(missing_docs)] pub fn is_iteration_statement(&self) -> bool { matches!( self, @@ -707,16 +778,19 @@ impl<'a> Directive<'a> { } impl<'a> BlockStatement<'a> { + #[allow(missing_docs)] pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { Self { span, body, scope_id: Cell::default() } } + #[allow(missing_docs)] pub fn new_with_scope_id(span: Span, body: Vec<'a, Statement<'a>>, scope_id: ScopeId) -> Self { Self { span, body, scope_id: Cell::new(Some(scope_id)) } } } impl<'a> Declaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { match self { Self::VariableDeclaration(decl) => decl.is_typescript_syntax(), @@ -725,7 +799,14 @@ impl<'a> Declaration<'a> { _ => true, } } - + /// Get the identifier bound by this declaration. + /// + /// ## Example + /// ```ts + /// const x = 1; // None. may change in the future. + /// class Foo {} // Some(IdentifierReference { name: "Foo", .. }) + /// enum Bar {} // Some(IdentifierReference { name: "Bar", .. }) + /// ``` pub fn id(&self) -> Option<&BindingIdentifier<'a>> { match self { Declaration::FunctionDeclaration(decl) => decl.id.as_ref(), @@ -738,6 +819,7 @@ impl<'a> Declaration<'a> { } } + #[allow(missing_docs)] pub fn declare(&self) -> bool { match self { Declaration::VariableDeclaration(decl) => decl.declare, @@ -753,32 +835,39 @@ impl<'a> Declaration<'a> { } impl<'a> VariableDeclaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { self.declare } + /// Returns `true` if any of this declaration's variables have an initializer. pub fn has_init(&self) -> bool { self.declarations.iter().any(|decl| decl.init.is_some()) } } impl VariableDeclarationKind { + /// `var x` pub fn is_var(&self) -> bool { matches!(self, Self::Var) } + /// `const x` pub fn is_const(&self) -> bool { matches!(self, Self::Const) } + /// `let x` or `const x` pub fn is_lexical(&self) -> bool { matches!(self, Self::Const | Self::Let) } + /// `await using x` pub fn is_await(&self) -> bool { matches!(self, Self::AwaitUsing) } + #[allow(missing_docs)] pub fn as_str(&self) -> &'static str { match self { Self::Var => "var", @@ -798,6 +887,7 @@ impl fmt::Display for VariableDeclarationKind { } impl<'a> ForStatement<'a> { + #[allow(missing_docs)] pub fn new( span: Span, init: Option>, @@ -818,6 +908,7 @@ impl<'a> ForStatementInit<'a> { } impl<'a> ForInStatement<'a> { + #[allow(missing_docs)] pub fn new( span: Span, left: ForStatementLeft<'a>, @@ -829,6 +920,7 @@ impl<'a> ForInStatement<'a> { } impl<'a> ForOfStatement<'a> { + #[allow(missing_docs)] pub fn new( span: Span, r#await: bool, @@ -849,18 +941,21 @@ impl<'a> ForStatementLeft<'a> { } impl<'a> SwitchStatement<'a> { + #[allow(missing_docs)] pub fn new(span: Span, discriminant: Expression<'a>, cases: Vec<'a, SwitchCase<'a>>) -> Self { Self { span, discriminant, cases, scope_id: Cell::default() } } } impl<'a> SwitchCase<'a> { + /// `true` for `default:` cases. pub fn is_default_case(&self) -> bool { self.test.is_none() } } impl<'a> CatchClause<'a> { + #[allow(missing_docs)] pub fn new( span: Span, param: Option>, @@ -871,16 +966,19 @@ impl<'a> CatchClause<'a> { } impl<'a> BindingPattern<'a> { + #[allow(missing_docs)] pub fn get_identifier(&self) -> Option> { self.kind.get_identifier() } + #[allow(missing_docs)] pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { self.kind.get_binding_identifier() } } impl<'a> BindingPatternKind<'a> { + #[allow(missing_docs)] pub fn get_identifier(&self) -> Option> { match self { Self::BindingIdentifier(ident) => Some(ident.name.clone()), @@ -889,6 +987,7 @@ impl<'a> BindingPatternKind<'a> { } } + #[allow(missing_docs)] pub fn get_binding_identifier(&self) -> Option<&BindingIdentifier<'a>> { match self { Self::BindingIdentifier(ident) => Some(ident), @@ -897,6 +996,7 @@ impl<'a> BindingPatternKind<'a> { } } + #[allow(missing_docs)] pub fn is_destructuring_pattern(&self) -> bool { match self { Self::ObjectPattern(_) | Self::ArrayPattern(_) => true, @@ -905,37 +1005,43 @@ impl<'a> BindingPatternKind<'a> { } } + #[allow(missing_docs)] pub fn is_binding_identifier(&self) -> bool { matches!(self, Self::BindingIdentifier(_)) } + #[allow(missing_docs)] pub fn is_assignment_pattern(&self) -> bool { matches!(self, Self::AssignmentPattern(_)) } } impl<'a> ObjectPattern<'a> { + /// `true` for empty object patterns (`{}`). pub fn is_empty(&self) -> bool { self.properties.is_empty() && self.rest.is_none() } + /// The number of properties, including rest properties, in this object pattern. pub fn len(&self) -> usize { self.properties.len() + usize::from(self.rest.is_some()) } } impl<'a> ArrayPattern<'a> { + /// `true` for empty array patterns (`[]`). pub fn is_empty(&self) -> bool { self.elements.is_empty() && self.rest.is_none() } + /// The number of elements, including rest elements, in this array pattern. pub fn len(&self) -> usize { self.elements.len() + usize::from(self.rest.is_some()) } } impl<'a> Function<'a> { - #![allow(clippy::too_many_arguments)] + #![allow(clippy::too_many_arguments, missing_docs)] pub fn new( r#type: FunctionType, span: Span, @@ -979,6 +1085,7 @@ impl<'a> Function<'a> { self.id.as_ref().and_then(|id| id.symbol_id.get()) } + /// `true` for overload signatures and `declare function` statements. pub fn is_typescript_syntax(&self) -> bool { matches!( self.r#type, @@ -987,28 +1094,34 @@ impl<'a> Function<'a> { || self.declare } + /// `true` for function expressions pub fn is_expression(&self) -> bool { self.r#type == FunctionType::FunctionExpression } + /// `true` for function declarations pub fn is_function_declaration(&self) -> bool { matches!(self.r#type, FunctionType::FunctionDeclaration) } + /// `true` for `declare function` statements pub fn is_ts_declare_function(&self) -> bool { matches!(self.r#type, FunctionType::TSDeclareFunction) } + /// `true` for non-expression functions pub fn is_declaration(&self) -> bool { matches!(self.r#type, FunctionType::FunctionDeclaration | FunctionType::TSDeclareFunction) } + /// `true` if this function's body has a `"use strict"` directive. pub fn is_strict(&self) -> bool { self.body.as_ref().is_some_and(|body| body.has_use_strict_directive()) } } impl<'a> FormalParameters<'a> { + /// Number of parameters bound in this parameter list. pub fn parameters_count(&self) -> usize { self.items.len() + self.rest.as_ref().map_or(0, |_| 1) } @@ -1023,10 +1136,36 @@ impl<'a> FormalParameters<'a> { } impl<'a> FormalParameter<'a> { + /// `true` if a `public` accessibility modifier is present. Use + /// [`has_modifier`](FormalParameter::has_modifier) if you want to check for + /// _any_ modifier, including `readonly` and `override`. + /// + /// ## Example + /// ```ts + /// class Foo { + /// constructor( + /// public x: number, // <- true + /// private y: string, // <- false + /// z: string // <- false + /// ) {} + /// } pub fn is_public(&self) -> bool { matches!(self.accessibility, Some(TSAccessibility::Public)) } + /// `true` if any modifier, accessibility or otherwise, is present. + /// + /// ## Example + /// ```ts + /// class Foo { + /// constructor( + /// public a: number, // <- true + /// readonly b: string, // <- true + /// override c: string, // <- true + /// d: string // <- false + /// ) {} + /// } + /// ``` #[inline] pub fn has_modifier(&self) -> bool { self.accessibility.is_some() || self.readonly || self.r#override @@ -1034,32 +1173,39 @@ impl<'a> FormalParameter<'a> { } impl FormalParameterKind { + /// `true` when part of a TypeScript method or function signature. pub fn is_signature(&self) -> bool { matches!(self, Self::Signature) } } impl<'a> FormalParameters<'a> { + /// `true` if no parameters are bound. pub fn is_empty(&self) -> bool { self.items.is_empty() } + /// `true` if at least one parameter is bound, including [rest bindings](BindingRestElement). pub fn has_parameter(&self) -> bool { !self.is_empty() || self.rest.is_some() } } impl<'a> FunctionBody<'a> { + /// `true` if this function body contains no statements or directives. pub fn is_empty(&self) -> bool { self.directives.is_empty() && self.statements.is_empty() } + /// `true` if this function body contains a `"use strict"` directive. + #[allow(missing_docs)] pub fn has_use_strict_directive(&self) -> bool { self.directives.iter().any(Directive::is_use_strict) } } impl<'a> ArrowFunctionExpression<'a> { + #[allow(missing_docs)] pub fn new( span: Span, expression: bool, @@ -1093,7 +1239,7 @@ impl<'a> ArrowFunctionExpression<'a> { } impl<'a> Class<'a> { - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, missing_docs)] pub fn new( r#type: ClassType, span: Span, @@ -1145,6 +1291,7 @@ impl<'a> Class<'a> { self.r#type == ClassType::ClassDeclaration } + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { self.declare || self.r#abstract } @@ -1167,6 +1314,7 @@ impl<'a> ClassElement<'a> { } } + #[allow(missing_docs)] pub fn computed(&self) -> bool { match self { Self::TSIndexSignature(_) | Self::StaticBlock(_) => false, @@ -1176,6 +1324,7 @@ impl<'a> ClassElement<'a> { } } + #[allow(missing_docs)] pub fn accessibility(&self) -> Option { match self { Self::StaticBlock(_) | Self::TSIndexSignature(_) | Self::AccessorProperty(_) => None, @@ -1184,6 +1333,7 @@ impl<'a> ClassElement<'a> { } } + #[allow(missing_docs)] pub fn method_definition_kind(&self) -> Option { match self { Self::TSIndexSignature(_) @@ -1194,6 +1344,7 @@ impl<'a> ClassElement<'a> { } } + #[allow(missing_docs)] pub fn property_key(&self) -> Option<&PropertyKey<'a>> { match self { Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, @@ -1203,6 +1354,8 @@ impl<'a> ClassElement<'a> { } } + /// Try to get the statically known name of this [`ClassElement`]. Handles + /// computed members that use literals. pub fn static_name(&self) -> Option> { match self { Self::TSIndexSignature(_) | Self::StaticBlock(_) => None, @@ -1217,6 +1370,8 @@ impl<'a> ClassElement<'a> { matches!(self, Self::PropertyDefinition(_) | Self::AccessorProperty(_)) } + /// `true` for overloads, declarations, index signatures, and abstract + /// methods, etc. That is, any non-concrete implementation. pub fn is_ts_empty_body_function(&self) -> bool { match self { Self::PropertyDefinition(_) @@ -1227,6 +1382,7 @@ impl<'a> ClassElement<'a> { } } + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { match self { Self::TSIndexSignature(_) => true, @@ -1239,6 +1395,7 @@ impl<'a> ClassElement<'a> { } } + /// `true` for [decorated](Decorator) class elements. pub fn has_decorator(&self) -> bool { match self { Self::MethodDefinition(method) => !method.decorators.is_empty(), @@ -1267,24 +1424,29 @@ impl<'a> ClassElement<'a> { } impl PropertyDefinitionType { + /// `true` for abstract properties and methods. pub fn is_abstract(&self) -> bool { matches!(self, Self::TSAbstractPropertyDefinition) } } impl MethodDefinitionKind { + /// `true` for constructors. pub fn is_constructor(&self) -> bool { matches!(self, Self::Constructor) } + /// `true` for regular methods. pub fn is_method(&self) -> bool { matches!(self, Self::Method) } + /// `true` for setter methods. pub fn is_set(&self) -> bool { matches!(self, Self::Set) } + /// `true` for getter methods. pub fn is_get(&self) -> bool { matches!(self, Self::Get) } @@ -1296,6 +1458,7 @@ impl MethodDefinitionKind { matches!(self, Self::Get | Self::Set) } + #[allow(missing_docs)] pub fn scope_flags(self) -> ScopeFlags { match self { Self::Constructor => ScopeFlags::Constructor | ScopeFlags::Function, @@ -1307,24 +1470,28 @@ impl MethodDefinitionKind { } impl MethodDefinitionType { + #[allow(missing_docs)] pub fn is_abstract(&self) -> bool { matches!(self, Self::TSAbstractMethodDefinition) } } impl<'a> PrivateIdentifier<'a> { + #[allow(missing_docs)] pub fn new(span: Span, name: Atom<'a>) -> Self { Self { span, name } } } impl<'a> StaticBlock<'a> { + #[allow(missing_docs)] pub fn new(span: Span, body: Vec<'a, Statement<'a>>) -> Self { Self { span, body, scope_id: Cell::default() } } } impl<'a> ModuleDeclaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { match self { ModuleDeclaration::ImportDeclaration(_) => false, @@ -1336,10 +1503,12 @@ impl<'a> ModuleDeclaration<'a> { } } + #[allow(missing_docs)] pub fn is_import(&self) -> bool { matches!(self, Self::ImportDeclaration(_)) } + #[allow(missing_docs)] pub fn is_export(&self) -> bool { matches!( self, @@ -1351,10 +1520,12 @@ impl<'a> ModuleDeclaration<'a> { ) } + #[allow(missing_docs)] pub fn is_default_export(&self) -> bool { matches!(self, Self::ExportDefaultDeclaration(_)) } + #[allow(missing_docs)] pub fn source(&self) -> Option<&StringLiteral<'a>> { match self { Self::ImportDeclaration(decl) => Some(&decl.source), @@ -1366,6 +1537,7 @@ impl<'a> ModuleDeclaration<'a> { } } + #[allow(missing_docs)] pub fn with_clause(&self) -> Option<&Box<'a, WithClause<'a>>> { match self { Self::ImportDeclaration(decl) => decl.with_clause.as_ref(), @@ -1379,12 +1551,14 @@ impl<'a> ModuleDeclaration<'a> { } impl AccessorPropertyType { + #[allow(missing_docs)] pub fn is_abstract(&self) -> bool { matches!(self, Self::TSAbstractAccessorProperty) } } impl<'a> ImportDeclarationSpecifier<'a> { + #[allow(missing_docs)] pub fn local(&self) -> &BindingIdentifier<'a> { match self { ImportDeclarationSpecifier::ImportSpecifier(specifier) => &specifier.local, @@ -1393,12 +1567,14 @@ impl<'a> ImportDeclarationSpecifier<'a> { } } + #[allow(missing_docs)] pub fn name(&self) -> Cow<'a, str> { Cow::Borrowed(self.local().name.as_str()) } } impl<'a> ImportAttributeKey<'a> { + #[allow(missing_docs)] pub fn as_atom(&self) -> Atom<'a> { match self { Self::Identifier(identifier) => identifier.name.clone(), @@ -1408,6 +1584,7 @@ impl<'a> ImportAttributeKey<'a> { } impl<'a> ExportNamedDeclaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { self.export_kind == ImportOrExportKind::Type || self.declaration.as_ref().map_or(false, Declaration::is_typescript_syntax) @@ -1415,24 +1592,28 @@ impl<'a> ExportNamedDeclaration<'a> { } impl<'a> ExportDefaultDeclaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { self.declaration.is_typescript_syntax() } } impl<'a> ExportAllDeclaration<'a> { + #[allow(missing_docs)] pub fn is_typescript_syntax(&self) -> bool { self.export_kind.is_type() } } impl<'a> ExportSpecifier<'a> { + #[allow(missing_docs)] pub fn new(span: Span, local: ModuleExportName<'a>, exported: ModuleExportName<'a>) -> Self { Self { span, local, exported, export_kind: ImportOrExportKind::Value } } } impl<'a> ExportDefaultDeclarationKind<'a> { + #[allow(missing_docs)] #[inline] pub fn is_typescript_syntax(&self) -> bool { match self { @@ -1456,6 +1637,7 @@ impl<'a> fmt::Display for ModuleExportName<'a> { } impl<'a> ModuleExportName<'a> { + #[allow(missing_docs)] pub fn name(&self) -> Atom<'a> { match self { Self::IdentifierName(identifier) => identifier.name.clone(), @@ -1464,6 +1646,7 @@ impl<'a> ModuleExportName<'a> { } } + #[allow(missing_docs)] pub fn identifier_name(&self) -> Option> { match self { Self::IdentifierName(identifier) => Some(identifier.name.clone()), diff --git a/crates/oxc_ast/src/ast_impl/jsx.rs b/crates/oxc_ast/src/ast_impl/jsx.rs index 95f80ef84..651a6b3d6 100644 --- a/crates/oxc_ast/src/ast_impl/jsx.rs +++ b/crates/oxc_ast/src/ast_impl/jsx.rs @@ -1,5 +1,5 @@ //! [JSX](https://facebook.github.io/jsx) - +#![warn(missing_docs)] use std::fmt; use oxc_span::{Atom, Span}; @@ -16,6 +16,7 @@ export type JSXMemberExpressionObject = JSXIdentifier | JSXMemberExpression; // 1.2 JSX Elements impl<'a> JSXIdentifier<'a> { + /// Create a new JSX identifier with the given `name`. pub fn new(span: Span, name: Atom<'a>) -> Self { Self { span, name } } @@ -35,6 +36,9 @@ impl<'a> fmt::Display for JSXNamespacedName<'a> { } impl<'a> JSXElementName<'a> { + /// Get this name's contained identifier reference, returning [`None`] if it + /// is some other variant. Note that [namespaced + /// identifiers](JSXElementName::NamespacedName) are not included. pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> { match self { JSXElementName::Identifier(_) @@ -45,6 +49,7 @@ impl<'a> JSXElementName<'a> { } } + #[allow(missing_docs)] pub fn get_identifier_name(&self) -> Option> { match self { Self::Identifier(id) => Some(id.as_ref().name.clone()), @@ -55,12 +60,17 @@ impl<'a> JSXElementName<'a> { } impl<'a> JSXMemberExpression<'a> { + /// Get the identifier being referenced, if there is one. Will return + /// [`None`] for `this` expressions or if semantic analysis was skipped. pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> { self.object.get_identifier() } } impl<'a> JSXMemberExpressionObject<'a> { + /// Get the identifier being referenced, if there is one. Will return + /// [`None`] for [`this`](JSXMemberExpressionObject::ThisExpression) + /// expressions or if semantic analysis was skipped. pub fn get_identifier(&self) -> Option<&IdentifierReference<'a>> { let mut object = self; loop { @@ -111,20 +121,35 @@ impl<'a> JSXExpression<'a> { } impl<'a> JSXAttribute<'a> { + /// Returns `true` if this attribute's name is the expected `name`. + /// + /// Use [`JSXAttribute::is_identifier_ignore_case`] if you want to ignore + /// upper/lower case differences. pub fn is_identifier(&self, name: &str) -> bool { matches!(&self.name, JSXAttributeName::Identifier(ident) if ident.name == name) } + /// Returns `true` if this attribute's name is the expected `name`, ignoring + /// casing. pub fn is_identifier_ignore_case(&self, name: &str) -> bool { matches!(&self.name, JSXAttributeName::Identifier(ident) if ident.name.eq_ignore_ascii_case(name)) } + /// Returns `true` if this is a React `key`. + /// + /// ## Example + /// ```tsx + /// // -> `true` + /// // -> `false` + /// ``` pub fn is_key(&self) -> bool { self.is_identifier("key") } } impl<'a> JSXAttributeName<'a> { + /// Try to convert this attribute name into an [identifier](JSXIdentifier). + /// Returns [`None`] for [namespaced names](JSXAttributeName::NamespacedName). pub fn as_identifier(&self) -> Option<&JSXIdentifier<'a>> { match self { Self::Identifier(ident) => Some(ident.as_ref()), @@ -132,6 +157,14 @@ impl<'a> JSXAttributeName<'a> { } } + /// Get the rightmost identifier in the attribute name. + /// + /// ## Example + /// ```tsx + /// // -> `Foo` + /// // -> `Bar` + /// // -> `Baz` + /// ``` pub fn get_identifier(&self) -> &JSXIdentifier<'a> { match self { Self::Identifier(ident) => ident.as_ref(), @@ -140,6 +173,8 @@ impl<'a> JSXAttributeName<'a> { } } impl<'a> JSXAttributeValue<'a> { + /// Get the contained [`StringLiteral`], or [`None`] if this is some other + /// kind of value. pub fn as_string_literal(&self) -> Option<&StringLiteral<'a>> { match self { Self::StringLiteral(lit) => Some(lit.as_ref()), @@ -173,6 +208,7 @@ impl<'a> JSXAttributeItem<'a> { } impl<'a> JSXChild<'a> { + /// Returns `true` if this an [expression container](JSXChild::ExpressionContainer). pub const fn is_expression_container(&self) -> bool { matches!(self, Self::ExpressionContainer(_)) } diff --git a/crates/oxc_ast/src/ast_impl/literal.rs b/crates/oxc_ast/src/ast_impl/literal.rs index 5f70ab317..a0ea735a1 100644 --- a/crates/oxc_ast/src/ast_impl/literal.rs +++ b/crates/oxc_ast/src/ast_impl/literal.rs @@ -1,4 +1,5 @@ //! Literals +#![warn(missing_docs)] use std::{ borrow::Cow, @@ -14,10 +15,12 @@ use oxc_syntax::number::NumberBase; use crate::ast::*; impl BooleanLiteral { + /// Create a new boolean literal representing the given `value`. pub fn new(span: Span, value: bool) -> Self { Self { span, value } } + /// `"true"` or `"false"` depending on this boolean's value. pub fn as_str(&self) -> &'static str { if self.value { "true" @@ -42,6 +45,7 @@ impl ContentHash for NullLiteral { } impl NullLiteral { + /// Create a new `null` literal at the given location. pub fn new(span: Span) -> Self { Self { span } } @@ -55,6 +59,7 @@ impl fmt::Display for NullLiteral { } impl<'a> NumericLiteral<'a> { + /// Create a numeric literal representing the given `value`. pub fn new(span: Span, value: f64, raw: &'a str, base: NumberBase) -> Self { Self { span, value, raw, base } } @@ -101,6 +106,7 @@ impl<'a> fmt::Display for NumericLiteral<'a> { } impl<'a> BigIntLiteral<'a> { + /// Is this BigInt literal zero? (`0n`). pub fn is_zero(&self) -> bool { self.raw == "0n" } @@ -119,6 +125,7 @@ impl<'a> fmt::Display for RegExp<'a> { } impl<'a> RegExpPattern<'a> { + /// Returns the number of characters in the pattern. pub fn len(&self) -> usize { match self { Self::Raw(it) | Self::Invalid(it) => it.len(), @@ -126,10 +133,13 @@ impl<'a> RegExpPattern<'a> { } } + /// Returns `true` if the pattern is empty (i.e. has a + /// [len](RegExpPattern::len) of `0`). pub fn is_empty(&self) -> bool { self.len() == 0 } + /// Returns the string as this regular expression would appear in source code. pub fn source_text(&self, source_text: &'a str) -> Cow { match self { Self::Raw(raw) | Self::Invalid(raw) => Cow::Borrowed(raw), @@ -152,6 +162,8 @@ impl<'a> RegExpPattern<'a> { } } + /// Flatten this regular expression into a compiled [`Pattern`], returning + /// [`None`] if the pattern is invalid or not parsed. pub fn as_pattern(&self) -> Option<&Pattern<'a>> { if let Self::Pattern(it) = self { Some(it.as_ref()) @@ -257,6 +269,7 @@ impl fmt::Display for RegExpFlags { } impl<'a> StringLiteral<'a> { + /// Create a new string literal representing the given `value`. pub fn new(span: Span, value: Atom<'a>) -> Self { Self { span, value } } diff --git a/crates/oxc_ast/src/ast_impl/ts.rs b/crates/oxc_ast/src/ast_impl/ts.rs index c0b575515..8e1d37e90 100644 --- a/crates/oxc_ast/src/ast_impl/ts.rs +++ b/crates/oxc_ast/src/ast_impl/ts.rs @@ -2,6 +2,7 @@ //! //! [AST Spec](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/ast-spec) //! [Archived TypeScript spec](https://github.com/microsoft/TypeScript/blob/3c99d50da5a579d9fa92d02664b1b66d4ff55944/doc/spec-ARCHIVED.md) +#![warn(missing_docs)] use std::{cell::Cell, fmt}; @@ -11,6 +12,7 @@ use oxc_span::{Atom, Span}; use crate::ast::*; impl<'a> TSEnumDeclaration<'a> { + /// Create a new enum declaration. pub fn new( span: Span, id: BindingIdentifier<'a>, @@ -22,6 +24,7 @@ impl<'a> TSEnumDeclaration<'a> { } } impl<'a> TSEnumMemberName<'a> { + /// Get the name of this enum member if it can be determined statically. pub fn static_name(&self) -> Option<&'a str> { match self { Self::StaticIdentifier(ident) => Some(ident.name.as_str()), @@ -34,6 +37,15 @@ impl<'a> TSEnumMemberName<'a> { } impl<'a> TSType<'a> { + /// Get the first identifier reference in this type. + /// + /// For qualified (i.e. namespaced) types, the left-most identifier is + /// returned. + /// + /// ``` + /// let ty = get_type_for("foo.bar.Baz"); // TSType::TSQualifiedName + /// get_identifier_reference(&ty); // Some(IdentifierReference { name: "foo", .. }) + /// ``` pub fn get_identifier_reference(&self) -> Option> { match self { TSType::TSTypeReference(reference) => { @@ -48,6 +60,7 @@ impl<'a> TSType<'a> { } } + /// Returns `true` if this type is a type reference to `const`. pub fn is_const_type_reference(&self) -> bool { matches!(self, TSType::TSTypeReference(reference) if reference.type_name.is_const()) } @@ -63,6 +76,7 @@ impl<'a> TSType<'a> { } } + /// Returns `true` if this is a keyword type (e.g. `number`, `any`, `string`). #[rustfmt::skip] pub fn is_keyword(&self) -> bool { matches!(self, TSType::TSAnyKeyword(_) | TSType::TSBigIntKeyword(_) | TSType::TSBooleanKeyword(_) @@ -73,12 +87,22 @@ impl<'a> TSType<'a> { ) } + /// Returns `true` if this is a [keyword] or literal type. + /// + /// [keyword]: Self::is_keyword pub fn is_keyword_or_literal(&self) -> bool { self.is_keyword() || matches!(self, TSType::TSLiteralType(_)) } } impl<'a> TSTypeName<'a> { + /// Get the "leftmost" identifier in a dot-separated type name. + /// + /// ## Example + /// ```ts + /// type Foo = Bar; // -> Bar + /// type Foo = Bar.Baz; // -> Bar + /// ``` pub fn get_first_name(name: &TSTypeName<'a>) -> IdentifierReference<'a> { match name { TSTypeName::IdentifierReference(name) => (*name).clone(), @@ -86,6 +110,7 @@ impl<'a> TSTypeName<'a> { } } + /// Returns `true` if this is a reference to `const`. pub fn is_const(&self) -> bool { if let TSTypeName::IdentifierReference(ident) = self { if ident.name == "const" { @@ -95,10 +120,13 @@ impl<'a> TSTypeName<'a> { false } + /// Returns `true` if this is an [`TSTypeName::IdentifierReference`]. pub fn is_identifier(&self) -> bool { matches!(self, Self::IdentifierReference(_)) } + /// Returns `true` if this is a [qualified name](TSTypeName::QualifiedName) + /// (i.e. a dot-separated name). pub fn is_qualified_name(&self) -> bool { matches!(self, Self::QualifiedName(_)) } @@ -130,11 +158,13 @@ impl<'a> TSType<'a> { } impl TSAccessibility { + /// Returns `true` for `private` accessibility modifiers. #[inline] pub fn is_private(self) -> bool { matches!(self, Self::Private) } + /// Converts this modifier into a string as it would appear in the source code. pub fn as_str(self) -> &'static str { match self { Self::Public => "public", @@ -157,6 +187,7 @@ impl fmt::Display for TSAccessibility { } impl<'a> TSModuleDeclaration<'a> { + /// Create a new module declaration with no bound scope. pub fn new( span: Span, id: TSModuleDeclarationName<'a>, @@ -167,16 +198,21 @@ impl<'a> TSModuleDeclaration<'a> { Self { span, id, body, kind, declare, scope_id: Cell::default() } } + /// Returns `true` if this module's body exists and uses strict mode + /// semantics (as determined by [`TSModuleDeclarationBody::is_strict`]). pub fn is_strict(&self) -> bool { self.body.as_ref().is_some_and(TSModuleDeclarationBody::is_strict) } } impl TSModuleDeclarationKind { + /// Returns `true` for `declare global { ... }` pub fn is_global(self) -> bool { matches!(self, TSModuleDeclarationKind::Global) } + /// Declaration keyword as a string, identical to how it would appear in the + /// source code. pub fn as_str(&self) -> &'static str { match self { Self::Global => "global", @@ -187,10 +223,25 @@ impl TSModuleDeclarationKind { } impl<'a> TSModuleDeclarationName<'a> { + /// Returns `true` if this name is a string literal. + /// + /// ## Example + /// ```ts + /// // true + /// module "*.less" { + /// const styles: { [key: string]: string }; + /// export default styles; + /// } + /// + /// // false + /// module bar {} + /// namespace bang {} + /// ``` pub fn is_string_literal(&self) -> bool { matches!(self, Self::StringLiteral(_)) } + /// Get the static name of this module declaration name. pub fn name(&self) -> Atom<'a> { match self { Self::Identifier(ident) => ident.name.clone(), @@ -209,10 +260,12 @@ impl<'a> fmt::Display for TSModuleDeclarationName<'a> { } impl<'a> TSModuleDeclarationBody<'a> { + /// Does the body of this module use strict mode semantics? pub fn is_strict(&self) -> bool { matches!(self, Self::TSModuleBlock(block) if block.is_strict()) } + /// Returns `true` if this module contains no statements. pub fn is_empty(&self) -> bool { match self { TSModuleDeclarationBody::TSModuleDeclaration(declaration) => declaration.body.is_none(), @@ -220,6 +273,8 @@ impl<'a> TSModuleDeclarationBody<'a> { } } + /// Get a mutable reference to `self` as a [`TSModuleBlock`]. Returns + /// [`None`] if the body is something other than a block. pub fn as_module_block_mut(&mut self) -> Option<&mut TSModuleBlock<'a>> { match self { TSModuleDeclarationBody::TSModuleBlock(block) => Some(block.as_mut()), @@ -231,6 +286,7 @@ impl<'a> TSModuleDeclarationBody<'a> { } impl<'a> TSModuleBlock<'a> { + /// Returns `true` if this module contains a `"use strict"` directive. pub fn is_strict(&self) -> bool { self.directives.iter().any(Directive::is_use_strict) } @@ -267,16 +323,19 @@ impl<'a> Decorator<'a> { } impl ImportOrExportKind { + /// Returns `true` for "regular" imports and exports. pub fn is_value(&self) -> bool { matches!(self, Self::Value) } + /// Returns `true` if this is an `import type` or `export type` statement. pub fn is_type(&self) -> bool { matches!(self, Self::Type) } } impl TSTypeOperatorOperator { + /// Get the operator string as it would appear in the source code. pub fn to_str(self) -> &'static str { match self { TSTypeOperatorOperator::Keyof => "keyof",