feat(ast)!: introduce ArrowFunctionBody enum (#24987)

## Summary

- Introduces `ArrowFunctionBody` to represent concise expression and block bodies explicitly.
- Removes the `ArrowFunctionExpression::expression` boolean.
- Makes invalid arrow body combinations unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct ArrowFunctionExpression<'a> {
    pub expression: bool,
    pub body: Box<'a, FunctionBody<'a>>,
}
```

to:

```rust
pub enum ArrowFunctionBody<'a> {
    FunctionBody(Box<'a, FunctionBody<'a>>) = 64,
    INHERIT(Expression<'a>),
}

pub struct ArrowFunctionExpression<'a> {
    pub body: ArrowFunctionBody<'a>,
}
```

Concise arrow bodies are now stored directly as expressions instead of synthetic `FunctionBody` and `ExpressionStatement` nodes. Block bodies continue to use `FunctionBody`.

## Why?

The previous representation required consumers to maintain this implicit invariant:

```text
expression == true
→ no directives
→ exactly one statement
→ that statement is an ExpressionStatement
```

Invalid combinations could cause panics, skipped work, or fallback behavior. The enum encodes the grammar directly and makes those invalid states unrepresentable.

Concise expressions also now have `ArrowFunctionExpression` as their direct traversal ancestor, without synthetic `FunctionBody` or `ExpressionStatement` nodes.

## How to migrate

Use the arrow body accessors or match on `ArrowFunctionBody` rather than inspecting `expression` and synthetic statements.

Common field migrations:

| Previous representation | New representation |
| --- | --- |
| `arrow.expression` | `arrow.is_expression()` |
| `arrow.get_expression()` | `arrow.get_expression()` |
| `&arrow.body.statements` | `arrow.get_function_body().map(\|body\| &body.statements)` |
| `&mut arrow.body.statements` | `arrow.get_function_body_mut().map(\|body\| &mut body.statements)` |
| Synthetic expression statement in `arrow.body` | Direct `ArrowFunctionBody` expression variant |
| `arrow.body = function_body` | `arrow.body = ArrowFunctionBody::FunctionBody(function_body)` |
| Expression-to-block conversion | Create a `FunctionBody` containing a return statement |
| Block-to-expression conversion | Assign the expression directly to `arrow.body` |
| Builder `(expression, body)` arguments | Pass a single `ArrowFunctionBody` |

closes https://github.com/oxc-project/backlog/issues/211
This commit is contained in:
camc314 committed 2026-07-29 13:32:32 +00:00
1 parent a922355564
commit c917f204a7
166 files changed
+5291 -1496

No files matched your search

+102 -11
View File
@@ -2542,16 +2542,112 @@ function deserializeFunctionBody(pos) {
return node;
}
function deserializeArrowFunctionBody(pos) {
switch (uint8[pos]) {
case 0:
return deserializeBoxBooleanLiteral(pos + 8);
case 1:
return deserializeBoxNullLiteral(pos + 8);
case 2:
return deserializeBoxNumericLiteral(pos + 8);
case 3:
return deserializeBoxBigIntLiteral(pos + 8);
case 4:
return deserializeBoxRegExpLiteral(pos + 8);
case 5:
return deserializeBoxStringLiteral(pos + 8);
case 6:
return deserializeBoxTemplateLiteral(pos + 8);
case 7:
return deserializeBoxIdentifierReference(pos + 8);
case 8:
return deserializeBoxSuper(pos + 8);
case 9:
return deserializeBoxArrayExpression(pos + 8);
case 10:
return deserializeBoxArrowFunctionExpression(pos + 8);
case 11:
return deserializeBoxAssignmentExpression(pos + 8);
case 12:
return deserializeBoxAwaitExpression(pos + 8);
case 13:
return deserializeBoxBinaryExpression(pos + 8);
case 14:
return deserializeBoxCallExpression(pos + 8);
case 15:
return deserializeBoxChainExpression(pos + 8);
case 16:
return deserializeBoxClass(pos + 8);
case 17:
return deserializeBoxConditionalExpression(pos + 8);
case 18:
return deserializeBoxFunction(pos + 8);
case 19:
return deserializeBoxImportExpression(pos + 8);
case 20:
return deserializeBoxLogicalExpression(pos + 8);
case 21:
return deserializeBoxNewExpression(pos + 8);
case 22:
return deserializeBoxObjectExpression(pos + 8);
case 23:
return deserializeBoxParenthesizedExpression(pos + 8);
case 24:
return deserializeBoxSequenceExpression(pos + 8);
case 25:
return deserializeBoxTaggedTemplateExpression(pos + 8);
case 26:
return deserializeBoxThisExpression(pos + 8);
case 27:
return deserializeBoxUnaryExpression(pos + 8);
case 28:
return deserializeBoxUpdateExpression(pos + 8);
case 29:
return deserializeBoxYieldExpression(pos + 8);
case 30:
return deserializeBoxPrivateInExpression(pos + 8);
case 31:
return deserializeBoxImportMeta(pos + 8);
case 32:
return deserializeBoxNewTarget(pos + 8);
case 33:
return deserializeBoxJSXElement(pos + 8);
case 34:
return deserializeBoxJSXFragment(pos + 8);
case 35:
return deserializeBoxTSAsExpression(pos + 8);
case 36:
return deserializeBoxTSSatisfiesExpression(pos + 8);
case 37:
return deserializeBoxTSTypeAssertion(pos + 8);
case 38:
return deserializeBoxTSNonNullExpression(pos + 8);
case 39:
return deserializeBoxTSInstantiationExpression(pos + 8);
case 40:
return deserializeBoxV8IntrinsicExpression(pos + 8);
case 48:
return deserializeBoxComputedMemberExpression(pos + 8);
case 49:
return deserializeBoxStaticMemberExpression(pos + 8);
case 50:
return deserializeBoxPrivateFieldExpression(pos + 8);
case 64:
return deserializeBoxFunctionBody(pos + 8);
default:
throw Error(`Unexpected discriminant ${uint8[pos]} for ArrowFunctionBody`);
}
}
function deserializeArrowFunctionExpression(pos) {
let expression = deserializeBool(pos + 48),
start,
let start,
end,
previousParent = parent,
node = (parent = {
__proto__: NodeProto,
type: "ArrowFunctionExpression",
expression,
async: deserializeBool(pos + 49),
expression: uint8[pos + 40] !== 64,
async: deserializeBool(pos + 56),
typeParameters: null,
params: null,
returnType: null,
@@ -2562,16 +2658,11 @@ function deserializeArrowFunctionExpression(pos) {
end: (end = deserializeI32(pos + 4)),
range: [start, end],
parent,
}),
body = deserializeBoxFunctionBody(pos + 40);
if (expression === true) {
body = body.body[0].expression;
body.parent = parent;
}
});
node.typeParameters = deserializeOptionBoxTSTypeParameterDeclaration(pos + 16);
node.params = deserializeBoxFormalParameters(pos + 24);
node.returnType = deserializeOptionBoxTSTypeAnnotation(pos + 32);
node.body = body;
node.body = deserializeArrowFunctionBody(pos + 40);
parent = previousParent;
return node;
}
+22 -6
View File
@@ -1984,6 +1984,22 @@ pub struct FunctionBody<'a> {
pub statements: Vec<'a, Statement<'a>>,
}
/// The body of an [`ArrowFunctionExpression`].
///
/// Arrow functions have either a concise expression body (`() => expression`) or a block body
/// (`() => { statements }`).
#[ast(visit)]
#[derive(Debug)]
#[generate_derive(CloneIn, Dummy, ReplaceWith, TakeIn)]
#[generate_derive(ContentEq, ESTree, GetAddress, GetSpan, GetSpanMut)]
#[estree(no_ts_def)]
pub enum ArrowFunctionBody<'a> {
FunctionBody(Box<'a, FunctionBody<'a>>) = 64,
// `Expression` variants added here by `#[ast]` macro
INHERIT(Expression<'a>),
}
/// Arrow Function Definitions
#[ast(visit)]
#[scope(
@@ -1993,22 +2009,22 @@ pub struct FunctionBody<'a> {
#[derive(Debug)]
#[generate_derive(CloneIn, Dummy, ReplaceWith, TakeIn)]
#[generate_derive(ContentEq, ESTree, GetSpan, GetSpanMut, UnstableAddress)]
#[estree(add_fields(id = Null, generator = False))]
#[estree(
add_fields(expression = ArrowFunctionExpressionExpression, id = Null, generator = False),
field_order(expression, r#async, type_parameters, params, return_type, body, id, generator, span),
)]
pub struct ArrowFunctionExpression<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
/// Is the function body an arrow expression? i.e. `() => expr` instead of `() => {}`
pub expression: bool,
pub r#async: bool,
#[ts]
pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
pub params: Box<'a, FormalParameters<'a>>,
#[ts]
pub return_type: Option<Box<'a, TSTypeAnnotation<'a>>>,
/// See `expression` for whether this arrow expression returns an expression.
// ESTree: https://github.com/estree/estree/blob/master/es2015.md#arrowfunctionexpression
#[estree(via = ArrowFunctionExpressionBody)]
pub body: Box<'a, FunctionBody<'a>>,
#[estree(ts_type = "FunctionBody | Expression")]
pub body: ArrowFunctionBody<'a>,
pub scope_id: Cell<Option<ScopeId>>,
/// `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
#[builder(default)]
+59 -12
View File
@@ -3,6 +3,7 @@ use std::{
fmt::{self, Display},
};
use oxc_allocator::Box as ArenaBox;
use oxc_span::{GetSpan, Span};
use oxc_str::{Ident, Str};
use oxc_syntax::{operator::UnaryOperator, scope::ScopeFlags, symbol::SymbolId};
@@ -1579,25 +1580,71 @@ impl FunctionBody<'_> {
}
}
impl<'a> ArrowFunctionBody<'a> {
/// Returns `true` if this arrow function has a block body.
pub fn is_function_body(&self) -> bool {
matches!(self, Self::FunctionBody(_))
}
/// Returns the block body, if this arrow function has one.
pub fn as_function_body(&self) -> Option<&FunctionBody<'a>> {
match self {
Self::FunctionBody(body) => Some(body),
_ => None,
}
}
/// Returns the mutable block body, if this arrow function has one.
pub fn as_function_body_mut(&mut self) -> Option<&mut FunctionBody<'a>> {
match self {
Self::FunctionBody(body) => Some(body),
_ => None,
}
}
/// Converts this arrow function body into a block body, if it has one.
pub fn into_function_body(self) -> Option<ArenaBox<'a, FunctionBody<'a>>> {
match self {
Self::FunctionBody(body) => Some(body),
_ => None,
}
}
/// Returns `true` if this is an empty block body.
pub fn is_empty(&self) -> bool {
self.as_function_body().is_some_and(FunctionBody::is_empty)
}
/// Returns `true` if this block body has a `"use strict"` directive.
pub fn has_use_strict_directive(&self) -> bool {
self.as_function_body().is_some_and(FunctionBody::has_use_strict_directive)
}
}
impl<'a> ArrowFunctionExpression<'a> {
/// Returns `true` if this arrow function has a concise expression body.
pub fn is_expression(&self) -> bool {
self.body.is_expression()
}
/// Get expression part of `ArrowFunctionExpression`: `() => expression_part`.
pub fn get_expression(&self) -> Option<&Expression<'a>> {
if self.expression
&& let Statement::ExpressionStatement(expr_stmt) = &self.body.statements[0]
{
return Some(&expr_stmt.expression);
}
None
self.body.as_expression()
}
/// Get expression part of `ArrowFunctionExpression`: `() => expression_part`.
pub fn get_expression_mut(&mut self) -> Option<&mut Expression<'a>> {
if self.expression
&& let Statement::ExpressionStatement(expr_stmt) = &mut self.body.statements[0]
{
return Some(&mut expr_stmt.expression);
}
None
self.body.as_expression_mut()
}
/// Get block body of `ArrowFunctionExpression`: `() => { statements }`.
pub fn get_function_body(&self) -> Option<&FunctionBody<'a>> {
self.body.as_function_body()
}
/// Get mutable block body of `ArrowFunctionExpression`: `() => { statements }`.
pub fn get_function_body_mut(&mut self) -> Option<&mut FunctionBody<'a>> {
self.body.as_function_body_mut()
}
/// Returns `true` if this arrow function's body has a `"use strict"` directive.
+16 -12
View File
@@ -702,8 +702,11 @@ const _: () = {
assert!(offset_of!(FunctionBody, directives) == 16);
assert!(offset_of!(FunctionBody, statements) == 40);
// Padding: 4 bytes
assert!(size_of::<ArrowFunctionExpression>() == 56);
assert!(size_of::<ArrowFunctionBody>() == 16);
assert!(align_of::<ArrowFunctionBody>() == 8);
// Padding: 5 bytes
assert!(size_of::<ArrowFunctionExpression>() == 64);
assert!(align_of::<ArrowFunctionExpression>() == 8);
assert!(offset_of!(ArrowFunctionExpression, span) == 0);
assert!(offset_of!(ArrowFunctionExpression, node_id) == 8);
@@ -712,10 +715,9 @@ const _: () = {
assert!(offset_of!(ArrowFunctionExpression, params) == 24);
assert!(offset_of!(ArrowFunctionExpression, return_type) == 32);
assert!(offset_of!(ArrowFunctionExpression, body) == 40);
assert!(offset_of!(ArrowFunctionExpression, expression) == 48);
assert!(offset_of!(ArrowFunctionExpression, r#async) == 49);
assert!(offset_of!(ArrowFunctionExpression, pure) == 50);
assert!(offset_of!(ArrowFunctionExpression, pife) == 51);
assert!(offset_of!(ArrowFunctionExpression, r#async) == 56);
assert!(offset_of!(ArrowFunctionExpression, pure) == 57);
assert!(offset_of!(ArrowFunctionExpression, pife) == 58);
// Padding: 3 bytes
assert!(size_of::<YieldExpression>() == 32);
@@ -2517,8 +2519,11 @@ const _: () = if cfg!(target_family = "wasm") || align_of::<u64>() == 8 {
assert!(offset_of!(FunctionBody, directives) == 12);
assert!(offset_of!(FunctionBody, statements) == 28);
// Padding: 0 bytes
assert!(size_of::<ArrowFunctionExpression>() == 36);
assert!(size_of::<ArrowFunctionBody>() == 8);
assert!(align_of::<ArrowFunctionBody>() == 4);
// Padding: 1 bytes
assert!(size_of::<ArrowFunctionExpression>() == 40);
assert!(align_of::<ArrowFunctionExpression>() == 4);
assert!(offset_of!(ArrowFunctionExpression, span) == 0);
assert!(offset_of!(ArrowFunctionExpression, node_id) == 8);
@@ -2527,10 +2532,9 @@ const _: () = if cfg!(target_family = "wasm") || align_of::<u64>() == 8 {
assert!(offset_of!(ArrowFunctionExpression, params) == 20);
assert!(offset_of!(ArrowFunctionExpression, return_type) == 24);
assert!(offset_of!(ArrowFunctionExpression, body) == 28);
assert!(offset_of!(ArrowFunctionExpression, expression) == 32);
assert!(offset_of!(ArrowFunctionExpression, r#async) == 33);
assert!(offset_of!(ArrowFunctionExpression, pure) == 34);
assert!(offset_of!(ArrowFunctionExpression, pife) == 35);
assert!(offset_of!(ArrowFunctionExpression, r#async) == 36);
assert!(offset_of!(ArrowFunctionExpression, pure) == 37);
assert!(offset_of!(ArrowFunctionExpression, pife) == 38);
// Padding: 3 bytes
assert!(size_of::<YieldExpression>() == 24);
File diff suppressed because it is too large. Load diff
@@ -2301,6 +2301,72 @@ impl<'new_alloc> CloneIn<'new_alloc> for FunctionBody<'_> {
}
}
impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionBody<'_> {
type Cloned = ArrowFunctionBody<'new_alloc>;
fn clone_in_impl(
&self,
with_semantic_ids: CloneInSemanticIds,
allocator: &'new_alloc Allocator,
) -> Self::Cloned {
match self {
Self::FunctionBody(it) => ArrowFunctionBody::FunctionBody(CloneIn::clone_in_impl(
it,
with_semantic_ids,
allocator,
)),
Self::BooleanLiteral(_)
| Self::NullLiteral(_)
| Self::NumericLiteral(_)
| Self::BigIntLiteral(_)
| Self::RegExpLiteral(_)
| Self::StringLiteral(_)
| Self::TemplateLiteral(_)
| Self::Identifier(_)
| Self::Super(_)
| Self::ArrayExpression(_)
| Self::ArrowFunctionExpression(_)
| Self::AssignmentExpression(_)
| Self::AwaitExpression(_)
| Self::BinaryExpression(_)
| Self::CallExpression(_)
| Self::ChainExpression(_)
| Self::ClassExpression(_)
| Self::ConditionalExpression(_)
| Self::FunctionExpression(_)
| Self::ImportExpression(_)
| Self::LogicalExpression(_)
| Self::NewExpression(_)
| Self::ObjectExpression(_)
| Self::ParenthesizedExpression(_)
| Self::SequenceExpression(_)
| Self::TaggedTemplateExpression(_)
| Self::ThisExpression(_)
| Self::UnaryExpression(_)
| Self::UpdateExpression(_)
| Self::YieldExpression(_)
| Self::PrivateInExpression(_)
| Self::ImportMeta(_)
| Self::NewTarget(_)
| Self::JSXElement(_)
| Self::JSXFragment(_)
| Self::TSAsExpression(_)
| Self::TSSatisfiesExpression(_)
| Self::TSTypeAssertion(_)
| Self::TSNonNullExpression(_)
| Self::TSInstantiationExpression(_)
| Self::V8IntrinsicExpression(_)
| Self::ComputedMemberExpression(_)
| Self::StaticMemberExpression(_)
| Self::PrivateFieldExpression(_) => ArrowFunctionBody::from(CloneIn::clone_in_impl(
self.to_expression(),
with_semantic_ids,
allocator,
)),
}
}
}
impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionExpression<'_> {
type Cloned = ArrowFunctionExpression<'new_alloc>;
@@ -2312,7 +2378,6 @@ impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionExpression<'_> {
ArrowFunctionExpression {
node_id: CloneIn::clone_in_impl(&self.node_id, with_semantic_ids, allocator),
span: CloneIn::clone_in_impl(&self.span, with_semantic_ids, allocator),
expression: CloneIn::clone_in_impl(&self.expression, with_semantic_ids, allocator),
r#async: CloneIn::clone_in_impl(&self.r#async, with_semantic_ids, allocator),
type_parameters: CloneIn::clone_in_impl(
&self.type_parameters,
@@ -1114,10 +1114,68 @@ impl ContentEq for FunctionBody<'_> {
}
}
impl ContentEq for ArrowFunctionBody<'_> {
fn content_eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::FunctionBody(a), Self::FunctionBody(b)) => a.content_eq(b),
(Self::BooleanLiteral(a), Self::BooleanLiteral(b)) => a.content_eq(b),
(Self::NullLiteral(a), Self::NullLiteral(b)) => a.content_eq(b),
(Self::NumericLiteral(a), Self::NumericLiteral(b)) => a.content_eq(b),
(Self::BigIntLiteral(a), Self::BigIntLiteral(b)) => a.content_eq(b),
(Self::RegExpLiteral(a), Self::RegExpLiteral(b)) => a.content_eq(b),
(Self::StringLiteral(a), Self::StringLiteral(b)) => a.content_eq(b),
(Self::TemplateLiteral(a), Self::TemplateLiteral(b)) => a.content_eq(b),
(Self::Identifier(a), Self::Identifier(b)) => a.content_eq(b),
(Self::Super(a), Self::Super(b)) => a.content_eq(b),
(Self::ArrayExpression(a), Self::ArrayExpression(b)) => a.content_eq(b),
(Self::ArrowFunctionExpression(a), Self::ArrowFunctionExpression(b)) => a.content_eq(b),
(Self::AssignmentExpression(a), Self::AssignmentExpression(b)) => a.content_eq(b),
(Self::AwaitExpression(a), Self::AwaitExpression(b)) => a.content_eq(b),
(Self::BinaryExpression(a), Self::BinaryExpression(b)) => a.content_eq(b),
(Self::CallExpression(a), Self::CallExpression(b)) => a.content_eq(b),
(Self::ChainExpression(a), Self::ChainExpression(b)) => a.content_eq(b),
(Self::ClassExpression(a), Self::ClassExpression(b)) => a.content_eq(b),
(Self::ConditionalExpression(a), Self::ConditionalExpression(b)) => a.content_eq(b),
(Self::FunctionExpression(a), Self::FunctionExpression(b)) => a.content_eq(b),
(Self::ImportExpression(a), Self::ImportExpression(b)) => a.content_eq(b),
(Self::LogicalExpression(a), Self::LogicalExpression(b)) => a.content_eq(b),
(Self::NewExpression(a), Self::NewExpression(b)) => a.content_eq(b),
(Self::ObjectExpression(a), Self::ObjectExpression(b)) => a.content_eq(b),
(Self::ParenthesizedExpression(a), Self::ParenthesizedExpression(b)) => a.content_eq(b),
(Self::SequenceExpression(a), Self::SequenceExpression(b)) => a.content_eq(b),
(Self::TaggedTemplateExpression(a), Self::TaggedTemplateExpression(b)) => {
a.content_eq(b)
}
(Self::ThisExpression(a), Self::ThisExpression(b)) => a.content_eq(b),
(Self::UnaryExpression(a), Self::UnaryExpression(b)) => a.content_eq(b),
(Self::UpdateExpression(a), Self::UpdateExpression(b)) => a.content_eq(b),
(Self::YieldExpression(a), Self::YieldExpression(b)) => a.content_eq(b),
(Self::PrivateInExpression(a), Self::PrivateInExpression(b)) => a.content_eq(b),
(Self::ImportMeta(a), Self::ImportMeta(b)) => a.content_eq(b),
(Self::NewTarget(a), Self::NewTarget(b)) => a.content_eq(b),
(Self::JSXElement(a), Self::JSXElement(b)) => a.content_eq(b),
(Self::JSXFragment(a), Self::JSXFragment(b)) => a.content_eq(b),
(Self::TSAsExpression(a), Self::TSAsExpression(b)) => a.content_eq(b),
(Self::TSSatisfiesExpression(a), Self::TSSatisfiesExpression(b)) => a.content_eq(b),
(Self::TSTypeAssertion(a), Self::TSTypeAssertion(b)) => a.content_eq(b),
(Self::TSNonNullExpression(a), Self::TSNonNullExpression(b)) => a.content_eq(b),
(Self::TSInstantiationExpression(a), Self::TSInstantiationExpression(b)) => {
a.content_eq(b)
}
(Self::V8IntrinsicExpression(a), Self::V8IntrinsicExpression(b)) => a.content_eq(b),
(Self::ComputedMemberExpression(a), Self::ComputedMemberExpression(b)) => {
a.content_eq(b)
}
(Self::StaticMemberExpression(a), Self::StaticMemberExpression(b)) => a.content_eq(b),
(Self::PrivateFieldExpression(a), Self::PrivateFieldExpression(b)) => a.content_eq(b),
_ => false,
}
}
}
impl ContentEq for ArrowFunctionExpression<'_> {
fn content_eq(&self, other: &Self) -> bool {
ContentEq::content_eq(&self.expression, &other.expression)
&& ContentEq::content_eq(&self.r#async, &other.r#async)
ContentEq::content_eq(&self.r#async, &other.r#async)
&& ContentEq::content_eq(&self.type_parameters, &other.type_parameters)
&& ContentEq::content_eq(&self.params, &other.params)
&& ContentEq::content_eq(&self.return_type, &other.return_type)
+10 -2
View File
@@ -1261,15 +1261,23 @@ impl<'a> Dummy<'a> for FunctionBody<'a> {
}
}
impl<'a> Dummy<'a> for ArrowFunctionBody<'a> {
/// Create a dummy [`ArrowFunctionBody`].
///
/// Has cost of making 1 allocation (16 bytes).
fn dummy(allocator: &'a Allocator) -> Self {
Self::NullLiteral(Dummy::dummy(allocator))
}
}
impl<'a> Dummy<'a> for ArrowFunctionExpression<'a> {
/// Create a dummy [`ArrowFunctionExpression`].
///
/// Has cost of making 2 allocations (112 bytes).
/// Has cost of making 2 allocations (64 bytes).
fn dummy(allocator: &'a Allocator) -> Self {
Self {
node_id: Dummy::dummy(allocator),
span: Dummy::dummy(allocator),
expression: Dummy::dummy(allocator),
r#async: Dummy::dummy(allocator),
type_parameters: Dummy::dummy(allocator),
params: Dummy::dummy(allocator),
+57 -2
View File
@@ -1370,16 +1370,71 @@ impl ESTree for FunctionBody<'_> {
}
}
impl ESTree for ArrowFunctionBody<'_> {
fn serialize<S: Serializer>(&self, serializer: S) {
match self {
Self::FunctionBody(it) => it.serialize(serializer),
Self::BooleanLiteral(_)
| Self::NullLiteral(_)
| Self::NumericLiteral(_)
| Self::BigIntLiteral(_)
| Self::RegExpLiteral(_)
| Self::StringLiteral(_)
| Self::TemplateLiteral(_)
| Self::Identifier(_)
| Self::Super(_)
| Self::ArrayExpression(_)
| Self::ArrowFunctionExpression(_)
| Self::AssignmentExpression(_)
| Self::AwaitExpression(_)
| Self::BinaryExpression(_)
| Self::CallExpression(_)
| Self::ChainExpression(_)
| Self::ClassExpression(_)
| Self::ConditionalExpression(_)
| Self::FunctionExpression(_)
| Self::ImportExpression(_)
| Self::LogicalExpression(_)
| Self::NewExpression(_)
| Self::ObjectExpression(_)
| Self::ParenthesizedExpression(_)
| Self::SequenceExpression(_)
| Self::TaggedTemplateExpression(_)
| Self::ThisExpression(_)
| Self::UnaryExpression(_)
| Self::UpdateExpression(_)
| Self::YieldExpression(_)
| Self::PrivateInExpression(_)
| Self::ImportMeta(_)
| Self::NewTarget(_)
| Self::JSXElement(_)
| Self::JSXFragment(_)
| Self::TSAsExpression(_)
| Self::TSSatisfiesExpression(_)
| Self::TSTypeAssertion(_)
| Self::TSNonNullExpression(_)
| Self::TSInstantiationExpression(_)
| Self::V8IntrinsicExpression(_)
| Self::ComputedMemberExpression(_)
| Self::StaticMemberExpression(_)
| Self::PrivateFieldExpression(_) => self.to_expression().serialize(serializer),
}
}
}
impl ESTree for ArrowFunctionExpression<'_> {
fn serialize<S: Serializer>(&self, serializer: S) {
let mut state = serializer.serialize_struct();
state.serialize_field("type", &JsonSafeString("ArrowFunctionExpression"));
state.serialize_field("expression", &self.expression);
state.serialize_field(
"expression",
&crate::serialize::js::ArrowFunctionExpressionExpression(self),
);
state.serialize_field("async", &self.r#async);
state.serialize_ts_field("typeParameters", &self.type_parameters);
state.serialize_field("params", &self.params);
state.serialize_ts_field("returnType", &self.return_type);
state.serialize_field("body", &crate::serialize::js::ArrowFunctionExpressionBody(self));
state.serialize_field("body", &self.body);
state.serialize_field("id", &crate::serialize::basic::Null(self));
state.serialize_field("generator", &crate::serialize::basic::False(self));
state.serialize_span(self.span);
@@ -433,6 +433,60 @@ impl GetAddress for BindingPattern<'_> {
}
}
impl GetAddress for ArrowFunctionBody<'_> {
// `#[inline]` because compiler should boil this down to a single assembly instruction
#[inline]
fn address(&self) -> Address {
match self {
Self::FunctionBody(it) => GetAddress::address(it),
Self::BooleanLiteral(it) => GetAddress::address(it),
Self::NullLiteral(it) => GetAddress::address(it),
Self::NumericLiteral(it) => GetAddress::address(it),
Self::BigIntLiteral(it) => GetAddress::address(it),
Self::RegExpLiteral(it) => GetAddress::address(it),
Self::StringLiteral(it) => GetAddress::address(it),
Self::TemplateLiteral(it) => GetAddress::address(it),
Self::Identifier(it) => GetAddress::address(it),
Self::Super(it) => GetAddress::address(it),
Self::ArrayExpression(it) => GetAddress::address(it),
Self::ArrowFunctionExpression(it) => GetAddress::address(it),
Self::AssignmentExpression(it) => GetAddress::address(it),
Self::AwaitExpression(it) => GetAddress::address(it),
Self::BinaryExpression(it) => GetAddress::address(it),
Self::CallExpression(it) => GetAddress::address(it),
Self::ChainExpression(it) => GetAddress::address(it),
Self::ClassExpression(it) => GetAddress::address(it),
Self::ConditionalExpression(it) => GetAddress::address(it),
Self::FunctionExpression(it) => GetAddress::address(it),
Self::ImportExpression(it) => GetAddress::address(it),
Self::LogicalExpression(it) => GetAddress::address(it),
Self::NewExpression(it) => GetAddress::address(it),
Self::ObjectExpression(it) => GetAddress::address(it),
Self::ParenthesizedExpression(it) => GetAddress::address(it),
Self::SequenceExpression(it) => GetAddress::address(it),
Self::TaggedTemplateExpression(it) => GetAddress::address(it),
Self::ThisExpression(it) => GetAddress::address(it),
Self::UnaryExpression(it) => GetAddress::address(it),
Self::UpdateExpression(it) => GetAddress::address(it),
Self::YieldExpression(it) => GetAddress::address(it),
Self::PrivateInExpression(it) => GetAddress::address(it),
Self::ImportMeta(it) => GetAddress::address(it),
Self::NewTarget(it) => GetAddress::address(it),
Self::JSXElement(it) => GetAddress::address(it),
Self::JSXFragment(it) => GetAddress::address(it),
Self::TSAsExpression(it) => GetAddress::address(it),
Self::TSSatisfiesExpression(it) => GetAddress::address(it),
Self::TSTypeAssertion(it) => GetAddress::address(it),
Self::TSNonNullExpression(it) => GetAddress::address(it),
Self::TSInstantiationExpression(it) => GetAddress::address(it),
Self::V8IntrinsicExpression(it) => GetAddress::address(it),
Self::ComputedMemberExpression(it) => GetAddress::address(it),
Self::StaticMemberExpression(it) => GetAddress::address(it),
Self::PrivateFieldExpression(it) => GetAddress::address(it),
}
}
}
impl GetAddress for ClassElement<'_> {
// `#[inline]` because compiler should boil this down to a single assembly instruction
#[inline]
@@ -973,6 +973,58 @@ impl GetSpan for FunctionBody<'_> {
}
}
impl GetSpan for ArrowFunctionBody<'_> {
fn span(&self) -> Span {
match self {
Self::FunctionBody(it) => GetSpan::span(&**it),
Self::BooleanLiteral(it) => GetSpan::span(&**it),
Self::NullLiteral(it) => GetSpan::span(&**it),
Self::NumericLiteral(it) => GetSpan::span(&**it),
Self::BigIntLiteral(it) => GetSpan::span(&**it),
Self::RegExpLiteral(it) => GetSpan::span(&**it),
Self::StringLiteral(it) => GetSpan::span(&**it),
Self::TemplateLiteral(it) => GetSpan::span(&**it),
Self::Identifier(it) => GetSpan::span(&**it),
Self::Super(it) => GetSpan::span(&**it),
Self::ArrayExpression(it) => GetSpan::span(&**it),
Self::ArrowFunctionExpression(it) => GetSpan::span(&**it),
Self::AssignmentExpression(it) => GetSpan::span(&**it),
Self::AwaitExpression(it) => GetSpan::span(&**it),
Self::BinaryExpression(it) => GetSpan::span(&**it),
Self::CallExpression(it) => GetSpan::span(&**it),
Self::ChainExpression(it) => GetSpan::span(&**it),
Self::ClassExpression(it) => GetSpan::span(&**it),
Self::ConditionalExpression(it) => GetSpan::span(&**it),
Self::FunctionExpression(it) => GetSpan::span(&**it),
Self::ImportExpression(it) => GetSpan::span(&**it),
Self::LogicalExpression(it) => GetSpan::span(&**it),
Self::NewExpression(it) => GetSpan::span(&**it),
Self::ObjectExpression(it) => GetSpan::span(&**it),
Self::ParenthesizedExpression(it) => GetSpan::span(&**it),
Self::SequenceExpression(it) => GetSpan::span(&**it),
Self::TaggedTemplateExpression(it) => GetSpan::span(&**it),
Self::ThisExpression(it) => GetSpan::span(&**it),
Self::UnaryExpression(it) => GetSpan::span(&**it),
Self::UpdateExpression(it) => GetSpan::span(&**it),
Self::YieldExpression(it) => GetSpan::span(&**it),
Self::PrivateInExpression(it) => GetSpan::span(&**it),
Self::ImportMeta(it) => GetSpan::span(&**it),
Self::NewTarget(it) => GetSpan::span(&**it),
Self::JSXElement(it) => GetSpan::span(&**it),
Self::JSXFragment(it) => GetSpan::span(&**it),
Self::TSAsExpression(it) => GetSpan::span(&**it),
Self::TSSatisfiesExpression(it) => GetSpan::span(&**it),
Self::TSTypeAssertion(it) => GetSpan::span(&**it),
Self::TSNonNullExpression(it) => GetSpan::span(&**it),
Self::TSInstantiationExpression(it) => GetSpan::span(&**it),
Self::V8IntrinsicExpression(it) => GetSpan::span(&**it),
Self::ComputedMemberExpression(it) => GetSpan::span(&**it),
Self::StaticMemberExpression(it) => GetSpan::span(&**it),
Self::PrivateFieldExpression(it) => GetSpan::span(&**it),
}
}
}
impl GetSpan for ArrowFunctionExpression<'_> {
#[inline]
fn span(&self) -> Span {
@@ -973,6 +973,58 @@ impl GetSpanMut for FunctionBody<'_> {
}
}
impl GetSpanMut for ArrowFunctionBody<'_> {
fn span_mut(&mut self) -> &mut Span {
match self {
Self::FunctionBody(it) => GetSpanMut::span_mut(&mut **it),
Self::BooleanLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::NullLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::NumericLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::BigIntLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::RegExpLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::StringLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::TemplateLiteral(it) => GetSpanMut::span_mut(&mut **it),
Self::Identifier(it) => GetSpanMut::span_mut(&mut **it),
Self::Super(it) => GetSpanMut::span_mut(&mut **it),
Self::ArrayExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ArrowFunctionExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::AssignmentExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::AwaitExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::BinaryExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::CallExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ChainExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ClassExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ConditionalExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::FunctionExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ImportExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::LogicalExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::NewExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ObjectExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ParenthesizedExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::SequenceExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::TaggedTemplateExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ThisExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::UnaryExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::UpdateExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::YieldExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::PrivateInExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ImportMeta(it) => GetSpanMut::span_mut(&mut **it),
Self::NewTarget(it) => GetSpanMut::span_mut(&mut **it),
Self::JSXElement(it) => GetSpanMut::span_mut(&mut **it),
Self::JSXFragment(it) => GetSpanMut::span_mut(&mut **it),
Self::TSAsExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::TSSatisfiesExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::TSTypeAssertion(it) => GetSpanMut::span_mut(&mut **it),
Self::TSNonNullExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::TSInstantiationExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::V8IntrinsicExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::ComputedMemberExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::StaticMemberExpression(it) => GetSpanMut::span_mut(&mut **it),
Self::PrivateFieldExpression(it) => GetSpanMut::span_mut(&mut **it),
}
}
}
impl GetSpanMut for ArrowFunctionExpression<'_> {
#[inline]
fn span_mut(&mut self) -> &mut Span {
@@ -192,6 +192,8 @@ impl<'a> ReplaceWith<'a> for FormalParameterRest<'a> {}
impl<'a> ReplaceWith<'a> for FunctionBody<'a> {}
impl<'a> ReplaceWith<'a> for ArrowFunctionBody<'a> {}
impl<'a> ReplaceWith<'a> for ArrowFunctionExpression<'a> {}
impl<'a> ReplaceWith<'a> for YieldExpression<'a> {}
@@ -194,6 +194,8 @@ impl<'a> TakeIn<'a> for FormalParameterRest<'a> {}
impl<'a> TakeIn<'a> for FunctionBody<'a> {}
impl<'a> TakeIn<'a> for ArrowFunctionBody<'a> {}
impl<'a> TakeIn<'a> for ArrowFunctionExpression<'a> {}
impl<'a> TakeIn<'a> for YieldExpression<'a> {}
+55
View File
@@ -3969,6 +3969,61 @@ impl BindingPattern<'_> {
}
}
impl ArrowFunctionBody<'_> {
/// Get [`NodeId`] of [`ArrowFunctionBody`].
// `#[inline(always)]` because this should boil down to a single instruction.
#[inline(always)]
pub fn node_id(&self) -> NodeId {
match self {
Self::FunctionBody(it) => it.node_id(),
Self::BooleanLiteral(it) => it.node_id(),
Self::NullLiteral(it) => it.node_id(),
Self::NumericLiteral(it) => it.node_id(),
Self::BigIntLiteral(it) => it.node_id(),
Self::RegExpLiteral(it) => it.node_id(),
Self::StringLiteral(it) => it.node_id(),
Self::TemplateLiteral(it) => it.node_id(),
Self::Identifier(it) => it.node_id(),
Self::Super(it) => it.node_id(),
Self::ArrayExpression(it) => it.node_id(),
Self::ArrowFunctionExpression(it) => it.node_id(),
Self::AssignmentExpression(it) => it.node_id(),
Self::AwaitExpression(it) => it.node_id(),
Self::BinaryExpression(it) => it.node_id(),
Self::CallExpression(it) => it.node_id(),
Self::ChainExpression(it) => it.node_id(),
Self::ClassExpression(it) => it.node_id(),
Self::ConditionalExpression(it) => it.node_id(),
Self::FunctionExpression(it) => it.node_id(),
Self::ImportExpression(it) => it.node_id(),
Self::LogicalExpression(it) => it.node_id(),
Self::NewExpression(it) => it.node_id(),
Self::ObjectExpression(it) => it.node_id(),
Self::ParenthesizedExpression(it) => it.node_id(),
Self::SequenceExpression(it) => it.node_id(),
Self::TaggedTemplateExpression(it) => it.node_id(),
Self::ThisExpression(it) => it.node_id(),
Self::UnaryExpression(it) => it.node_id(),
Self::UpdateExpression(it) => it.node_id(),
Self::YieldExpression(it) => it.node_id(),
Self::PrivateInExpression(it) => it.node_id(),
Self::ImportMeta(it) => it.node_id(),
Self::NewTarget(it) => it.node_id(),
Self::JSXElement(it) => it.node_id(),
Self::JSXFragment(it) => it.node_id(),
Self::TSAsExpression(it) => it.node_id(),
Self::TSSatisfiesExpression(it) => it.node_id(),
Self::TSTypeAssertion(it) => it.node_id(),
Self::TSNonNullExpression(it) => it.node_id(),
Self::TSInstantiationExpression(it) => it.node_id(),
Self::V8IntrinsicExpression(it) => it.node_id(),
Self::ComputedMemberExpression(it) => it.node_id(),
Self::StaticMemberExpression(it) => it.node_id(),
Self::PrivateFieldExpression(it) => it.node_id(),
}
}
}
impl ClassElement<'_> {
/// Get [`NodeId`] of [`ClassElement`].
// `#[inline(always)]` because this should boil down to a single instruction.
@@ -3895,6 +3895,407 @@ impl<'a> From<AssignmentTargetPattern<'a>> for ForStatementLeft<'a> {
}
}
impl<'a> ArrowFunctionBody<'a> {
/// Return if an [`ArrowFunctionBody`] is an [`Expression`].
#[inline]
pub fn is_expression(&self) -> bool {
matches!(
self,
Self::BooleanLiteral(_)
| Self::NullLiteral(_)
| Self::NumericLiteral(_)
| Self::BigIntLiteral(_)
| Self::RegExpLiteral(_)
| Self::StringLiteral(_)
| Self::TemplateLiteral(_)
| Self::Identifier(_)
| Self::Super(_)
| Self::ArrayExpression(_)
| Self::ArrowFunctionExpression(_)
| Self::AssignmentExpression(_)
| Self::AwaitExpression(_)
| Self::BinaryExpression(_)
| Self::CallExpression(_)
| Self::ChainExpression(_)
| Self::ClassExpression(_)
| Self::ConditionalExpression(_)
| Self::FunctionExpression(_)
| Self::ImportExpression(_)
| Self::LogicalExpression(_)
| Self::NewExpression(_)
| Self::ObjectExpression(_)
| Self::ParenthesizedExpression(_)
| Self::SequenceExpression(_)
| Self::TaggedTemplateExpression(_)
| Self::ThisExpression(_)
| Self::UnaryExpression(_)
| Self::UpdateExpression(_)
| Self::YieldExpression(_)
| Self::PrivateInExpression(_)
| Self::ImportMeta(_)
| Self::NewTarget(_)
| Self::JSXElement(_)
| Self::JSXFragment(_)
| Self::TSAsExpression(_)
| Self::TSSatisfiesExpression(_)
| Self::TSTypeAssertion(_)
| Self::TSNonNullExpression(_)
| Self::TSInstantiationExpression(_)
| Self::V8IntrinsicExpression(_)
| Self::ComputedMemberExpression(_)
| Self::StaticMemberExpression(_)
| Self::PrivateFieldExpression(_)
)
}
/// Convert an [`ArrowFunctionBody`] to an [`Expression`].
///
/// # Panics
/// Panics if not convertible.
#[inline]
pub fn into_expression(self) -> Expression<'a> {
Expression::try_from(self).unwrap()
}
/// Convert an [`&ArrowFunctionBody`] to an [`&Expression`].
///
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
/// [`&Expression`]: Expression
#[inline]
pub fn as_expression(&self) -> Option<&Expression<'a>> {
if self.is_expression() {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
Some(unsafe { NonNull::from_ref(self).cast::<Expression>().as_ref() })
} else {
None
}
}
/// Convert an [`&mut ArrowFunctionBody`] to an [`&mut Expression`].
///
/// [`&mut ArrowFunctionBody`]: ArrowFunctionBody
/// [`&mut Expression`]: Expression
#[inline]
pub fn as_expression_mut(&mut self) -> Option<&mut Expression<'a>> {
if self.is_expression() {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
Some(unsafe { NonNull::from_mut(self).cast::<Expression>().as_mut() })
} else {
None
}
}
/// Convert an [`&ArrowFunctionBody`] to an [`&Expression`].
///
/// # Panics
/// Panics if not convertible.
///
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
/// [`&Expression`]: Expression
#[inline]
pub fn to_expression(&self) -> &Expression<'a> {
self.as_expression().unwrap()
}
/// Convert an [`&mut ArrowFunctionBody`] to an [`&mut Expression`].
///
/// # Panics
/// Panics if not convertible.
///
/// [`&mut ArrowFunctionBody`]: ArrowFunctionBody
/// [`&mut Expression`]: Expression
#[inline]
pub fn to_expression_mut(&mut self) -> &mut Expression<'a> {
self.as_expression_mut().unwrap()
}
}
impl<'a> Expression<'a> {
/// Convert an [`&Expression`] to an [`&ArrowFunctionBody`].
///
/// [`&Expression`]: Expression
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
#[inline]
pub fn as_arrow_function_body(&self) -> &ArrowFunctionBody<'a> {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
unsafe { NonNull::from_ref(self).cast::<ArrowFunctionBody>().as_ref() }
}
}
impl<'a> TryFrom<ArrowFunctionBody<'a>> for Expression<'a> {
type Error = ();
/// Convert an [`ArrowFunctionBody`] to an [`Expression`].
///
/// # Errors
/// Returns `Err` if not convertible.
#[inline]
fn try_from(value: ArrowFunctionBody<'a>) -> Result<Self, Self::Error> {
// Compiler should implement this as a check of discriminant and then zero-cost transmute,
// as discriminants for `parent` and `child` are aligned
match value {
ArrowFunctionBody::BooleanLiteral(o) => Ok(Expression::BooleanLiteral(o)),
ArrowFunctionBody::NullLiteral(o) => Ok(Expression::NullLiteral(o)),
ArrowFunctionBody::NumericLiteral(o) => Ok(Expression::NumericLiteral(o)),
ArrowFunctionBody::BigIntLiteral(o) => Ok(Expression::BigIntLiteral(o)),
ArrowFunctionBody::RegExpLiteral(o) => Ok(Expression::RegExpLiteral(o)),
ArrowFunctionBody::StringLiteral(o) => Ok(Expression::StringLiteral(o)),
ArrowFunctionBody::TemplateLiteral(o) => Ok(Expression::TemplateLiteral(o)),
ArrowFunctionBody::Identifier(o) => Ok(Expression::Identifier(o)),
ArrowFunctionBody::Super(o) => Ok(Expression::Super(o)),
ArrowFunctionBody::ArrayExpression(o) => Ok(Expression::ArrayExpression(o)),
ArrowFunctionBody::ArrowFunctionExpression(o) => {
Ok(Expression::ArrowFunctionExpression(o))
}
ArrowFunctionBody::AssignmentExpression(o) => Ok(Expression::AssignmentExpression(o)),
ArrowFunctionBody::AwaitExpression(o) => Ok(Expression::AwaitExpression(o)),
ArrowFunctionBody::BinaryExpression(o) => Ok(Expression::BinaryExpression(o)),
ArrowFunctionBody::CallExpression(o) => Ok(Expression::CallExpression(o)),
ArrowFunctionBody::ChainExpression(o) => Ok(Expression::ChainExpression(o)),
ArrowFunctionBody::ClassExpression(o) => Ok(Expression::ClassExpression(o)),
ArrowFunctionBody::ConditionalExpression(o) => Ok(Expression::ConditionalExpression(o)),
ArrowFunctionBody::FunctionExpression(o) => Ok(Expression::FunctionExpression(o)),
ArrowFunctionBody::ImportExpression(o) => Ok(Expression::ImportExpression(o)),
ArrowFunctionBody::LogicalExpression(o) => Ok(Expression::LogicalExpression(o)),
ArrowFunctionBody::NewExpression(o) => Ok(Expression::NewExpression(o)),
ArrowFunctionBody::ObjectExpression(o) => Ok(Expression::ObjectExpression(o)),
ArrowFunctionBody::ParenthesizedExpression(o) => {
Ok(Expression::ParenthesizedExpression(o))
}
ArrowFunctionBody::SequenceExpression(o) => Ok(Expression::SequenceExpression(o)),
ArrowFunctionBody::TaggedTemplateExpression(o) => {
Ok(Expression::TaggedTemplateExpression(o))
}
ArrowFunctionBody::ThisExpression(o) => Ok(Expression::ThisExpression(o)),
ArrowFunctionBody::UnaryExpression(o) => Ok(Expression::UnaryExpression(o)),
ArrowFunctionBody::UpdateExpression(o) => Ok(Expression::UpdateExpression(o)),
ArrowFunctionBody::YieldExpression(o) => Ok(Expression::YieldExpression(o)),
ArrowFunctionBody::PrivateInExpression(o) => Ok(Expression::PrivateInExpression(o)),
ArrowFunctionBody::ImportMeta(o) => Ok(Expression::ImportMeta(o)),
ArrowFunctionBody::NewTarget(o) => Ok(Expression::NewTarget(o)),
ArrowFunctionBody::JSXElement(o) => Ok(Expression::JSXElement(o)),
ArrowFunctionBody::JSXFragment(o) => Ok(Expression::JSXFragment(o)),
ArrowFunctionBody::TSAsExpression(o) => Ok(Expression::TSAsExpression(o)),
ArrowFunctionBody::TSSatisfiesExpression(o) => Ok(Expression::TSSatisfiesExpression(o)),
ArrowFunctionBody::TSTypeAssertion(o) => Ok(Expression::TSTypeAssertion(o)),
ArrowFunctionBody::TSNonNullExpression(o) => Ok(Expression::TSNonNullExpression(o)),
ArrowFunctionBody::TSInstantiationExpression(o) => {
Ok(Expression::TSInstantiationExpression(o))
}
ArrowFunctionBody::V8IntrinsicExpression(o) => Ok(Expression::V8IntrinsicExpression(o)),
ArrowFunctionBody::ComputedMemberExpression(o) => {
Ok(Expression::ComputedMemberExpression(o))
}
ArrowFunctionBody::StaticMemberExpression(o) => {
Ok(Expression::StaticMemberExpression(o))
}
ArrowFunctionBody::PrivateFieldExpression(o) => {
Ok(Expression::PrivateFieldExpression(o))
}
_ => Err(()),
}
}
}
impl<'a> From<Expression<'a>> for ArrowFunctionBody<'a> {
/// Convert an [`Expression`] to an [`ArrowFunctionBody`].
#[inline]
fn from(value: Expression<'a>) -> Self {
// Compiler should implement this as zero-cost transmute as discriminants
// for `child` and `parent` are aligned
match value {
Expression::BooleanLiteral(o) => ArrowFunctionBody::BooleanLiteral(o),
Expression::NullLiteral(o) => ArrowFunctionBody::NullLiteral(o),
Expression::NumericLiteral(o) => ArrowFunctionBody::NumericLiteral(o),
Expression::BigIntLiteral(o) => ArrowFunctionBody::BigIntLiteral(o),
Expression::RegExpLiteral(o) => ArrowFunctionBody::RegExpLiteral(o),
Expression::StringLiteral(o) => ArrowFunctionBody::StringLiteral(o),
Expression::TemplateLiteral(o) => ArrowFunctionBody::TemplateLiteral(o),
Expression::Identifier(o) => ArrowFunctionBody::Identifier(o),
Expression::Super(o) => ArrowFunctionBody::Super(o),
Expression::ArrayExpression(o) => ArrowFunctionBody::ArrayExpression(o),
Expression::ArrowFunctionExpression(o) => ArrowFunctionBody::ArrowFunctionExpression(o),
Expression::AssignmentExpression(o) => ArrowFunctionBody::AssignmentExpression(o),
Expression::AwaitExpression(o) => ArrowFunctionBody::AwaitExpression(o),
Expression::BinaryExpression(o) => ArrowFunctionBody::BinaryExpression(o),
Expression::CallExpression(o) => ArrowFunctionBody::CallExpression(o),
Expression::ChainExpression(o) => ArrowFunctionBody::ChainExpression(o),
Expression::ClassExpression(o) => ArrowFunctionBody::ClassExpression(o),
Expression::ConditionalExpression(o) => ArrowFunctionBody::ConditionalExpression(o),
Expression::FunctionExpression(o) => ArrowFunctionBody::FunctionExpression(o),
Expression::ImportExpression(o) => ArrowFunctionBody::ImportExpression(o),
Expression::LogicalExpression(o) => ArrowFunctionBody::LogicalExpression(o),
Expression::NewExpression(o) => ArrowFunctionBody::NewExpression(o),
Expression::ObjectExpression(o) => ArrowFunctionBody::ObjectExpression(o),
Expression::ParenthesizedExpression(o) => ArrowFunctionBody::ParenthesizedExpression(o),
Expression::SequenceExpression(o) => ArrowFunctionBody::SequenceExpression(o),
Expression::TaggedTemplateExpression(o) => {
ArrowFunctionBody::TaggedTemplateExpression(o)
}
Expression::ThisExpression(o) => ArrowFunctionBody::ThisExpression(o),
Expression::UnaryExpression(o) => ArrowFunctionBody::UnaryExpression(o),
Expression::UpdateExpression(o) => ArrowFunctionBody::UpdateExpression(o),
Expression::YieldExpression(o) => ArrowFunctionBody::YieldExpression(o),
Expression::PrivateInExpression(o) => ArrowFunctionBody::PrivateInExpression(o),
Expression::ImportMeta(o) => ArrowFunctionBody::ImportMeta(o),
Expression::NewTarget(o) => ArrowFunctionBody::NewTarget(o),
Expression::JSXElement(o) => ArrowFunctionBody::JSXElement(o),
Expression::JSXFragment(o) => ArrowFunctionBody::JSXFragment(o),
Expression::TSAsExpression(o) => ArrowFunctionBody::TSAsExpression(o),
Expression::TSSatisfiesExpression(o) => ArrowFunctionBody::TSSatisfiesExpression(o),
Expression::TSTypeAssertion(o) => ArrowFunctionBody::TSTypeAssertion(o),
Expression::TSNonNullExpression(o) => ArrowFunctionBody::TSNonNullExpression(o),
Expression::TSInstantiationExpression(o) => {
ArrowFunctionBody::TSInstantiationExpression(o)
}
Expression::V8IntrinsicExpression(o) => ArrowFunctionBody::V8IntrinsicExpression(o),
Expression::ComputedMemberExpression(o) => {
ArrowFunctionBody::ComputedMemberExpression(o)
}
Expression::StaticMemberExpression(o) => ArrowFunctionBody::StaticMemberExpression(o),
Expression::PrivateFieldExpression(o) => ArrowFunctionBody::PrivateFieldExpression(o),
}
}
}
impl<'a> ArrowFunctionBody<'a> {
/// Return if an [`ArrowFunctionBody`] is a [`MemberExpression`].
#[inline]
pub fn is_member_expression(&self) -> bool {
matches!(
self,
Self::ComputedMemberExpression(_)
| Self::StaticMemberExpression(_)
| Self::PrivateFieldExpression(_)
)
}
/// Convert an [`ArrowFunctionBody`] to a [`MemberExpression`].
///
/// # Panics
/// Panics if not convertible.
#[inline]
pub fn into_member_expression(self) -> MemberExpression<'a> {
MemberExpression::try_from(self).unwrap()
}
/// Convert an [`&ArrowFunctionBody`] to a [`&MemberExpression`].
///
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
/// [`&MemberExpression`]: MemberExpression
#[inline]
pub fn as_member_expression(&self) -> Option<&MemberExpression<'a>> {
if self.is_member_expression() {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
Some(unsafe { NonNull::from_ref(self).cast::<MemberExpression>().as_ref() })
} else {
None
}
}
/// Convert an [`&mut ArrowFunctionBody`] to a [`&mut MemberExpression`].
///
/// [`&mut ArrowFunctionBody`]: ArrowFunctionBody
/// [`&mut MemberExpression`]: MemberExpression
#[inline]
pub fn as_member_expression_mut(&mut self) -> Option<&mut MemberExpression<'a>> {
if self.is_member_expression() {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
Some(unsafe { NonNull::from_mut(self).cast::<MemberExpression>().as_mut() })
} else {
None
}
}
/// Convert an [`&ArrowFunctionBody`] to a [`&MemberExpression`].
///
/// # Panics
/// Panics if not convertible.
///
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
/// [`&MemberExpression`]: MemberExpression
#[inline]
pub fn to_member_expression(&self) -> &MemberExpression<'a> {
self.as_member_expression().unwrap()
}
/// Convert an [`&mut ArrowFunctionBody`] to a [`&mut MemberExpression`].
///
/// # Panics
/// Panics if not convertible.
///
/// [`&mut ArrowFunctionBody`]: ArrowFunctionBody
/// [`&mut MemberExpression`]: MemberExpression
#[inline]
pub fn to_member_expression_mut(&mut self) -> &mut MemberExpression<'a> {
self.as_member_expression_mut().unwrap()
}
}
impl<'a> MemberExpression<'a> {
/// Convert a [`&MemberExpression`] to an [`&ArrowFunctionBody`].
///
/// [`&MemberExpression`]: MemberExpression
/// [`&ArrowFunctionBody`]: ArrowFunctionBody
#[inline]
pub fn as_arrow_function_body(&self) -> &ArrowFunctionBody<'a> {
// SAFETY: Transmute is safe because discriminants + types are identical between
// `parent` and `child` for the shared variants
unsafe { NonNull::from_ref(self).cast::<ArrowFunctionBody>().as_ref() }
}
}
impl<'a> TryFrom<ArrowFunctionBody<'a>> for MemberExpression<'a> {
type Error = ();
/// Convert an [`ArrowFunctionBody`] to a [`MemberExpression`].
///
/// # Errors
/// Returns `Err` if not convertible.
#[inline]
fn try_from(value: ArrowFunctionBody<'a>) -> Result<Self, Self::Error> {
// Compiler should implement this as a check of discriminant and then zero-cost transmute,
// as discriminants for `parent` and `child` are aligned
match value {
ArrowFunctionBody::ComputedMemberExpression(o) => {
Ok(MemberExpression::ComputedMemberExpression(o))
}
ArrowFunctionBody::StaticMemberExpression(o) => {
Ok(MemberExpression::StaticMemberExpression(o))
}
ArrowFunctionBody::PrivateFieldExpression(o) => {
Ok(MemberExpression::PrivateFieldExpression(o))
}
_ => Err(()),
}
}
}
impl<'a> From<MemberExpression<'a>> for ArrowFunctionBody<'a> {
/// Convert a [`MemberExpression`] to an [`ArrowFunctionBody`].
#[inline]
fn from(value: MemberExpression<'a>) -> Self {
// Compiler should implement this as zero-cost transmute as discriminants
// for `child` and `parent` are aligned
match value {
MemberExpression::ComputedMemberExpression(o) => {
ArrowFunctionBody::ComputedMemberExpression(o)
}
MemberExpression::StaticMemberExpression(o) => {
ArrowFunctionBody::StaticMemberExpression(o)
}
MemberExpression::PrivateFieldExpression(o) => {
ArrowFunctionBody::PrivateFieldExpression(o)
}
}
}
}
impl<'a> ExportDefaultDeclarationKind<'a> {
/// Return if an [`ExportDefaultDeclarationKind`] is an [`Expression`].
#[inline]
+5 -22
View File
@@ -811,31 +811,14 @@ impl ESTree for ExportAllDeclarationWithClause<'_, '_> {
// Misc
// ----------------------------------------
/// Serializer for `body` field of `ArrowFunctionExpression`.
///
/// Serialize as either an expression (if `expression` property is set),
/// or a `BlockStatement` (if it's not).
/// Serializer for the derived `expression` field of [`ArrowFunctionExpression`].
#[ast_meta]
#[estree(
ts_type = "FunctionBody | Expression",
raw_deser = "
let body = DESER[Box<FunctionBody>](POS_OFFSET.body);
if (THIS.expression === true) {
body = body.body[0].expression;
if (PARENT) body.parent = parent;
}
body
"
)]
pub struct ArrowFunctionExpressionBody<'a>(pub &'a ArrowFunctionExpression<'a>);
#[estree(ts_type = "boolean", raw_deser = "uint8[POS_OFFSET.body] !== 64", raw_deser_inline)]
pub struct ArrowFunctionExpressionExpression<'a>(pub &'a ArrowFunctionExpression<'a>);
impl ESTree for ArrowFunctionExpressionBody<'_> {
impl ESTree for ArrowFunctionExpressionExpression<'_> {
fn serialize<S: Serializer>(&self, serializer: S) {
if let Some(expression) = self.0.get_expression() {
expression.serialize(serializer);
} else {
self.0.body.serialize(serializer);
}
self.0.is_expression().serialize(serializer);
}
}
+100 -99
View File
@@ -8,122 +8,123 @@ use crate::ast::{EnumDetails, EnumVariant, InheritedEnum};
pub static ENUMS: phf::Map<&'static str, EnumDetails> = ::phf::Map {
key: 16287231350648472473,
disps: &[
(0, 17),
(0, 3),
(0, 0),
(0, 9),
(0, 12),
(0, 2),
(0, 11),
(0, 4),
(0, 1),
(0, 0),
(0, 7),
(0, 37),
(0, 2),
(0, 64),
(0, 30),
(0, 5),
(1, 1),
(0, 4),
(0, 0),
(0, 20),
(0, 3),
(1, 32),
(0, 19),
(0, 72),
(0, 12),
(0, 24),
(1, 82),
(0, 0),
(1, 81),
(0, 52),
(0, 0),
(0, 55),
(0, 9),
(2, 34),
(0, 41),
(0, 30),
(1, 30),
(0, 0),
(0, 2),
(0, 60),
(0, 14),
(0, 45),
(2, 19),
(10, 57),
(0, 31),
(0, 56),
(0, 0),
(7, 6),
(1, 24),
(6, 16),
(0, 4),
],
entries: &[
("JSXAttributeValue", EnumDetails { is_fieldless: false, inherits: &[] }),
("JSXAttributeName", EnumDetails { is_fieldless: false, inherits: &[] }),
("ImportOrExportKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXElementName", EnumDetails { is_fieldless: false, inherits: &[] }),
("UpdateOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentTargetProperty", EnumDetails { is_fieldless: false, inherits: &[] }),
("AssignmentTarget", EnumDetails { is_fieldless: false, inherits: &[3, 4] }),
("ErrorSeverity", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSEnumMemberName", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSModuleReference", EnumDetails { is_fieldless: false, inherits: &[] }),
("Declaration", EnumDetails { is_fieldless: false, inherits: &[] }),
("Term", EnumDetails { is_fieldless: false, inherits: &[] }),
("ImportAttributeKey", EnumDetails { is_fieldless: false, inherits: &[] }),
("FormalParameterKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("LanguageVariant", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSLiteral", EnumDetails { is_fieldless: false, inherits: &[] }),
("WithClauseKeyword", EnumDetails { is_fieldless: true, inherits: &[] }),
("FileExtension", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSType", EnumDetails { is_fieldless: false, inherits: &[] }),
("CharacterClassEscapeKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("Language", EnumDetails { is_fieldless: true, inherits: &[] }),
("ModuleExportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("ArrayExpressionElement", EnumDetails { is_fieldless: false, inherits: &[0] }),
("ChainElement", EnumDetails { is_fieldless: false, inherits: &[1] }),
("TSMappedTypeModifierOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("MethodDefinitionType", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSModuleDeclarationKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXExpression", EnumDetails { is_fieldless: false, inherits: &[0] }),
("ObjectPropertyKind", EnumDetails { is_fieldless: false, inherits: &[] }),
("BindingPattern", EnumDetails { is_fieldless: false, inherits: &[] }),
("Statement", EnumDetails { is_fieldless: false, inherits: &[5, 6] }),
("AccessorPropertyType", EnumDetails { is_fieldless: true, inherits: &[] }),
("CommentKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTupleElement", EnumDetails { is_fieldless: false, inherits: &[7] }),
("ForStatementInit", EnumDetails { is_fieldless: false, inherits: &[0] }),
("TSLiteral", EnumDetails { is_fieldless: false, inherits: &[] }),
("FunctionType", EnumDetails { is_fieldless: true, inherits: &[] }),
("Argument", EnumDetails { is_fieldless: false, inherits: &[0] }),
("Expression", EnumDetails { is_fieldless: false, inherits: &[1] }),
("ImportDeclarationSpecifier", EnumDetails { is_fieldless: false, inherits: &[] }),
("CharacterClassContents", EnumDetails { is_fieldless: false, inherits: &[] }),
("LogicalOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("CommentPosition", EnumDetails { is_fieldless: true, inherits: &[] }),
("PropertyKey", EnumDetails { is_fieldless: false, inherits: &[0] }),
("ModuleDeclaration", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSMethodSignatureKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTupleElement", EnumDetails { is_fieldless: false, inherits: &[7] }),
("ExportLocalName", EnumDetails { is_fieldless: false, inherits: &[] }),
("PropertyKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentTargetPattern", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSModuleDeclarationBody", EnumDetails { is_fieldless: false, inherits: &[] }),
("LookAroundAssertionKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTypeQueryExprName", EnumDetails { is_fieldless: false, inherits: &[8] }),
("ImportPhase", EnumDetails { is_fieldless: true, inherits: &[] }),
("ClassType", EnumDetails { is_fieldless: true, inherits: &[] }),
("CharacterKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("BinaryOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSImportTypeQualifier", EnumDetails { is_fieldless: false, inherits: &[] }),
("JSXAttributeItem", EnumDetails { is_fieldless: false, inherits: &[] }),
("PropertyDefinitionType", EnumDetails { is_fieldless: true, inherits: &[] }),
("MethodDefinitionKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXMemberExpressionObject", EnumDetails { is_fieldless: false, inherits: &[] }),
("UpdateOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("CommentKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("MethodDefinitionType", EnumDetails { is_fieldless: true, inherits: &[] }),
("Statement", EnumDetails { is_fieldless: false, inherits: &[5, 6] }),
("TSEnumMemberName", EnumDetails { is_fieldless: false, inherits: &[] }),
("LanguageVariant", EnumDetails { is_fieldless: true, inherits: &[] }),
("BigintBase", EnumDetails { is_fieldless: true, inherits: &[] }),
("BindingPattern", EnumDetails { is_fieldless: false, inherits: &[] }),
("CommentPosition", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTypeName", EnumDetails { is_fieldless: false, inherits: &[] }),
("PropertyKey", EnumDetails { is_fieldless: false, inherits: &[0] }),
("FileExtension", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXExpression", EnumDetails { is_fieldless: false, inherits: &[0] }),
("AssignmentTarget", EnumDetails { is_fieldless: false, inherits: &[3, 4] }),
("LogicalOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("CharacterClassEscapeKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("ModuleExportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("Declaration", EnumDetails { is_fieldless: false, inherits: &[] }),
("ImportDeclarationSpecifier", EnumDetails { is_fieldless: false, inherits: &[] }),
("ExportImportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("Expression", EnumDetails { is_fieldless: false, inherits: &[1] }),
("JSXElementName", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSModuleDeclarationName", EnumDetails { is_fieldless: false, inherits: &[] }),
("CharacterClassContentsKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("ArrayExpressionElement", EnumDetails { is_fieldless: false, inherits: &[0] }),
("TSType", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSImportTypeQualifier", EnumDetails { is_fieldless: false, inherits: &[] }),
("ErrorSeverity", EnumDetails { is_fieldless: true, inherits: &[] }),
("BinaryOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("ImportAttributeKey", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSAccessibility", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXAttributeName", EnumDetails { is_fieldless: false, inherits: &[] }),
("PropertyDefinitionType", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("ImportPhase", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTypeQueryExprName", EnumDetails { is_fieldless: false, inherits: &[8] }),
("JSXChild", EnumDetails { is_fieldless: false, inherits: &[] }),
("JSXMemberExpressionObject", EnumDetails { is_fieldless: false, inherits: &[] }),
("ForStatementInit", EnumDetails { is_fieldless: false, inherits: &[0] }),
("MemberExpression", EnumDetails { is_fieldless: false, inherits: &[] }),
("Term", EnumDetails { is_fieldless: false, inherits: &[] }),
("ImportOrExportKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("PropertyKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("MethodDefinitionKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("ClassType", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSMethodSignatureKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentTargetProperty", EnumDetails { is_fieldless: false, inherits: &[] }),
("FormalParameterKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("ClassElement", EnumDetails { is_fieldless: false, inherits: &[] }),
("ModuleDeclaration", EnumDetails { is_fieldless: false, inherits: &[] }),
("JSXAttributeItem", EnumDetails { is_fieldless: false, inherits: &[] }),
("ImportImportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("Argument", EnumDetails { is_fieldless: false, inherits: &[0] }),
("CharacterKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTypePredicateName", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSModuleDeclarationKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("ExportDefaultDeclarationKind", EnumDetails { is_fieldless: false, inherits: &[0] }),
("TSTypeOperatorOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("Language", EnumDetails { is_fieldless: true, inherits: &[] }),
("AccessorPropertyType", EnumDetails { is_fieldless: true, inherits: &[] }),
("BoundaryAssertionKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSMappedTypeModifierOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("ObjectPropertyKind", EnumDetails { is_fieldless: false, inherits: &[] }),
("CommentContent", EnumDetails { is_fieldless: true, inherits: &[] }),
("SimpleAssignmentTarget", EnumDetails { is_fieldless: false, inherits: &[1] }),
("WithClauseKeyword", EnumDetails { is_fieldless: true, inherits: &[] }),
("ExportExportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("VariableDeclarationKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("UnaryOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("JSXChild", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSSignature", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSTypePredicateName", EnumDetails { is_fieldless: false, inherits: &[] }),
("ExportDefaultDeclarationKind", EnumDetails { is_fieldless: false, inherits: &[0] }),
("ExportImportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("ForStatementLeft", EnumDetails { is_fieldless: false, inherits: &[2] }),
("BigintBase", EnumDetails { is_fieldless: true, inherits: &[] }),
("BoundaryAssertionKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("CommentContent", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSTypeOperatorOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
("TSModuleDeclarationBody", EnumDetails { is_fieldless: false, inherits: &[] }),
("ClassElement", EnumDetails { is_fieldless: false, inherits: &[] }),
("AssignmentTargetPattern", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSTypeName", EnumDetails { is_fieldless: false, inherits: &[] }),
("SimpleAssignmentTarget", EnumDetails { is_fieldless: false, inherits: &[1] }),
("ModuleKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentTargetMaybeDefault", EnumDetails { is_fieldless: false, inherits: &[2] }),
("TSAccessibility", EnumDetails { is_fieldless: true, inherits: &[] }),
("MemberExpression", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSModuleReference", EnumDetails { is_fieldless: false, inherits: &[] }),
("NumberBase", EnumDetails { is_fieldless: true, inherits: &[] }),
("ImportImportName", EnumDetails { is_fieldless: false, inherits: &[] }),
("CharacterClassContentsKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("AssignmentTargetMaybeDefault", EnumDetails { is_fieldless: false, inherits: &[2] }),
("ArrowFunctionBody", EnumDetails { is_fieldless: false, inherits: &[0] }),
("ForStatementLeft", EnumDetails { is_fieldless: false, inherits: &[2] }),
("ModuleKind", EnumDetails { is_fieldless: true, inherits: &[] }),
("CharacterClassContents", EnumDetails { is_fieldless: false, inherits: &[] }),
("JSXAttributeValue", EnumDetails { is_fieldless: false, inherits: &[] }),
("TSSignature", EnumDetails { is_fieldless: false, inherits: &[] }),
("UnaryOperator", EnumDetails { is_fieldless: true, inherits: &[] }),
],
};
@@ -420,7 +420,7 @@ pub static STRUCTS: phf::Map<&'static str, StructDetails> = ::phf::Map {
(
"ArrowFunctionExpression",
StructDetails {
field_order: Some(&[1, 0, 7, 8, 3, 4, 5, 6, 2, 9, 10]),
field_order: Some(&[1, 0, 7, 3, 4, 5, 6, 2, 8, 9]),
is_node: true,
is_transparent: false,
},
+15 -1
View File
@@ -501,6 +501,11 @@ pub trait Visit<'a>: Sized {
walk_function_body(self, it);
}
#[inline]
fn visit_arrow_function_body(&mut self, it: &ArrowFunctionBody<'a>) {
walk_arrow_function_body(self, it);
}
#[inline]
fn visit_arrow_function_expression(&mut self, it: &ArrowFunctionExpression<'a>) {
walk_arrow_function_expression(self, it);
@@ -2533,6 +2538,15 @@ pub mod walk {
visitor.leave_node(kind);
}
#[inline]
pub fn walk_arrow_function_body<'a, V: Visit<'a>>(visitor: &mut V, it: &ArrowFunctionBody<'a>) {
// No `AstKind` for this type
match it {
ArrowFunctionBody::FunctionBody(it) => visitor.visit_function_body(it),
match_expression!(ArrowFunctionBody) => visitor.visit_expression(it.to_expression()),
}
}
#[inline]
pub fn walk_arrow_function_expression<'a, V: Visit<'a>>(
visitor: &mut V,
@@ -2558,7 +2572,7 @@ pub mod walk {
if let Some(return_type) = &it.return_type {
visitor.visit_ts_type_annotation(return_type);
}
visitor.visit_function_body(&it.body);
visitor.visit_arrow_function_body(&it.body);
visitor.leave_scope();
visitor.leave_node(kind);
}
+18 -1
View File
@@ -528,6 +528,11 @@ pub trait VisitJs<'a>: Sized {
walk_function_body(self, it);
}
#[inline]
fn visit_arrow_function_body(&mut self, it: &ArrowFunctionBody<'a>) {
walk_arrow_function_body(self, it);
}
#[inline]
fn visit_arrow_function_expression(&mut self, it: &ArrowFunctionExpression<'a>) {
walk_arrow_function_expression(self, it);
@@ -2188,6 +2193,18 @@ pub mod walk_js {
visitor.leave_node(kind);
}
#[inline]
pub fn walk_arrow_function_body<'a, V: VisitJs<'a>>(
visitor: &mut V,
it: &ArrowFunctionBody<'a>,
) {
// No `AstKind` for this type
match it {
ArrowFunctionBody::FunctionBody(it) => visitor.visit_function_body(it),
match_expression!(ArrowFunctionBody) => visitor.visit_expression(it.to_expression()),
}
}
#[inline]
pub fn walk_arrow_function_expression<'a, V: VisitJs<'a>>(
visitor: &mut V,
@@ -2207,7 +2224,7 @@ pub mod walk_js {
);
visitor.visit_span(&it.span);
visitor.visit_formal_parameters(&it.params);
visitor.visit_function_body(&it.body);
visitor.visit_arrow_function_body(&it.body);
visitor.leave_scope();
visitor.leave_node(kind);
}
@@ -520,6 +520,11 @@ pub trait VisitJsMut<'a>: Sized {
walk_function_body(self, it);
}
#[inline]
fn visit_arrow_function_body(&mut self, it: &mut ArrowFunctionBody<'a>) {
walk_arrow_function_body(self, it);
}
#[inline]
fn visit_arrow_function_expression(&mut self, it: &mut ArrowFunctionExpression<'a>) {
walk_arrow_function_expression(self, it);
@@ -2282,6 +2287,20 @@ pub mod walk_js_mut {
visitor.leave_node(kind);
}
#[inline]
pub fn walk_arrow_function_body<'a, V: VisitJsMut<'a>>(
visitor: &mut V,
it: &mut ArrowFunctionBody<'a>,
) {
// No `AstType` for this type
match it {
ArrowFunctionBody::FunctionBody(it) => visitor.visit_function_body(it),
match_expression!(ArrowFunctionBody) => {
visitor.visit_expression(it.to_expression_mut())
}
}
}
#[inline]
pub fn walk_arrow_function_expression<'a, V: VisitJsMut<'a>>(
visitor: &mut V,
@@ -2301,7 +2320,7 @@ pub mod walk_js_mut {
);
visitor.visit_span(&mut it.span);
visitor.visit_formal_parameters(&mut it.params);
visitor.visit_function_body(&mut it.body);
visitor.visit_arrow_function_body(&mut it.body);
visitor.leave_scope();
visitor.leave_node(kind);
}
@@ -493,6 +493,11 @@ pub trait VisitMut<'a>: Sized {
walk_function_body(self, it);
}
#[inline]
fn visit_arrow_function_body(&mut self, it: &mut ArrowFunctionBody<'a>) {
walk_arrow_function_body(self, it);
}
#[inline]
fn visit_arrow_function_expression(&mut self, it: &mut ArrowFunctionExpression<'a>) {
walk_arrow_function_expression(self, it);
@@ -2616,6 +2621,20 @@ pub mod walk_mut {
visitor.leave_node(kind);
}
#[inline]
pub fn walk_arrow_function_body<'a, V: VisitMut<'a>>(
visitor: &mut V,
it: &mut ArrowFunctionBody<'a>,
) {
// No `AstType` for this type
match it {
ArrowFunctionBody::FunctionBody(it) => visitor.visit_function_body(it),
match_expression!(ArrowFunctionBody) => {
visitor.visit_expression(it.to_expression_mut())
}
}
}
#[inline]
pub fn walk_arrow_function_expression<'a, V: VisitMut<'a>>(
visitor: &mut V,
@@ -2641,7 +2660,7 @@ pub mod walk_mut {
if let Some(return_type) = &mut it.return_type {
visitor.visit_ts_type_annotation(return_type);
}
visitor.visit_function_body(&mut it.body);
visitor.visit_arrow_function_body(&mut it.body);
visitor.leave_scope();
visitor.leave_node(kind);
}
+7 -5
View File
@@ -1855,13 +1855,15 @@ impl GenExpr for ArrowFunctionExpression<'_> {
p.print_soft_space();
p.print_str("=>");
p.print_soft_space();
if self.expression {
if let Some(Statement::ExpressionStatement(stmt)) = &self.body.statements.first() {
match &self.body {
ArrowFunctionBody::FunctionBody(b) => {
b.print(p, body_ctx);
}
expression @ match_expression!(ArrowFunctionBody) => {
let expression = expression.to_expression();
p.start_of_arrow_expr = p.code_len();
stmt.expression.print_expr(p, Precedence::Comma, body_ctx);
expression.print_expr(p, Precedence::Comma, body_ctx);
}
} else {
self.body.print(p, body_ctx);
}
});
}
@@ -550,11 +550,22 @@ fn iife_call_may_have_side_effects<'a>(
call: &CallExpression<'a>,
ctx: &impl MayHaveSideEffectsContext<'a>,
) -> Option<bool> {
let (params, body) = match &call.callee {
let (params, body_may_have_side_effects) = match &call.callee {
Expression::FunctionExpression(f) if !f.r#async && !f.generator => {
(&f.params, f.body.as_deref()?)
let body = f.body.as_deref()?;
(&f.params, body.statements.iter().any(|stmt| stmt.may_have_side_effects(ctx)))
}
Expression::ArrowFunctionExpression(f) if !f.r#async => {
let body_may_have_side_effects = match &f.body {
ArrowFunctionBody::FunctionBody(b) => {
b.statements.iter().any(|stmt| stmt.may_have_side_effects(ctx))
}
match_expression!(ArrowFunctionBody) => {
f.get_expression().unwrap().may_have_side_effects(ctx)
}
};
(&f.params, body_may_have_side_effects)
}
Expression::ArrowFunctionExpression(f) if !f.r#async => (&f.params, &*f.body),
_ => return None,
};
@@ -570,7 +581,7 @@ fn iife_call_may_have_side_effects<'a>(
return Some(true);
}
Some(body.statements.iter().any(|stmt| stmt.may_have_side_effects(ctx)))
Some(body_may_have_side_effects)
}
// `PF` in <https://github.com/rollup/rollup/blob/master/src/ast/nodes/shared/knownGlobals.ts>
@@ -4846,17 +4846,41 @@ impl<'a> AstNode<'a, FunctionBody<'a>> {
}
}
impl<'a> AstNode<'a, ArrowFunctionBody<'a>> {
#[inline]
pub fn as_ast_nodes(&self) -> &AstNodes<'a> {
let parent = self.parent;
let node = match self.inner {
ArrowFunctionBody::FunctionBody(s) => {
AstNodes::FunctionBody(self.allocator.alloc(AstNode {
inner: s.as_ref(),
parent,
allocator: self.allocator,
following_span_start: self.following_span_start,
}))
}
it @ match_expression!(ArrowFunctionBody) => {
return self
.allocator
.alloc(AstNode {
inner: it.to_expression(),
parent,
allocator: self.allocator,
following_span_start: self.following_span_start,
})
.as_ast_nodes();
}
};
self.allocator.alloc(node)
}
}
impl<'a> AstNode<'a, ArrowFunctionExpression<'a>> {
#[inline]
pub fn node_id(&self) -> NodeId {
self.inner.node_id()
}
#[inline]
pub fn expression(&self) -> bool {
self.inner.expression
}
#[inline]
pub fn r#async(&self) -> bool {
self.inner.r#async
@@ -4906,10 +4930,10 @@ impl<'a> AstNode<'a, ArrowFunctionExpression<'a>> {
}
#[inline]
pub fn body(&self) -> &AstNode<'a, FunctionBody<'a>> {
pub fn body(&self) -> &AstNode<'a, ArrowFunctionBody<'a>> {
let following_span_start = self.following_span_start;
self.allocator.alloc(AstNode {
inner: self.inner.body.as_ref(),
inner: &self.inner.body,
allocator: self.allocator,
parent: AstNodes::ArrowFunctionExpression(transmute_self(self)),
following_span_start,
@@ -2473,6 +2473,37 @@ impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, FunctionBody<'a>> {
}
}
impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArrowFunctionBody<'a>> {
#[inline]
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
let allocator = self.allocator;
let parent = self.parent;
match self.inner {
ArrowFunctionBody::FunctionBody(inner) => {
allocator
.alloc(AstNode::<FunctionBody> {
inner,
parent,
allocator,
following_span_start: self.following_span_start,
})
.fmt(f);
}
it @ match_expression!(ArrowFunctionBody) => {
let inner = it.to_expression();
allocator
.alloc(AstNode::<'a, Expression> {
inner,
parent,
allocator,
following_span_start: self.following_span_start,
})
.fmt(f);
}
}
}
}
impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'a, ArrowFunctionExpression<'a>> {
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
let is_suppressed = f.comments().is_suppressed(self.span().start);
+11 -11
View File
@@ -139,17 +139,17 @@ impl<T: GetSpan> AstNode<'_, T> {
}
}
impl<'a> AstNode<'a, ExpressionStatement<'a>> {
/// Check if this ExpressionStatement is the body of an arrow function expression
///
/// Example:
/// `() => expression;`
/// ^^^^^^^^^^ This ExpressionStatement is the body of an arrow function
///
/// `() => { return expression; }`
/// ^^^^^^^^^^^^^^^^^^^^ This ExpressionStatement is NOT the body of an arrow function
pub fn is_arrow_function_body(&self) -> bool {
matches!(self.parent().parent(), AstNodes::ArrowFunctionExpression(arrow) if arrow.expression)
impl<'a> AstNode<'a, ArrowFunctionBody<'a>> {
/// Returns the concise expression body as an [`AstNode`].
pub fn as_expression(&self) -> Option<&AstNode<'a, Expression<'a>>> {
self.allocator
.alloc(self.inner.as_expression().map(|inner| AstNode {
inner,
parent: self.parent,
allocator: self.allocator,
following_span_start: self.following_span_start,
}))
.as_ref()
}
}
@@ -83,10 +83,7 @@ impl NeedsParentheses<'_> for AstNode<'_, IdentifierReference<'_>> {
}
// Needs at least one `satisfies`/`as` wrapper, with a statement right outside it
!ptr::eq(self.parent(), parent)
&& matches!(
parent, AstNodes::ExpressionStatement(stmt) if !stmt.is_arrow_function_body()
)
!ptr::eq(self.parent(), parent) && matches!(parent, AstNodes::ExpressionStatement(_))
};
match self.name.as_str() {
@@ -114,9 +111,10 @@ impl NeedsParentheses<'_> for AstNode<'_, IdentifierReference<'_>> {
let mut child_span = self.span;
for parent in self.ancestors() {
let dominated = match parent {
AstNodes::ExpressionStatement(s) => {
return is_computed_member_object && !s.is_arrow_function_body();
AstNodes::ExpressionStatement(_) => {
return is_computed_member_object;
}
AstNodes::ArrowFunctionExpression(_) => return false,
AstNodes::ForStatement(s) => {
return is_computed_member_object
&& matches!(&s.init, Some(init) if init.span() == child_span);
@@ -256,11 +254,10 @@ impl NeedsParentheses<'_> for AstNode<'_, StringLiteral<'_>> {
// https://github.com/prettier/prettier/blob/00146ea15c30e16ad6526893c735e35683192efc/src/language-js/parentheses/needs-parentheses.js#L594-L609
if let AstNodes::ExpressionStatement(stmt) = self.parent() {
// `() => "foo"`
!stmt.is_arrow_function_body()
&& matches!(
stmt.parent(),
AstNodes::Program(_) | AstNodes::FunctionBody(_) | AstNodes::BlockStatement(_)
)
matches!(
stmt.parent(),
AstNodes::Program(_) | AstNodes::FunctionBody(_) | AstNodes::BlockStatement(_)
)
} else {
false
}
@@ -457,18 +454,11 @@ impl NeedsParentheses<'_> for AstNode<'_, BinaryExpression<'_>> {
///
/// <https://github.com/prettier/prettier/issues/907#issuecomment-284304321>
fn is_in_for_initializer(expr: &AstNode<'_, BinaryExpression<'_>>) -> bool {
let mut ancestors = expr.ancestors();
let ancestors = expr.ancestors();
while let Some(parent) = ancestors.next() {
for parent in ancestors {
match parent {
AstNodes::ExpressionStatement(stmt) => {
if stmt.is_arrow_function_body() {
// Expression body: `() => expr`
// Skip `FunctionBody` and `ArrowFunctionExpression`
let skipped = ancestors.by_ref().nth(1);
debug_assert!(matches!(skipped, Some(AstNodes::ArrowFunctionExpression(_))));
continue;
}
// Block body: `() => { expr; }` or `function() { expr; }` - continue checking
// because for regular ForStatement, parens are still needed
if matches!(stmt.parent(), AstNodes::FunctionBody(_)) {
@@ -618,13 +608,7 @@ impl NeedsParentheses<'_> for AstNode<'_, AssignmentExpression<'_>> {
// Expression statements, only object destructuring needs parens:
// - `a = b` = no parens
// - `{ x } = obj` -> `({ x } = obj)` = needed to prevent parsing as block statement
// - `() => { x } = obj` -> `() => ({ x } = obj)` = needed in arrow function body
// - `() => a = b` -> `() => (a = b)` = also parens needed
AstNodes::ExpressionStatement(stmt) => {
if stmt.is_arrow_function_body() {
return true;
}
AstNodes::ExpressionStatement(_) => {
matches!(self.left, AssignmentTarget::ObjectAssignmentTarget(_))
&& is_first_in_statement(
self.span,
@@ -632,6 +616,13 @@ impl NeedsParentheses<'_> for AstNode<'_, AssignmentExpression<'_>> {
FirstInStatementMode::ExpressionStatementOrArrow,
)
}
// Concise arrow bodies always need parens,
// otherwise the `{` of an object pattern would parse as a block body
// and a bare `=` reads as the arrow's own body:
// - `() => { x } = obj` -> `() => ({ x } = obj)`
// - `() => a = b` -> `() => (a = b)`
#[expect(clippy::match_same_arms)]
AstNodes::ArrowFunctionExpression(_) => true,
// Sequence expressions, need to traverse up to find if we're in a for statement context:
// - `a = 1, b = 2` in for loops don't need parens
// - `(a = 1, b = 2)` elsewhere usually need parens
@@ -696,9 +687,9 @@ impl NeedsParentheses<'_> for AstNode<'_, SequenceExpression<'_>> {
match self.parent() {
AstNodes::ReturnStatement(_)
| AstNodes::ThrowStatement(_)
| AstNodes::ArrowFunctionExpression(_)
// There's a precedence for writing `x++, y++`
| AstNodes::ForStatement(_) => false,
AstNodes::ExpressionStatement(stmt) => !stmt.is_arrow_function_body(),
_ => true,
}
}
@@ -1125,26 +1116,25 @@ fn is_first_in_statement(
let is_not_first_iteration = index > 0;
match ancestor {
AstNodes::ExpressionStatement(stmt) => {
if stmt.is_arrow_function_body() {
if mode == FirstInStatementMode::ExpressionStatementOrArrow {
if is_not_first_iteration
&& matches!(
stmt.expression,
AstNodes::ExpressionStatement(_) => return true,
AstNodes::ArrowFunctionExpression(arrow) => {
if mode == FirstInStatementMode::ExpressionStatementOrArrow {
if is_not_first_iteration
&& matches!(
arrow.get_expression(),
Some(
Expression::SequenceExpression(_)
| Expression::AssignmentExpression(_)
)
{
// The original node doesn't need parens,
// because an ancestor requires parens.
break;
}
} else {
return false;
)
{
// The original node doesn't need parens,
// because an ancestor requires parens.
break;
}
return true;
}
return true;
return false;
}
AstNodes::StaticMemberExpression(_)
| AstNodes::TaggedTemplateExpression(_)
@@ -118,11 +118,8 @@ impl<'a, 'b> FormatJsArrowFunctionExpression<'a, 'b> {
);
});
let format_body = FormatMaybeCachedFunctionBody {
body,
expression: arrow.expression(),
mode: self.options.cache_mode,
};
let format_body =
FormatContentWithCacheMode::new(body.span(), body, self.options.cache_mode);
// With arrays, arrow self and objects, they have a natural line breaking strategy:
// Arrays and objects become blocks:
@@ -181,7 +178,7 @@ impl<'a, 'b> FormatJsArrowFunctionExpression<'a, 'b> {
if body_has_soft_line_break {
write!(f, [space(), format_body]);
} else {
let should_add_parens = arrow.expression && should_add_parens(body);
let should_add_parens = body.as_expression().is_some_and(should_add_parens);
let is_last_call_arg = matches!(
self.options.call_argument_layout,
@@ -273,11 +270,7 @@ impl<'a, 'b> ArrowFunctionLayout<'a, 'b> {
loop {
if is_non_grouped_or_grouped_last_argument
&& current.expression()
&& let Some(AstNodes::ExpressionStatement(expr_stmt)) =
current.body().statements().first().map(AstNode::<Statement>::as_ast_nodes)
&& let AstNodes::ArrowFunctionExpression(next) =
&expr_stmt.expression().as_ast_nodes()
&& let AstNodes::ArrowFunctionExpression(next) = current.body().as_ast_nodes()
{
should_break = should_break || Self::should_break_chain(current);
@@ -636,11 +629,11 @@ impl<'a> Format<'a, JsFormatContext<'a>> for ArrowChain<'a, '_> {
});
let format_tail_body_inner = format_with(|f| {
let format_tail_body = FormatMaybeCachedFunctionBody {
body: tail_body,
expression: tail.expression(),
mode: self.options.cache_mode,
};
let format_tail_body = FormatContentWithCacheMode::new(
tail_body.span(),
tail_body,
self.options.cache_mode,
);
// Ensure that the parens of sequence expressions end up on their own line if the
// body breaks
@@ -653,7 +646,7 @@ impl<'a> Format<'a, JsFormatContext<'a>> for ArrowChain<'a, '_> {
write!(f, [token("("), format_tail_body, token(")")]);
}
} else {
let should_add_parens = tail.expression && should_add_parens(tail_body);
let should_add_parens = tail_body.as_expression().is_some_and(should_add_parens);
if should_add_parens {
// Known divergence from Prettier: with a signature that exactly fills the line,
// Prettier breaks it because the hug layout's literal space is counted
@@ -761,18 +754,13 @@ fn has_own_line_comment_before_body<'a>(
f.comments().has_own_line_comment_in_range(signature_end, arrow.body().span().start)
}
fn should_add_parens(body: &AstNode<'_, FunctionBody<'_>>) -> bool {
let AstNodes::ExpressionStatement(stmt) = body.statements().first().unwrap().as_ast_nodes()
else {
unreachable!()
};
fn should_add_parens(expression: &AstNode<'_, Expression<'_>>) -> bool {
// Add parentheses to avoid confusion between `a => b ? c : d` and `a <= b ? c : d`
// but only if the body isn't an object/function or class expression because parentheses are always required in that
// case and added by the object expression itself
if matches!(&stmt.expression, Expression::ConditionalExpression(_)) {
if matches!(&**expression, Expression::ConditionalExpression(_)) {
!matches!(
ExpressionLeftSide::leftmost(stmt.expression()).as_ref(),
ExpressionLeftSide::leftmost(expression).as_ref(),
Expression::ObjectExpression(_)
| Expression::FunctionExpression(_)
| Expression::ClassExpression(_)
@@ -840,33 +828,6 @@ fn format_signature<'a, 'b>(
})
}
/// Formats a function body with additional caching depending on [`mode`](Self::mode).
pub struct FormatMaybeCachedFunctionBody<'a, 'b> {
/// The body to format.
pub body: &'b AstNode<'a, FunctionBody<'a>>,
/// Is the function body an arrow expression? i.e. `() => expr` instead of `() => {}`
pub expression: bool,
/// If the body should be cached or if the formatter should try to retrieve it from the cache.
pub mode: FunctionCacheMode,
}
impl<'a> Format<'a, JsFormatContext<'a>> for FormatMaybeCachedFunctionBody<'a, '_> {
fn fmt(&self, f: &mut JsFormatter<'_, 'a>) {
let content = format_with(|f| {
if self.expression
&& let AstNodes::ExpressionStatement(s) =
&self.body.statements().first().unwrap().as_ast_nodes()
{
return s.expression().fmt(f);
}
self.body.fmt(f);
});
FormatContentWithCacheMode::new(self.body.span, content, self.mode).fmt(f);
}
}
/// Format a sequence expression in an arrow function body that has a leading comment.
///
/// When an arrow function body is a sequence expression (e.g., `() => (a, b, c)`) and has
@@ -146,11 +146,11 @@ impl<'a, 'b> BinaryLikeExpression<'a, 'b> {
AstNodes::ReturnStatement(_)
| AstNodes::ThrowStatement(_)
| AstNodes::ForStatement(_)
| AstNodes::TemplateLiteral(_) => true,
| AstNodes::TemplateLiteral(_)
| AstNodes::ArrowFunctionExpression(_) => true,
AstNodes::JSXExpressionContainer(container) => {
matches!(container.parent(), AstNodes::JSXAttribute(_))
}
AstNodes::ExpressionStatement(statement) => statement.is_arrow_function_body(),
AstNodes::ConditionalExpression(conditional) => !matches!(
conditional.parent(),
AstNodes::ReturnStatement(_)
@@ -323,7 +323,7 @@ fn should_group_first_argument(
// fit entirely on the line or break fully. Only a single arrow
// with a block body can be grouped to collapse the braces.
Expression::ArrowFunctionExpression(arrow) => {
if arrow.expression {
if arrow.is_expression() {
return false;
}
}
@@ -1010,6 +1010,11 @@ fn is_commonjs_or_amd_call(
}
}
"define" => {
// The AMD layout only applies in statement position, matching Prettier's
// `parent.type === "ExpressionStatement"` check. A concise arrow body is not statement
// position: its parent is the `ArrowFunctionExpression`, so `() => define(...)` formats
// as a normal call. (oxc used to accept it, because concise bodies were wrapped in a
// synthetic `ExpressionStatement` before `ArrowFunctionBody` existed.)
let in_statement = matches!(call.parent(), AstNodes::ExpressionStatement(_));
if in_statement {
match arguments.len() {
@@ -1102,7 +1107,7 @@ fn is_react_hook_with_deps_array(
return false;
}
if callback.expression {
if callback.is_expression() {
return false;
}
@@ -1134,7 +1139,7 @@ fn is_decorated_function(argument: &AstNode<'_, Argument<'_>>) -> bool {
return false;
};
if arrow.expression {
if arrow.is_expression() {
return false;
}
+2 -8
View File
@@ -11,9 +11,7 @@ use crate::{
ast_nodes::AstNode,
format_args,
formatter::{Buffer, prelude::*, trivia::FormatLeadingComments},
print::{
arrow_function_expression::FormatMaybeCachedFunctionBody, semicolon::OptionalSemicolon,
},
print::semicolon::OptionalSemicolon,
write,
};
@@ -127,11 +125,7 @@ impl<'a, 'b> FormatFunction<'a, 'b> {
f,
[
space(),
FormatMaybeCachedFunctionBody {
body,
mode: self.options.cache_mode,
expression: false
}
FormatContentWithCacheMode::new(body.span, body, self.options.cache_mode)
]
);
}
+9 -20
View File
@@ -40,9 +40,8 @@ impl<'a> AnyJsxTagWithChildren<'a, '_> {
}
fn format_trailing_comments(&self, f: &mut JsFormatter<'_, 'a>) {
let trailing_comments = if let AstNodes::ArrowFunctionExpression(arrow) =
self.parent().parent().parent()
&& arrow.expression
let trailing_comments = if let AstNodes::ArrowFunctionExpression(arrow) = self.parent()
&& arrow.is_expression()
{
f.context().comments().comments_before(arrow.span.end)
} else if let AstNodes::ConditionalExpression(conditional) = self.parent() {
@@ -94,7 +93,8 @@ impl<'a> AnyJsxTagWithChildren<'a, '_> {
AstNodes::ArrayExpression(_)
| AstNodes::JSXAttribute(_)
| AstNodes::JSXExpressionContainer(_)
| AstNodes::ConditionalExpression(_) => WrapState::NoWrap,
| AstNodes::ConditionalExpression(_)
| AstNodes::ExpressionStatement(_) => WrapState::NoWrap,
AstNodes::StaticMemberExpression(member) => {
if member.optional {
WrapState::NoWrap
@@ -107,15 +107,6 @@ impl<'a> AnyJsxTagWithChildren<'a, '_> {
WrapState::NoWrap
}
AstNodes::NewExpression(new) if new.is_argument_span(self.span()) => WrapState::NoWrap,
AstNodes::ExpressionStatement(stmt) => {
// `() => <div></div>`
// ^^^^^^^^^^^
if stmt.is_arrow_function_body() {
WrapState::WrapOnBreak
} else {
WrapState::NoWrap
}
}
AstNodes::ComputedMemberExpression(member) => {
if member.optional {
WrapState::NoWrap
@@ -123,6 +114,9 @@ impl<'a> AnyJsxTagWithChildren<'a, '_> {
WrapState::WrapOnBreak
}
}
// Concise arrow body: `() => <div></div>`
#[expect(clippy::match_same_arms)]
AstNodes::ArrowFunctionExpression(_) => WrapState::WrapOnBreak,
_ => WrapState::WrapOnBreak,
}
}
@@ -252,14 +246,9 @@ impl<'a> Format<'a, JsFormatContext<'a>> for AnyJsxTagWithChildren<'a, '_> {
/// // As JSX attribute:
/// <Tooltip title={[].map(name => (<Foo>{name}</Foo>))} />;
/// ```
pub fn should_expand(mut parent: &AstNodes<'_>) -> bool {
if let AstNodes::ExpressionStatement(stmt) = parent {
// If the parent is a JSXExpressionContainer, we need to check its parent
// to determine if it should expand.
parent = stmt.grand_parent();
}
pub fn should_expand(parent: &AstNodes<'_>) -> bool {
let maybe_jsx_expression_container = match parent {
AstNodes::ArrowFunctionExpression(arrow) if arrow.expression => match arrow.parent() {
AstNodes::ArrowFunctionExpression(arrow) if arrow.is_expression() => match arrow.parent() {
AstNodes::CallExpression(call) => call.parent(),
_ => return false,
},
+2 -1
View File
@@ -443,7 +443,8 @@ impl<'a> FormatWrite<'a> for AstNode<'a, AwaitExpression<'a>> {
let mut await_expression = None;
for ancestor in self.ancestors().skip(1) {
match ancestor {
AstNodes::BlockStatement(_)
AstNodes::ArrowFunctionExpression(_)
| AstNodes::BlockStatement(_)
| AstNodes::FunctionBody(_)
| AstNodes::SwitchCase(_)
| AstNodes::Program(_)
+4 -3
View File
@@ -71,7 +71,7 @@ pub fn keeps_trailing_comment_inside_parens(expr: &Expression<'_>, gated: bool)
right => keeps_trailing_comment_inside_parens(right, false),
}
}
Expression::ArrowFunctionExpression(arrow) if arrow.expression => arrow
Expression::ArrowFunctionExpression(arrow) => arrow
.get_expression()
.is_some_and(|body| keeps_trailing_comment_inside_parens(body, true)),
_ => false,
@@ -91,9 +91,10 @@ pub fn write_trailing_comments_inside_parens<'a>(
is_sequence: bool,
) {
let parens_survive = match parent {
AstNodes::VariableDeclarator(_) | AstNodes::ReturnStatement(_) => true,
AstNodes::ArrowFunctionExpression(_)
| AstNodes::VariableDeclarator(_)
| AstNodes::ReturnStatement(_) => true,
AstNodes::AssignmentExpression(_) => is_sequence,
AstNodes::ExpressionStatement(statement) => statement.is_arrow_function_body(),
_ => false,
};
if parens_survive
@@ -11,10 +11,7 @@ use super::FormatWrite;
impl<'a> FormatWrite<'a> for AstNode<'a, SequenceExpression<'a>> {
fn write(&self, f: &mut JsFormatter<'_, 'a>) {
let is_arrow_body = matches!(
self.parent(),
AstNodes::ExpressionStatement(statement) if statement.is_arrow_function_body()
);
let is_arrow_body = matches!(self.parent(), AstNodes::ArrowFunctionExpression(_));
let format_inner = format_with(|f| {
let mut expressions = self.expressions().iter();
@@ -36,8 +33,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, SequenceExpression<'a>> {
});
if matches!(self.parent(), AstNodes::ForStatement(_))
|| (matches!(self.parent(), AstNodes::ExpressionStatement(statement)
if !statement.is_arrow_function_body()))
|| matches!(self.parent(), AstNodes::ExpressionStatement(_))
{
write!(f, [indent(&rest)]);
} else {
@@ -552,9 +552,13 @@ impl<'a> AssignmentLike<'a, '_> {
// Determine if the chain is eligible based on the following checks:
// 1. For variable declarators: only continue if this isn't the final assignment in the chain
(matches!(parent, AstNodes::VariableDeclarator(_)) && !right_is_tail) ||
// 2. For assignment expressions: continue unless this is the final assignment in an expression statement
// 2. For assignment expressions: continue unless this is the final assignment
// in an expression statement or concise arrow body.
matches!(parent, AstNodes::AssignmentExpression(parent_assignment)
if !right_is_tail || !matches!(parent_assignment.parent(), AstNodes::ExpressionStatement(_))
if !right_is_tail || !matches!(
parent_assignment.parent(),
AstNodes::ArrowFunctionExpression(_) | AstNodes::ExpressionStatement(_)
)
)
} else {
false
@@ -564,14 +568,11 @@ impl<'a> AssignmentLike<'a, '_> {
if right_is_tail {
match right_expression {
Expression::ArrowFunctionExpression(arrow) => {
if arrow.expression {
let Statement::ExpressionStatement(stmt) = &arrow.body.statements[0]
else {
unreachable!()
};
if matches!(&stmt.expression, Expression::ArrowFunctionExpression(_)) {
return Some(AssignmentLikeLayout::ChainTailArrowFunction);
}
if matches!(
arrow.get_expression(),
Some(Expression::ArrowFunctionExpression(_))
) {
return Some(AssignmentLikeLayout::ChainTailArrowFunction);
}
Some(AssignmentLikeLayout::ChainTail)
}
@@ -75,7 +75,7 @@ pub fn is_test_call_expression(call: &AstNode<CallExpression<'_>>) -> bool {
(function.params.parameters_count(), true)
}
Argument::ArrowFunctionExpression(arrow) => {
(arrow.params.parameters_count(), !arrow.expression)
(arrow.params.parameters_count(), !arrow.is_expression())
}
_ => return false,
};
@@ -93,7 +93,7 @@ impl<'a, 'b> MemberChain<'a, 'b> {
has_computed_property ||
is_factory(&identifier.name) ||
// If an identifier has a name that is shorter than the tab width, then we join it with the "head"
(matches!(parent.without_chain_expression(), AstNodes::ExpressionStatement(stmt) if !stmt.is_arrow_function_body())
(matches!(parent.without_chain_expression(), AstNodes::ExpressionStatement(_))
&& has_short_name(&identifier.name, f.options().indent_width.value()))
}
Expression::ThisExpression(_) => true,
@@ -31,12 +31,13 @@ const assignmentChain = () =>
thirdVeryLongIdentifier);
const amd = () =>
define("a-very-long-module-name", [
"first-long-dependency",
"second-long-dependency",
], () => {
return value;
});
define(
"a-very-long-module-name",
["first-long-dependency", "second-long-dependency"],
() => {
return value;
},
);
-------------------
{ printWidth: 100 }
@@ -0,0 +1,18 @@
// The AMD `define` layout (arguments hugged on one line, never broken) applies only in
// statement position, matching Prettier. A concise arrow body is not statement position.
// Statement position: AMD layout.
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
define("name", ["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Concise arrow body: NOT statement position, so the arguments break normally.
const d = () => define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Block body: the call is a statement again, so AMD layout applies.
const e = () => {
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
};
// `require` is not gated on statement position, so a concise body keeps its layout.
const r = () => require("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
@@ -0,0 +1,83 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// The AMD `define` layout (arguments hugged on one line, never broken) applies only in
// statement position, matching Prettier. A concise arrow body is not statement position.
// Statement position: AMD layout.
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
define("name", ["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Concise arrow body: NOT statement position, so the arguments break normally.
const d = () => define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Block body: the call is a statement again, so AMD layout applies.
const e = () => {
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
};
// `require` is not gated on statement position, so a concise body keeps its layout.
const r = () => require("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// The AMD `define` layout (arguments hugged on one line, never broken) applies only in
// statement position, matching Prettier. A concise arrow body is not statement position.
// Statement position: AMD layout.
define([
"aaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbb",
], () => {});
define("name", [
"aaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbb",
], () => {});
// Concise arrow body: NOT statement position, so the arguments break normally.
const d = () =>
define(
["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
() => {},
);
// Block body: the call is a statement again, so AMD layout applies.
const e = () => {
define([
"aaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbb",
], () => {});
};
// `require` is not gated on statement position, so a concise body keeps its layout.
const r = () =>
require("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
-------------------
{ printWidth: 100 }
-------------------
// The AMD `define` layout (arguments hugged on one line, never broken) applies only in
// statement position, matching Prettier. A concise arrow body is not statement position.
// Statement position: AMD layout.
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
define("name", ["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Concise arrow body: NOT statement position, so the arguments break normally.
const d = () => define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
// Block body: the call is a statement again, so AMD layout applies.
const e = () => {
define(["aaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"], () => {});
};
// `require` is not gated on statement position, so a concise body keeps its layout.
const r = () => require("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
===================== End =====================
@@ -0,0 +1,48 @@
// Own-line comment *inside* the source parentheses of a concise arrow body.
// The parens are not part of the AST, so the body's span starts at the expression:
// the comment is a leading comment of the body, and the body moves to its own line.
// (Distinct from `arrow-body-leading-comment-in-call.js`, where the comment sits
// *before* the opening paren.)
const array = () => (
// c
[1, 2, 3]
);
const object = () => (
// c
({ a: 1 })
);
const arrow = () => (
// c
() => 1
);
const template = () => (
// c
`tpl ${x}
more`
);
// Block comment on the same line as the expression stays hugged.
const inlineBlock = () => (
/* c */ [1, 2, 3]
);
// Chain tail body.
const chainTail = (a) => (b) => (
// c
[1, 2]
);
// As a call argument: the body breaks and the call gets a trailing comma.
foo(() => (
// c
[1, 2, 3]
));
foo((a) => (b) => (
// c
[1, 2]
));
@@ -0,0 +1,149 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
// Own-line comment *inside* the source parentheses of a concise arrow body.
// The parens are not part of the AST, so the body's span starts at the expression:
// the comment is a leading comment of the body, and the body moves to its own line.
// (Distinct from `arrow-body-leading-comment-in-call.js`, where the comment sits
// *before* the opening paren.)
const array = () => (
// c
[1, 2, 3]
);
const object = () => (
// c
({ a: 1 })
);
const arrow = () => (
// c
() => 1
);
const template = () => (
// c
`tpl ${x}
more`
);
// Block comment on the same line as the expression stays hugged.
const inlineBlock = () => (
/* c */ [1, 2, 3]
);
// Chain tail body.
const chainTail = (a) => (b) => (
// c
[1, 2]
);
// As a call argument: the body breaks and the call gets a trailing comma.
foo(() => (
// c
[1, 2, 3]
));
foo((a) => (b) => (
// c
[1, 2]
));
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
// Own-line comment *inside* the source parentheses of a concise arrow body.
// The parens are not part of the AST, so the body's span starts at the expression:
// the comment is a leading comment of the body, and the body moves to its own line.
// (Distinct from `arrow-body-leading-comment-in-call.js`, where the comment sits
// *before* the opening paren.)
const array = () =>
// c
[1, 2, 3];
const object = () =>
// c
({ a: 1 });
const arrow =
() =>
// c
() =>
1;
const template = () =>
// c
`tpl ${x}
more`;
// Block comment on the same line as the expression stays hugged.
const inlineBlock = () => /* c */ [1, 2, 3];
// Chain tail body.
const chainTail = (a) => (b) =>
// c
[1, 2];
// As a call argument: the body breaks and the call gets a trailing comma.
foo(() =>
// c
[1, 2, 3],
);
foo((a) => (b) =>
// c
[1, 2],
);
-------------------
{ printWidth: 100 }
-------------------
// Own-line comment *inside* the source parentheses of a concise arrow body.
// The parens are not part of the AST, so the body's span starts at the expression:
// the comment is a leading comment of the body, and the body moves to its own line.
// (Distinct from `arrow-body-leading-comment-in-call.js`, where the comment sits
// *before* the opening paren.)
const array = () =>
// c
[1, 2, 3];
const object = () =>
// c
({ a: 1 });
const arrow =
() =>
// c
() =>
1;
const template = () =>
// c
`tpl ${x}
more`;
// Block comment on the same line as the expression stays hugged.
const inlineBlock = () => /* c */ [1, 2, 3];
// Chain tail body.
const chainTail = (a) => (b) =>
// c
[1, 2];
// As a call argument: the body breaks and the call gets a trailing comma.
foo(() =>
// c
[1, 2, 3],
);
foo((a) => (b) =>
// c
[1, 2],
);
===================== End =====================
@@ -1,7 +1,10 @@
use oxc_allocator::{ArenaBox, CloneIn, GetAllocator};
use oxc_ast::ast::{
ArrowFunctionExpression, Expression, FormalParameter, Function, Statement, TSType,
TSTypeAnnotation, UnaryExpression,
use oxc_ast::{
ast::{
ArrowFunctionBody, ArrowFunctionExpression, Expression, FormalParameter, Function, TSType,
TSTypeAnnotation, UnaryExpression,
},
match_expression,
};
use oxc_span::SPAN;
@@ -116,16 +119,15 @@ impl<'a> IsolatedDeclarations<'a> {
return None;
}
if function.expression
&& let Some(Statement::ExpressionStatement(stmt)) = function.body.statements.first()
{
return self
.infer_type_from_expression(&stmt.expression)
.map(|type_annotation| TSTypeAnnotation::boxed(SPAN, type_annotation, self));
match &function.body {
ArrowFunctionBody::FunctionBody(function_body) => {
FunctionReturnType::infer(self, function_body)
.map(|type_annotation| TSTypeAnnotation::boxed(SPAN, type_annotation, self))
}
expression @ match_expression!(ArrowFunctionBody) => self
.infer_type_from_expression(expression.to_expression())
.map(|type_annotation| TSTypeAnnotation::boxed(SPAN, type_annotation, self)),
}
FunctionReturnType::infer(self, &function.body)
.map(|type_annotation| TSTypeAnnotation::boxed(SPAN, type_annotation, self))
}
pub(crate) fn is_need_to_infer_type_from_expression(expr: &Expression<'a>) -> bool {
@@ -48,7 +48,7 @@ impl<'a> IsolatedDeclarations<'a> {
if return_type.is_none() {
self.error(function_must_have_explicit_return_type(Span::new(
func.params.span.start,
func.body.span.start + 1,
func.body.span().start + 1,
)));
}
+4 -22
View File
@@ -784,29 +784,11 @@ pub fn is_default_this_binding<'a>(
_ => return true,
}
}
AstKind::ExpressionStatement(expr_stmt) => {
let Some(function_body) = outermost_paren_parent(parent, semantic) else {
return true;
};
let AstKind::FunctionBody(_) = function_body.kind() else {
return true;
};
let Some(arrow_func) = outermost_paren_parent(function_body, semantic) else {
return true;
};
let AstKind::ArrowFunctionExpression(expr) = arrow_func.kind() else {
return true;
};
if !expr.expression
|| expr_stmt.expression.span() != current_node.span()
|| !is_callee(arrow_func, semantic)
{
return true;
}
current_node = outermost_paren_parent(arrow_func, semantic).unwrap();
}
AstKind::ArrowFunctionExpression(expr) => {
if current_node.span() != expr.body.span || !is_callee(parent, semantic) {
if !expr.is_expression()
|| current_node.span() != expr.body.span()
|| !is_callee(parent, semantic)
{
return true;
}
current_node = outermost_paren_parent(parent, semantic).unwrap();
+4 -2
View File
@@ -1410,8 +1410,10 @@ impl RuleRunner for crate::rules::eslint::radix::Radix {
}
impl RuleRunner for crate::rules::eslint::require_await::RequireAwait {
const NODE_TYPES: Option<&AstTypesBitset> =
Some(&AstTypesBitset::from_types(&[AstType::FunctionBody]));
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
AstType::ArrowFunctionExpression,
AstType::FunctionBody,
]));
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
}
@@ -1,7 +1,8 @@
pub mod return_checker;
use std::borrow::Cow;
use serde::Deserialize;
use serde_json::Value;
use oxc_ast::{
AstKind,
ast::{Expression, FunctionBody, Statement},
@@ -10,10 +11,7 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;
use self::return_checker::{StatementReturnStatus, check_function_body, is_void_arrow_return};
use crate::{
AstNode,
ast_util::{get_enclosing_function, outermost_paren},
@@ -21,6 +19,10 @@ use crate::{
rule::{DefaultRuleConfig, Rule},
};
pub mod return_checker;
use self::return_checker::{StatementReturnStatus, check_function_body};
#[derive(Debug, Clone, Copy)]
enum MissingReturnHint {
SwitchWithoutDefault,
@@ -173,18 +175,25 @@ impl Rule for ArrayCallbackReturn {
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let (function_body, always_explicit_return, is_async_empty) = match node.kind() {
let (function_body, concise_expression, always_explicit_return, is_async_empty) = match node
.kind()
{
// Async, generator, and single expression arrow functions
// always have explicit return value
AstKind::ArrowFunctionExpression(arrow) => (
&arrow.body,
arrow.r#async || arrow.expression,
arrow.r#async && arrow.body.statements.is_empty(),
),
AstKind::ArrowFunctionExpression(arrow) => {
let function_body = arrow.get_function_body();
(
function_body,
arrow.get_expression(),
arrow.r#async || arrow.is_expression(),
arrow.r#async && function_body.is_some_and(|body| body.statements.is_empty()),
)
}
AstKind::Function(function) => {
if let Some(body) = &function.body {
(
body,
Some(body.as_ref()),
None,
function.r#async || function.generator,
function.r#async && body.statements.is_empty(),
)
@@ -207,10 +216,16 @@ impl Rule for ArrayCallbackReturn {
("forEach", false, _, _) => (),
("forEach", true, false, _) => {
if return_status.may_return_explicit() {
let return_spans = return_checker::get_explicit_return_spans(function_body)
.into_iter()
.next()
.unwrap_or_else(|| function_body.span());
let return_spans = concise_expression.map_or_else(
|| {
let function_body = function_body.unwrap();
return_checker::get_explicit_return_spans(function_body)
.into_iter()
.next()
.unwrap_or_else(|| function_body.span())
},
GetSpan::span,
);
ctx.diagnostic(expect_no_return(
&full_array_method_name(array_method),
@@ -224,10 +239,21 @@ impl Rule for ArrayCallbackReturn {
return;
}
if is_void_arrow_return(&function_body.statements) {
if concise_expression.is_some_and(Expression::is_void) {
return;
}
if let Some(expression) = concise_expression {
ctx.diagnostic(expect_void_return(
&full_array_method_name(array_method),
array_method_span,
expression.span(),
));
return;
}
let function_body = function_body.unwrap();
let (return_spans, has_void) =
return_checker::get_no_voided_return_spans(function_body, self.allow_void);
@@ -251,7 +277,7 @@ impl Rule for ArrayCallbackReturn {
ctx.diagnostic(expect_return(
&full_array_method_name(array_method),
array_method_span,
function_body,
function_body.unwrap(),
self.allow_implicit,
));
}
@@ -261,7 +287,7 @@ impl Rule for ArrayCallbackReturn {
ctx.diagnostic(expect_return(
&full_array_method_name(array_method),
array_method_span,
function_body,
function_body.unwrap(),
self.allow_implicit,
));
}
@@ -271,7 +297,7 @@ impl Rule for ArrayCallbackReturn {
ctx.diagnostic(expect_return(
&full_array_method_name(array_method),
array_method_span,
function_body,
function_body.unwrap(),
self.allow_implicit,
));
}
@@ -1,5 +1,4 @@
use oxc_allocator::ArenaVec;
use oxc_ast::ast::{ArrowFunctionExpression, Function, FunctionBody, ReturnStatement, Statement};
use oxc_ast::ast::{ArrowFunctionExpression, Function, FunctionBody, ReturnStatement};
use oxc_ast_visit::VisitJs;
use oxc_cfg::{
EdgeType, InstructionKind, ReturnInstructionKind,
@@ -230,23 +229,3 @@ impl VisitJs<'_> for ReturnStatementFinder {
fn visit_arrow_function_expression(&mut self, _it: &ArrowFunctionExpression<'_>) {}
}
pub fn is_void_arrow_return(statements: &ArenaVec<'_, Statement>) -> bool {
if statements.is_empty() {
return false;
}
if statements.len() > 1 {
return false;
}
let Some(statement_return) = statements.first() else {
return false;
};
let Statement::ExpressionStatement(expression_return) = statement_return else {
return false;
};
expression_return.expression.is_void()
}
@@ -193,7 +193,7 @@ impl Rule for ArrowBodyStyle {
return;
};
if arrow_func_expr.expression {
if arrow_func_expr.is_expression() {
self.run_for_arrow_expression(arrow_func_expr, ctx);
} else {
self.run_for_arrow_block(arrow_func_expr, node, ctx);
@@ -222,7 +222,7 @@ impl ArrowBodyStyle {
return;
}
ctx.diagnostic_with_fix(expected_block_diagnostic(arrow_func_expr.body.span), |fixer| {
ctx.diagnostic_with_fix(expected_block_diagnostic(arrow_func_expr.body.span()), |fixer| {
Self::fix_concise_to_block(arrow_func_expr, fixer, ctx)
});
}
@@ -238,7 +238,7 @@ impl ArrowBodyStyle {
ctx: &LintContext<'a>,
) {
let ArrowBodyStyle(mode, _config) = &self;
let body = &arrow_func_expr.body;
let body = arrow_func_expr.get_function_body().unwrap();
match mode {
Mode::Never => {
@@ -328,11 +328,11 @@ impl ArrowBodyStyle {
let inner_expr = expr.get_inner_expression();
if matches!(inner_expr, Expression::ObjectExpression(_)) {
let inner_text = ctx.source_range(inner_expr.span());
return fixer.replace(body.span, format!("{{return {inner_text}}}"));
return fixer.replace(body.span(), format!("{{return {inner_text}}}"));
}
// For all other expressions, just wrap in `{ return ... }`
fixer.replace(body.span, format!("{{return {expr_text}}}"))
fixer.replace(body.span(), format!("{{return {expr_text}}}"))
}
/// Fix: Convert block body to concise body
@@ -376,11 +376,11 @@ impl ArrowBodyStyle {
fix.push(fixer.delete_range(Span::sized(return_statement.span.end - 1, 1)));
}
fix.push(fixer.replace(
Span::sized(arrow_func_expr.body.span.start, 1),
Span::sized(arrow_func_expr.body.span().start, 1),
if needs_parens { "(" } else { "" },
));
fix.push(fixer.replace(
Span::sized(arrow_func_expr.body.span.end - 1, 1),
Span::sized(arrow_func_expr.body.span().end - 1, 1),
if needs_parens { ")" } else { "" },
));
@@ -6,7 +6,10 @@ use serde::{Deserialize, Serialize};
use oxc_ast::{
AstKind,
ast::{AccessorProperty, Expression, PropertyDefinition, TSAccessibility},
ast::{
AccessorProperty, ArrowFunctionBody, Expression, FunctionBody, PropertyDefinition,
TSAccessibility,
},
};
use oxc_ast_visit::Visit;
use oxc_diagnostics::OxcDiagnostic;
@@ -174,6 +177,11 @@ impl Rule for ClassMethodsUseThis {
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
enum FunctionLikeBody<'a, 'b> {
Function(&'b FunctionBody<'a>),
Arrow(&'b ArrowFunctionBody<'a>),
}
let function_pair = match node.kind() {
AstKind::AccessorProperty(accessor) => {
if accessor.r#static
@@ -190,11 +198,12 @@ impl Rule for ClassMethodsUseThis {
}
accessor.value.as_ref().and_then(|value| match value {
Expression::ArrowFunctionExpression(arrow_function) => {
Some((&arrow_function.body, &accessor.key))
}
Expression::FunctionExpression(function_expression) => {
Some((function_expression.body.as_ref()?, &accessor.key))
Some((FunctionLikeBody::Arrow(&arrow_function.body), &accessor.key))
}
Expression::FunctionExpression(function_expression) => Some((
FunctionLikeBody::Function(function_expression.body.as_deref()?),
&accessor.key,
)),
_ => None,
})
}
@@ -212,7 +221,7 @@ impl Rule for ClassMethodsUseThis {
return;
}
let Some(function_body) = method_definition.value.body.as_ref() else { return };
Some((function_body, &method_definition.key))
Some((FunctionLikeBody::Function(function_body), &method_definition.key))
}
AstKind::PropertyDefinition(property_definition) => {
if property_definition.r#static
@@ -228,12 +237,14 @@ impl Rule for ClassMethodsUseThis {
return;
}
property_definition.value.as_ref().and_then(|value| match value {
Expression::ArrowFunctionExpression(arrow_function) => {
Some((&arrow_function.body, &property_definition.key))
}
Expression::FunctionExpression(function_expression) => {
Some((function_expression.body.as_ref()?, &property_definition.key))
}
Expression::ArrowFunctionExpression(arrow_function) => Some((
FunctionLikeBody::Arrow(&arrow_function.body),
&property_definition.key,
)),
Expression::FunctionExpression(function_expression) => Some((
FunctionLikeBody::Function(function_expression.body.as_deref()?),
&property_definition.key,
)),
_ => None,
})
}
@@ -248,7 +259,10 @@ impl Rule for ClassMethodsUseThis {
return;
}
let mut finder = ThisFinder::new();
finder.visit_function_body(function_body);
match function_body {
FunctionLikeBody::Function(body) => finder.visit_function_body(body),
FunctionLikeBody::Arrow(body) => finder.visit_arrow_function_body(body),
}
if !finder.has_this {
ctx.diagnostic(class_methods_use_this_diagnostic(name.span(), name.name()));
}
@@ -340,7 +340,7 @@ impl FuncStyle {
fn arrow_contains_this_or_super(arrow: &ArrowFunctionExpression) -> bool {
let mut finder = ThisOrSuperFinder { found: false };
finder.visit_function_body(&arrow.body);
finder.visit_arrow_function_body(&arrow.body);
finder.found
}
@@ -221,7 +221,7 @@ impl GetterReturn {
// const fn = () => 1;
// ```
if let AstKind::ArrowFunctionExpression(arrow_expr) = node.kind() {
if arrow_expr.expression {
if arrow_expr.is_expression() {
break 'returns true;
}
// If the signature of function supports the return of the `undefined` value,
@@ -56,16 +56,6 @@ impl Rule for NoNew {
let Some(node) = outermost_paren_parent(node, ctx) else { return };
if matches!(node.kind(), AstKind::ExpressionStatement(_)) {
let mut ancestors = ctx
.nodes()
.ancestors(node.id())
.filter(|a| !matches!(a.kind(), AstKind::ParenthesizedExpression(_)));
ancestors.next(); // skip `FunctionBody`
if let Some(node) = ancestors.next()
&& matches!(node.kind(), AstKind::ArrowFunctionExpression(e) if e.expression)
{
return;
}
let span = Span::new(expr.span.start, expr.callee.span().end);
ctx.diagnostic(no_new_diagnostic(span));
}
@@ -5,7 +5,7 @@ use oxc_ast::{
use oxc_ast_visit::VisitJs;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -155,10 +155,10 @@ impl Rule for NoPromiseExecutorReturn {
if self.allow_void && expr.get_inner_expression().is_void() {
return;
}
ctx.diagnostic(no_promise_executor_return_diagnostic(arrow.body.span));
ctx.diagnostic(no_promise_executor_return_diagnostic(arrow.body.span()));
} else {
// Arrow function with block body: check for return statements
self.check_function_body(&arrow.body, ctx);
self.check_function_body(arrow.get_function_body().unwrap(), ctx);
}
}
Expression::FunctionExpression(func) => {
@@ -105,7 +105,7 @@ impl Rule for NoReturnAssign {
"Return statements should not contain an assignment.",
));
}
AstKind::ArrowFunctionExpression(arrow) if arrow.expression => {
AstKind::ArrowFunctionExpression(arrow) if arrow.is_expression() => {
ctx.diagnostic(no_return_assign_diagnostic(
assign.span(),
"Arrow functions should not return an assignment.",
@@ -163,14 +163,11 @@ impl NoSequences {
}
// Check for ArrowFunctionExpression body
// In oxc's AST: SequenceExpr -> ParenthesizedExpr -> ExpressionStatement -> FunctionBody -> ArrowFunctionExpr
// In oxc's AST: SequenceExpr -> ParenthesizedExpr -> ArrowFunctionExpr
// Arrow body needs double parentheses because the first layer is syntactically required
// (otherwise `() => a, b` would be parsed as `(() => a), b`)
let is_arrow_body = matches!(cur.kind(), AstKind::ExpressionStatement(_))
&& matches!(
nodes.parent_node(nodes.parent_node(cur.id()).id()).kind(),
AstKind::ArrowFunctionExpression(arrow) if arrow.expression
);
let is_arrow_body =
matches!(cur.kind(), AstKind::ArrowFunctionExpression(arrow) if arrow.is_expression());
if is_arrow_body {
// Arrow body needs at least 2 levels of parentheses
@@ -99,7 +99,7 @@ fn is_parent_arrow_function_expression<'a>(node: &AstNode<'a>, ctx: &LintContext
return false;
};
arrow_function_expression.expression
arrow_function_expression.is_expression()
}
impl NoUnusedExpressions {
@@ -250,17 +250,7 @@ fn is_value_context(parent: &AstNode, child: &AstNode, semantic: &Semantic<'_>)
// The right-hand side of an assignment is always in a value context (being read for assignment)
assign_expr.right.span().contains_inclusive(child.span())
}
AstKind::ExpressionStatement(_) => {
let grandparent = semantic.nodes().parent_node(parent.id());
if let AstKind::FunctionBody(_) = grandparent.kind()
&& let AstKind::ArrowFunctionExpression(arrow) =
semantic.nodes().parent_kind(grandparent.id())
&& arrow.expression
{
return true;
}
false
}
AstKind::ArrowFunctionExpression(arrow) => arrow.is_expression(),
AstKind::ParenthesizedExpression(_)
| AstKind::TSAsExpression(_)
| AstKind::TSSatisfiesExpression(_)
@@ -579,8 +579,7 @@ impl<'a> Symbol<'_, 'a> {
}
AstKind::Function(f) if f.is_declaration() => break,
// implicit return in an arrow function
AstKind::ArrowFunctionExpression(f) if f.expression =>
{
AstKind::ArrowFunctionExpression(f) if f.is_expression() => {
return false;
}
AstKind::ReturnStatement(_) => {
@@ -664,12 +663,7 @@ impl<'a> Symbol<'_, 'a> {
AstKind::ReturnStatement(_) => return true,
AstKind::ExpressionStatement(_) => {}
AstKind::Function(f) if f.is_expression() => {}
// note: intentionally not using
// ArrowFunctionExpression::get_expression since it returns
// `Some` even if
// 1. there are more than one statements
// 2. the expression is surrounded by braces
AstKind::ArrowFunctionExpression(f) if f.expression => {
AstKind::ArrowFunctionExpression(f) if f.is_expression() => {
return true;
}
x if x.is_statement() => return false,
@@ -256,7 +256,7 @@ fn make_function_shorthand<'a>(
}
Either::Right(func) => {
let next_token = ctx
.find_prev_token_from(func.body.span.start, "=>")
.find_prev_token_from(func.body.span().start, "=>")
.map(|offset| offset + 2 /* "=>".len() */);
let Some(arrow_token) = next_token else {
return fixer.noop();
@@ -373,7 +373,7 @@ fn check_longform_methods<'a>(
if rule.avoid_explicit_return_arrows
&& let Expression::ArrowFunctionExpression(func) = &property.value.without_parentheses()
&& !arrow_uses_lexical_identifiers(ctx, func)
&& !func.expression
&& !func.is_expression()
{
make_function_shorthand(ctx, property, Either::Right(func));
}
@@ -1,8 +1,8 @@
use oxc_ast::{
AstKind,
ast::{
ArrowFunctionExpression, AwaitExpression, ForOfStatement, Function, FunctionType,
MethodDefinition, ObjectProperty, PropertyKey,
ArrowFunctionExpression, AwaitExpression, ForOfStatement, Function, FunctionBody,
FunctionType, MethodDefinition, ObjectProperty, PropertyKey,
},
};
use oxc_ast_visit::{VisitJs, walk_js::walk_for_of_statement};
@@ -87,9 +87,31 @@ declare_oxc_lint!(
impl Rule for RequireAwait {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::FunctionBody(body) = node.kind() else {
match node.kind() {
AstKind::ArrowFunctionExpression(func) => Self::check_arrow_expression(func, ctx),
AstKind::FunctionBody(body) => Self::check_function_body(body, node, ctx),
_ => {}
}
}
}
impl RequireAwait {
fn check_arrow_expression(func: &ArrowFunctionExpression, ctx: &LintContext) {
if !func.r#async {
return;
};
}
let Some(expression) = func.get_expression() else { return };
let mut finder = AwaitFinder { found: false };
finder.visit_expression(expression);
if !finder.found {
let need_delete_span = get_delete_span(ctx, func.span.start);
ctx.diagnostic_with_dangerous_fix(require_await_diagnostic(func.span), |fixer| {
fixer.delete_range(need_delete_span)
});
}
}
fn check_function_body<'a>(body: &FunctionBody<'a>, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if body.is_empty() {
return;
}
@@ -228,20 +228,19 @@ impl Rule for PreferEndingWithAnExpect {
impl PreferEndingWithAnExpect {
fn is_valid_last_statement<'a>(
&self,
function_body: &'a FunctionBody<'a>,
function_body: FunctionArgument<'a>,
ctx: &LintContext<'a>,
) -> bool {
let Some(statement) = function_body.statements.last() else {
return false;
};
let statement_expression = match statement {
Statement::ExpressionStatement(expression_statement) => {
let statement_expression = match function_body {
FunctionArgument::Expression(expression) => expression,
FunctionArgument::Body(function_body) => {
let Some(Statement::ExpressionStatement(expression_statement)) =
function_body.statements.last()
else {
return false;
};
&expression_statement.expression
}
_ => {
return false;
}
};
let call_expression = match &statement_expression {
@@ -274,10 +273,21 @@ impl PreferEndingWithAnExpect {
}
}
fn function_argument<'a>(argument: &'a Argument<'a>) -> Option<&'a FunctionBody<'a>> {
#[derive(Clone, Copy)]
enum FunctionArgument<'a> {
Body(&'a FunctionBody<'a>),
Expression(&'a Expression<'a>),
}
fn function_argument<'a>(argument: &'a Argument<'a>) -> Option<FunctionArgument<'a>> {
match argument {
Argument::ArrowFunctionExpression(array_fn) => Some(&array_fn.body),
Argument::FunctionExpression(function) => function.body.as_ref().map(AsRef::as_ref),
Argument::ArrowFunctionExpression(array_fn) => array_fn
.get_expression()
.map(FunctionArgument::Expression)
.or_else(|| array_fn.get_function_body().map(FunctionArgument::Body)),
Argument::FunctionExpression(function) => {
function.body.as_deref().map(FunctionArgument::Body)
}
_ => None,
}
}
@@ -1,4 +1,4 @@
use oxc_ast::ast::{Expression, FunctionBody};
use oxc_ast::ast::Expression;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
@@ -12,7 +12,7 @@ use crate::{
fixer::RuleFix,
rule::{DefaultRuleConfig, Rule},
rules::shared::prefer_expect_assertions::{
DOCUMENTATION, PreferExpectAssertionsConfig, PreferExpectAssertionsRuleImpl,
CallbackBody, DOCUMENTATION, PreferExpectAssertionsConfig, PreferExpectAssertionsRuleImpl,
resolve_expect_local_name, should_check,
},
utils::collect_possible_jest_call_node,
@@ -98,7 +98,7 @@ impl PreferExpectAssertionsRuleImpl for PreferExpectAssertions {
ctx.diagnostic_with_suggestions(have_expect_assertions(span, prefix), suggestions);
}
fn should_check_node(&self, body: &FunctionBody<'_>, is_async: bool, prefix: &str) -> bool {
fn should_check_node(&self, body: CallbackBody<'_>, is_async: bool, prefix: &str) -> bool {
should_check(self.0.as_ref(), body, is_async, prefix)
}
}
@@ -1045,6 +1045,10 @@ fn test() {
});",
Some(serde_json::json!([{ "onlyFunctionsWithExpectInCallback": true }])),
),
(
r#"test("x", () => promise.then(() => expect(x).toBe(y)))"#,
Some(serde_json::json!([{ "onlyFunctionsWithExpectInCallback": true }])),
),
(
"it('responds ok', function () {
client.get('/user', response => {
@@ -59,20 +59,11 @@ impl Rule for MissingThrow {
impl MissingThrow {
fn has_missing_throw<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
if !matches!(ctx.nodes().parent_kind(node.id()), AstKind::ExpressionStatement(_)) {
return false;
match ctx.nodes().parent_kind(node.id()) {
AstKind::ArrowFunctionExpression(arrow) => !arrow.is_expression(),
AstKind::ExpressionStatement(_) => true,
_ => false,
}
let expression_statement_id = ctx.nodes().parent_id(node.id());
let expression_statement_parent_id = ctx.nodes().parent_id(expression_statement_id);
if !matches!(ctx.nodes().kind(expression_statement_parent_id), AstKind::FunctionBody(_)) {
return true;
}
!matches!(
ctx.nodes().parent_kind(expression_statement_parent_id),
AstKind::ArrowFunctionExpression(arrow_expr) if arrow_expr.expression
)
}
}
@@ -589,31 +589,26 @@ where
F: FnMut(Spread<'a, '_>),
{
fn iter_spreads(ctx: &'ctx LintContext<'a>, map_cb: &Expression<'a>, cb: F) -> Option<Self> {
let (mut visitor, body) = match map_cb {
let mut visitor = match map_cb {
Expression::ArrowFunctionExpression(f) => {
let v = Self {
let mut visitor = Self {
ctx,
cb,
cb_scope_id: f.scope_id(),
is_in_return: f.expression,
return_span: f.expression.then(|| f.body.span()),
is_in_return: f.is_expression(),
return_span: f.is_expression().then(|| f.body.span()),
};
(v, f.body.as_ref())
visitor.visit_arrow_function_body(&f.body);
return Some(visitor);
}
Expression::FunctionExpression(f) => {
let v = Self {
ctx,
cb,
cb_scope_id: f.scope_id(),
is_in_return: false,
return_span: None,
};
let body = f.body.as_ref().map(AsRef::as_ref)?;
(v, body)
Self { ctx, cb, cb_scope_id: f.scope_id(), is_in_return: false, return_span: None }
}
_ => unreachable!(),
};
let Expression::FunctionExpression(function) = map_cb else { unreachable!() };
let body = function.body.as_deref()?;
visitor.visit_function_body(body);
Some(visitor)
}
@@ -223,7 +223,7 @@ impl Rule for AlwaysReturn {
fn is_function_with_block_statement(node: &AstNode) -> bool {
matches!(node.kind(), AstKind::Function(function_expr) if !function_expr.declare) // e.g. function () {}
|| matches!(node.kind(), AstKind::ArrowFunctionExpression(arrow_func_expr) if !arrow_func_expr.expression) // e.g. () => {}
|| matches!(node.kind(), AstKind::ArrowFunctionExpression(arrow_func_expr) if !arrow_func_expr.is_expression()) // e.g. () => {}
}
fn is_member_call(node: &AstNode, member_name: &str) -> bool {
@@ -383,20 +383,30 @@ fn has_ignored_assignment(
node: &AstNode,
ignore_assignment_variable: &FxHashSet<Cow<str>>,
) -> bool {
let is_ignored_assignment = |expression: &Expression| {
let Expression::AssignmentExpression(assignment_expr) = expression else { return false };
let object_name = get_root_object_name(&assignment_expr.left);
object_name.is_some_and(|name| ignore_assignment_variable.contains(name))
};
if let AstKind::ArrowFunctionExpression(arrow) = node.kind()
&& let Some(expression) = arrow.get_expression()
{
return is_ignored_assignment(expression);
}
let body_statements = match node.kind() {
AstKind::Function(func_expr) => func_expr.body.as_ref().map(|body| &body.statements),
AstKind::ArrowFunctionExpression(arrow_func_expr) => Some(&arrow_func_expr.body.statements),
AstKind::ArrowFunctionExpression(arrow_func_expr) => {
arrow_func_expr.get_function_body().map(|body| &body.statements)
}
_ => None,
};
body_statements.is_some_and(|statements| {
statements.iter().any(|it| match it {
Statement::ExpressionStatement(expression) => match &expression.expression {
Expression::AssignmentExpression(assignment_expr) => {
let object_name = get_root_object_name(&assignment_expr.left);
object_name.is_some_and(|name| ignore_assignment_variable.contains(name))
}
_ => false,
},
Statement::ExpressionStatement(expression) => {
is_ignored_assignment(&expression.expression)
}
_ => false,
})
})
@@ -293,7 +293,7 @@ fn is_arrow_function_expression_return(node: &AstNode, ctx: &LintContext) -> boo
}
if let AstKind::ArrowFunctionExpression(arrow_func) = ctx.nodes().parent_kind(parent.id()) {
return arrow_func.expression;
return arrow_func.is_expression();
}
false
@@ -1,4 +1,3 @@
use oxc_allocator::ArenaBox;
use oxc_ast::{
AstKind,
ast::{Expression, FunctionBody, Statement},
@@ -68,7 +67,9 @@ impl Rule for NoReturnInFinally {
};
match arg_expr {
Expression::ArrowFunctionExpression(arrow_expr) => {
find_return_statement(&arrow_expr.body, ctx);
if let Some(body) = arrow_expr.get_function_body() {
find_return_statement(body, ctx);
}
}
Expression::FunctionExpression(func_expr) => {
let Some(func_body) = &func_expr.body else {
@@ -82,7 +83,7 @@ impl Rule for NoReturnInFinally {
}
}
fn find_return_statement<'a>(func_body: &ArenaBox<'_, FunctionBody<'a>>, ctx: &LintContext<'a>) {
fn find_return_statement<'a>(func_body: &FunctionBody<'a>, ctx: &LintContext<'a>) {
let Some(return_stmt) =
func_body.statements.iter().find(|stmt| matches!(stmt, Statement::ReturnStatement(_)))
else {
@@ -8,7 +8,7 @@ use oxc_ast::{
AstKind,
ast::{
ArrowFunctionExpression, CallExpression, Expression, Function, FunctionBody,
ReturnStatement, Statement,
ReturnStatement,
},
};
use oxc_ast_visit::VisitJs;
@@ -222,14 +222,12 @@ fn check_arrow_cb_arg<'a>(
allow_reject: bool,
arrow_expr: &ArrowFunctionExpression<'a>,
) {
if arrow_expr.expression {
if let Some(Statement::ExpressionStatement(expr_stmt)) = arrow_expr.body.statements.first()
&& let Expression::CallExpression(call_expr) = &expr_stmt.expression
{
if arrow_expr.is_expression() {
if let Some(Expression::CallExpression(call_expr)) = arrow_expr.get_expression() {
check_for_resolve_reject(ctx, allow_reject, call_expr);
}
} else {
check_function_body(ctx, allow_reject, &arrow_expr.body);
check_function_body(ctx, allow_reject, arrow_expr.get_function_body().unwrap());
}
}
@@ -9,15 +9,18 @@ use serde::Deserialize;
use oxc_ast::{
AstKind, AstType,
ast::{
Argument, ArrayExpressionElement, ArrowFunctionExpression, BindingPattern, CallExpression,
ChainElement, Expression, FormalParameters, Function, FunctionBody, IdentifierReference,
StaticMemberExpression, VariableDeclarationKind, VariableDeclarator,
Argument, ArrayExpressionElement, ArrowFunctionBody, ArrowFunctionExpression,
BindingPattern, CallExpression, ChainElement, Expression, FormalParameters, Function,
FunctionBody, IdentifierReference, StaticMemberExpression, VariableDeclarationKind,
VariableDeclarator,
},
match_expression,
};
use oxc_ast_visit::{
VisitJs,
walk_js::{walk_arrow_function_expression, walk_function, walk_function_body},
walk_js::{
walk_arrow_function_body, walk_arrow_function_expression, walk_function, walk_function_body,
},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
@@ -475,7 +478,7 @@ impl Rule for ExhaustiveDeps {
found_dependencies.visit_formal_parameters(callback_node.parameters());
if let Some(function_body) = callback_node.body() {
found_dependencies.visit_function_body(function_body);
function_body.visit(&mut found_dependencies);
}
(found_dependencies.found_dependencies, found_dependencies.refs_inside_cleanups)
@@ -515,7 +518,7 @@ impl Rule for ExhaustiveDeps {
let contains_set_state_call = {
let mut finder = ExhaustiveDepsVisitor::new(ctx.semantic());
if let Some(function_body) = callback_node.body() {
finder.visit_function_body_root(function_body);
function_body.visit_root(&mut finder);
}
finder.set_state_call
};
@@ -793,6 +796,28 @@ enum CallbackNode<'a> {
ArrowFunction(&'a ArrowFunctionExpression<'a>),
}
#[derive(Clone, Copy)]
enum CallbackBody<'a, 'b> {
Function(&'b FunctionBody<'a>),
Arrow(&'b ArrowFunctionBody<'a>),
}
impl<'a> CallbackBody<'a, '_> {
fn visit(self, visitor: &mut ExhaustiveDepsVisitor<'a, '_>) {
match self {
Self::Function(body) => visitor.visit_function_body(body),
Self::Arrow(body) => visitor.visit_arrow_function_body(body),
}
}
fn visit_root(self, visitor: &mut ExhaustiveDepsVisitor<'a, '_>) {
match self {
Self::Function(body) => walk_function_body(visitor, body),
Self::Arrow(body) => walk_arrow_function_body(visitor, body),
}
}
}
impl<'a> CallbackNode<'a> {
fn is_async(&self) -> bool {
match self {
@@ -808,10 +833,10 @@ impl<'a> CallbackNode<'a> {
}
}
fn body(&self) -> Option<&FunctionBody<'a>> {
fn body(&self) -> Option<CallbackBody<'a, '_>> {
match self {
CallbackNode::Function(func) => func.body.as_deref(),
CallbackNode::ArrowFunction(func) => Some(&func.body),
CallbackNode::Function(func) => func.body.as_deref().map(CallbackBody::Function),
CallbackNode::ArrowFunction(func) => Some(CallbackBody::Arrow(&func.body)),
}
}
}
@@ -1082,8 +1107,12 @@ fn is_stable_value<'a, 'b>(
{
// if the variables is a function, check whether the function is stable
let function_body = match init.get_inner_expression() {
Expression::ArrowFunctionExpression(arrow_func) => Some(&arrow_func.body),
Expression::FunctionExpression(func) => func.body.as_ref(),
Expression::ArrowFunctionExpression(arrow_func) => {
Some(CallbackBody::Arrow(&arrow_func.body))
}
Expression::FunctionExpression(func) => {
func.body.as_deref().map(CallbackBody::Function)
}
_ => None,
};
if let Some(function_body) = function_body {
@@ -1167,8 +1196,10 @@ fn is_stable_value<'a, 'b>(
}
AstKind::ArrowFunctionExpression(_) | AstKind::Function(_) => {
let function_body = match node.kind() {
AstKind::ArrowFunctionExpression(arrow_func) => Some(&arrow_func.body),
AstKind::Function(func) => func.body.as_ref(),
AstKind::ArrowFunctionExpression(arrow_func) => {
Some(CallbackBody::Arrow(&arrow_func.body))
}
AstKind::Function(func) => func.body.as_deref().map(CallbackBody::Function),
_ => unreachable!(),
};
@@ -1181,7 +1212,7 @@ fn is_stable_value<'a, 'b>(
}
fn is_function_stable<'a, 'b>(
function_body: &'b FunctionBody<'a>,
function_body: CallbackBody<'a, 'b>,
function_symbol_id: Option<SymbolId>,
ctx: &'b LintContext<'a>,
component_scope_id: ScopeId,
@@ -1189,7 +1220,7 @@ fn is_function_stable<'a, 'b>(
) -> bool {
let deps = {
let mut collector = ExhaustiveDepsVisitor::new(ctx.semantic());
collector.visit_function_body(function_body);
function_body.visit(&mut collector);
collector.found_dependencies
};
@@ -1257,10 +1288,6 @@ impl<'a, 'b> ExhaustiveDepsVisitor<'a, 'b> {
}
}
fn visit_function_body_root(&mut self, function_body: &FunctionBody<'a>) {
walk_function_body(self, function_body);
}
fn iter_destructure_bindings<F>(&self, mut cb: F) -> Option<bool>
where
F: FnMut(std::borrow::Cow<'a, str>),
@@ -562,8 +562,8 @@ mod fix {
.return_type
.as_ref()
.map_or_else(String::new, |item| ctx.source_range(item.span).to_string()),
body: ctx.source_range(arrow.body.span).to_string(),
expression_body: arrow.expression,
body: ctx.source_range(arrow.body.span()).to_string(),
expression_body: arrow.is_expression(),
}
}
}
@@ -6,13 +6,12 @@ use oxc_ast::{
AstKind,
ast::{
ArrowFunctionExpression, Expression, JSXAttributeName, JSXAttributeValue, JSXElementName,
JSXExpression, JSXMemberExpression, JSXMemberExpressionObject, Statement,
StaticMemberExpression,
JSXExpression, JSXMemberExpression, JSXMemberExpressionObject, StaticMemberExpression,
},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};
use oxc_str::{CompactStr, Ident};
use schemars::{
JsonSchema, SchemaGenerator,
@@ -304,7 +303,7 @@ impl Rule for JsxHandlerNames {
{
(Some(Cow::Borrowed(name.as_str())), span, is_props_handler)
} else {
(None, arrow_function.body.span, false)
(None, arrow_function.body.span(), false)
}
}
JSXExpression::Identifier(ident) => {
@@ -387,10 +386,7 @@ impl JsxHandlerNames {
/// true if the expression is in the form of "foo.bar" or "() => foo.bar()"
/// like event handler methods in class components.
fn is_member_expression_callee(arrow_function: &ArrowFunctionExpression<'_>) -> bool {
let Some(Statement::ExpressionStatement(stmt)) = arrow_function.body.statements.first() else {
return false;
};
let Expression::CallExpression(callee_expr) = &stmt.expression else {
let Some(Expression::CallExpression(callee_expr)) = arrow_function.get_expression() else {
return false;
};
callee_expr.callee.is_member_expression()
@@ -475,16 +471,13 @@ fn test_normalize_handler_name() {
fn get_event_handler_name_from_arrow_function<'a>(
arrow_function: &'a ArrowFunctionExpression<'a>,
) -> Option<(Ident<'a>, Span, bool)> {
if !arrow_function.expression {
if !arrow_function.is_expression() {
// Ignore arrow functions with block bodies like `() => { this.handleChange() }`.
// The event handler name can only be extracted from arrow functions
// with a single expression body, such as `() => this.handleChange()`.
return None;
}
let Some(Statement::ExpressionStatement(stmt)) = arrow_function.body.statements.first() else {
return None;
};
let Expression::CallExpression(call_expr) = &stmt.expression else {
let Some(Expression::CallExpression(call_expr)) = arrow_function.get_expression() else {
return None;
};
+2 -5
View File
@@ -10,7 +10,7 @@ use oxc_ast::{
ast::{
ArrayExpression, ArrayExpressionElement, CallExpression, Expression, IdentifierReference,
JSXAttributeItem, JSXAttributeName, JSXAttributeValue, JSXChild, JSXElement, JSXExpression,
JSXFragment, Statement,
JSXFragment,
},
};
use oxc_diagnostics::OxcDiagnostic;
@@ -258,10 +258,7 @@ fn is_in_array_or_iter<'a, 'b>(
let parent = ctx.nodes().parent_node(node.id());
match parent.kind() {
AstKind::ArrowFunctionExpression(arrow_expr) => {
let is_arrow_expr_statement = matches!(
arrow_expr.body.statements.first(),
Some(Statement::ExpressionStatement(_))
);
let is_arrow_expr_statement = arrow_expr.is_expression();
if !is_explicit_return && !is_arrow_expr_statement {
return None;
}
@@ -19,8 +19,8 @@ use crate::{
rule::{DefaultRuleConfig, Rule},
rules::ContextHost,
utils::{
expression_contains_jsx, function_body_contains_jsx, function_contains_jsx, is_hoc_call,
is_react_component_name,
arrow_function_body_contains_jsx, expression_contains_jsx, function_contains_jsx,
is_hoc_call, is_react_component_name,
},
};
@@ -299,7 +299,7 @@ fn is_hoc_component(call: &CallExpression, ctx: &LintContext) -> bool {
!is_passthrough_function(func) && function_contains_jsx(func)
}
Argument::ArrowFunctionExpression(arrow) => {
!is_passthrough_arrow(arrow) && function_body_contains_jsx(&arrow.body)
!is_passthrough_arrow(arrow) && arrow_function_body_contains_jsx(&arrow.body)
}
_ => false,
})
@@ -346,7 +346,9 @@ fn is_passthrough_arrow(arrow: &oxc_ast::ast::ArrowFunctionExpression) -> bool {
// Expression arrow: `() => <Comp {...props} />`
arrow.get_expression().is_some_and(is_simple_jsx_passthrough)
// Block body with single return: `() => { return <Comp {...props} />; }`
|| is_single_return_passthrough(&arrow.body.statements)
|| arrow
.get_function_body()
.is_some_and(|body| is_single_return_passthrough(&body.statements))
}
/// Check if statements consist of a single return with a simple JSX passthrough
@@ -391,7 +393,7 @@ fn is_function_returning_null(expr: &Expression) -> bool {
return expr.is_null();
}
// `() => { return null; }`
arrow.body.statements.iter().any(|stmt| {
arrow.get_function_body().unwrap().statements.iter().any(|stmt| {
matches!(stmt, Statement::ReturnStatement(ret) if ret.argument.as_ref().is_some_and(Expression::is_null))
})
}
@@ -75,6 +75,7 @@ impl Rule for NoRenderReturnValue {
| AstKind::ObjectProperty(_)
| AstKind::ReturnStatement(_)
| AstKind::AssignmentExpression(_)
| AstKind::ArrowFunctionExpression(_)
) {
ctx.diagnostic(no_render_return_value_diagnostic(ident.span.merge(property_span)));
}
@@ -83,7 +84,7 @@ impl Rule for NoRenderReturnValue {
if ctx.scoping().scope_flags(scope_id).is_arrow()
&& let AstKind::ArrowFunctionExpression(e) =
ctx.nodes().kind(ctx.scoping().get_node_id(scope_id))
&& e.expression
&& e.is_expression()
{
ctx.diagnostic(no_render_return_value_diagnostic(ident.span.merge(property_span)));
}
@@ -18,7 +18,7 @@ use crate::{
context::{ContextHost, LintContext},
rule::{DefaultRuleConfig, Rule},
utils::{
expression_contains_jsx, function_body_contains_jsx, function_contains_jsx,
arrow_function_body_contains_jsx, expression_contains_jsx, function_contains_jsx,
is_create_element_call, is_es6_component, is_hoc_call, is_react_component_name,
is_react_hook,
},
@@ -216,7 +216,7 @@ impl NoUnstableNestedComponents {
return None;
}
let contains_jsx = function_body_contains_jsx(&arrow.body);
let contains_jsx = arrow_function_body_contains_jsx(&arrow.body);
if !contains_jsx {
return None;
}
@@ -326,7 +326,7 @@ fn find_parent_component_name(
}
AstKind::ArrowFunctionExpression(arrow) => {
if is_first_argument_of_hoc_call(ancestor, ctx)
|| !function_body_contains_jsx(&arrow.body)
|| !arrow_function_body_contains_jsx(&arrow.body)
{
continue;
}
@@ -583,7 +583,7 @@ fn is_hoc_component_call(call: &CallExpression<'_>, ctx: &LintContext<'_>) -> bo
fn argument_contains_jsx(arg: &Argument<'_>, ctx: &LintContext<'_>) -> bool {
match arg {
Argument::FunctionExpression(func) => function_contains_jsx(func),
Argument::ArrowFunctionExpression(arrow) => function_body_contains_jsx(&arrow.body),
Argument::ArrowFunctionExpression(arrow) => arrow_function_body_contains_jsx(&arrow.body),
Argument::CallExpression(call) => is_hoc_component_call(call, ctx),
_ => arg.as_expression().is_some_and(expression_contains_jsx),
}
@@ -139,7 +139,7 @@ fn contains_return_statement(node: &AstNode, ctx: &LintContext) -> bool {
&mut |basic_block_id, _state_going_into_this_rule| {
// If its an arrow function with an expression, marked as founded and stop walking.
if let AstKind::ArrowFunctionExpression(arrow_expr) = node.kind()
&& arrow_expr.expression
&& arrow_expr.is_expression()
{
return (FoundReturn::Yes, STOP_WALKING_ON_THIS_PATH);
}
@@ -277,7 +277,7 @@ impl<'a, 'b> AssertionVisitor<'a, 'b> {
}
}
Expression::ArrowFunctionExpression(arrow_expr) => {
self.visit_function_body(&arrow_expr.body);
self.visit_arrow_function_body(&arrow_expr.body);
}
Expression::CallExpression(call_expr) => {
self.visit_call_expression(call_expr);
@@ -127,7 +127,10 @@ fn is_correct_place_to_call_expect<'a>(
let mut current = ctx.nodes().parent_node(current_node.id());
// loop until find the closest function body
while !matches!(current.kind(), AstKind::FunctionBody(_) | AstKind::Program(_)) {
while !matches!(
current.kind(),
AstKind::FunctionBody(_) | AstKind::ArrowFunctionExpression(_) | AstKind::Program(_)
) {
current = ctx.nodes().parent_node(current.id());
}
@@ -136,7 +139,11 @@ fn is_correct_place_to_call_expect<'a>(
return None;
}
let parent = ctx.nodes().parent_node(current.id());
let parent = if matches!(current.kind(), AstKind::ArrowFunctionExpression(_)) {
current
} else {
ctx.nodes().parent_node(current.id())
};
match parent.kind() {
AstKind::Function(function) => {
@@ -105,10 +105,8 @@ pub fn run_on_jest_node<'a, 'c>(jest_node: &PossibleJestNode<'a, 'c>, ctx: &'c L
/// Returns `None` if the function body doesn't contain exactly one await of a call expression.
fn get_awaited_call_span_from_arrow(arrow: &oxc_ast::ast::ArrowFunctionExpression) -> Option<Span> {
// Case 1: Arrow function with expression body (async () => await doSomething())
if arrow.expression {
if let Some(first) = arrow.body.statements.first()
&& let Statement::ExpressionStatement(expr_stmt) = first
&& let Expression::AwaitExpression(await_expr) = &expr_stmt.expression
if arrow.is_expression() {
if let Some(Expression::AwaitExpression(await_expr)) = arrow.get_expression()
&& let Expression::CallExpression(call) = &await_expr.argument
{
return Some(call.span);
@@ -117,7 +115,7 @@ fn get_awaited_call_span_from_arrow(arrow: &oxc_ast::ast::ArrowFunctionExpressio
}
// Case 2: Arrow function with block body
get_awaited_call_span_from_block(&arrow.body)
get_awaited_call_span_from_block(arrow.get_function_body().unwrap())
}
fn get_awaited_call_span_from_block(body: &oxc_ast::ast::FunctionBody) -> Option<Span> {
@@ -10,7 +10,7 @@ use crate::{
};
use oxc_ast::{
AstKind,
ast::{Argument, CallExpression, Expression, FunctionBody, Statement},
ast::{Argument, ArrowFunctionBody, CallExpression, Expression, FunctionBody, Statement},
};
use oxc_ast_visit::VisitJs;
use oxc_diagnostics::OxcDiagnostic;
@@ -28,6 +28,33 @@ pub struct PreferExpectAssertionsConfig {
pub only_functions_with_expect_in_loop: bool,
}
#[derive(Clone, Copy)]
pub enum CallbackBody<'a> {
Function(&'a FunctionBody<'a>),
Expression(&'a Expression<'a>),
}
impl<'a> CallbackBody<'a> {
fn visit<V: VisitJs<'a>>(self, visitor: &mut V) {
match self {
Self::Function(body) => visitor.visit_function_body(body),
Self::Expression(expression) => visitor.visit_expression(expression),
}
}
fn first_expression(self) -> Option<&'a Expression<'a>> {
match self {
Self::Function(body) => {
let Statement::ExpressionStatement(statement) = body.statements.first()? else {
return None;
};
Some(&statement.expression)
}
Self::Expression(expression) => Some(expression),
}
}
}
impl PreferExpectAssertionsConfig {
#[inline]
pub fn compute_config(&self) -> bool {
@@ -232,7 +259,7 @@ pub trait PreferExpectAssertionsRuleImpl {
};
let mut scanner = HookScanner::new(file_expect_prefix);
scanner.visit_function_body(body);
body.visit(&mut scanner);
if !scanner.has_expect_has_assertions {
return;
@@ -301,26 +328,43 @@ pub trait PreferExpectAssertionsRuleImpl {
if Self::check_first_statement(body, prefix, ctx) {
return;
}
let insert_pos = Span::new(body.span.start + 1, body.span.start + 1);
let fixer = RuleFixer::new(FixKind::Suggestion, ctx);
let suggestions = [
fixer
.insert_text_before_range(insert_pos, format!("{prefix}.hasAssertions();"))
.with_message(format!("Add `{prefix}.hasAssertions()`")),
fixer
.insert_text_before_range(insert_pos, format!("{prefix}.assertions();"))
.with_message(format!("Add `{prefix}.assertions(<number of assertions>)`")),
];
let suggestions = match body {
CallbackBody::Function(body) => {
let insert_pos = Span::new(body.span.start + 1, body.span.start + 1);
[
fixer
.insert_text_before_range(insert_pos, format!("{prefix}.hasAssertions();"))
.with_message(format!("Add `{prefix}.hasAssertions()`")),
fixer
.insert_text_before_range(insert_pos, format!("{prefix}.assertions();"))
.with_message(format!("Add `{prefix}.assertions(<number of assertions>)")),
]
}
CallbackBody::Expression(expression) => {
let source = fixer.source_range(expression.span());
[
fixer
.replace(
expression.span(),
format!("{{{prefix}.hasAssertions();return {source};}}"),
)
.with_message(format!("Add `{prefix}.hasAssertions()`")),
fixer
.replace(
expression.span(),
format!("{{{prefix}.assertions();return {source};}}"),
)
.with_message(format!("Add `{prefix}.assertions(<number of assertions>)")),
]
}
};
self.report_have_expect_assertions(call_expr.span, prefix, suggestions, ctx);
}
fn check_first_statement(body: &FunctionBody<'_>, prefix: &str, ctx: &LintContext<'_>) -> bool {
let Some(Statement::ExpressionStatement(first_expr_stmt)) = body.statements.first() else {
return false;
};
let Expression::CallExpression(first_call) = &first_expr_stmt.expression else {
fn check_first_statement(body: CallbackBody<'_>, prefix: &str, ctx: &LintContext<'_>) -> bool {
let Some(Expression::CallExpression(first_call)) = body.first_expression() else {
return false;
};
@@ -352,7 +396,7 @@ pub trait PreferExpectAssertionsRuleImpl {
ctx: &LintContext<'_>,
);
fn should_check_node(&self, body: &FunctionBody<'_>, is_async: bool, prefix: &str) -> bool;
fn should_check_node(&self, body: CallbackBody<'_>, is_async: bool, prefix: &str) -> bool;
}
fn is_covered_by_hook(
@@ -374,7 +418,7 @@ fn is_covered_by_hook(
pub fn should_check(
config: &PreferExpectAssertionsConfig,
body: &FunctionBody<'_>,
body: CallbackBody<'_>,
is_async: bool,
prefix: &str,
) -> bool {
@@ -388,7 +432,7 @@ pub fn should_check(
}
let mut scanner = BodyScanner::new(prefix);
scanner.visit_function_body(body);
scanner.visit_callback_body(body);
let has_callback =
config.only_functions_with_expect_in_callback && scanner.has_expect_in_callback;
@@ -459,6 +503,17 @@ impl BodyScanner {
self.in_loop = was;
}
fn visit_callback_body(&mut self, body: CallbackBody<'_>) {
match body {
CallbackBody::Function(body) => self.visit_function_body(body),
CallbackBody::Expression(expression) => {
self.expression_depth += 1;
self.visit_expression(expression);
self.expression_depth -= 1;
}
}
}
fn is_expect_call(&self, call_expr: &CallExpression<'_>) -> bool {
let name = get_node_name(&call_expr.callee);
name == self.prefix.as_str() || name.starts_with(self.prefix_dot.as_str())
@@ -484,6 +539,16 @@ impl<'a> VisitJs<'a> for BodyScanner {
self.expression_depth -= 1;
}
fn visit_arrow_function_body(&mut self, body: &ArrowFunctionBody<'a>) {
if body.is_expression() {
self.expression_depth += 1;
oxc_ast_visit::walk_js::walk_arrow_function_body(self, body);
self.expression_depth -= 1;
} else {
oxc_ast_visit::walk_js::walk_arrow_function_body(self, body);
}
}
fn visit_for_statement(&mut self, it: &oxc_ast::ast::ForStatement<'a>) {
self.visit_loop(|s| oxc_ast_visit::walk_js::walk_for_statement(s, it));
}
@@ -567,10 +632,13 @@ fn find_test_callback<'a>(call_expr: &'a CallExpression<'a>) -> Option<&'a Expre
})
}
fn callback_body<'a>(callback: &'a Expression<'a>) -> Option<&'a FunctionBody<'a>> {
fn callback_body<'a>(callback: &'a Expression<'a>) -> Option<CallbackBody<'a>> {
match callback {
Expression::FunctionExpression(func) => func.body.as_ref().map(AsRef::as_ref),
Expression::ArrowFunctionExpression(func) => Some(&func.body),
Expression::FunctionExpression(func) => func.body.as_deref().map(CallbackBody::Function),
Expression::ArrowFunctionExpression(func) => func
.get_expression()
.map(CallbackBody::Expression)
.or_else(|| func.get_function_body().map(CallbackBody::Function)),
_ => None,
}
}
@@ -83,13 +83,11 @@ pub fn run<'a>(node: &oxc_semantic::AstNode<'a>, ctx: &LintContext<'a>) {
if !arrow_func.params.is_empty() {
return;
}
let Some(stmt) = arrow_func.body.statements.first() else {
return;
};
if let Some(expr) = arrow_func.get_expression() {
report(is_once, property_span, Some(arrow_func.span), expr, ctx);
} else if let Statement::ReturnStatement(return_stmt) = stmt {
} else if let Some(Statement::ReturnStatement(return_stmt)) =
arrow_func.get_function_body().unwrap().statements.first()
{
let Some(arg_expr) = &return_stmt.argument else {
return;
};
@@ -162,17 +162,21 @@ fn is_mutable(symbol_id: SymbolId, ctx: &LintContext<'_>) -> bool {
fn get_mock_return<'a>(argument_expression: &'a Expression<'a>) -> Option<&'a Expression<'a>> {
match argument_expression {
Expression::ArrowFunctionExpression(arrow_func) => {
if arrow_func.r#async
|| arrow_func.body.statements.len() > 1
|| arrow_func.params.has_parameter()
{
if arrow_func.r#async || arrow_func.params.has_parameter() {
return None;
}
let stmt = arrow_func.body.statements.first()?;
if let Some(expression) = arrow_func.get_expression() {
return Some(expression);
}
let body = arrow_func.get_function_body()?;
if body.statements.len() > 1 {
return None;
}
let stmt = body.statements.first()?;
match stmt {
Statement::ExpressionStatement(stmt_expr) => Some(&stmt_expr.expression),
Statement::ReturnStatement(return_statement) => {
let Some(arg_expr) = &return_statement.argument else {
return None;
@@ -172,9 +172,12 @@ impl RequireHookConfig {
}
}
Argument::ArrowFunctionExpression(arrow_func_expr)
if !arrow_func_expr.expression =>
if !arrow_func_expr.is_expression() =>
{
self.check_block_body(&arrow_func_expr.body.statements, ctx);
self.check_block_body(
&arrow_func_expr.get_function_body().unwrap().statements,
ctx,
);
}
_ => (),
}
@@ -116,17 +116,14 @@ pub fn run<'a>(
diagnostic(ctx, arrow_expr.span, Message::UnexpectedDescribeArgument);
}
if arrow_expr.expression && !arrow_expr.body.statements.is_empty() {
let stmt = &arrow_expr.body.statements[0];
let Statement::ExpressionStatement(expr_stmt) = stmt else {
return;
};
if let Expression::CallExpression(call_expr) = &expr_stmt.expression {
diagnostic(ctx, call_expr.span, Message::UnexpectedReturnInDescribe);
}
if arrow_expr.is_expression()
&& let Some(Expression::CallExpression(call_expr)) = arrow_expr.get_expression()
{
diagnostic(ctx, call_expr.span, Message::UnexpectedReturnInDescribe);
}
if let Some(span) = find_first_return_stmt_span(&arrow_expr.body) {
if let Some(span) = arrow_expr.get_function_body().and_then(find_first_return_stmt_span)
{
diagnostic(ctx, span, Message::UnexpectedReturnInDescribe);
}
}
@@ -356,7 +356,7 @@ fn is_acceptable_return_node<'a, 'b>(
| AstKind::FunctionBody(_) => {
node = ctx.nodes().parent_node(node.id());
}
AstKind::ArrowFunctionExpression(arrow_expr) => return arrow_expr.expression,
AstKind::ArrowFunctionExpression(arrow_expr) => return arrow_expr.is_expression(),
AstKind::AwaitExpression(_) => return true,
_ => return false,
}
@@ -291,10 +291,10 @@ fn is_top_level_promise_chain(expr: &Expression) -> bool {
fn get_checkable_callback_body<'a>(callback: &'a Argument<'a>) -> Option<&'a FunctionBody<'a>> {
match callback {
Argument::ArrowFunctionExpression(arrow) => {
if arrow.expression || !arrow.params.items.is_empty() {
if arrow.is_expression() || !arrow.params.items.is_empty() {
return None;
}
Some(&arrow.body)
arrow.get_function_body()
}
Argument::FunctionExpression(func) => {
if !func.params.items.is_empty() {
@@ -300,7 +300,7 @@ impl ExplicitFunctionReturnType {
if !self.allow_concise_arrow_function_expressions_starting_with_void {
return false;
}
if !func.expression {
if !func.is_expression() {
return false;
}
let Some(expr) = func.get_expression() else { return false };
@@ -597,7 +597,7 @@ fn ancestor_has_return_type<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bo
if let AstKind::ObjectProperty(prop) = parent.kind()
&& let Expression::ArrowFunctionExpression(func) = &prop.value
&& !func.body.statements.is_empty()
&& func.get_function_body().is_some_and(|body| !body.statements.is_empty())
&& func.return_type.is_some()
{
return true;
@@ -631,7 +631,10 @@ fn ancestor_has_return_type<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bo
fn all_return_statements_are_functions(node: &AstNode) -> bool {
let function_body = match node.kind() {
AstKind::ArrowFunctionExpression(arrow_func_expr) => &arrow_func_expr.body,
AstKind::ArrowFunctionExpression(arrow_func_expr) => {
let Some(body) = arrow_func_expr.get_function_body() else { return false };
body
}
AstKind::Function(func) => {
if let Some(func_body) = &func.body {
func_body
@@ -509,7 +509,7 @@ impl<'a, 'c> ExplicitTypesChecker<'a, 'c> {
return;
}
if arrow.expression {
if arrow.is_expression() {
let Some(expr) = arrow.get_expression() else {
debug_assert!(
false,
@@ -535,7 +535,7 @@ impl<'a, 'c> ExplicitTypesChecker<'a, 'c> {
// `export const foo = () => () => (): number => 1`
Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => {
debug_assert!(self.rule.allow_higher_order_functions);
walk_js::walk_function_body(self, &arrow.body);
walk_js::walk_arrow_function_body(self, &arrow.body);
return;
}
_ => {
@@ -545,7 +545,7 @@ impl<'a, 'c> ExplicitTypesChecker<'a, 'c> {
}
}
} else {
walk_js::walk_function_body(self, &arrow.body);
walk_js::walk_arrow_function_body(self, &arrow.body);
// AST is immutable in linter, so `unstable_address` produces stable `Address`es
let is_hof = self.is_higher_order_function(arrow.unstable_address());
@@ -191,9 +191,15 @@ fn get_assignments_inside_expression<'a>(
Expression::CallExpression(call) => {
// Immediately Invoked Function Expression (IIFE)
if let Expression::ArrowFunctionExpression(expr) = call.callee.without_parentheses()
&& let Some(Expression::AssignmentExpression(assignment)) = expr.get_expression()
{
assignments.push(assignment);
}
let function_body = match call.callee.without_parentheses() {
Expression::ArrowFunctionExpression(expr) => Some(&expr.body),
Expression::FunctionExpression(expr) => expr.body.as_ref(),
Expression::ArrowFunctionExpression(expr) => expr.get_function_body(),
Expression::FunctionExpression(expr) => expr.body.as_deref(),
_ => None,
};
@@ -165,6 +165,11 @@ impl Rule for ConsistentFunctionScoping {
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
enum FunctionLikeBody<'a, 'b> {
Function(&'b FunctionBody<'a>),
Arrow(&'b ArrowFunctionBody<'a>),
}
let (
function_declaration_symbol_id,
function_name,
@@ -200,7 +205,7 @@ impl Rule for ConsistentFunctionScoping {
(
binding_ident.symbol_id(),
Some(binding_ident.name.as_str()),
function_body,
FunctionLikeBody::Function(function_body),
function.id.as_ref().map_or(
Span::sized(function.span.start, 8),
|func_binding_ident| func_binding_ident.span,
@@ -211,7 +216,7 @@ impl Rule for ConsistentFunctionScoping {
(
function_id.symbol_id(),
Some(function_id.name.as_str()),
function_body,
FunctionLikeBody::Function(function_body),
function_id.span(),
func_scope_id,
)
@@ -227,7 +232,7 @@ impl Rule for ConsistentFunctionScoping {
(
binding_ident.symbol_id(),
Some(binding_ident.name.as_str()),
&arrow_function.body,
FunctionLikeBody::Arrow(&arrow_function.body),
binding_ident.span(),
arrow_function.scope_id(),
)
@@ -258,7 +263,10 @@ impl Rule for ConsistentFunctionScoping {
// get all references in the function body
let (function_body_var_references, is_parent_this_referenced) = {
let mut rf = ReferencesFinder::default();
rf.visit_function_body(function_body);
match function_body {
FunctionLikeBody::Function(body) => rf.visit_function_body(body),
FunctionLikeBody::Arrow(body) => rf.visit_arrow_function_body(body),
}
(rf.references, rf.is_parent_this_referenced)
};
@@ -111,14 +111,19 @@ fn is_simple_operation(node: &CallExpression) -> bool {
let Some(callback_arg) = node.arguments.first() else {
return false;
};
if let Argument::ArrowFunctionExpression(callback) = callback_arg
&& let Some(expression) = callback.get_expression()
{
return matches!(expression, Expression::BinaryExpression(_));
}
let function_body = match callback_arg {
// `array.reduce((accumulator, element) => accumulator + element)`
Argument::ArrowFunctionExpression(callback) => &callback.body,
Argument::ArrowFunctionExpression(callback) => callback.get_function_body().unwrap(),
Argument::FunctionExpression(callback) => {
let Some(body) = &callback.body else {
return false;
};
body
body.as_ref()
}
_ => return false,
};
@@ -519,7 +519,10 @@ fn expression_references_variable(expr: &Expression<'_>, var_name: &str) -> bool
return false;
}
// Check the function body for references
arrow.body.statements.iter().any(|stmt| {
if let Some(expression) = arrow.get_expression() {
return expression_references_variable(expression, var_name);
}
arrow.get_function_body().unwrap().statements.iter().any(|stmt| {
if let Statement::ExpressionStatement(expr_stmt) = stmt {
expression_references_variable(&expr_stmt.expression, var_name)
} else if let Statement::ReturnStatement(ret) = stmt {
@@ -108,7 +108,7 @@ impl Rule for NoInvalidRemoveEventListener {
Span::new(func_expr.span.start, func_expr.params.span.end)
}
Argument::ArrowFunctionExpression(arrow_expr) => {
Span::new(arrow_expr.span.start, arrow_expr.body.span.start)
Span::new(arrow_expr.span.start, arrow_expr.body.span().start)
}
Argument::CallExpression(_) => listener_span,
_ => unreachable!(),
@@ -1,10 +1,7 @@
use oxc_ast::{
AstKind,
ast::{Expression, Statement},
};
use oxc_ast::{AstKind, ast::Expression};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use oxc_span::{GetSpan, Span};
use crate::{AstNode, context::LintContext, rule::Rule};
@@ -67,14 +64,12 @@ impl Rule for NoUnreadableIife {
return;
};
if !arrow_expr.expression {
if !arrow_expr.is_expression() {
return;
}
let Statement::ExpressionStatement(expr_stmt) = &arrow_expr.body.statements[0] else {
return;
};
if matches!(expr_stmt.expression, Expression::ParenthesizedExpression(_)) {
ctx.diagnostic(no_unreadable_iife_diagnostic(expr_stmt.span));
let Some(expression) = arrow_expr.get_expression() else { return };
if matches!(expression, Expression::ParenthesizedExpression(_)) {
ctx.diagnostic(no_unreadable_iife_diagnostic(expression.span()));
}
}
}
@@ -271,16 +271,6 @@ fn is_bind_member_expression(node: &AstNode) -> bool {
member_expr.static_property_name().is_some_and(|name| name == "bind")
}
fn match_arrow_function_body<'a>(ctx: &LintContext<'a>, parent: &AstNode<'a>) -> bool {
let parent = ctx.nodes().parent_node(parent.id());
if !matches!(parent.kind(), AstKind::FunctionBody(_)) {
return false;
}
let grand_parent = ctx.nodes().parent_node(parent.id());
matches!(grand_parent.kind(), AstKind::ArrowFunctionExpression(_))
}
fn generate_fix<'a>(
call_expr: &CallExpression,
is_reject: bool,
@@ -325,10 +315,8 @@ fn generate_fix<'a>(
return fixer.noop();
};
let is_arrow_function_body = match parent.kind() {
AstKind::ExpressionStatement(_) => match_arrow_function_body(ctx, parent),
_ => false,
};
let is_arrow_function_body =
matches!(parent.kind(), AstKind::ArrowFunctionExpression(arrow) if arrow.is_expression());
let mut replace_range = if is_reject { parent.kind().span() } else { call_expr.span() };
let replacement_text = if is_reject {
@@ -342,7 +330,7 @@ fn generate_fix<'a>(
text = format!("{text};");
// `=> Promise.reject(error)` -> `=> { throw error; }`
if is_arrow_function_body {
replace_range = outermost_paren(parent, ctx).kind().span();
replace_range = node.kind().span();
text = format!("{{ {text} }}");
}
text
@@ -152,14 +152,13 @@ fn is_undefined(arg: &Argument) -> bool {
}
fn is_has_function_return_type(node: &AstNode, ctx: &LintContext<'_>) -> bool {
let parent_node = ctx.nodes().parent_node(node.id());
match parent_node.kind() {
match node.kind() {
AstKind::Program(_) => false,
AstKind::ArrowFunctionExpression(arrow_func_express) => {
arrow_func_express.return_type.is_some()
}
AstKind::Function(func) => func.return_type.is_some(),
_ => is_has_function_return_type(parent_node, ctx),
_ => is_has_function_return_type(ctx.nodes().parent_node(node.id()), ctx),
}
}
@@ -210,22 +209,10 @@ impl Rule for NoUselessUndefined {
);
}
// `() => undefined`
AstKind::ExpressionStatement(_) => {
AstKind::ArrowFunctionExpression(arrow) if arrow.is_expression() => {
if !self.check_arrow_function_body {
return;
}
let grand_parent_node = ctx.nodes().parent_node(parent_node.id());
let grand_parent_node_kind = grand_parent_node.kind();
let AstKind::FunctionBody(func_body) = grand_parent_node_kind else {
return;
};
let grand_grand_parent_node =
ctx.nodes().parent_node(grand_parent_node.id());
let grand_grand_parent_node_kind = grand_grand_parent_node.kind();
let AstKind::ArrowFunctionExpression(_) = grand_grand_parent_node_kind
else {
return;
};
if is_has_function_return_type(parent_node, ctx) {
return;
@@ -233,7 +220,7 @@ impl Rule for NoUselessUndefined {
ctx.diagnostic_with_fix(
no_useless_undefined_diagnostic(undefined_literal.span),
|fixer| fixer.replace(func_body.span, "{}"),
|fixer| fixer.replace(arrow.body.span(), "{}"),
);
}
// `let foo = undefined` / `var foo = undefined`
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff. Show more