use crate::prelude::*;
use crate::syntax::expr::ExpressionContext;
use crate::ParsedSyntax::{Absent, Present};
use crate::{JsParser, ParseRecoveryTokenSet, ParsedSyntax};
use biome_js_syntax::JsSyntaxKind::{EOF, JS_ARRAY_HOLE};
use biome_js_syntax::{JsSyntaxKind, TextRange, T};
use biome_parser::ParserProgress;
use super::class::parse_initializer_clause;
pub(crate) trait ParseWithDefaultPattern {
fn pattern_with_default_kind() -> JsSyntaxKind;
fn expected_pattern_error(p: &JsParser, range: TextRange) -> ParseDiagnostic;
fn parse_pattern(&self, p: &mut JsParser) -> ParsedSyntax;
fn parse_pattern_with_optional_default(&self, p: &mut JsParser) -> ParsedSyntax {
self.parse_pattern(p).and_then(|pattern| {
let m = pattern.precede(p);
parse_initializer_clause(p, ExpressionContext::default()).ok();
Present(m.complete(p, Self::pattern_with_default_kind()))
})
}
}
pub(crate) trait ParseArrayPattern<P: ParseWithDefaultPattern> {
fn bogus_pattern_kind() -> JsSyntaxKind;
fn array_pattern_kind() -> JsSyntaxKind;
fn rest_pattern_kind() -> JsSyntaxKind;
fn list_kind() -> JsSyntaxKind;
fn expected_element_error(p: &JsParser, range: TextRange) -> ParseDiagnostic;
fn pattern_with_default(&self) -> P;
fn parse_array_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
if !p.at(T!['[']) {
return Absent;
}
let m = p.start();
p.bump(T!['[']);
let elements = p.start();
let mut progress = ParserProgress::default();
{
while !p.at(EOF) && !p.at(T![']']) {
progress.assert_progressing(p);
let recovery = ParseRecoveryTokenSet::new(
Self::bogus_pattern_kind(),
token_set!(EOF, T![,], T![']'], T![=], T![;], T![...], T![')']),
)
.enable_recovery_on_line_break();
let element = self.parse_any_array_element(p, &recovery);
if element
.or_recover_with_token_set(p, &recovery, Self::expected_element_error)
.is_err()
{
break;
}
if !p.at(T![']']) {
p.expect(T![,]);
}
}
}
elements.complete(p, Self::list_kind());
p.expect(T![']']);
Present(m.complete(p, Self::array_pattern_kind()))
}
fn parse_any_array_element(
&self,
p: &mut JsParser,
recovery: &ParseRecoveryTokenSet<JsSyntaxKind>,
) -> ParsedSyntax {
match p.cur() {
T![,] => Present(p.start().complete(p, JS_ARRAY_HOLE)),
T![...] => self
.parse_rest_pattern(p)
.map(|rest_pattern| validate_rest_pattern(p, rest_pattern, T![']'], recovery)),
_ => self
.pattern_with_default()
.parse_pattern_with_optional_default(p),
}
}
fn parse_rest_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
if !p.at(T![...]) {
return Absent;
}
let m = p.start();
let rest_end = p.cur_range().end();
p.bump(T![...]);
let with_default = self.pattern_with_default();
with_default.parse_pattern(p).or_add_diagnostic(p, |p, _| {
P::expected_pattern_error(p, TextRange::new(rest_end, rest_end))
});
Present(m.complete(p, Self::rest_pattern_kind()))
}
}
pub(crate) trait ParseObjectPattern {
fn bogus_pattern_kind() -> JsSyntaxKind;
fn object_pattern_kind() -> JsSyntaxKind;
fn list_kind() -> JsSyntaxKind;
fn expected_property_pattern_error(p: &JsParser, range: TextRange) -> ParseDiagnostic;
fn parse_object_pattern(&self, p: &mut JsParser) -> ParsedSyntax {
if !p.at(T!['{']) {
return Absent;
}
let m = p.start();
p.bump(T!['{']);
let elements = p.start();
let mut progress = ParserProgress::default();
while !p.at(T!['}']) {
progress.assert_progressing(p);
if p.at(T![,]) {
p.error(Self::expected_property_pattern_error(p, p.cur_range()));
p.bump_any(); continue;
}
let recovery_set = ParseRecoveryTokenSet::new(
Self::bogus_pattern_kind(),
token_set!(EOF, T![,], T!['}'], T![...], T![;], T![')'], T![=]),
)
.enable_recovery_on_line_break();
let pattern = self.parse_any_property_pattern(p, &recovery_set);
if pattern
.or_recover_with_token_set(p, &recovery_set, Self::expected_property_pattern_error)
.is_err()
{
break;
}
if !p.at(T!['}']) {
p.expect(T![,]);
}
}
elements.complete(p, Self::list_kind());
p.expect(T!['}']);
Present(m.complete(p, Self::object_pattern_kind()))
}
fn parse_any_property_pattern(
&self,
p: &mut JsParser,
recovery: &ParseRecoveryTokenSet<JsSyntaxKind>,
) -> ParsedSyntax {
if p.at(T![...]) {
self.parse_rest_property_pattern(p)
.map(|rest_pattern| validate_rest_pattern(p, rest_pattern, T!['}'], recovery))
} else {
self.parse_property_pattern(p)
}
}
fn parse_property_pattern(&self, p: &mut JsParser) -> ParsedSyntax;
fn parse_rest_property_pattern(&self, p: &mut JsParser) -> ParsedSyntax;
}
fn validate_rest_pattern(
p: &mut JsParser,
mut rest: CompletedMarker,
end_token: JsSyntaxKind,
recovery: &ParseRecoveryTokenSet<JsSyntaxKind>,
) -> CompletedMarker {
if p.at(end_token) {
return rest;
}
if p.at(T![=]) {
let kind = rest.kind(p);
let rest_range = rest.range(p);
let rest_marker = rest.undo_completion(p);
let default_start = p.cur_range().start();
p.bump(T![=]);
if let Ok(recovered) = recovery.recover(p) {
recovered.undo_completion(p).abandon(p); }
p.error(
p.err_builder(
"rest element cannot have a default",
default_start..p.cur_range().start(),
)
.with_detail(
default_start..p.cur_range().start(),
"Remove the default value here",
)
.with_detail(rest_range, "Rest element"),
);
let mut invalid = rest_marker.complete(p, kind);
invalid.change_to_bogus(p);
invalid
} else if p.at(T![,]) && p.nth_at(1, end_token) {
p.error(
p.err_builder("rest element may not have a trailing comma", p.cur_range())
.with_detail(p.cur_range(), "Remove the trailing comma here")
.with_detail(rest.range(p), "Rest element"),
);
rest.change_to_bogus(p);
rest
} else {
p.error(
p.err_builder("rest element must be the last element", rest.range(p),)
.with_hint(
format!(
"Move the rest element to the end of the pattern, right before the closing '{}'",
end_token.to_string().unwrap(),
),
),
);
rest.change_to_bogus(p);
rest
}
}