oxc/crates/oxc_parser/src/js/arrow.rs
overlookmotel fdd480d84c refactor(parser): do not use AstBuilder::*_from_* methods (#7068)
Preparation for #7073. Avoid using `AstBuilder::*_from_*` methods to construct enums, use explicit construction instead.

Before:

```rs
let ident = self.ast.binding_pattern_kind_from_binding_identifier(ident);
```

After:

```rs
let ident = BindingPatternKind::BindingIdentifier(ident);
```

Often this produces shorter code, as well as (in my opinion) being easier to read.
2024-11-02 01:22:55 +00:00

341 lines
13 KiB
Rust

use oxc_allocator::Box;
use oxc_ast::{ast::*, NONE};
use oxc_diagnostics::Result;
use oxc_span::{GetSpan, Span};
use oxc_syntax::precedence::Precedence;
use super::Tristate;
use crate::{diagnostics, lexer::Kind, ParserImpl};
type ArrowFunctionHead<'a> = (
Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
Box<'a, FormalParameters<'a>>,
Option<Box<'a, TSTypeAnnotation<'a>>>,
bool,
Span,
);
impl<'a> ParserImpl<'a> {
pub(super) fn try_parse_parenthesized_arrow_function_expression(
&mut self,
) -> Result<Option<Expression<'a>>> {
match self.is_parenthesized_arrow_function_expression() {
Tristate::False => Ok(None),
Tristate::True => self.parse_parenthesized_arrow_function(),
Tristate::Maybe => self.parse_possible_parenthesized_arrow_function_expression(),
}
}
pub(super) fn try_parse_async_simple_arrow_function_expression(
&mut self,
) -> Result<Option<Expression<'a>>> {
if self.at(Kind::Async)
&& self.is_un_parenthesized_async_arrow_function_worker() == Tristate::True
{
let span = self.start_span();
self.bump_any(); // bump `async`
let expr = self.parse_binary_expression_or_higher(Precedence::Comma)?;
return self
.parse_simple_arrow_function_expression(span, expr, /* async */ true)
.map(Some);
}
Ok(None)
}
fn is_parenthesized_arrow_function_expression(&mut self) -> Tristate {
match self.cur_kind() {
Kind::LParen | Kind::LAngle | Kind::Async => {
self.lookahead(Self::is_parenthesized_arrow_function_expression_worker)
}
_ => Tristate::False,
}
}
fn is_parenthesized_arrow_function_expression_worker(&mut self) -> Tristate {
let mut offset = 0;
if self.at(Kind::Async) {
let second_token = self.peek_token();
let second = second_token.kind;
if second_token.is_on_new_line {
return Tristate::False;
}
if second != Kind::LParen && second != Kind::LAngle {
return Tristate::False;
}
offset = 1;
}
let first = self.nth_kind(offset);
let second = self.nth_kind(offset + 1);
match first {
Kind::LParen => {
match second {
// Simple cases: "() =>", "(): ", and "() {".
// This is an arrow function with no parameters.
// The last one is not actually an arrow function,
// but this is probably what the user intended.
Kind::RParen => {
let third = self.nth_kind(offset + 2);
return match third {
Kind::Colon if self.is_ts => Tristate::Maybe,
Kind::Arrow | Kind::LCurly => Tristate::True,
_ => Tristate::False,
};
}
// If encounter "([" or "({", this could be the start of a binding pattern.
// Examples:
// ([ x ]) => { }
// ({ x }) => { }
// ([ x ])
// ({ x })
Kind::LBrack | Kind::LCurly => {
return Tristate::Maybe;
}
// Simple case: "(..."
// This is an arrow function with a rest parameter.
Kind::Dot3 => {
return match self.nth_kind(offset + 1) {
// '(...ident' is a lambda
Kind::Ident => Tristate::True,
// '(...null' is not a lambda
kind if kind.is_literal() => Tristate::False,
_ => Tristate::Maybe,
};
}
_ => {}
}
let third = self.nth_kind(offset + 2);
// Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This
// isn't actually allowed, but we want to treat it as a lambda so we can provide
// a good error message.
if second.is_modifier_kind()
&& second != Kind::Async
&& third.is_binding_identifier()
{
if third == Kind::As {
return Tristate::False; // https://github.com/microsoft/TypeScript/issues/44466
}
return Tristate::True;
}
// If we had "(" followed by something that's not an identifier,
// then this definitely doesn't look like a lambda. "this" is not
// valid, but we want to parse it and then give a semantic error.
if !second.is_binding_identifier() && second != Kind::This {
return Tristate::False;
}
match third {
// If we have something like "(a:", then we must have a
// type-annotated parameter in an arrow function expression.
Kind::Colon => Tristate::True,
// If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda.
Kind::Question => {
let fourth = self.nth_kind(offset + 3);
if matches!(fourth, Kind::Colon | Kind::Comma | Kind::Eq | Kind::RParen) {
return Tristate::True;
}
Tristate::False
}
// If we have "(a," or "(a=" or "(a)" this *could* be an arrow function
Kind::Comma | Kind::Eq | Kind::RParen => Tristate::Maybe,
// It is definitely not an arrow function
_ => Tristate::False,
}
}
Kind::LAngle => {
// If we have "<" not followed by an identifier,
// then this definitely is not an arrow function.
if !second.is_binding_identifier() && second != Kind::Const {
return Tristate::False;
}
// JSX overrides
if self.source_type.is_jsx() {
// <const Ident extends Ident>
// ^^^^^ Optional
offset += if second == Kind::Const { 3 } else { 2 };
return match self.nth_kind(offset) {
Kind::Extends => {
let third = self.nth_kind(offset + 1);
if matches!(third, Kind::Eq | Kind::RAngle | Kind::Slash) {
Tristate::False
} else if third.is_binding_identifier() {
Tristate::Maybe
} else {
Tristate::True
}
}
Kind::Eq | Kind::Comma => Tristate::True,
_ => Tristate::False,
};
}
Tristate::Maybe
}
_ => unreachable!(),
}
}
fn is_un_parenthesized_async_arrow_function_worker(&mut self) -> Tristate {
if self.at(Kind::Async) {
let first_token = self.peek_token();
let first = first_token.kind;
// If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function
// but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher"
if first_token.is_on_new_line || first == Kind::Arrow {
return Tristate::False;
}
// Check for un-parenthesized AsyncArrowFunction
if first.is_binding_identifier() {
// Arrow before newline is checked in `parse_simple_arrow_function_expression`
if self.nth_at(2, Kind::Arrow) {
return Tristate::True;
}
}
}
Tristate::False
}
pub(crate) fn parse_simple_arrow_function_expression(
&mut self,
span: Span,
ident: Expression<'a>,
r#async: bool,
) -> Result<Expression<'a>> {
let has_await = self.ctx.has_await();
self.ctx = self.ctx.union_await_if(r#async);
let params = {
let ident = match ident {
Expression::Identifier(ident) => {
let ident = ident.unbox();
self.ast.alloc_binding_identifier(ident.span, ident.name)
}
_ => unreachable!(),
};
let params_span = self.end_span(ident.span);
let ident = BindingPatternKind::BindingIdentifier(ident);
let pattern = self.ast.binding_pattern(ident, NONE, false);
let formal_parameter = self.ast.plain_formal_parameter(params_span, pattern);
self.ast.alloc_formal_parameters(
params_span,
FormalParameterKind::ArrowFormalParameters,
self.ast.vec1(formal_parameter),
NONE,
)
};
self.ctx = self.ctx.and_await(has_await);
if self.cur_token().is_on_new_line {
self.error(diagnostics::lineterminator_before_arrow(self.cur_token().span()));
}
self.expect(Kind::Arrow)?;
self.parse_arrow_function_body(
span, /* type_parameters */ None, params, /* return_type */ None, r#async,
)
}
fn parse_parenthesized_arrow_function_head(&mut self) -> Result<ArrowFunctionHead<'a>> {
let span = self.start_span();
let r#async = self.eat(Kind::Async);
let has_await = self.ctx.has_await();
self.ctx = self.ctx.union_await_if(r#async);
let type_parameters = self.parse_ts_type_parameters()?;
let (this_param, params) =
self.parse_formal_parameters(FormalParameterKind::ArrowFormalParameters)?;
if let Some(this_param) = this_param {
// const x = (this: number) => {};
self.error(diagnostics::ts_arrow_function_this_parameter(this_param.span));
}
let return_type = self.parse_ts_return_type_annotation(Kind::Arrow, false)?;
self.ctx = self.ctx.and_await(has_await);
if self.cur_token().is_on_new_line {
self.error(diagnostics::lineterminator_before_arrow(self.cur_token().span()));
}
self.expect(Kind::Arrow)?;
Ok((type_parameters, params, return_type, r#async, span))
}
/// [ConciseBody](https://tc39.es/ecma262/#prod-ConciseBody)
/// [lookahead ≠ {] `ExpressionBody`[?In, ~Await]
/// { `FunctionBody`[~Yield, ~Await] }
/// `ExpressionBody`[In, Await] :
/// `AssignmentExpression`[?In, ~Yield, ?Await]
fn parse_arrow_function_body(
&mut self,
span: Span,
type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
params: Box<'a, FormalParameters<'a>>,
return_type: Option<Box<'a, TSTypeAnnotation<'a>>>,
r#async: bool,
) -> Result<Expression<'a>> {
let has_await = self.ctx.has_await();
let has_yield = self.ctx.has_yield();
self.ctx = self.ctx.and_await(r#async).and_yield(false);
let expression = !self.at(Kind::LCurly);
let body = if expression {
let expr = self.parse_assignment_expression_or_higher()?;
let span = expr.span();
let expr_stmt = self.ast.statement_expression(span, expr);
self.ast.alloc_function_body(span, self.ast.vec(), self.ast.vec1(expr_stmt))
} else {
self.parse_function_body()?
};
self.ctx = self.ctx.and_await(has_await).and_yield(has_yield);
Ok(self.ast.expression_arrow_function(
self.end_span(span),
expression,
r#async,
type_parameters,
params,
return_type,
body,
))
}
/// Section [Arrow Function](https://tc39.es/ecma262/#sec-arrow-function-definitions)
/// `ArrowFunction`[In, Yield, Await] :
/// `ArrowParameters`[?Yield, ?Await] [no `LineTerminator` here] => `ConciseBody`[?In]
fn parse_parenthesized_arrow_function(&mut self) -> Result<Option<Expression<'a>>> {
let (type_parameters, params, return_type, r#async, span) =
self.parse_parenthesized_arrow_function_head()?;
self.parse_arrow_function_body(span, type_parameters, params, return_type, r#async)
.map(Some)
}
fn parse_possible_parenthesized_arrow_function_expression(
&mut self,
) -> Result<Option<Expression<'a>>> {
let pos = self.cur_token().start;
if self.state.not_parenthesized_arrow.contains(&pos) {
return Ok(None);
}
if let Some((type_parameters, params, return_type, r#async, span)) =
self.try_parse(ParserImpl::parse_parenthesized_arrow_function_head)
{
return self
.parse_arrow_function_body(span, type_parameters, params, return_type, r#async)
.map(Some);
}
self.state.not_parenthesized_arrow.insert(pos);
Ok(None)
}
}