use crate::parser::rewrite_parser::{RewriteMarker, RewriteParser, RewriteToken};
use crate::parser::JsParserCheckpoint;
use crate::prelude::*;
use crate::rewrite::{rewrite_events, RewriteParseEvents};
use crate::syntax::class::parse_initializer_clause;
use crate::syntax::expr::{
is_at_identifier, parse_conditional_expr, parse_unary_expr, ExpressionContext,
};
use crate::syntax::js_parse_error::{
expected_assignment_target, expected_identifier, expected_object_member_name,
invalid_assignment_error,
};
use crate::syntax::object::{is_at_object_member_name, parse_object_member_name};
use crate::syntax::pattern::{ParseArrayPattern, ParseObjectPattern, ParseWithDefaultPattern};
use crate::JsParser;
use crate::ParsedSyntax::{Absent, Present};
use biome_js_syntax::{JsSyntaxKind::*, *};
use biome_parser::diagnostic::expected_any;
use biome_rowan::AstNode;
pub(crate) fn expression_to_assignment_pattern(
p: &mut JsParser,
target: CompletedMarker,
checkpoint: JsParserCheckpoint,
) -> CompletedMarker {
match target.kind(p) {
JS_OBJECT_EXPRESSION => {
p.rewind(checkpoint);
ObjectAssignmentPattern.parse_object_pattern(p).unwrap()
}
JS_ARRAY_EXPRESSION => {
p.rewind(checkpoint);
ArrayAssignmentPattern.parse_array_pattern(p).unwrap()
}
_ => expression_to_assignment(p, target, checkpoint),
}
}
pub(crate) fn parse_assignment_pattern(p: &mut JsParser) -> ParsedSyntax {
let checkpoint = p.checkpoint();
let assignment_expression = parse_conditional_expr(p, ExpressionContext::default());
assignment_expression
.map(|expression| expression_to_assignment_pattern(p, expression, checkpoint))
}
pub(crate) fn expression_to_assignment(
p: &mut JsParser,
target: CompletedMarker,
checkpoint: JsParserCheckpoint,
) -> CompletedMarker {
try_expression_to_assignment(p, target, checkpoint).unwrap_or_else(
|mut invalid_assignment_target| {
invalid_assignment_target.change_kind(p, JS_BOGUS_ASSIGNMENT);
p.error(invalid_assignment_error(
p,
invalid_assignment_target.range(p),
));
invalid_assignment_target
},
)
}
pub(crate) enum AssignmentExprPrecedence {
Unary,
Conditional,
}
impl AssignmentExprPrecedence {
fn parse_expression(&self, p: &mut JsParser, context: ExpressionContext) -> ParsedSyntax {
match self {
AssignmentExprPrecedence::Unary => parse_unary_expr(p, context),
AssignmentExprPrecedence::Conditional => parse_conditional_expr(p, context),
}
}
}
pub(crate) fn parse_assignment(
p: &mut JsParser,
expr_kind: AssignmentExprPrecedence,
context: ExpressionContext,
) -> ParsedSyntax {
let checkpoint = p.checkpoint();
let assignment_expression = expr_kind.parse_expression(p, context);
assignment_expression.map(|expr| expression_to_assignment(p, expr, checkpoint))
}
struct ArrayAssignmentPatternElement;
impl ParseWithDefaultPattern for ArrayAssignmentPatternElement {
#[inline]
fn pattern_with_default_kind() -> JsSyntaxKind {
JS_ARRAY_ASSIGNMENT_PATTERN_ELEMENT
}
#[inline]
fn expected_pattern_error(p: &JsParser, range: TextRange) -> ParseDiagnostic {
expected_assignment_target(p, range)
}
#[inline]
fn parse_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
parse_assignment_pattern(p)
}
}
struct ArrayAssignmentPattern;
impl ParseArrayPattern<ArrayAssignmentPatternElement> for ArrayAssignmentPattern {
#[inline]
fn bogus_pattern_kind() -> JsSyntaxKind {
JS_BOGUS_ASSIGNMENT
}
#[inline]
fn array_pattern_kind() -> JsSyntaxKind {
JS_ARRAY_ASSIGNMENT_PATTERN
}
#[inline]
fn rest_pattern_kind() -> JsSyntaxKind {
JS_ARRAY_ASSIGNMENT_PATTERN_REST_ELEMENT
}
fn list_kind() -> JsSyntaxKind {
JS_ARRAY_ASSIGNMENT_PATTERN_ELEMENT_LIST
}
#[inline]
fn expected_element_error(p: &JsParser, range: TextRange) -> ParseDiagnostic {
expected_any(&["assignment target", "rest element", "comma"], range, p)
}
#[inline]
fn pattern_with_default(&self) -> ArrayAssignmentPatternElement {
ArrayAssignmentPatternElement
}
}
struct ObjectAssignmentPattern;
impl ParseObjectPattern for ObjectAssignmentPattern {
#[inline]
fn bogus_pattern_kind() -> JsSyntaxKind {
JS_BOGUS_ASSIGNMENT
}
#[inline]
fn object_pattern_kind() -> JsSyntaxKind {
JS_OBJECT_ASSIGNMENT_PATTERN
}
fn list_kind() -> JsSyntaxKind {
JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY_LIST
}
#[inline]
fn expected_property_pattern_error(p: &JsParser, range: TextRange) -> ParseDiagnostic {
expected_any(&["assignment target", "rest property"], range, p)
}
fn parse_property_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
let m = p.start();
let kind = if (is_at_identifier(p) || p.at(T![=])) && !p.nth_at(1, T![:]) {
parse_assignment(
p,
AssignmentExprPrecedence::Conditional,
ExpressionContext::default(),
)
.or_add_diagnostic(p, expected_identifier);
JS_OBJECT_ASSIGNMENT_PATTERN_SHORTHAND_PROPERTY
} else if is_at_object_member_name(p) || p.at(T![:]) || p.nth_at(1, T![:]) {
parse_object_member_name(p).or_add_diagnostic(p, expected_object_member_name);
p.expect(T![:]);
parse_assignment_pattern(p).or_add_diagnostic(p, expected_assignment_target);
JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY
} else {
m.abandon(p);
return Absent;
};
parse_initializer_clause(p, ExpressionContext::default()).ok();
Present(m.complete(p, kind))
}
fn parse_rest_property_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
if !p.at(T![...]) {
return Absent;
}
let m = p.start();
p.bump(T![...]);
let target = parse_assignment_pattern(p).or_add_diagnostic(p, expected_assignment_target);
if let Some(mut target) = target {
if matches!(
target.kind(p),
JS_OBJECT_ASSIGNMENT_PATTERN | JS_ARRAY_ASSIGNMENT_PATTERN
) {
target.change_kind(p, JS_BOGUS_ASSIGNMENT);
p.error(p.err_builder(
"object and array assignment targets are not allowed in rest patterns",
target.range(p),
));
}
}
Present(m.complete(p, JS_OBJECT_ASSIGNMENT_PATTERN_REST))
}
}
fn try_expression_to_assignment(
p: &mut JsParser,
target: CompletedMarker,
checkpoint: JsParserCheckpoint,
) -> Result<CompletedMarker, CompletedMarker> {
if !matches!(
target.kind(p),
JS_PARENTHESIZED_EXPRESSION
| JS_STATIC_MEMBER_EXPRESSION
| JS_COMPUTED_MEMBER_EXPRESSION
| JS_IDENTIFIER_EXPRESSION
| TS_NON_NULL_ASSERTION_EXPRESSION
| TS_TYPE_ASSERTION_EXPRESSION
| TS_AS_EXPRESSION
| TS_SATISFIES_EXPRESSION
) {
return Err(target);
}
let mut reparse_assignment = ReparseAssignment::new();
rewrite_events(&mut reparse_assignment, checkpoint, p);
Ok(reparse_assignment.result.unwrap())
}
struct ReparseAssignment {
parents: Vec<(JsSyntaxKind, Option<RewriteMarker>)>,
result: Option<CompletedMarker>,
inside_assignment: bool,
}
impl ReparseAssignment {
pub fn new() -> Self {
Self {
parents: Vec::default(),
result: None,
inside_assignment: true,
}
}
}
impl RewriteParseEvents for ReparseAssignment {
fn start_node(&mut self, kind: JsSyntaxKind, p: &mut RewriteParser) {
if !self.inside_assignment {
self.parents.push((kind, Some(p.start())));
return;
}
let mapped_kind = match kind {
JS_PARENTHESIZED_EXPRESSION => JS_PARENTHESIZED_ASSIGNMENT,
JS_STATIC_MEMBER_EXPRESSION => {
self.inside_assignment = false;
JS_STATIC_MEMBER_ASSIGNMENT
}
JS_COMPUTED_MEMBER_EXPRESSION => {
self.inside_assignment = false;
JS_COMPUTED_MEMBER_ASSIGNMENT
}
JS_IDENTIFIER_EXPRESSION => JS_IDENTIFIER_ASSIGNMENT,
TS_NON_NULL_ASSERTION_EXPRESSION => TS_NON_NULL_ASSERTION_ASSIGNMENT,
TS_AS_EXPRESSION => TS_AS_ASSIGNMENT,
TS_SATISFIES_EXPRESSION => TS_SATISFIES_ASSIGNMENT,
TS_TYPE_ASSERTION_EXPRESSION => TS_TYPE_ASSERTION_ASSIGNMENT,
JS_REFERENCE_IDENTIFIER => {
self.parents.push((kind, None)); return;
}
_ => {
self.inside_assignment = false;
if AnyTsType::can_cast(kind)
&& matches!(
self.parents.last(),
Some((
TS_AS_ASSIGNMENT
| TS_SATISFIES_ASSIGNMENT
| TS_TYPE_ASSERTION_ASSIGNMENT,
_
))
)
{
kind
} else {
JS_BOGUS_ASSIGNMENT
}
}
};
self.parents.push((mapped_kind, Some(p.start())));
}
fn finish_node(&mut self, p: &mut RewriteParser) {
let (kind, m) = self.parents.pop().unwrap();
if let Some(m) = m {
let mut completed = m.complete(p, kind);
match kind {
JS_IDENTIFIER_ASSIGNMENT => {
let name = completed.text(p);
if matches!(name, "eval" | "arguments") && p.is_strict_mode() {
let error = p.err_builder(
format!("Illegal use of `{}` as an identifier in strict mode", name),
completed.range(p),
);
p.error(error);
completed.change_to_bogus(p);
}
}
JS_BOGUS_ASSIGNMENT => {
let range = completed.range(p);
p.error(
p.err_builder(
format!("Invalid assignment to `{}`", completed.text(p)),
range,
)
.with_hint("This expression cannot be assigned to"),
);
}
_ => {}
}
self.result = Some(completed.into());
}
if AnyTsType::can_cast(kind)
&& matches!(
self.parents.last(),
Some((
TS_TYPE_ASSERTION_ASSIGNMENT | TS_AS_ASSIGNMENT | TS_SATISFIES_ASSIGNMENT,
_
))
)
{
self.inside_assignment = true;
}
}
fn token(&mut self, token: RewriteToken, p: &mut RewriteParser) {
let parent = self.parents.last_mut();
if let Some((parent_kind, _)) = parent {
if matches!(
*parent_kind,
JS_COMPUTED_MEMBER_ASSIGNMENT | JS_STATIC_MEMBER_ASSIGNMENT
) && token.kind == T![?.]
{
*parent_kind = JS_BOGUS_ASSIGNMENT
}
}
p.bump(token)
}
}