From 22cd68b3fdacd2feb6933bceb2ba3746b8a93e2d Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 25 Nov 2021 14:27:16 +0100 Subject: [PATCH] feat(rslint_parser): ParsedSyntax, ConditionalParsedSyntax, and InvalidParsedSyntax Introduces the new `ParsedSyntax`, `ConditionalParsedSyntax`, and `InvalidParsedSyntax` that all require explicit error handling. See #1815 --- crates/rslint_parser/src/lib.rs | 96 ++++- crates/rslint_parser/src/parser.rs | 32 +- .../rslint_parser/src/parser/parse_error.rs | 91 +++++ .../src/parser/parse_recovery.rs | 90 +++++ .../rslint_parser/src/parser/parsed_syntax.rs | 364 ++++++++++++++++++ .../single_token_parse_recovery.rs} | 6 +- crates/rslint_parser/src/syntax.rs | 1 + crates/rslint_parser/src/syntax/class.rs | 62 +-- crates/rslint_parser/src/syntax/decl.rs | 17 +- crates/rslint_parser/src/syntax/expr.rs | 113 +++--- crates/rslint_parser/src/syntax/function.rs | 118 ++++-- .../src/syntax/js_parse_error.rs | 63 +++ crates/rslint_parser/src/syntax/object.rs | 197 +++++----- crates/rslint_parser/src/syntax/pat.rs | 25 +- crates/rslint_parser/src/syntax/program.rs | 28 +- crates/rslint_parser/src/syntax/stmt.rs | 225 ++++++----- crates/rslint_parser/src/syntax/typescript.rs | 37 +- crates/rslint_parser/src/syntax/util.rs | 1 + .../inline/err/block_stmt_in_class.rast | 7 +- .../test_data/inline/err/class_decl_err.rast | 14 +- .../test_data/inline/err/function_broken.rast | 5 +- .../test_data/inline/err/function_decl_err.js | 1 + .../inline/err/function_decl_err.rast | 31 +- .../inline/err/method_getter_err.rast | 7 +- .../err/object_expr_error_prop_name.rast | 18 +- .../inline/err/object_expr_method.rast | 10 +- .../paren_or_arrow_expr_invalid_params.rast | 8 + .../inline/ok/object_expr_async_method.rast | 7 +- .../ok/object_expr_generator_method.rast | 9 +- .../test_data/inline/ok/try_stmt.rast | 9 +- 30 files changed, 1307 insertions(+), 385 deletions(-) create mode 100644 crates/rslint_parser/src/parser/parse_error.rs create mode 100644 crates/rslint_parser/src/parser/parse_recovery.rs create mode 100644 crates/rslint_parser/src/parser/parsed_syntax.rs rename crates/rslint_parser/src/{parse_recovery.rs => parser/single_token_parse_recovery.rs} (94%) create mode 100644 crates/rslint_parser/src/syntax/js_parse_error.rs diff --git a/crates/rslint_parser/src/lib.rs b/crates/rslint_parser/src/lib.rs index 439e5e4c4b3..21aea96643a 100644 --- a/crates/rslint_parser/src/lib.rs +++ b/crates/rslint_parser/src/lib.rs @@ -64,7 +64,6 @@ mod lossless_tree_sink; mod lossy_tree_sink; mod numbers; mod parse; -pub(crate) mod parse_recovery; mod state; mod syntax_node; mod token_source; @@ -84,7 +83,7 @@ pub use crate::{ lossy_tree_sink::LossyTreeSink, numbers::BigInt, parse::*, - parser::{Checkpoint, CompletedMarker, Marker, Parser}, + parser::{Checkpoint, CompletedMarker, Marker, ParseRecovery, Parser}, state::{ParserState, StrictMode}, syntax_node::*, token_set::TokenSet, @@ -100,6 +99,8 @@ pub use rslint_syntax::*; /// It also includes labels and possibly notes pub type ParserError = rslint_errors::Diagnostic; +use crate::parser::{ConditionalParsedSyntax, ParsedSyntax}; +use rslint_errors::Diagnostic; use std::ops::Range; /// Abstracted token for `TokenSource` @@ -246,3 +247,94 @@ impl From for Syntax { Syntax::new(kind) } } + +/// A syntax feature that may or may not be supported depending on the file type and parser configuration +pub trait SyntaxFeature: Sized { + /// Returns `true` if the current parsing context supports this syntax feature. + fn is_supported(&self, p: &Parser) -> bool; + + /// Returns `true` if the current parsing context doesn't support this syntax feature. + fn is_unsupported(&self, p: &Parser) -> bool { + !self.is_supported(p) + } + + /// Creates a syntax that is only valid if this syntax feature is supported in the current + /// parsing context, adds a diagnostic if not. + /// + /// Returns [Valid] if this syntax feature is supported. + /// + /// Returns [Invalid], creates a diagnostic with the passed in error builder, + /// and adds it to the parsing diagnostics if this syntax feature isn't supported. + fn exclusive_syntax( + &self, + p: &mut Parser, + syntax: S, + error_builder: E, + ) -> ConditionalParsedSyntax + where + S: Into, + E: FnOnce(&Parser, &CompletedMarker) -> Diagnostic, + { + syntax.into().exclusive_for(self, p, error_builder) + } + + /// Creates a syntax that is only valid if this syntax feature is supported in the current + /// parsing context. + /// + /// Returns [Valid] if this syntax feature is supported and [Invalid] if this syntax isn't supported. + fn exclusive_syntax_no_error(&self, p: &Parser, syntax: S) -> ConditionalParsedSyntax + where + S: Into, + { + syntax.into().exclusive_for_no_error(self, p) + } + + /// Creates a syntax that is only valid if the current parsing context doesn't support this syntax feature, + /// and adds a diagnostic if it does. + /// + /// Returns [Valid] if the parsing context doesn't support this syntax feature + /// + /// Creates a diagnostic using the passed error builder, adds it to the parsing diagnostics, and returns + /// [Invalid] if the parsing context does support this syntax feature. + fn excluding_syntax( + &self, + p: &mut Parser, + syntax: S, + error_builder: E, + ) -> ConditionalParsedSyntax + where + S: Into, + E: FnOnce(&Parser, &CompletedMarker) -> Diagnostic, + { + syntax.into().excluding(self, p, error_builder) + } + + /// Creates a syntax that is only valid if this syntax feature isn't supported in the current + /// parsing context. + /// + /// Returns [Valid] if this syntax feature isn't supported and [Invalid] if it is. + fn excluding_syntax_no_error(&self, p: &Parser, syntax: S) -> ConditionalParsedSyntax + where + S: Into, + { + syntax.into().excluding_no_error(self, p) + } +} + +pub enum JsSyntaxFeature { + #[allow(unused)] + #[doc(alias = "LooseMode")] + SloppyMode, + StrictMode, + TypeScript, +} + +impl SyntaxFeature for JsSyntaxFeature { + fn is_supported(&self, p: &Parser) -> bool { + match self { + JsSyntaxFeature::SloppyMode => p.state.strict.is_none(), + JsSyntaxFeature::StrictMode => p.state.strict.is_some(), + JsSyntaxFeature::TypeScript => p.syntax.file_kind == FileKind::TypeScript, + } + } +} diff --git a/crates/rslint_parser/src/parser.rs b/crates/rslint_parser/src/parser.rs index 4c492435cef..6e022dc7c6d 100644 --- a/crates/rslint_parser/src/parser.rs +++ b/crates/rslint_parser/src/parser.rs @@ -3,6 +3,11 @@ //! the parser yields events like `Start node`, `Error`, etc. //! These events are then applied to a `TreeSink`. +pub(crate) mod parse_error; +mod parse_recovery; +mod parsed_syntax; +pub(crate) mod single_token_parse_recovery; + use drop_bomb::DropBomb; use rslint_errors::Diagnostic; use rslint_syntax::SyntaxKind::EOF; @@ -10,6 +15,12 @@ use std::borrow::BorrowMut; use std::cell::Cell; use std::ops::Range; +pub use parse_error::*; +pub use parsed_syntax::{ConditionalParsedSyntax, InvalidParsedSyntax, ParsedSyntax}; +#[allow(deprecated)] +pub use single_token_parse_recovery::SingleTokenParseRecovery; + +pub use crate::parser::parse_recovery::{ParseRecovery, RecoveryError, RecoveryResult}; use crate::*; /// An extremely fast, error tolerant, completely lossless JavaScript parser @@ -273,7 +284,7 @@ impl<'t> Parser<'t> { .expect("Parser source and tokens mismatch") } - /// Try to eat a specific token kind, if the kind is not there then add a missing marker and add an error to the events stack. + /// Try to eat a specific token kind, if the kind is not there then adds an error to the events stack. pub fn expect(&mut self, kind: SyntaxKind) -> bool { if self.eat(kind) { true @@ -297,12 +308,21 @@ impl<'t> Parser<'t> { .primary(self.cur_tok().range, "unexpected") }; - self.missing(); self.error(err); false } } + /// Tries to eat a specific token kind, adds a missing marker and an error to the events stack if it's not there. + pub fn expect_required(&mut self, kind: SyntaxKind) -> bool { + if !self.expect(kind) { + self.missing(); + false + } else { + true + } + } + /// Get the byte index range of a completed marker for error reporting. pub fn marker_range(&self, marker: &CompletedMarker) -> Range { match self.events[marker.start_pos as usize] { @@ -318,6 +338,7 @@ impl<'t> Parser<'t> { /// /// # Panics /// Panics if the AST node represented by the marker does not match the generic + #[deprecated(note = "Unsafe and fairly expensive.")] pub fn parse_marker(&self, marker: &CompletedMarker) -> T { let events = self .events @@ -428,7 +449,7 @@ impl<'t> Parser<'t> { if self.state.no_recovery { Some(true).filter(|_| self.eat(kind)) } else { - Some(self.expect(kind)) + Some(self.expect_required(kind)) } } @@ -455,6 +476,9 @@ impl<'t> Parser<'t> { start..end } + #[deprecated( + note = "Use ParseRecovery instead which signals with a Result if the recovery was successful or not" + )] pub fn expr_with_semi_recovery(&mut self, assign: bool) -> Option { let func = if assign { syntax::expr::assign_expr @@ -725,7 +749,7 @@ mod tests { let mut p = Parser::new(token_source, 0, Syntax::default()); let m = p.start(); - p.expect(SyntaxKind::JS_STRING_LITERAL); + p.expect_required(SyntaxKind::JS_STRING_LITERAL); m.complete(&mut p, SyntaxKind::JS_STRING_LITERAL_EXPRESSION); } diff --git a/crates/rslint_parser/src/parser/parse_error.rs b/crates/rslint_parser/src/parser/parse_error.rs new file mode 100644 index 00000000000..6ad79b8511c --- /dev/null +++ b/crates/rslint_parser/src/parser/parse_error.rs @@ -0,0 +1,91 @@ +use crate::Parser; +use rslint_errors::{Diagnostic, Span}; +use std::ops::Range; + +///! Provides helper functions to build common diagnostic messages + +/// Creates a diagnostic saying that the node [name] was expected at range +pub(crate) fn expected_node(name: &str, range: Range) -> ExpectedNodeDiagnosticBuilder { + ExpectedNodeDiagnosticBuilder::with_single_node(name, range) +} + +/// Creates a diagnostic saying that any of the nodes in [names] was expected at range +pub(crate) fn expected_any(names: &[&str], range: Range) -> ExpectedNodeDiagnosticBuilder { + ExpectedNodeDiagnosticBuilder::with_any(names, range) +} + +pub trait ToDiagnostic { + fn to_diagnostic(&self, p: &Parser) -> Diagnostic; +} + +pub struct ExpectedNodeDiagnosticBuilder { + names: String, + range: Range, +} + +impl ExpectedNodeDiagnosticBuilder { + fn with_single_node(name: &str, range: Range) -> Self { + ExpectedNodeDiagnosticBuilder { + names: format!("{} {}", article_for(name), name), + range, + } + } + + fn with_any(names: &[&str], range: Range) -> Self { + debug_assert!(names.len() > 1, "Requires at least 2 names"); + + if names.len() < 2 { + return Self::with_single_node(names.first().unwrap_or(&""), range); + } + + let mut joined_names = String::new(); + + for (index, name) in names.iter().enumerate() { + if index > 0 { + joined_names.push_str(", "); + } + + if index == names.len() - 1 { + joined_names.push_str("or "); + } + + joined_names.push_str(article_for(name)); + joined_names.push(' '); + joined_names.push_str(name); + } + + Self { + names: joined_names, + range, + } + } +} + +impl ToDiagnostic for ExpectedNodeDiagnosticBuilder { + fn to_diagnostic(&self, p: &Parser) -> Diagnostic { + let range = &self.range; + + let msg = if range.is_empty() && p.tokens.source().get(range.to_owned()) == None { + format!( + "expected {} but instead found the end of the file", + self.names + ) + } else { + format!( + "expected {} but instead found '{}'", + self.names, + p.source(range.as_text_range()) + ) + }; + + let diag = p.err_builder(&msg); + diag.primary(&self.range, format!("Expected {} here", self.names)) + } +} + +fn article_for(name: &str) -> &'static str { + match name.chars().next() { + Some('a' | 'e' | 'i' | 'o' | 'u') => "an", + _ => "a", + } +} diff --git a/crates/rslint_parser/src/parser/parse_recovery.rs b/crates/rslint_parser/src/parser/parse_recovery.rs new file mode 100644 index 00000000000..a2bdb538656 --- /dev/null +++ b/crates/rslint_parser/src/parser/parse_recovery.rs @@ -0,0 +1,90 @@ +use crate::{CompletedMarker, Parser, TokenSet}; +use rslint_syntax::SyntaxKind; +use rslint_syntax::SyntaxKind::EOF; +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum RecoveryError { + /// Recovery failed because the parser reached the end of file + Eof, + + /// Recovery failed because it didn't eat any tokens. Meaning, the parser is already in a recovered state. + /// This is an error because: + /// a) It shouldn't create a completed marker wrapping no tokens + /// b) This results in an infinite-loop if the recovery is used inside of a while loop. For example, + /// it's common that list parsing also recovers at the end of a statement or block. However, list elements + /// don't start with a `;` or `}` which is why parsing, for example, an array element fails again and + /// the array expression triggers another recovery. Handling this as an error ensures that list parsing + /// rules break out of the loop the same way as they would at the EOF. + AlreadyRecovered, +} + +impl Error for RecoveryError {} + +impl Display for RecoveryError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RecoveryError::Eof => write!(f, "EOF"), + RecoveryError::AlreadyRecovered => write!(f, "already recovered"), + } + } +} + +pub type RecoveryResult = Result; + +/// Recovers the parser by finding a token/point (depending on the configuration) from where +/// the caller knows how to proceed parsing. The recovery wraps all the skipped tokens inside of an `Unknown` node. +/// A safe recovery point for an array element could by finding the next `,` or `]`. +pub struct ParseRecovery { + node_kind: SyntaxKind, + recovery_set: TokenSet, + line_break: bool, +} + +impl ParseRecovery { + /// Creates a new parse recovery that eats all tokens until it finds any token in the passed recovery set. + pub fn new(node_kind: SyntaxKind, recovery_set: TokenSet) -> Self { + Self { + node_kind, + recovery_set, + line_break: false, + } + } + + /// Enable recovery on line breaks + pub fn enable_recovery_on_line_break(mut self) -> Self { + self.line_break = true; + self + } + + // TODO: Add a `recover_until` which recovers until the parser reached a token inside of the recovery set + // or the passed in `parse_*` rule was able to successfully parse an element. + + /// Tries to recover by parsing all tokens into an `Unknown*` node until the parser finds any token + /// specified in the recovery set, the EOF, or a line break (depending on configuration). + /// Returns `Ok(unknown_node)` if recovery was successful, and `Err(RecoveryError::Eof)` if the parser + /// is at the end of the file (before starting recovery). + pub fn recover(&self, p: &mut Parser) -> RecoveryResult { + if p.at(EOF) { + return Err(RecoveryError::Eof); + } + + if self.recovered(p) { + return Err(RecoveryError::AlreadyRecovered); + } + + let m = p.start(); + + while !self.recovered(p) { + p.bump_any(); + } + + Ok(m.complete(p, self.node_kind)) + } + + #[inline] + fn recovered(&self, p: &Parser) -> bool { + p.at_ts(self.recovery_set) || p.at(EOF) || (self.line_break && p.has_linebreak_before_n(0)) + } +} diff --git a/crates/rslint_parser/src/parser/parsed_syntax.rs b/crates/rslint_parser/src/parser/parsed_syntax.rs new file mode 100644 index 00000000000..012886501e1 --- /dev/null +++ b/crates/rslint_parser/src/parser/parsed_syntax.rs @@ -0,0 +1,364 @@ +use crate::parser::parse_recovery::RecoveryResult; +use crate::parser::ConditionalParsedSyntax::{Invalid, Valid}; +use crate::parser::ParseRecovery; +use crate::parser::ParsedSyntax::{Absent, Present}; +use crate::{CompletedMarker, Marker, Parser, SyntaxFeature}; +use rslint_errors::{Diagnostic, Span}; +use rslint_syntax::SyntaxKind; +use std::ops::Range; + +/// Result of a parse function. +/// +/// A parse rule should return [Present] if it is able to parse a node or at least parts of it. For example, +/// the `parse_for_statement` should return [Present] for `for (` even tough many of the required children are missing +/// because it is still able to parse parts of the for statement. +/// +/// A parse rule must return [Absent] if the expected node isn't present in the source code. +/// In most cases, this means if the first expected token isn't present, for example, +/// if the `for` keyword isn't present when parsing a for statement. +/// However, it can be possible for rules to recover even if the first token doesn't match. One example +/// is when parsing an assignment target that has an optional default. The rule can recover even +/// if the assignment target is missing as long as the cursor is then positioned at an `=` token. +/// The rule must then return [Present] with the partial parsed node. +/// +/// A parse rule must rewind the parser and return [Absent] if it started parsing an incomplete node but +/// in the end can't determine its type to ensure that the caller can do a proper error recovery. +/// +/// This is a custom enum over using `Option` because [Absent] values must be handled by the caller. +#[derive(Debug, Clone, PartialEq, Eq)] +#[must_use = "this `ParsedSyntax` may be an `Absent` variant, which should be handled"] +pub enum ParsedSyntax { + /// A syntax that isn't present in the source code. Used when a parse rule can't match the current + /// token of the parser. + Absent, + + /// A completed syntax node with all or some of its children. + Present(CompletedMarker), +} + +impl ParsedSyntax { + /// Converts from `ParsedSyntax` to `Option`. + /// + /// Converts `self` into an `Option`, consuming `self` + pub fn ok(self) -> Option { + match self { + Absent => None, + Present(marker) => Some(marker), + } + } + + /// Returns `true` if the parsed syntax is [Present] + #[must_use] + pub fn is_present(&self) -> bool { + matches!(self, Present(_)) + } + + /// Returns `true` if the parsed syntax is [Absent] + #[must_use] + pub fn is_absent(&self) -> bool { + matches!(self, Absent) + } + + /// It returns the syntax if present or adds a missing marker and a diagnostic at the current parser position. + pub fn or_missing_with_error( + self, + p: &mut Parser, + error_builder: E, + ) -> Option + where + E: FnOnce(&Parser, Range) -> Diagnostic, + { + match self { + Present(syntax) => Some(syntax), + Absent => { + p.missing(); + let diagnostic = error_builder(p, p.cur_tok().range); + p.error(diagnostic); + None + } + } + } + + /// It returns the syntax if present or adds a missing marker. + pub fn or_missing(self, p: &mut Parser) -> Option { + match self { + Present(syntax) => Some(syntax), + Absent => { + p.missing(); + None + } + } + } + + /// It creates and returns a marker preceding this parsed syntax if it is present or starts + /// a new marker, marks the first slot as missing and adds an error to the current parser position. + /// See [CompletedMarker.precede] + #[must_use] + pub fn precede_or_missing_with_error(self, p: &mut Parser, error_builder: E) -> Marker + where + E: FnOnce(&Parser, Range) -> Diagnostic, + { + match self { + Present(completed) => completed.precede(p), + Absent => { + let diagnostic = error_builder(p, p.cur_tok().range); + p.error(diagnostic); + + let m = p.start(); + p.missing(); + m + } + } + } + + /// It creates and returns a marker preceding this parsed syntax if it is present or starts a new marker + /// and marks its first slot as missing. + /// + /// See [CompletedMarker.precede] + #[must_use] + pub fn precede_or_missing(self, p: &mut Parser) -> Marker { + match self { + Present(completed) => completed.precede(p), + Absent => { + let m = p.start(); + p.missing(); + m + } + } + } + + /// Returns this Syntax if it is present in the source text or tries to recover the + /// parser if the syntax is absent. The recovery... + /// + /// * eats all unexpected tokens into an `Unknown*` node until the parser reaches one + /// of the "safe tokens" configured in the [ParseRecovery]. + /// * creates an error using the passed in error builder and adds it to the parsing diagnostics. + /// + /// The error recovery can fail if the parser is located at the EOF token or if the parser + /// is already at a valid position according to the [ParseRecovery]. + pub fn or_recover( + self, + p: &mut Parser, + recovery: ParseRecovery, + error_builder: E, + ) -> RecoveryResult + where + E: FnOnce(&Parser, Range) -> Diagnostic, + { + match self { + Present(syntax) => Ok(syntax), + Absent => match recovery.recover(p) { + Ok(recovered) => { + let diagnostic = error_builder(p, recovered.range(p).as_range()); + p.error(diagnostic); + Ok(recovered) + } + + Err(recovery_error) => { + let diagnostic = error_builder(p, p.cur_tok().range); + p.error(diagnostic); + Err(recovery_error) + } + }, + } + } + + /// Restricts this parsed syntax to only be valid if the current parsing context supports the passed in language feature + /// and adds a diagnostic if not. + /// + /// Returns [Valid] if the parsing context supports the passed syntax feature. + /// + /// Creates a diagnostic using the passed error builder, adds it to the parsing diagnostics, and returns + /// [Invalid] if the parsing context doesn't support the passed syntax feature. + pub fn exclusive_for( + self, + feature: &F, + p: &mut Parser, + error_builder: E, + ) -> ConditionalParsedSyntax + where + F: SyntaxFeature, + E: FnOnce(&Parser, &CompletedMarker) -> Diagnostic, + { + if feature.is_supported(p) { + Valid(self) + } else { + if let Present(marker) = &self { + let diagnostic = error_builder(p, marker); + p.error(diagnostic); + } + + Invalid(InvalidParsedSyntax(self)) + } + } + + /// Restricts this parsed syntax to only be valid if the current parsing context supports the passed in language feature. + /// + /// Returns [Valid] if the parsing context supports the passed syntax feature. + /// + /// Returns [Invalid] if the parsing context doesn't support the passed syntax feature. + pub fn exclusive_for_no_error(self, feature: &F, p: &Parser) -> ConditionalParsedSyntax + where + F: SyntaxFeature, + { + if feature.is_supported(p) { + Valid(self) + } else { + Invalid(InvalidParsedSyntax(self)) + } + } + + /// Restricts this parsed syntax to only be valid if the current parsing context doesn't support the passed in language feature + /// and adds a diagnostic if it does. + /// + /// Returns [Valid] if the parsing context doesn't support the passed syntax feature. + /// + /// Creates a diagnostic using the passed error builder, adds it to the parsing diagnostics, and returns + /// [Invalid] if the parsing context does support the passed syntax feature. + pub fn excluding( + self, + feature: &F, + p: &mut Parser, + error_builder: E, + ) -> ConditionalParsedSyntax + where + F: SyntaxFeature, + E: FnOnce(&Parser, &CompletedMarker) -> Diagnostic, + { + if feature.is_unsupported(p) { + Valid(self) + } else { + if let Present(marker) = &self { + let diagnostic = error_builder(p, marker); + p.error(diagnostic); + } + + Invalid(InvalidParsedSyntax(self)) + } + } + + /// Restricts this parsed syntax to only be valid if the current parsing context doesn't support the passed in language feature. + /// + /// Returns [Valid] if the parsing context doesn't support the passed syntax feature. + /// + /// Returns [Invalid] if the parsing context does support the passed syntax feature. + pub fn excluding_no_error(self, feature: &F, p: &Parser) -> ConditionalParsedSyntax + where + F: SyntaxFeature, + { + if feature.is_unsupported(p) { + Valid(self) + } else { + Invalid(InvalidParsedSyntax(self)) + } + } +} + +impl From for ParsedSyntax { + fn from(marker: CompletedMarker) -> Self { + Present(marker) + } +} + +impl From> for ParsedSyntax { + fn from(option: Option) -> Self { + match option { + Some(completed) => Present(completed), + None => Absent, + } + } +} + +/// A parsed syntax that is only valid in some parsing contexts but not in others. +/// One use case for this is for syntax that is only valid if the parsing context supports +/// a certain language feature, for example: +/// +/// * Syntax that is only supported in strict or sloppy mode: for example, `with` statements +/// * Syntax that is only supported in certain file types: Typescript, JSX, Import / Export statements +/// * Syntax that is only available in certain language versions: experimental features, private field existence test +/// +/// A parse rule must explicitly handle conditional syntax in the case it is invalid because it +/// represents content that shouldn't be there. This normally involves to wrap this syntax in an +/// `Unknown*` node or one of its parent. +#[derive(Debug, Clone, PartialEq, Eq)] +#[must_use = "this `ConditionalParsedSyntax` may be an `Invalid` variant, which should be handled"] +pub enum ConditionalParsedSyntax { + /// Syntax that is valid in the current parsing context + Valid(ParsedSyntax), + + /// Syntax that is invalid in the current parsing context because it doesn't support a specific + /// language feature. + Invalid(InvalidParsedSyntax), +} + +impl ConditionalParsedSyntax { + /// Returns `true` if this syntax is valid in this parsing context. + #[allow(unused)] + #[must_use] + pub fn is_valid(&self) -> bool { + matches!(self, Invalid(_)) + } + + /// Returns `true` if this syntax is invalid in this parsing context. + #[allow(unused)] + pub fn is_invalid(&self) -> bool { + matches!(self, Valid(_)) + } + + /// Returns `true` if this syntax is present in the source text. + #[must_use] + pub fn is_present(&self) -> bool { + matches!( + self, + Valid(Present(_)) | Invalid(InvalidParsedSyntax(Present(_))) + ) + } + + /// Returns `true` if this syntax is absent from the source text. + pub fn is_absent(&self) -> bool { + matches!(self, Valid(Absent) | Invalid(InvalidParsedSyntax(Absent))) + } + + /// Converts this into a parsed syntax by wrapping any present invalid syntax in an unknown node. + pub fn or_invalid_to_unknown(self, p: &mut Parser, unknown_kind: SyntaxKind) -> ParsedSyntax { + match self { + Valid(parsed) => parsed, + Invalid(unsupported) => unsupported.or_to_unknown(p, unknown_kind), + } + } +} + +/// Parsed syntax that is invalid in this parsing context. +#[must_use = "this 'UnsupportedParsedSyntax' contains syntax not supported in this parsing context, which must be handled."] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InvalidParsedSyntax(ParsedSyntax); + +impl InvalidParsedSyntax { + pub fn new(syntax: ParsedSyntax) -> Self { + Self(syntax) + } + + /// Converts this into a parsed syntax by wrapping any present invalid syntax in an unknown node. + /// Is a no-op if the syntax is absent in the source text. + pub fn or_to_unknown(self, p: &mut Parser, unknown_kind: SyntaxKind) -> ParsedSyntax { + match self.0 { + Absent => Absent, + Present(mut unsupported) => { + unsupported.change_kind(p, unknown_kind); + Present(unsupported) + } + } + } +} + +impl From for InvalidParsedSyntax { + fn from(syntax: ParsedSyntax) -> Self { + InvalidParsedSyntax::new(syntax) + } +} + +impl From for InvalidParsedSyntax { + fn from(marker: CompletedMarker) -> Self { + InvalidParsedSyntax::new(Present(marker)) + } +} diff --git a/crates/rslint_parser/src/parse_recovery.rs b/crates/rslint_parser/src/parser/single_token_parse_recovery.rs similarity index 94% rename from crates/rslint_parser/src/parse_recovery.rs rename to crates/rslint_parser/src/parser/single_token_parse_recovery.rs index 0ec50015e7b..1fc9b39c4af 100644 --- a/crates/rslint_parser/src/parse_recovery.rs +++ b/crates/rslint_parser/src/parser/single_token_parse_recovery.rs @@ -6,7 +6,8 @@ use rslint_lexer::{SyntaxKind, T}; /// /// By default it doesn't check curly braces, use [with_braces_included] to turn opt-in the check #[derive(Debug)] -pub struct ParseRecovery { +#[deprecated(note = "Use ParsedSyntax with ParseRecovery instead")] +pub struct SingleTokenParseRecovery { /// The [Diagnostic] to emit error: Option, /// It tells the parser to recover if the position is inside a set of [tokens](TokenSet) @@ -18,7 +19,8 @@ pub struct ParseRecovery { unknown_node_kind: SyntaxKind, } -impl ParseRecovery { +#[allow(deprecated)] +impl SingleTokenParseRecovery { pub fn new(recovery: TokenSet, unknown_node_kind: SyntaxKind) -> Self { Self { error: None, diff --git a/crates/rslint_parser/src/syntax.rs b/crates/rslint_parser/src/syntax.rs index 085d53a7c31..b5d4cd860d8 100644 --- a/crates/rslint_parser/src/syntax.rs +++ b/crates/rslint_parser/src/syntax.rs @@ -11,6 +11,7 @@ mod class; pub mod decl; pub mod expr; mod function; +mod js_parse_error; mod object; pub mod pat; pub mod program; diff --git a/crates/rslint_parser/src/syntax/class.rs b/crates/rslint_parser/src/syntax/class.rs index e396ef25683..f7793f10999 100644 --- a/crates/rslint_parser/src/syntax/class.rs +++ b/crates/rslint_parser/src/syntax/class.rs @@ -1,7 +1,10 @@ -use crate::parse_recovery::ParseRecovery; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; +use crate::parser::ParsedSyntax; use crate::syntax::decl::{formal_param_pat, parameter_list, parameters_list}; use crate::syntax::expr::assign_expr; use crate::syntax::function::{function_body, ts_parameter_types, ts_return_type}; +use crate::syntax::js_parse_error; use crate::syntax::object::{computed_member_name, literal_member_name}; use crate::syntax::pat::opt_binding_identifier; use crate::syntax::stmt::{block_impl, is_semi, optional_semi}; @@ -9,6 +12,7 @@ use crate::syntax::typescript::{ abstract_readonly_modifiers, maybe_ts_type_annotation, try_parse_index_signature, ts_heritage_clause, ts_modifier, ts_type_params, DISALLOWED_TYPE_NAMES, }; +use crate::ParsedSyntax::Present; use crate::{CompletedMarker, Event, Marker, Parser, ParserState, StrictMode, TokenSet}; use rslint_syntax::SyntaxKind::*; use rslint_syntax::{SyntaxKind, T}; @@ -54,7 +58,7 @@ impl From for SyntaxKind { fn class(p: &mut Parser, kind: ClassKind) -> CompletedMarker { let m = p.start(); - p.expect(T![class]); + p.expect_required(T![class]); // class bodies are implicitly strict let mut guard = p.with_state(ParserState { @@ -103,9 +107,9 @@ fn class(p: &mut Parser, kind: ClassKind) -> CompletedMarker { extends_clause(&mut guard); implements_clause(&mut guard); - guard.expect(T!['{']); + guard.expect_required(T!['{']); class_members(&mut *guard); - guard.expect(T!['}']); + guard.expect_required(T!['}']); m.complete(&mut *guard, kind.into()) } @@ -229,10 +233,10 @@ fn class_member(p: &mut Parser) -> CompletedMarker { if declare && !has_access_modifier { // declare() and declare: foo if is_method_class_member(p, offset) { - literal_member_name(p); // bump declare as identifier + literal_member_name(p).ok().unwrap(); // bump declare as identifier return method_class_member_body(p, member_marker); } else if is_property_class_member(p, offset) { - literal_member_name(p); // bump declare as identifier + literal_member_name(p).ok().unwrap(); // bump declare as identifier return property_class_member_body(p, member_marker); } else { let msg = if p.typescript() { @@ -423,7 +427,8 @@ fn class_member(p: &mut Parser) -> CompletedMarker { member_name, "constructor" | "\"constructor\"" | "'constructor'" ); - let member = class_member_name(p); + let member = + class_member_name(p).or_missing_with_error(p, js_parse_error::expected_class_member_name); if is_method_class_member(p, 0) { if let Some(range) = readonly_range.clone() { @@ -538,19 +543,22 @@ fn class_member(p: &mut Parser) -> CompletedMarker { } // So we've seen a get that now must be followed by a getter/setter name - class_member_name(p); - p.expect(T!['(']); + class_member_name(p) + .or_missing_with_error(p, js_parse_error::expected_class_member_name); + p.expect_required(T!['(']); let completed = if is_getter { - p.expect(T![')']); + p.expect_required(T![')']); ts_return_type(p); - function_body(p); + function_body(p) + .or_missing_with_error(p, js_parse_error::expected_function_body); member_marker.complete(p, JS_GETTER_CLASS_MEMBER) } else { formal_param_pat(p); - p.expect(T![')']); - function_body(p); + p.expect_required(T![')']); + function_body(p) + .or_missing_with_error(p, js_parse_error::expected_function_body); member_marker.complete(p, JS_SETTER_CLASS_MEMBER) }; @@ -563,7 +571,8 @@ fn class_member(p: &mut Parser) -> CompletedMarker { let err = p .err_builder("expected `;`, a property, or a method for a class body, but found none") .primary(p.cur_tok().range, ""); - ParseRecovery::with_error( + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( token_set![T![;], T![ident], T![async], T![yield], T!['}'], T![#]], JS_UNKNOWN_MEMBER, err, @@ -704,7 +713,7 @@ fn is_method_class_member(p: &Parser, mut offset: usize) -> bool { } fn method_class_member(p: &mut Parser, m: Marker) -> CompletedMarker { - class_member_name(p); + class_member_name(p).or_missing_with_error(p, js_parse_error::expected_function_body); method_class_member_body(p, m) } @@ -714,7 +723,7 @@ fn method_class_member_body(p: &mut Parser, m: Marker) -> CompletedMarker { ts_parameter_types(p); parameter_list(p); ts_return_type(p); - function_body(p); + function_body(p).or_missing_with_error(p, js_parse_error::expected_function_body); m.complete(p, JS_METHOD_CLASS_MEMBER) } @@ -758,7 +767,10 @@ fn constructor_class_member_body(p: &mut Parser, member_marker: Marker) -> Compl ..p.state.clone() }); - block_impl(&mut guard, JS_FUNCTION_BODY, None); + let p = &mut *guard; + + block_impl(p, JS_FUNCTION_BODY) + .or_missing_with_error(p, js_parse_error::expected_function_body); } // FIXME(RDambrosio016): if there is no body we need to issue errors for any assign patterns @@ -832,20 +844,18 @@ fn ts_access_modifier<'a>(p: &'a Parser) -> Option<&'a str> { } /// Parses a `JsAnyClassMemberName` and returns its completion marker -fn class_member_name(p: &mut Parser) -> Option { - let result = match p.cur() { - T![#] => private_class_member_name(p), +fn class_member_name(p: &mut Parser) -> ParsedSyntax { + match p.cur() { + T![#] => Present(private_class_member_name(p)), T!['['] => computed_member_name(p), - _ => literal_member_name(p)?, - }; - - Some(result) + _ => literal_member_name(p), + } } pub(crate) fn private_class_member_name(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T![#]); - p.expect(T![ident]); + p.expect_required(T![#]); + p.expect_required(T![ident]); m.complete(p, JS_PRIVATE_CLASS_MEMBER_NAME) } diff --git a/crates/rslint_parser/src/syntax/decl.rs b/crates/rslint_parser/src/syntax/decl.rs index 3915057279a..34b0b247f89 100644 --- a/crates/rslint_parser/src/syntax/decl.rs +++ b/crates/rslint_parser/src/syntax/decl.rs @@ -3,7 +3,9 @@ use super::expr::assign_expr; use super::pat::pattern; use super::typescript::*; -use crate::parse_recovery::ParseRecovery; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; +use crate::parser::ParsedSyntax; use crate::syntax::function::function_body; use crate::{SyntaxKind::*, *}; @@ -114,7 +116,7 @@ pub(super) fn parameters_list( ) { let mut first = true; - p.state.allow_object_expr = p.expect(T!['(']); + p.state.allow_object_expr = p.expect_required(T!['(']); let parameters_list = p.start(); @@ -125,7 +127,7 @@ pub(super) fn parameters_list( p.eat(T![,]); break; } else { - p.expect(T![,]); + p.expect_required(T![,]); } if p.at(T![...]) { @@ -201,7 +203,8 @@ pub(super) fn parameters_list( } else { // test_err formal_params_invalid // function (a++, c) {} - ParseRecovery::new( + #[allow(deprecated)] + SingleTokenParseRecovery::new( token_set![ T![ident], T![await], @@ -222,10 +225,10 @@ pub(super) fn parameters_list( parameters_list.complete(p, LIST); p.state.allow_object_expr = true; - p.expect(T![')']); + p.expect_required(T![')']); } -pub(super) fn arrow_body(p: &mut Parser) -> Option { +pub(super) fn arrow_body(p: &mut Parser) -> ParsedSyntax { let mut guard = p.with_state(ParserState { in_function: true, ..p.state.clone() @@ -233,6 +236,6 @@ pub(super) fn arrow_body(p: &mut Parser) -> Option { if guard.at(T!['{']) { function_body(&mut *guard) } else { - assign_expr(&mut *guard) + assign_expr(&mut *guard).into() } } diff --git a/crates/rslint_parser/src/syntax/expr.rs b/crates/rslint_parser/src/syntax/expr.rs index 78c3691d948..dd38a34fc56 100644 --- a/crates/rslint_parser/src/syntax/expr.rs +++ b/crates/rslint_parser/src/syntax/expr.rs @@ -7,9 +7,11 @@ use super::decl::{arrow_body, parameter_list}; use super::pat::pattern; use super::typescript::*; use super::util::*; -use crate::parse_recovery::ParseRecovery; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; use crate::syntax::class::class_expression; use crate::syntax::function::function_expression; +use crate::syntax::js_parse_error; use crate::syntax::object::object_expr; use crate::syntax::stmt::is_semi; use crate::{SyntaxKind::*, *}; @@ -249,7 +251,7 @@ fn assign_expr_recursive( // } pub fn yield_expr(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T![yield]); + p.expect_required(T![yield]); if !is_semi(p, 0) && (p.at(T![*]) || p.at_ts(STARTS_EXPR)) { p.eat(T![*]); @@ -276,7 +278,7 @@ pub fn conditional_expr(p: &mut Parser) -> Option { in_cond_expr: true, ..p.state.clone() })); - p.expect(T![:]); + p.expect_required(T![:]); assign_expr(p); return Some(m.complete(p, JS_CONDITIONAL_EXPRESSION)); } @@ -397,9 +399,9 @@ fn binary_or_logical_expression_recursive( /// `"(" Expr ")"` pub fn paren_expr(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T!['(']); + p.expect_required(T!['(']); expr(p); - p.expect(T![')']); + p.expect_required(T![')']); m.complete(p, JS_PARENTHESIZED_EXPRESSION) } @@ -492,7 +494,7 @@ pub fn member_or_new_expr(p: &mut Parser, new_expr: bool) -> Option CompletedMarker { let super_marker = p.start(); - p.expect(T![super]); + p.expect_required(T![super]); super_marker.complete(p, JS_SUPER_EXPRESSION) } @@ -582,7 +584,7 @@ pub fn static_member_expression( operator: SyntaxKind, ) -> CompletedMarker { let m = lhs.precede(p); - p.expect(operator); + p.expect_required(operator); let member_name = any_reference_member(p); @@ -617,8 +619,8 @@ fn reference_identifier_member(p: &mut Parser) -> Option { fn reference_private_member(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T![#]); - p.expect(T![ident]); + p.expect_required(T![#]); + p.expect_required(T![ident]); m.complete(p, JS_REFERENCE_PRIVATE_MEMBER) } @@ -640,12 +642,12 @@ pub fn computed_member_expression( // foo[ let m = lhs.precede(p); if optional_chain { - p.expect(T![?.]); + p.expect_required(T![?.]); } - p.expect(T!['[']); + p.expect_required(T!['[']); expr(p); - p.expect(T![']']); + p.expect_required(T![']']); m.complete(p, JS_COMPUTED_MEMBER_EXPRESSION) } @@ -676,7 +678,7 @@ pub fn identifier_name(p: &mut Parser) -> Option { // foo(a,b var pub fn args(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T!['(']); + p.expect_required(T!['(']); let args_list = p.start(); while !p.at(EOF) && !p.at(T![')']) { @@ -694,7 +696,7 @@ pub fn args(p: &mut Parser) -> CompletedMarker { } args_list.complete(p, LIST); - p.expect(T![')']); + p.expect_required(T![')']); m.complete(p, ARG_LIST) } @@ -712,7 +714,7 @@ pub fn paren_or_arrow_expr(p: &mut Parser, can_be_arrow: bool) -> CompletedMarke let m = p.start(); let checkpoint = p.checkpoint(); let start = p.cur_tok().range.start; - p.expect(T!['(']); + p.expect_required(T!['(']); let mut spread_range = None; let mut trailing_comma_marker = None; let mut params_marker = None; @@ -747,13 +749,18 @@ pub fn paren_or_arrow_expr(p: &mut Parser, can_be_arrow: bool) -> CompletedMarke if temp.eat(T![=]) { // formal params will handle this error assign_expr(&mut *temp); - temp.expect(T![')']); + temp.expect_required(T![')']); } else { let err = temp.err_builder(&format!("expect a closing parenthesis after a spread element, but instead found `{}`", temp.cur_src())) .primary(temp.cur_tok().range, ""); - ParseRecovery::with_error(EXPR_RECOVERY_SET, JS_UNKNOWN_PATTERN, err) - .recover(&mut temp); + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( + EXPR_RECOVERY_SET, + JS_UNKNOWN_PATTERN, + err, + ) + .recover(&mut temp); } } break; @@ -782,7 +789,7 @@ pub fn paren_or_arrow_expr(p: &mut Parser, can_be_arrow: bool) -> CompletedMarke temp.bump_any(); // bump ; into sequence expression which may or may not miss a lhs } } else { - temp.expect(T![')']); + temp.expect_required(T![')']); break; } } @@ -812,8 +819,7 @@ pub fn paren_or_arrow_expr(p: &mut Parser, can_be_arrow: bool) -> CompletedMarke } } p.expect_no_recover(T![=>])?; - arrow_body(p)?; - Some(()) + arrow_body(p).ok() }; // we can't just rewind the parser, since the function rewinds, and cloning and replacing the // events does not work apparently, therefore we need to clone the entire parser @@ -854,7 +860,7 @@ pub fn paren_or_arrow_expr(p: &mut Parser, can_be_arrow: bool) -> CompletedMarke } p.bump_any(); - arrow_body(p); + arrow_body(p).or_missing_with_error(p, js_parse_error::expected_arrow_body); return m.complete(p, JS_ARROW_FUNCTION_EXPRESSION); } } @@ -986,11 +992,18 @@ pub fn primary_expr(p: &mut Parser) -> Option { ); } } - p.expect(T![=>]); - arrow_body(&mut *p.with_state(ParserState { - in_async: true, - ..p.state.clone() - })); + p.expect_required(T![=>]); + { + let mut guard = p.with_state(ParserState { + in_async: true, + ..p.state.clone() + }); + arrow_body(&mut *guard).or_missing_with_error( + &mut *guard, + js_parse_error::expected_arrow_body, + ); + } + m.complete(p, JS_ARROW_FUNCTION_EXPRESSION) } else { reference_identifier_expression(p)? @@ -1021,7 +1034,7 @@ pub fn primary_expr(p: &mut Parser) -> Option { ident.change_kind(p, JS_IDENTIFIER_BINDING); let m = ident.precede(p); p.bump_any(); - arrow_body(p); + arrow_body(p).or_missing_with_error(p, js_parse_error::expected_arrow_body); m.complete(p, JS_ARROW_FUNCTION_EXPRESSION) } else { ident @@ -1071,9 +1084,9 @@ pub fn primary_expr(p: &mut Parser) -> Option { // test import_call // import("foo") - p.expect(T!['(']); + p.expect_required(T!['(']); assign_expr(p); - p.expect(T![')']); + p.expect_required(T![')']); m.complete(p, JS_IMPORT_CALL_EXPRESSION) } } @@ -1089,9 +1102,14 @@ pub fn primary_expr(p: &mut Parser) -> Option { let err = p .err_builder("Expected an expression, but found none") .primary(p.cur_tok().range, "Expected an expression here"); - ParseRecovery::with_error(p.state.expr_recovery_set, JS_UNKNOWN_EXPRESSION, err) - .enabled_braces_check() - .recover(p); + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( + p.state.expr_recovery_set, + JS_UNKNOWN_EXPRESSION, + err, + ) + .enabled_braces_check() + .recover(p); return None; } }; @@ -1111,9 +1129,14 @@ pub fn reference_identifier_expression(p: &mut Parser) -> Option Option) -> CompletedMarker { let m = tag.map(|m| m.precede(p)).unwrap_or_else(|| p.start()); - p.expect(BACKTICK); + p.expect_required(BACKTICK); let elements_list = p.start(); while !p.at(EOF) && !p.at(BACKTICK) { @@ -1137,7 +1160,7 @@ pub fn template(p: &mut Parser, tag: Option) -> CompletedMarker let e = p.start(); p.bump_any(); expr(p); - p.expect(T!['}']); + p.expect_required(T!['}']); e.complete(p, TEMPLATE_ELEMENT); }, t => unreachable!("Anything not template chunk or dollarcurly should have been eaten by the lexer, but {:?} was found", t), @@ -1164,7 +1187,7 @@ pub fn template(p: &mut Parser, tag: Option) -> CompletedMarker // [...a, ...b]; pub fn array_expr(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T!['[']); + p.expect_required(T!['[']); let elements_list = p.start(); while !p.at(EOF) { @@ -1187,18 +1210,18 @@ pub fn array_expr(p: &mut Parser) -> CompletedMarker { break; } - p.expect(T![,]); + p.expect_required(T![,]); } elements_list.complete(p, LIST); - p.expect(T![']']); + p.expect_required(T![']']); m.complete(p, JS_ARRAY_EXPRESSION) } /// A spread element consisting of three dots and an assignment expression such as `...foo` pub fn spread_element(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T![...]); + p.expect_required(T![...]); assign_expr(p); m.complete(p, SPREAD_ELEMENT) } @@ -1249,7 +1272,7 @@ pub fn lhs_expr(p: &mut Parser) -> Option { } if type_args.is_some() { - p.expect(T!['(']); + p.expect_required(T!['(']); } m.abandon(p); @@ -1302,14 +1325,14 @@ pub fn unary_expr(p: &mut Parser) -> Option { let m = p.start(); p.bump_any(); if p.eat(T![const]) { - p.expect(T![>]); + p.expect_required(T![>]); unary_expr(p); let mut res = m.complete(p, TS_CONST_ASSERTION); res.err_if_not_ts(p, "const assertions can only be used in TypeScript files"); return Some(res); } else { ts_type(p); - p.expect(T![>]); + p.expect_required(T![>]); unary_expr(p); let mut res = m.complete(p, TS_ASSERTION); res.err_if_not_ts(p, "type assertions can only be used in TypeScript files"); diff --git a/crates/rslint_parser/src/syntax/function.rs b/crates/rslint_parser/src/syntax/function.rs index 45d93b63e63..f4429889fb5 100644 --- a/crates/rslint_parser/src/syntax/function.rs +++ b/crates/rslint_parser/src/syntax/function.rs @@ -1,11 +1,18 @@ +use crate::parser::ConditionalParsedSyntax::Valid; +use crate::parser::ParsedSyntax; use crate::syntax::decl::parameter_list; +use crate::syntax::js_parse_error; use crate::syntax::pat::opt_binding_identifier; use crate::syntax::stmt::{block_impl, is_semi}; use crate::syntax::typescript::{ts_type_or_type_predicate_ann, ts_type_params}; +use crate::ConditionalParsedSyntax::Invalid; +use crate::JsSyntaxFeature::TypeScript; +use crate::ParsedSyntax::{Absent, Present}; use crate::{CompletedMarker, Parser, ParserState}; +use crate::{ConditionalParsedSyntax, SyntaxFeature}; use rslint_syntax::SyntaxKind::{ ERROR, JS_FUNCTION_BODY, JS_FUNCTION_DECLARATION, JS_FUNCTION_EXPRESSION, - JS_IDENTIFIER_BINDING, TS_TYPE_ANNOTATION, + JS_IDENTIFIER_BINDING, JS_UNKNOWN_EXPRESSION, JS_UNKNOWN_STATEMENT, TS_TYPE_ANNOTATION, }; use rslint_syntax::{SyntaxKind, T}; use std::collections::HashMap; @@ -21,28 +28,41 @@ use std::collections::HashMap; // function *foo() { // yield foo; // } +// +// test_err function_decl_err +// function() {} +// function {} +// function *() {} +// async function() {} +// async function *() {} +// function *foo() {} +// yield foo; +// function test(): number {} pub(super) fn function_declaration(p: &mut Parser) -> CompletedMarker { function(p, JS_FUNCTION_DECLARATION) + .or_invalid_to_unknown(p, JS_UNKNOWN_STATEMENT) + .ok() + .unwrap() } pub(super) fn function_expression(p: &mut Parser) -> CompletedMarker { function(p, JS_FUNCTION_EXPRESSION) + .or_invalid_to_unknown(p, JS_UNKNOWN_EXPRESSION) + .ok() + .unwrap() } -fn function(p: &mut Parser, kind: SyntaxKind) -> CompletedMarker { +fn function(p: &mut Parser, kind: SyntaxKind) -> ConditionalParsedSyntax { let m = p.start(); - if kind == JS_FUNCTION_DECLARATION { - // TS function declaration - p.eat(T![declare]); - } + let mut uses_ts_syntax = kind == JS_FUNCTION_DECLARATION && p.eat(T![declare]); let in_async = p.at(T![ident]) && p.cur_src() == "async"; if in_async { p.bump_remap(T![async]); } - p.expect(T![function]); + p.expect_required(T![function]); let in_generator = p.eat(T![*]); let guard = &mut *p.with_state(ParserState { @@ -67,27 +87,54 @@ fn function(p: &mut Parser, kind: SyntaxKind) -> CompletedMarker { guard.error(err); } - ts_parameter_types(guard); + let type_parameters = + parse_ts_parameter_types(guard).exclusive_for(&TypeScript, guard, |p, marker| { + p.err_builder("type parameters can only be used in TypeScript files") + .primary(marker.range(p), "") + }); + + uses_ts_syntax |= type_parameters.is_present(); + + if let Valid(type_parameters) = type_parameters { + type_parameters.or_missing(guard); + } + parameter_list(guard); - ts_return_type(guard); + + let return_type = parse_ts_return_type(guard).exclusive_for(&TypeScript, guard, |p, marker| { + p.err_builder("return types can only be used in TypeScript files") + .primary(marker.range(p), "") + }); + + uses_ts_syntax |= return_type.is_present(); + + if let Valid(return_type) = return_type { + return_type.or_missing(guard); + } if kind == JS_FUNCTION_DECLARATION { function_body_or_declaration(guard); } else { - function_body(guard); + function_body(guard).or_missing_with_error(guard, js_parse_error::expected_function_body); } - m.complete(guard, kind) + let function = m.complete(guard, kind); + + if uses_ts_syntax && TypeScript.is_unsupported(guard) { + Invalid(function.into()) + } else { + Valid(function.into()) + } } -pub(super) fn function_body(p: &mut Parser) -> Option { +pub(super) fn function_body(p: &mut Parser) -> ParsedSyntax { let mut guard = p.with_state(ParserState { in_constructor: false, in_function: true, ..p.state.clone() }); - block_impl(&mut *guard, JS_FUNCTION_BODY, None) + block_impl(&mut *guard, JS_FUNCTION_BODY) } // TODO 1725 This is probably not ideal (same with the `declare` keyword). We should @@ -100,22 +147,35 @@ pub(super) fn function_body_or_declaration(p: &mut Parser) { if p.typescript() && !p.at(T!['{']) && is_semi(p, 0) { p.eat(T![;]); } else { - let mut complete = function_body(p); - if let Some(ref mut block) = complete { - if p.state.in_declare { - let err = p - .err_builder( - "function implementations cannot be given in ambient (declare) contexts", - ) - .primary(block.range(p), ""); - - p.error(err); - block.change_kind(p, ERROR); + let body = function_body(p); + if p.state.in_declare { + match body { + Present(mut body) => { + let err = p + .err_builder( + "function implementations cannot be given in ambient (declare) contexts", + ) + .primary(body.range(p), ""); + + p.error(err); + body.change_kind(p, ERROR); + } + _ => p.missing(), } + } else { + body.or_missing_with_error(p, js_parse_error::expected_function_body); } } } +fn parse_ts_parameter_types(p: &mut Parser) -> ParsedSyntax { + if p.at(T![<]) { + Present(ts_type_params(p).unwrap()) + } else { + Absent + } +} + pub(crate) fn ts_parameter_types(p: &mut Parser) { if p.at(T![<]) { if let Some(ref mut ty) = ts_type_params(p) { @@ -124,6 +184,16 @@ pub(crate) fn ts_parameter_types(p: &mut Parser) { } } +fn parse_ts_return_type(p: &mut Parser) -> ParsedSyntax { + if p.at(T![:]) { + let return_type = p.start(); + ts_type_or_type_predicate_ann(p, T![:]); + Present(return_type.complete(p, TS_TYPE_ANNOTATION)) + } else { + Absent + } +} + pub(crate) fn ts_return_type(p: &mut Parser) { if p.at(T![:]) { let return_type = p.start(); diff --git a/crates/rslint_parser/src/syntax/js_parse_error.rs b/crates/rslint_parser/src/syntax/js_parse_error.rs new file mode 100644 index 00000000000..e73e03f5ee1 --- /dev/null +++ b/crates/rslint_parser/src/syntax/js_parse_error.rs @@ -0,0 +1,63 @@ +use crate::parser::{expected_any, expected_node, ToDiagnostic}; +use crate::Parser; +use rslint_errors::Diagnostic; +use std::ops::Range; + +///! Provides factory function to create common diagnostics for the JavaScript syntax + +pub(crate) fn expected_function_body(p: &Parser, range: Range) -> Diagnostic { + expected_node("function body", range).to_diagnostic(p) +} + +pub(crate) fn expected_class_member_name(p: &Parser, range: Range) -> Diagnostic { + expected_any( + &[ + "identifier", + "string literal", + "number literal", + "private field name", + "computed name", + ], + range, + ) + .to_diagnostic(p) +} + +pub(crate) fn expected_arrow_body(p: &Parser, range: Range) -> Diagnostic { + expected_any(&["function body", "expression"], range).to_diagnostic(p) +} + +pub(crate) fn expected_object_member(p: &Parser, range: Range) -> Diagnostic { + expected_any( + &[ + "property", + "shorthand property", + "getter", + "setter", + "method", + ], + range, + ) + .to_diagnostic(p) +} + +pub(crate) fn expected_object_member_name(p: &Parser, range: Range) -> Diagnostic { + expected_any( + &[ + "identifier", + "string literal", + "number literal", + "computed property", + ], + range, + ) + .to_diagnostic(p) +} + +pub(crate) fn expected_block_statement(p: &Parser, range: Range) -> Diagnostic { + expected_node("block statement", range).to_diagnostic(p) +} + +pub(crate) fn expected_catch_clause(p: &Parser, range: Range) -> Diagnostic { + expected_node("catch clause", range).to_diagnostic(p) +} diff --git a/crates/rslint_parser/src/syntax/object.rs b/crates/rslint_parser/src/syntax/object.rs index 9b752c47e4c..2007d416d99 100644 --- a/crates/rslint_parser/src/syntax/object.rs +++ b/crates/rslint_parser/src/syntax/object.rs @@ -1,12 +1,16 @@ -use crate::parse_recovery::ParseRecovery; -use crate::syntax::decl::{formal_param_pat, parameter_list, BASE_METHOD_RECOVERY_SET}; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; +use crate::parser::ParsedSyntax; +use crate::parser::ParsedSyntax::{Absent, Present}; +use crate::syntax::decl::{formal_param_pat, parameter_list}; use crate::syntax::expr::{assign_expr, expr, identifier_name, literal_expression}; use crate::syntax::function::{function_body, ts_parameter_types, ts_return_type}; -use crate::{CompletedMarker, Parser, ParserState, TokenSet}; +use crate::syntax::js_parse_error; +use crate::{CompletedMarker, ParseRecovery, Parser, ParserState, TokenSet}; use rslint_syntax::SyntaxKind::*; use rslint_syntax::T; -const STARTS_OBJ_PROP: TokenSet = token_set![ +const STARTS_MEMBER_NAME: TokenSet = token_set![ JS_STRING_LITERAL, JS_NUMBER_LITERAL, T![ident], @@ -21,7 +25,7 @@ const STARTS_OBJ_PROP: TokenSet = token_set![ // let b = {foo,} pub(super) fn object_expr(p: &mut Parser) -> CompletedMarker { let m = p.start(); - p.expect(T!['{']); + p.expect_required(T!['{']); let props_list = p.start(); let mut first = true; @@ -36,17 +40,26 @@ pub(super) fn object_expr(p: &mut Parser) -> CompletedMarker { } } - object_member(p); + let recovered_member = object_member(p).or_recover( + p, + ParseRecovery::new(JS_UNKNOWN_MEMBER, token_set![T![,], T!['}'], T![;], T![:]]) + .enable_recovery_on_line_break(), + js_parse_error::expected_object_member, + ); + + if recovered_member.is_err() { + break; + } } props_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); m.complete(p, JS_OBJECT_EXPRESSION) } /// An individual object property such as `"a": b` or `5: 6 + 6`. -fn object_member(p: &mut Parser) -> Option { +fn object_member(p: &mut Parser) -> ParsedSyntax { match p.cur() { // test object_expr_getter // let a = { @@ -57,9 +70,9 @@ fn object_member(p: &mut Parser) -> Option { T![ident] if p.cur_src() == "get" && !p.has_linebreak_before_n(1) - && STARTS_OBJ_PROP.contains(p.nth(1)) => + && STARTS_MEMBER_NAME.contains(p.nth(1)) => { - Some(getter_object_member(p)) + getter_object_member(p) } // test object_expr_setter @@ -71,9 +84,9 @@ fn object_member(p: &mut Parser) -> Option { T![ident] if p.cur_src() == "set" && !p.has_linebreak_before_n(1) - && STARTS_OBJ_PROP.contains(p.nth(1)) => + && STARTS_MEMBER_NAME.contains(p.nth(1)) => { - Some(setter_object_member(p)) + setter_object_member(p) } // test object_expr_async_method @@ -89,7 +102,7 @@ fn object_member(p: &mut Parser) -> Option { let m = p.start(); p.bump_any(); assign_expr(p); - Some(m.complete(p, JS_SPREAD)) + Present(m.complete(p, JS_SPREAD)) } T![*] => { @@ -99,9 +112,11 @@ fn object_member(p: &mut Parser) -> Option { } _ => { + let checkpoint = p.checkpoint(); let m = p.start(); let identifier_member_name = p.at(T![ident]) || p.cur().is_keyword(); - let member_name = object_member_name(p); + let member_name = object_member_name(p) + .or_missing_with_error(p, js_parse_error::expected_object_member); // test object_expr_method // let b = { @@ -114,15 +129,15 @@ fn object_member(p: &mut Parser) -> Option { // test_err object_expr_method // let b = { foo) } if p.at(T!['(']) || p.at(T![<]) { - method_object_member_body(p).ok()?; - Some(m.complete(p, JS_METHOD_OBJECT_MEMBER)) + method_object_member_body(p); + Present(m.complete(p, JS_METHOD_OBJECT_MEMBER)) } else if let Some(mut member_name) = member_name { // test object_expr_assign_prop // let b = { foo = 4, foo = bar } if p.eat(T![=]) { member_name.change_kind(p, NAME); assign_expr(p); - return Some(m.complete(p, INITIALIZED_PROP)); + return Present(m.complete(p, INITIALIZED_PROP)); } // ({foo}) @@ -131,13 +146,13 @@ fn object_member(p: &mut Parser) -> Option { && (matches!(p.cur(), T![,] | T!['}']) || p.has_linebreak_before_n(0)) { member_name.change_kind(p, JS_REFERENCE_IDENTIFIER_EXPRESSION); - Some(m.complete(p, JS_SHORTHAND_PROPERTY_OBJECT_MEMBER)) + Present(m.complete(p, JS_SHORTHAND_PROPERTY_OBJECT_MEMBER)) } else { // let b = { a: true } // If the member name was a literal OR we're at a colon - p.expect(T![:]); + p.expect_required(T![:]); assign_expr(p); - Some(m.complete(p, JS_PROPERTY_OBJECT_MEMBER)) + Present(m.complete(p, JS_PROPERTY_OBJECT_MEMBER)) } } else { // test_err object_expr_error_prop_name @@ -146,13 +161,19 @@ fn object_member(p: &mut Parser) -> Option { // test_err object_expr_non_ident_literal_prop // let b = {5} - ParseRecovery::new(token_set![T![:], T![,]], ERROR).recover(p); + #[allow(deprecated)] + SingleTokenParseRecovery::new(token_set![T![:], T![,]], ERROR).recover(p); if p.eat(T![:]) { assign_expr(p); - Some(m.complete(p, JS_PROPERTY_OBJECT_MEMBER)) + Present(m.complete(p, JS_PROPERTY_OBJECT_MEMBER)) } else { - Some(m.complete(p, JS_UNKNOWN_MEMBER)) + // It turns out that this isn't a valid member after all. Make sure to throw + // away everything that has been parsed so far so that the caller can + // do it's error recovery + m.abandon(p); + p.rewind(checkpoint); + Absent } } } @@ -160,45 +181,46 @@ fn object_member(p: &mut Parser) -> Option { } /// Parses a getter object member: `{ get a() { return "a"; } }` -fn getter_object_member(p: &mut Parser) -> CompletedMarker { - debug_assert!(p.at(T![ident]), "Expected an identifier"); - debug_assert!(p.cur_src() == "get", "Expected a get identifier"); +fn getter_object_member(p: &mut Parser) -> ParsedSyntax { + if !p.at(T![ident]) || p.cur_src() != "get" { + return Absent; + } let m = p.start(); p.bump_remap(T![get]); - object_member_name(p); + object_member_name(p).or_missing_with_error(p, js_parse_error::expected_object_member_name); - p.expect(T!['(']); - p.expect(T![')']); + p.expect_required(T!['(']); + p.expect_required(T![')']); ts_return_type(p); - function_body(p); + function_body(p).or_missing_with_error(p, js_parse_error::expected_function_body); - m.complete(p, JS_GETTER_OBJECT_MEMBER) + Present(m.complete(p, JS_GETTER_OBJECT_MEMBER)) } /// Parses a setter object member like `{ set a(value) { .. } }` -fn setter_object_member(p: &mut Parser) -> CompletedMarker { - debug_assert!(p.at(T![ident]), "Expected an identifier"); - debug_assert!(p.cur_src() == "set", "Expected a set identifier"); - +fn setter_object_member(p: &mut Parser) -> ParsedSyntax { + if !p.at(T![ident]) || p.cur_src() != "set" { + return Absent; + } let m = p.start(); p.bump_remap(T![set]); - object_member_name(p); + object_member_name(p).or_missing_with_error(p, js_parse_error::expected_object_member_name); - p.state.allow_object_expr = p.expect(T!['(']); + p.state.allow_object_expr = p.expect_required(T!['(']); formal_param_pat(p); - p.expect(T![')']); + p.expect_required(T![')']); - function_body(p); + function_body(p).or_missing_with_error(p, js_parse_error::expected_function_body); p.state.allow_object_expr = true; - m.complete(p, JS_SETTER_OBJECT_MEMBER) + Present(m.complete(p, JS_SETTER_OBJECT_MEMBER)) } // test object_prop_name @@ -206,7 +228,7 @@ fn setter_object_member(p: &mut Parser) -> CompletedMarker { pub fn object_prop_name(p: &mut Parser, binding: bool) -> Option { match p.cur() { JS_STRING_LITERAL | JS_NUMBER_LITERAL => literal_expression(p), - T!['['] => Some(computed_member_name(p)), + T!['['] => computed_member_name(p).ok(), _ if binding => super::pat::binding_identifier(p), _ => identifier_name(p), } @@ -215,23 +237,30 @@ pub fn object_prop_name(p: &mut Parser, binding: bool) -> Option Option { +fn object_member_name(p: &mut Parser) -> ParsedSyntax { match p.cur() { - T!['['] => Some(computed_member_name(p)), + T!['['] => computed_member_name(p), _ => literal_member_name(p), } } -pub(crate) fn computed_member_name(p: &mut Parser) -> CompletedMarker { - let m = p.start(); +fn is_at_object_member_name(p: &Parser) -> bool { + p.at_ts(STARTS_MEMBER_NAME) +} + +pub(crate) fn computed_member_name(p: &mut Parser) -> ParsedSyntax { + if !p.at(T!['[']) { + return Absent; + } - p.expect(T!['[']); + let m = p.start(); + p.expect_required(T!['[']); expr(p); - p.expect(T![']']); - m.complete(p, JS_COMPUTED_MEMBER_NAME) + p.expect_required(T![']']); + Present(m.complete(p, JS_COMPUTED_MEMBER_NAME)) } -pub(super) fn literal_member_name(p: &mut Parser) -> Option { +pub(super) fn literal_member_name(p: &mut Parser) -> ParsedSyntax { let m = p.start(); match p.cur() { JS_STRING_LITERAL | JS_NUMBER_LITERAL | T![ident] => { @@ -241,22 +270,20 @@ pub(super) fn literal_member_name(p: &mut Parser) -> Option { p.bump_remap(T![ident]); } _ => { - let err = p - .err_builder("Expected an identifier, a keyword, or a string or number literal") - .primary( - p.cur_tok().range, - "Expected an identifier, a keyword, or a string or number literal here", - ); - p.error(err); m.abandon(p); - return None; + return Absent; } } - Some(m.complete(p, JS_LITERAL_MEMBER_NAME)) + Present(m.complete(p, JS_LITERAL_MEMBER_NAME)) } /// Parses a method object member -fn method_object_member(p: &mut Parser) -> Option { +fn method_object_member(p: &mut Parser) -> ParsedSyntax { + let is_async = is_parser_at_async_method_member(p); + if !is_async && !p.at(T![*]) && !is_at_object_member_name(p) { + return Absent; + } + let m = p.start(); // test async_method @@ -264,57 +291,43 @@ fn method_object_member(p: &mut Parser) -> Option { // async foo() {} // async *foo() {} // } - let state = if is_parser_at_async_method_member(p) { + if is_async { p.bump_remap(T![async]); - - ParserState { - in_async: true, - in_generator: p.eat(T![*]), - ..p.state.clone() - } } else { - ParserState { - in_generator: p.eat(T![*]), - ..p.state.clone() - } - }; + p.missing(); + } + + let in_generator = p.eat_optional(T![*]); + object_member_name(p).or_missing_with_error(p, js_parse_error::expected_object_member_name); { - let mut guard = p.with_state(state); - object_member_name(&mut *guard); - method_object_member_body(&mut *guard).ok(); + let mut guard = p.with_state(ParserState { + in_async: is_async, + in_generator, + ..p.state.clone() + }); + method_object_member_body(&mut *guard); } - Some(m.complete(p, JS_METHOD_OBJECT_MEMBER)) + Present(m.complete(p, JS_METHOD_OBJECT_MEMBER)) } /// Parses the body of a method object member starting right after the member name. -fn method_object_member_body(p: &mut Parser) -> Result<(), ()> { +fn method_object_member_body(p: &mut Parser) { let old = p.state.to_owned(); p.state.in_function = true; - let result = if matches!(p.cur(), T!['('] | T![<]) { - ts_parameter_types(p); - parameter_list(p); - ts_return_type(p); - function_body(p); - Ok(()) - } else { - let err = p - .err_builder("expected a method definition, but found none") - .primary(p.cur_tok().range, ""); - - ParseRecovery::with_error(BASE_METHOD_RECOVERY_SET, JS_UNKNOWN_MEMBER, err).recover(p); - Err(()) - }; + ts_parameter_types(p); + parameter_list(p); + ts_return_type(p); + function_body(p).or_missing_with_error(p, js_parse_error::expected_function_body); p.state = old; - result } fn is_parser_at_async_method_member(p: &Parser) -> bool { p.cur() == T![ident] && p.cur_src() == "async" && !p.has_linebreak_before_n(1) - && (STARTS_OBJ_PROP.contains(p.nth(1)) || p.nth_at(1, T![*])) + && (STARTS_MEMBER_NAME.contains(p.nth(1)) || p.nth_at(1, T![*])) } diff --git a/crates/rslint_parser/src/syntax/pat.rs b/crates/rslint_parser/src/syntax/pat.rs index 108e6c87a76..420712b1cfe 100644 --- a/crates/rslint_parser/src/syntax/pat.rs +++ b/crates/rslint_parser/src/syntax/pat.rs @@ -1,6 +1,8 @@ use super::expr::{assign_expr, identifier_name, lhs_expr, reference_identifier_expression}; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; use crate::syntax::object::object_prop_name; -use crate::{parse_recovery::ParseRecovery, SyntaxKind::*, *}; +use crate::{SyntaxKind::*, *}; pub fn pattern(p: &mut Parser, parameters: bool, assignment: bool) -> Option { Some(match p.cur() { @@ -77,7 +79,8 @@ pub fn pattern(p: &mut Parser, parameters: bool, assignment: bool) -> Option CompletedMarker { let m = p.start(); - p.expect(T!['[']); + p.expect_required(T!['[']); let elements_list = p.start(); @@ -179,20 +183,20 @@ pub fn array_binding_pattern( m.complete(p, REST_PATTERN); break; } else if binding_element(p, parameters, assignment).is_none() { - ParseRecovery::new( + SingleTokenParseRecovery::new( token_set![T![await], T![ident], T![yield], T![:], T![=], T![']']], JS_UNKNOWN_PATTERN, ) .recover(p); } if !p.at(T![']']) { - p.expect(T![,]); + p.expect_required(T![,]); } } elements_list.complete(p, LIST); - p.expect(T![']']); + p.expect_required(T![']']); m.complete(p, ARRAY_PATTERN) } @@ -203,7 +207,7 @@ pub fn array_binding_pattern( // let { default: , bar } = {}; pub fn object_binding_pattern(p: &mut Parser, parameters: bool) -> CompletedMarker { let m = p.start(); - p.expect(T!['{']); + p.expect_required(T!['{']); let props_list = p.start(); let mut first = true; @@ -211,7 +215,7 @@ pub fn object_binding_pattern(p: &mut Parser, parameters: bool) -> CompletedMark if first { first = false; } else { - p.expect(T![,]); + p.expect_required(T![,]); if p.at(T!['}']) { break; } @@ -230,7 +234,7 @@ pub fn object_binding_pattern(p: &mut Parser, parameters: bool) -> CompletedMark } props_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); m.complete(p, OBJECT_PATTERN) } @@ -254,7 +258,8 @@ fn object_binding_prop(p: &mut Parser, parameters: bool) -> Option CompletedMarker { fn named_list(p: &mut Parser) -> Marker { let m = p.start(); - p.expect(T!['{']); + p.expect_required(T!['{']); let mut first = true; let specifiers_list = p.start(); while !p.at(EOF) && !p.at(T!['}']) { @@ -48,13 +48,13 @@ fn named_list(p: &mut Parser) -> Marker { p.bump_any(); break; } else { - p.expect(T![,]); + p.expect_required(T![,]); } specifier(p); } specifiers_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); m } @@ -103,7 +103,7 @@ pub fn import_decl(p: &mut Parser) -> CompletedMarker { ..p.state.clone() }); - p.expect(T![import]); + p.expect_required(T![import]); if p.at_ts(token_set![T![ident], T![async], T![yield]]) && p.nth_at(1, T![=]) { let mut complete = ts_import_equals_decl(p, m); @@ -147,7 +147,7 @@ pub fn import_decl(p: &mut Parser) -> CompletedMarker { if p.at_ts(token_set![T![async], T![yield], T![ident]]) { imported_binding(p); if p.cur_src() != "from" { - p.expect(T![,]); + p.expect_required(T![,]); } } @@ -220,7 +220,7 @@ fn imported_binding(p: &mut Parser) { pub fn export_decl(p: &mut Parser) -> CompletedMarker { let start = p.cur_tok().range.start; let m = p.start(); - p.expect(T![export]); + p.expect_required(T![export]); let declare = p.typescript() && p.cur_src() == "declare"; @@ -461,10 +461,10 @@ pub fn export_decl(p: &mut Parser) -> CompletedMarker { } if exports_ns || export_default { - p.expect(T![,]); + p.expect_required(T![,]); } - p.expect(T!['{']); + p.expect_required(T!['{']); let mut first = true; let specifiers = p.start(); @@ -479,7 +479,7 @@ pub fn export_decl(p: &mut Parser) -> CompletedMarker { } specifiers.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); if p.cur_src() == "from" { from_clause_and_semi(p, start); @@ -504,14 +504,14 @@ pub fn export_decl(p: &mut Parser) -> CompletedMarker { fn from_clause_and_semi(p: &mut Parser, start: usize) { debug_assert_eq!(p.cur_src(), "from"); p.bump_remap(T![from]); - p.expect(T![js_string_literal]); + p.expect_required(T![js_string_literal]); semi(p, start..p.cur_tok().range.start); } pub fn ts_import_equals_decl(p: &mut Parser, m: Marker) -> CompletedMarker { let start = p.cur_tok().range.start; identifier_name(p); - p.expect(T![=]); + p.expect_required(T![=]); if p.cur_src() == "require" && p.nth_at(1, T!['(']) { ts_external_module_ref(p); @@ -534,8 +534,8 @@ pub fn ts_external_module_ref(p: &mut Parser) -> CompletedMarker { p.bump_remap(T![require]); } - p.expect(T!['(']); - p.expect(JS_STRING_LITERAL); - p.expect(T![')']); + p.expect_required(T!['(']); + p.expect_required(JS_STRING_LITERAL); + p.expect_required(T![')']); m.complete(p, TS_EXTERNAL_MODULE_REF) } diff --git a/crates/rslint_parser/src/syntax/stmt.rs b/crates/rslint_parser/src/syntax/stmt.rs index bf47a2a6002..245381a05af 100644 --- a/crates/rslint_parser/src/syntax/stmt.rs +++ b/crates/rslint_parser/src/syntax/stmt.rs @@ -7,9 +7,15 @@ use super::pat::*; use super::program::{export_decl, import_decl}; use super::typescript::*; use super::util::{check_for_stmt_declaration, check_label_use, check_lhs}; -use crate::parse_recovery::ParseRecovery; +#[allow(deprecated)] +use crate::parser::single_token_parse_recovery::SingleTokenParseRecovery; +use crate::parser::ParsedSyntax; use crate::syntax::class::class_declaration; use crate::syntax::function::function_declaration; +use crate::syntax::js_parse_error; +use crate::JsSyntaxFeature::StrictMode; +use crate::ParsedSyntax::{Absent, Present}; +use crate::SyntaxFeature; use crate::{SyntaxKind::*, *}; pub const STMT_RECOVERY_SET: TokenSet = token_set![ @@ -85,12 +91,13 @@ pub(super) fn is_semi(p: &Parser, offset: usize) -> bool { } /// A generic statement such as a block, if, while, with, etc +#[allow(deprecated)] pub fn stmt(p: &mut Parser, recovery_set: impl Into>) -> Option { let res = match p.cur() { - T![;] => empty_stmt(p), - T!['{'] => block_stmt(p, recovery_set).unwrap(), // It is only ever None if there is no `{`, - T![if] => if_stmt(p), - T![with] => with_stmt(p), + T![;] => empty_stmt(p).ok().unwrap(), // It is only ever Err if there's no ; + T!['{'] => block_stmt(p).ok().unwrap(), // It is only ever None if there is no `{`, + T![if] => if_stmt(p).ok().unwrap(), // It is only ever Err if there's no if + T![with] => with_stmt(p).ok().unwrap(), // ever only Err if there's no with keyword T![while] => while_stmt(p), t if (t == T![const] && p.nth_at(1, T![enum])) || t == T![enum] => { let mut res = ts_enum(p); @@ -101,7 +108,7 @@ pub fn stmt(p: &mut Parser, recovery_set: impl Into>) -> Option T![for] => for_stmt(p), T![do] => do_stmt(p), T![switch] => switch_stmt(p), - T![try] => try_stmt(p), + T![try] => parse_try_statement(p).ok().unwrap(), // it is only ever Err if there's no try T![return] => return_stmt(p), T![break] => break_stmt(p), T![continue] => continue_stmt(p), @@ -135,7 +142,7 @@ pub fn stmt(p: &mut Parser, recovery_set: impl Into>) -> Option p.err_and_bump(err, JS_UNKNOWN_STATEMENT); return None; } - ParseRecovery::with_error( + SingleTokenParseRecovery::with_error( recovery_set.into().unwrap_or(STMT_RECOVERY_SET), JS_UNKNOWN_STATEMENT, err, @@ -155,6 +162,7 @@ pub fn stmt(p: &mut Parser, recovery_set: impl Into>) -> Option // } // } +#[allow(deprecated)] fn expr_stmt(p: &mut Parser) -> Option { let start = p.cur_tok().range.start; // this is *technically* wrong because it would be an expr stmt in js but for our purposes @@ -264,7 +272,7 @@ fn expr_stmt(p: &mut Parser) -> Option { pub fn debugger_stmt(p: &mut Parser) -> CompletedMarker { let m = p.start(); let range = p.cur_tok().range; - p.expect(T![debugger]); + p.expect_required(T![debugger]); semi(p, range); m.complete(p, JS_DEBUGGER_STATEMENT) } @@ -273,13 +281,14 @@ pub fn debugger_stmt(p: &mut Parser) -> CompletedMarker { // test throw_stmt // throw new Error("foo"); // throw "foo" +#[allow(deprecated)] pub fn throw_stmt(p: &mut Parser) -> CompletedMarker { // test_err throw_stmt_err // throw // new Error("oh no :(") let m = p.start(); let start = p.cur_tok().range.start; - p.expect(T![throw]); + p.expect_required(T![throw]); if p.has_linebreak_before_n(0) { let mut err = p .err_builder( @@ -309,7 +318,7 @@ pub fn throw_stmt(p: &mut Parser) -> CompletedMarker { pub fn break_stmt(p: &mut Parser) -> CompletedMarker { let m = p.start(); let start = p.cur_tok().range; - p.expect(T![break]); + p.expect_required(T![break]); let end = if !p.has_linebreak_before_n(0) && p.at(T![ident]) { let label_token = p.cur_tok(); @@ -345,7 +354,7 @@ pub fn break_stmt(p: &mut Parser) -> CompletedMarker { pub fn continue_stmt(p: &mut Parser) -> CompletedMarker { let m = p.start(); let start = p.cur_tok().range; - p.expect(T![continue]); + p.expect_required(T![continue]); let end = if !p.has_linebreak_before_n(0) && p.at(T![ident]) { let label_token = p.cur_tok(); @@ -377,13 +386,14 @@ pub fn continue_stmt(p: &mut Parser) -> CompletedMarker { // return foo; // return // } +#[allow(deprecated)] pub fn return_stmt(p: &mut Parser) -> CompletedMarker { // test_err return_stmt_err // return; // return foo; let m = p.start(); let start = p.cur_tok().range.start; - p.expect(T![return]); + p.expect_required(T![return]); if !p.has_linebreak_before_n(0) && p.at_ts(STARTS_EXPR) { p.expr_with_semi_recovery(false); } @@ -403,10 +413,14 @@ pub fn return_stmt(p: &mut Parser) -> CompletedMarker { /// An empty statement denoted by a single semicolon. // test empty_stmt // ; -pub fn empty_stmt(p: &mut Parser) -> CompletedMarker { - let m = p.start(); - p.expect(T![;]); - m.complete(p, JS_EMPTY_STATEMENT) +pub fn empty_stmt(p: &mut Parser) -> ParsedSyntax { + if p.at(T![;]) { + let m = p.start(); + p.bump_any(); // bump ; + m.complete(p, JS_EMPTY_STATEMENT).into() + } else { + Absent + } } /// A block statement consisting of statements wrapped in curly brackets. @@ -414,30 +428,16 @@ pub fn empty_stmt(p: &mut Parser) -> CompletedMarker { // {} // {{{{}}}} // { foo = bar; } -pub(crate) fn block_stmt( - p: &mut Parser, - recovery_set: impl Into>, -) -> Option { - block_impl(p, JS_BLOCK_STATEMENT, recovery_set) +pub(crate) fn block_stmt(p: &mut Parser) -> ParsedSyntax { + block_impl(p, JS_BLOCK_STATEMENT) } /// A block wrapped in curly brackets. Can either be a function body or a block statement. -pub(super) fn block_impl( - p: &mut Parser, - block_kind: SyntaxKind, - recovery_set: impl Into>, -) -> Option { +pub(super) fn block_impl(p: &mut Parser, block_kind: SyntaxKind) -> ParsedSyntax { if !p.at(T!['{']) { - let err = p - .err_builder(&format!( - "expected a block statement but instead found `{}`", - p.cur_src() - )) - .primary(p.cur_tok().range, ""); - - p.error(err); - return None; + return Absent; } + let m = p.start(); p.bump(T!['{']); @@ -447,15 +447,15 @@ pub(super) fn block_impl( None }; - statements(p, false, true, recovery_set); + statements(p, false, true, None); - p.expect(T!['}']); + p.expect_required(T!['}']); if let Some(old_parser_state) = old_parser_state { p.state = old_parser_state; } - Some(m.complete(p, block_kind)) + Present(m.complete(p, block_kind)) } #[must_use] @@ -581,9 +581,9 @@ pub(crate) fn statements( /// An expression wrapped in parentheses such as `()` pub fn parenthesized_expression(p: &mut Parser) { - p.state.allow_object_expr = p.expect(T!['(']); + p.state.allow_object_expr = p.expect_required(T!['(']); expr(p); - p.expect(T![')']); + p.expect_required(T![')']); p.state.allow_object_expr = true; } @@ -593,15 +593,19 @@ pub fn parenthesized_expression(p: &mut Parser) { // if (true) {} // if (true) false // if (bar) {} else if (true) {} else {} -pub fn if_stmt(p: &mut Parser) -> CompletedMarker { +pub fn if_stmt(p: &mut Parser) -> ParsedSyntax { // test_err if_stmt_err // if (true) else {} // if (true) else // if else {} // if () {} else {} // if (true)}}}} {} + if !p.at(T![if]) { + return Absent; + } + let m = p.start(); - p.expect(T![if]); + p.bump_any(); // bump if // (test) parenthesized_expression(&mut *p.with_state(ParserState { @@ -611,38 +615,42 @@ pub fn if_stmt(p: &mut Parser) -> CompletedMarker { // body // allows us to recover from `if (true) else {}` + // TODO replace with statement(p).or_recover(STMT_RECOVERY_SET.union(...)) stmt(p, STMT_RECOVERY_SET.union(token_set![T![else]])); // else clause if p.at(T![else]) { let else_clause = p.start(); - p.eat(T![else]); - stmt(p, None); + p.bump_any(); // bump else + stmt(p, None); // stmt(p).into_required(); else_clause.complete(p, JS_ELSE_CLAUSE); } - m.complete(p, JS_IF_STATEMENT) + Present(m.complete(p, JS_IF_STATEMENT)) } /// A with statement such as `with (foo) something()` -pub fn with_stmt(p: &mut Parser) -> CompletedMarker { +pub fn with_stmt(p: &mut Parser) -> ParsedSyntax { + if !p.at(T![with]) { + return Absent; + } + let m = p.start(); - p.expect(T![with]); + p.bump_any(); // with parenthesized_expression(p); stmt(p, None); - let mut complete = m.complete(p, JS_WITH_STATEMENT); - if p.state.strict.is_some() { - let err = p - .err_builder("`with` statements are not allowed in strict mode") - .primary(complete.range(p), ""); + let with_stmt = m.complete(p, JS_WITH_STATEMENT); - p.error(err); - complete.change_kind(p, ERROR); - } + // or SloppyMode.exclusive_syntax(...) but this reads better with the error message, saying that + // it's only forbidden in strict mode + let conditional = StrictMode.excluding_syntax(p, with_stmt, |p, marker| { + p.err_builder("`with` statements are not allowed in strict mode") + .primary(marker.range(p), "") + }); - complete + conditional.or_invalid_to_unknown(p, JS_UNKNOWN_STATEMENT) } /// A while statement such as `while(true) { do_something() }` @@ -656,7 +664,7 @@ pub fn while_stmt(p: &mut Parser) -> CompletedMarker { // while (true {} // while true) } let m = p.start(); - p.expect(T![while]); + p.expect_required(T![while]); parenthesized_expression(p); stmt( &mut *p.with_state(ParserState { @@ -809,10 +817,11 @@ pub(crate) fn variable_declarator( Some(m.complete(p, JS_VARIABLE_DECLARATOR)) } +#[allow(deprecated)] fn variable_initializer(p: &mut Parser) { let m = p.start(); - p.expect(T![=]); + p.expect_required(T![=]); p.expr_with_semi_recovery(true); m.complete(p, SyntaxKind::JS_EQUAL_VALUE_CLAUSE); @@ -829,7 +838,7 @@ pub fn do_stmt(p: &mut Parser) -> CompletedMarker { // do while true let m = p.start(); let start = p.cur_tok().range.start; - p.expect(T![do]); + p.expect_required(T![do]); stmt( &mut *p.with_state(ParserState { continue_allowed: true, @@ -838,12 +847,13 @@ pub fn do_stmt(p: &mut Parser) -> CompletedMarker { }), None, ); - p.expect(T![while]); + p.expect_required(T![while]); parenthesized_expression(p); semi(p, start..p.cur_tok().range.end); m.complete(p, JS_DO_WHILE_STATEMENT) } +#[allow(deprecated)] fn for_head(p: &mut Parser) -> SyntaxKind { let m = p.start(); if p.at(T![const]) || p.at(T![var]) || (p.cur_src() == "let" && FOLLOWS_LET.contains(p.nth(1))) @@ -868,7 +878,7 @@ fn for_head(p: &mut Parser) -> SyntaxKind { for_each_head(p, is_in) } else { p.state.for_head_error = None; - p.expect(T![;]); + p.expect_required(T![;]); normal_for_head(p); FOR_STMT } @@ -905,7 +915,7 @@ fn for_head(p: &mut Parser) -> SyntaxKind { return for_each_head(p, is_in); } - p.expect(T![;]); + p.expect_required(T![;]); normal_for_head(p); FOR_STMT } @@ -926,7 +936,7 @@ fn normal_for_head(p: &mut Parser) { let m = p.start(); expr(p); m.complete(p, FOR_STMT_TEST); - p.expect(T![;]); + p.expect_required(T![;]); } if !p.at(T![')']) { @@ -948,13 +958,13 @@ pub fn for_stmt(p: &mut Parser) -> CompletedMarker { // for let i = 5; i < 10; i++ {} // for let i = 5; i < 10; ++i {} let m = p.start(); - p.expect(T![for]); + p.expect_required(T![for]); // FIXME: This should emit an error for non-for-of p.eat(T![await]); - p.expect(T!['(']); + p.expect_required(T!['(']); let kind = for_head(p); - p.expect(T![')']); + p.expect_required(T![')']); stmt( &mut *p.with_state(ParserState { continue_allowed: true, @@ -973,7 +983,7 @@ fn switch_clause(p: &mut Parser) -> Option> { match p.cur() { T![default] => { p.bump_any(); - p.expect(T![:]); + p.expect_required(T![:]); // We stop the range here because we dont want to include the entire clause // including the statement list following it let end = p.cur_tok().range.end; @@ -988,7 +998,7 @@ fn switch_clause(p: &mut Parser) -> Option> { T![case] => { p.bump_any(); expr(p); - p.expect(T![:]); + p.expect_required(T![:]); let cons_list = p.start(); while !p.at_ts(token_set![T![default], T![case], T!['}'], EOF]) { stmt(p, None); @@ -1007,7 +1017,8 @@ fn switch_clause(p: &mut Parser) -> Option> { "Expected the start to a case or default clause here", ); - ParseRecovery::with_error(STMT_RECOVERY_SET, JS_UNKNOWN_STATEMENT, err) + #[allow(deprecated)] + SingleTokenParseRecovery::with_error(STMT_RECOVERY_SET, JS_UNKNOWN_STATEMENT, err) .enabled_braces_check() .recover(p); } @@ -1033,9 +1044,9 @@ pub fn switch_stmt(p: &mut Parser) -> CompletedMarker { // switch foo {} // switch {} let m = p.start(); - p.expect(T![switch]); + p.expect_required(T![switch]); parenthesized_expression(p); - p.expect(T!['{']); + p.expect_required(T!['{']); let cases_list = p.start(); let mut first_default: Option> = None; @@ -1063,31 +1074,40 @@ pub fn switch_stmt(p: &mut Parser) -> CompletedMarker { } } cases_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); m.complete(p, JS_SWITCH_STATEMENT) } -fn catch_clause(p: &mut Parser) { +fn parse_catch_clause(p: &mut Parser) -> ParsedSyntax { + if !p.at(T![catch]) { + return Absent; + } + let m = p.start(); - p.expect(T![catch]); + p.bump_any(); // bump catch - if p.at(T!['(']) { - catch_declaration(p); - } + catch_declaration(p).or_missing(p); + block_stmt(p).or_missing_with_error(p, js_parse_error::expected_block_statement); - block_stmt(p, STMT_RECOVERY_SET); - m.complete(p, JS_CATCH_CLAUSE); + Present(m.complete(p, JS_CATCH_CLAUSE)) } -fn catch_declaration(p: &mut Parser) { +fn catch_declaration(p: &mut Parser) -> ParsedSyntax { + if !p.at(T!['(']) { + return Absent; + } + let declaration_marker = p.start(); - p.eat(T!['(']); + p.bump_any(); // bump ( - let error_marker = p.start(); - let kind = pattern(p, false, false).map(|x| x.kind()); + let pattern_marker = pattern(p, false, false); + let pattern_kind = pattern_marker.map(|x| x.kind()); if p.at(T![:]) { + let error_marker = pattern_marker + .map(|m| m.precede(p)) + .unwrap_or_else(|| p.start()); let start = p.cur_tok().range.start; p.bump_any(); let ty = ts_type(p); @@ -1106,7 +1126,7 @@ fn catch_declaration(p: &mut Parser) { let end = ty .map(|x| usize::from(x.range(p).end())) .unwrap_or(p.cur_tok().range.start); - error_marker.complete(p, kind.filter(|_| p.typescript()).unwrap_or(ERROR)); + error_marker.complete(p, pattern_kind.filter(|_| p.typescript()).unwrap_or(ERROR)); if !p.typescript() { let err = p @@ -1115,12 +1135,10 @@ fn catch_declaration(p: &mut Parser) { p.error(err); } - } else { - error_marker.abandon(p); } - p.expect(T![')']); + p.expect_required(T![')']); - declaration_marker.complete(p, JS_CATCH_DECLARATION); + Present(declaration_marker.complete(p, JS_CATCH_DECLARATION)) } /// A try statement such as @@ -1138,28 +1156,33 @@ fn catch_declaration(p: &mut Parser) { // try {} catch {} finally {} // try {} catch (e) {} finally {} // try {} finally {} -pub fn try_stmt(p: &mut Parser) -> CompletedMarker { +pub fn parse_try_statement(p: &mut Parser) -> ParsedSyntax { // TODO: recover from `try catch` and `try finally`. The issue is block_items // will cause infinite recursion because parsing a stmt would not consume the catch token // and block_items would not exit, and if we exited on any error that would greatly limit // block_items error recovery + + if !p.at(T![try]) { + return Absent; + } + let m = p.start(); - p.expect(T![try]); - block_stmt(p, None); + p.bump_any(); // eat try - if p.at(T![catch]) { - catch_clause(p); - }; + block_stmt(p).or_missing_with_error(p, js_parse_error::expected_block_statement); + + let catch = parse_catch_clause(p); + + if p.at(T![finally]) { + catch.or_missing(p); - let kind = if p.at(T![finally]) { let finalizer = p.start(); p.bump_any(); - block_stmt(p, None); + block_stmt(p).or_missing_with_error(p, js_parse_error::expected_block_statement); finalizer.complete(p, JS_FINALLY_CLAUSE); - JS_TRY_FINALLY_STATEMENT + Present(m.complete(p, JS_TRY_FINALLY_STATEMENT)) } else { - JS_TRY_STATEMENT - }; - - m.complete(p, kind) + catch.or_missing_with_error(p, js_parse_error::expected_catch_clause); + Present(m.complete(p, JS_TRY_STATEMENT)) + } } diff --git a/crates/rslint_parser/src/syntax/typescript.rs b/crates/rslint_parser/src/syntax/typescript.rs index 85a18c92386..2547e90639c 100644 --- a/crates/rslint_parser/src/syntax/typescript.rs +++ b/crates/rslint_parser/src/syntax/typescript.rs @@ -3,7 +3,8 @@ use super::decl::*; use super::expr::{assign_expr, identifier_name, lhs_expr, literal_expression}; use super::stmt::{semi, statements, variable_declaration_statement}; -use crate::parse_recovery::ParseRecovery; +#[allow(deprecated)] +use crate::parser::SingleTokenParseRecovery; use crate::syntax::class::class_declaration; use crate::syntax::expr::any_reference_member; use crate::syntax::function::function_declaration; @@ -329,7 +330,7 @@ pub fn ts_ambient_external_module_decl( if p.cur_src() == "global" { p.bump_any(); } else { - p.expect(JS_STRING_LITERAL); + p.expect_required(JS_STRING_LITERAL); } if p.at(T!['{']) { ts_module_block(p); @@ -406,7 +407,7 @@ pub fn ts_interface(p: &mut Parser) -> Option { extends_list.complete(p, LIST); } - p.expect(T!['{']); + p.expect_required(T!['{']); let members_list = p.start(); while !p.at(EOF) && !p.at(T!['}']) { @@ -414,7 +415,7 @@ pub fn ts_interface(p: &mut Parser) -> Option { } members_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); Some(m.complete(p, TS_INTERFACE_DECL)) } @@ -560,7 +561,7 @@ pub(crate) fn try_parse_index_signature( pub fn ts_signature_member(p: &mut Parser, construct_sig: bool) -> Option { let m = p.start(); if construct_sig { - p.expect(T![new]); + p.expect_required(T![new]); } if p.at(T![<]) { @@ -596,9 +597,9 @@ fn type_member_semi(p: &mut Parser) { pub fn ts_enum(p: &mut Parser) -> CompletedMarker { let m = p.start(); p.eat(T![const]); - p.expect(T![enum]); + p.expect_required(T![enum]); identifier_name(p); - p.expect(T!['{']); + p.expect_required(T!['{']); let mut first = true; let members_list = p.start(); @@ -610,7 +611,7 @@ pub fn ts_enum(p: &mut Parser) -> CompletedMarker { p.eat(T![,]); break; } else { - p.expect(T![,]); + p.expect_required(T![,]); } let member = p.start(); @@ -622,7 +623,8 @@ pub fn ts_enum(p: &mut Parser) -> CompletedMarker { .err_builder("expected an identifier or string for an enum variant, but found none") .primary(p.cur_tok().range, ""); - ParseRecovery::with_error( + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( token_set![T!['}'], T![ident], T![yield], T![await], T![=], T![,]], ERROR, err, @@ -648,7 +650,7 @@ pub fn ts_enum(p: &mut Parser) -> CompletedMarker { members_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); m.complete(p, TS_ENUM) } @@ -920,7 +922,7 @@ pub fn ts_tuple(p: &mut Parser) -> Option { let opt_range = p.cur_tok().range; let is_opt = name && p.eat(T![?]); if name { - p.expect(T![:]); + p.expect_required(T![:]); } no_recover!(p, ts_type(p)); if !name && p.at(T![?]) { @@ -996,7 +998,7 @@ pub fn ts_non_array_type(p: &mut Parser) -> Option { let e = p.start(); p.bump_any(); ts_type(p); - p.expect(T!['}']); + p.expect_required(T!['}']); e.complete(p, TS_TEMPLATE_ELEMENT); }, t => unreachable!("Anything not template chunk or dollarcurly should have been eaten by the lexer, but {:?} was found", t), @@ -1045,7 +1047,7 @@ pub fn ts_non_array_type(p: &mut Parser) -> Option { type_member_semi(p); } members_list.complete(p, LIST); - p.expect(T!['}']); + p.expect_required(T!['}']); Some(m.complete(p, TS_OBJECT_TYPE)) } } @@ -1067,7 +1069,8 @@ pub fn ts_non_array_type(p: &mut Parser) -> Option { .err_builder("expected a type") .primary(p.cur_tok().range, ""); - ParseRecovery::with_error( + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( BASE_TS_RECOVERY_SET.union(token_set![ T![typeof], T!['{'], @@ -1196,7 +1199,8 @@ fn type_param(p: &mut Parser) -> Option { .err_builder("expected a type parameter, but found none") .primary(p.cur_tok().range, ""); - ParseRecovery::with_error( + #[allow(deprecated)] + SingleTokenParseRecovery::with_error( token_set![T![ident], T![yield], T![await], T![>], T![=]], ERROR, err, @@ -1444,6 +1448,7 @@ pub fn ts_type_name( )) .primary(p.cur_tok().range, ""); - ParseRecovery::with_error(set, ERROR, err).recover(p); + #[allow(deprecated)] + SingleTokenParseRecovery::with_error(set, ERROR, err).recover(p); None } diff --git a/crates/rslint_parser/src/syntax/util.rs b/crates/rslint_parser/src/syntax/util.rs index 03194bac10c..2e1d6459d31 100644 --- a/crates/rslint_parser/src/syntax/util.rs +++ b/crates/rslint_parser/src/syntax/util.rs @@ -235,6 +235,7 @@ pub fn check_lhs(p: &mut Parser, expr: JsAnyExpression, marker: &CompletedMarker /// Check if the var declaration in a for statement has multiple declarators, which is invalid pub fn check_for_stmt_declaration(p: &mut Parser, marker: &CompletedMarker) { + #[allow(deprecated)] let parsed = p.parse_marker::(marker); let excess = parsed.declarators().iter().skip(1).collect::>(); diff --git a/crates/rslint_parser/test_data/inline/err/block_stmt_in_class.rast b/crates/rslint_parser/test_data/inline/err/block_stmt_in_class.rast index 7c30facc3e8..4ca4ee30f5f 100644 --- a/crates/rslint_parser/test_data/inline/err/block_stmt_in_class.rast +++ b/crates/rslint_parser/test_data/inline/err/block_stmt_in_class.rast @@ -9,18 +9,19 @@ 2: L_CURLY@7..8 "{" [] [] 3: LIST@8..9 0: JS_UNKNOWN_MEMBER@8..9 - 0: JS_UNKNOWN_MEMBER@8..9 + 0: (empty) + 1: JS_UNKNOWN_MEMBER@8..9 0: L_CURLY@8..9 "{" [] [] 4: R_CURLY@9..10 "}" [] [] 1: JS_UNKNOWN_STATEMENT@10..11 0: R_CURLY@10..11 "}" [] [] 3: EOF@11..12 "" [Whitespace("\n")] [] -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '{' ┌─ block_stmt_in_class.js:1:9 │ 1 │ class S{{}} - │ ^ Expected an identifier, a keyword, or a string or number literal here + │ ^ Expected an identifier, a string literal, a number literal, a private field name, or a computed name here -- error[SyntaxError]: expected `;`, a property, or a method for a class body, but found none diff --git a/crates/rslint_parser/test_data/inline/err/class_decl_err.rast b/crates/rslint_parser/test_data/inline/err/class_decl_err.rast index 7f46ab04797..5ad856dde67 100644 --- a/crates/rslint_parser/test_data/inline/err/class_decl_err.rast +++ b/crates/rslint_parser/test_data/inline/err/class_decl_err.rast @@ -41,11 +41,13 @@ 3: JS_SETTER_CLASS_MEMBER@65..72 0: SET_KW@65..69 "set" [] [Whitespace(" ")] 1: (empty) - 2: OBJECT_PATTERN@69..72 + 2: (empty) + 3: OBJECT_PATTERN@69..72 0: L_CURLY@69..70 "{" [] [] 1: LIST@70..70 2: R_CURLY@70..72 "}" [] [Whitespace(" ")] - 3: (empty) + 4: (empty) + 5: (empty) 4: R_CURLY@72..73 "}" [] [] 3: JS_CLASS_DECLARATION@73..108 0: CLASS_KW@73..80 "class" [Whitespace("\n")] [Whitespace(" ")] @@ -122,11 +124,11 @@ error[SyntaxError]: expected `;`, a property, or a method for a class body, but │ ^ -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '{' ┌─ class_decl_err.js:5:17 │ 5 │ class foo { set {} } - │ ^ Expected an identifier, a keyword, or a string or number literal here + │ ^ Expected an identifier, a string literal, a number literal, a private field name, or a computed name here -- error[SyntaxError]: expected `'('` but instead found `{` @@ -143,11 +145,11 @@ error[SyntaxError]: expected `')'` but instead found `}` │ ^ unexpected -- -error[SyntaxError]: expected a block statement but instead found `}` +error[SyntaxError]: expected a function body but instead found '}' ┌─ class_decl_err.js:5:20 │ 5 │ class foo { set {} } - │ ^ + │ ^ Expected a function body here -- error[SyntaxError]: classes cannot extend multiple classes diff --git a/crates/rslint_parser/test_data/inline/err/function_broken.rast b/crates/rslint_parser/test_data/inline/err/function_broken.rast index a1f8dc7845f..ae5670ff475 100644 --- a/crates/rslint_parser/test_data/inline/err/function_broken.rast +++ b/crates/rslint_parser/test_data/inline/err/function_broken.rast @@ -10,6 +10,7 @@ 0: L_PAREN@12..13 "(" [] [] 1: LIST@13..13 2: R_PAREN@13..14 ")" [] [] + 3: (empty) 1: JS_UNKNOWN_STATEMENT@14..15 0: R_PAREN@14..15 ")" [] [] 2: JS_UNKNOWN_STATEMENT@15..16 @@ -36,11 +37,11 @@ 2: (empty) 3: EOF@25..26 "" [Whitespace("\n")] [] -- -error[SyntaxError]: expected a block statement but instead found `)` +error[SyntaxError]: expected a function body but instead found ')' ┌─ function_broken.js:1:15 │ 1 │ function foo())})}{{{ {} - │ ^ + │ ^ Expected a function body here -- error[SyntaxError]: Expected a statement or declaration, but found none diff --git a/crates/rslint_parser/test_data/inline/err/function_decl_err.js b/crates/rslint_parser/test_data/inline/err/function_decl_err.js index 5d379ae146e..e6f4e86aa06 100644 --- a/crates/rslint_parser/test_data/inline/err/function_decl_err.js +++ b/crates/rslint_parser/test_data/inline/err/function_decl_err.js @@ -5,3 +5,4 @@ async function() {} async function *() {} function *foo() {} yield foo; +function test(): number {} diff --git a/crates/rslint_parser/test_data/inline/err/function_decl_err.rast b/crates/rslint_parser/test_data/inline/err/function_decl_err.rast index 51d946e8cf5..b1306c309d1 100644 --- a/crates/rslint_parser/test_data/inline/err/function_decl_err.rast +++ b/crates/rslint_parser/test_data/inline/err/function_decl_err.rast @@ -1,7 +1,7 @@ -0: JS_ROOT@0..114 +0: JS_ROOT@0..141 0: (empty) 1: LIST@0..0 - 2: LIST@0..113 + 2: LIST@0..140 0: JS_FUNCTION_DECLARATION@0..13 0: FUNCTION_KW@0..8 "function" [] [] 1: JS_PARAMETER_LIST@8..11 @@ -83,7 +83,24 @@ 0: JS_REFERENCE_IDENTIFIER_EXPRESSION@109..112 0: IDENT@109..112 "foo" [] [] 1: SEMICOLON@112..113 ";" [] [] - 3: EOF@113..114 "" [Whitespace("\n")] [] + 7: JS_UNKNOWN_STATEMENT@113..140 + 0: FUNCTION_KW@113..123 "function" [Whitespace("\n")] [Whitespace(" ")] + 1: JS_IDENTIFIER_BINDING@123..127 + 0: IDENT@123..127 "test" [] [] + 2: JS_PARAMETER_LIST@127..129 + 0: L_PAREN@127..128 "(" [] [] + 1: LIST@128..128 + 2: R_PAREN@128..129 ")" [] [] + 3: TS_TYPE_ANNOTATION@129..138 + 0: COLON@129..131 ":" [] [Whitespace(" ")] + 1: TS_NUMBER@131..138 + 0: IDENT@131..138 "number" [] [Whitespace(" ")] + 4: JS_FUNCTION_BODY@138..140 + 0: L_CURLY@138..139 "{" [] [] + 1: LIST@139..139 + 2: LIST@139..139 + 3: R_CURLY@139..140 "}" [] [] + 3: EOF@140..141 "" [Whitespace("\n")] [] -- error[SyntaxError]: expected a name for the function in a function declaration, but found none ┌─ function_decl_err.js:1:9 @@ -164,6 +181,13 @@ error[SyntaxError]: Expected a semicolon or an implicit semicolon after a statem │ │ An explicit or implicit semicolon is expected here... │ ...Which is required to end this statement +-- +error[SyntaxError]: return types can only be used in TypeScript files + ┌─ function_decl_err.js:8:16 + │ +8 │ function test(): number {} + │ ^^^^^^^^ + -- function() {} function {} @@ -172,3 +196,4 @@ async function() {} async function *() {} function *foo() {} yield foo; +function test(): number {} diff --git a/crates/rslint_parser/test_data/inline/err/method_getter_err.rast b/crates/rslint_parser/test_data/inline/err/method_getter_err.rast index 2d99091de8b..5347f18b2e6 100644 --- a/crates/rslint_parser/test_data/inline/err/method_getter_err.rast +++ b/crates/rslint_parser/test_data/inline/err/method_getter_err.rast @@ -12,7 +12,8 @@ 0: GET_KW@11..17 "get" [Whitespace("\n ")] [Whitespace(" ")] 1: (empty) 2: (empty) - 3: JS_FUNCTION_BODY@17..19 + 3: (empty) + 4: JS_FUNCTION_BODY@17..19 0: L_CURLY@17..18 "{" [] [] 1: LIST@18..18 2: LIST@18..18 @@ -20,11 +21,11 @@ 4: R_CURLY@19..21 "}" [Whitespace("\n")] [] 3: EOF@21..22 "" [Whitespace("\n")] [] -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '{' ┌─ method_getter_err.js:2:6 │ 2 │ get {} - │ ^ Expected an identifier, a keyword, or a string or number literal here + │ ^ Expected an identifier, a string literal, a number literal, a private field name, or a computed name here -- error[SyntaxError]: expected `'('` but instead found `{` diff --git a/crates/rslint_parser/test_data/inline/err/object_expr_error_prop_name.rast b/crates/rslint_parser/test_data/inline/err/object_expr_error_prop_name.rast index fb741646803..d7fa1d83c8e 100644 --- a/crates/rslint_parser/test_data/inline/err/object_expr_error_prop_name.rast +++ b/crates/rslint_parser/test_data/inline/err/object_expr_error_prop_name.rast @@ -16,10 +16,11 @@ 0: L_CURLY@8..10 "{" [] [Whitespace(" ")] 1: LIST@10..25 0: JS_PROPERTY_OBJECT_MEMBER@10..25 - 0: ERROR@10..17 + 0: (empty) + 1: ERROR@10..17 0: JS_REGEX_LITERAL@10..17 "/: 6, /" [] [] - 1: COLON@17..19 ":" [] [Whitespace(" ")] - 2: JS_REGEX_LITERAL_EXPRESSION@19..25 + 2: COLON@17..19 ":" [] [Whitespace(" ")] + 3: JS_REGEX_LITERAL_EXPRESSION@19..25 0: JS_REGEX_LITERAL@19..25 "/foo/" [] [Whitespace(" ")] 2: R_CURLY@25..26 "}" [] [] 1: (empty) @@ -37,8 +38,7 @@ 0: L_CURLY@35..36 "{" [] [] 1: LIST@36..37 0: JS_UNKNOWN_MEMBER@36..37 - 0: ERROR@36..37 - 0: L_CURLY@36..37 "{" [] [] + 0: L_CURLY@36..37 "{" [] [] 2: R_CURLY@37..38 "}" [] [] 1: (empty) 2: JS_UNKNOWN_STATEMENT@38..39 @@ -72,18 +72,18 @@ 1: (empty) 3: EOF@95..96 "" [Whitespace("\n")] [] -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected a property, a shorthand property, a getter, a setter, or a method but instead found '/: 6, /' ┌─ object_expr_error_prop_name.js:1:11 │ 1 │ let a = { /: 6, /: /foo/ } - │ ^^^^^^^ Expected an identifier, a keyword, or a string or number literal here + │ ^^^^^^^ Expected a property, a shorthand property, a getter, a setter, or a method here -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected a property, a shorthand property, a getter, a setter, or a method but instead found '{' ┌─ object_expr_error_prop_name.js:2:10 │ 2 │ let a = {{}} - │ ^ Expected an identifier, a keyword, or a string or number literal here + │ ^ Expected a property, a shorthand property, a getter, a setter, or a method here -- error[SyntaxError]: Expected a statement or declaration, but found none diff --git a/crates/rslint_parser/test_data/inline/err/object_expr_method.rast b/crates/rslint_parser/test_data/inline/err/object_expr_method.rast index ab74d8a9d01..d7d9042fba1 100644 --- a/crates/rslint_parser/test_data/inline/err/object_expr_method.rast +++ b/crates/rslint_parser/test_data/inline/err/object_expr_method.rast @@ -19,10 +19,8 @@ 0: JS_LITERAL_MEMBER_NAME@10..13 0: IDENT@10..13 "foo" [] [] 1: (empty) - 1: (empty) - 2: JS_UNKNOWN_MEMBER@13..15 - 0: ERROR@13..15 - 0: R_PAREN@13..15 ")" [] [Whitespace(" ")] + 1: JS_UNKNOWN_MEMBER@13..15 + 0: R_PAREN@13..15 ")" [] [Whitespace(" ")] 2: R_CURLY@15..16 "}" [] [] 1: (empty) 3: EOF@16..17 "" [Whitespace("\n")] [] @@ -48,11 +46,11 @@ error[SyntaxError]: expected `,` but instead found `)` │ ^ unexpected -- -error[SyntaxError]: Expected an identifier, a keyword, or a string or number literal +error[SyntaxError]: expected a property, a shorthand property, a getter, a setter, or a method but instead found ')' ┌─ object_expr_method.js:1:14 │ 1 │ let b = { foo) } - │ ^ Expected an identifier, a keyword, or a string or number literal here + │ ^ Expected a property, a shorthand property, a getter, a setter, or a method here -- let b = { foo) } diff --git a/crates/rslint_parser/test_data/inline/err/paren_or_arrow_expr_invalid_params.rast b/crates/rslint_parser/test_data/inline/err/paren_or_arrow_expr_invalid_params.rast index ca57a3eb5de..076d3f01449 100644 --- a/crates/rslint_parser/test_data/inline/err/paren_or_arrow_expr_invalid_params.rast +++ b/crates/rslint_parser/test_data/inline/err/paren_or_arrow_expr_invalid_params.rast @@ -13,6 +13,7 @@ 0: PLUS@3..5 "+" [] [Whitespace(" ")] 2: (empty) 1: JS_NUMBER_LITERAL@5..6 "5" [] [] + 2: (empty) 1: (empty) 1: JS_UNKNOWN_STATEMENT@6..8 0: R_PAREN@6..8 ")" [] [Whitespace(" ")] @@ -67,6 +68,13 @@ error[SyntaxError]: Expected an expression, but found none 1 │ (5 + 5) => {} │ ^ Expected an expression here +-- +error[SyntaxError]: expected a function body, or an expression but instead found ')' + ┌─ paren_or_arrow_expr_invalid_params.js:1:7 + │ +1 │ (5 + 5) => {} + │ ^ Expected a function body, or an expression here + -- error[SyntaxError]: Expected a semicolon or an implicit semicolon after a statement, but found none ┌─ paren_or_arrow_expr_invalid_params.js:1:7 diff --git a/crates/rslint_parser/test_data/inline/ok/object_expr_async_method.rast b/crates/rslint_parser/test_data/inline/ok/object_expr_async_method.rast index 0d48f0d19fa..76a40b44f80 100644 --- a/crates/rslint_parser/test_data/inline/ok/object_expr_async_method.rast +++ b/crates/rslint_parser/test_data/inline/ok/object_expr_async_method.rast @@ -17,13 +17,14 @@ 1: LIST@9..45 0: JS_METHOD_OBJECT_MEMBER@9..26 0: ASYNC_KW@9..18 "async" [Whitespace("\n ")] [Whitespace(" ")] - 1: JS_LITERAL_MEMBER_NAME@18..21 + 1: (empty) + 2: JS_LITERAL_MEMBER_NAME@18..21 0: IDENT@18..21 "foo" [] [] - 2: JS_PARAMETER_LIST@21..24 + 3: JS_PARAMETER_LIST@21..24 0: L_PAREN@21..22 "(" [] [] 1: LIST@22..22 2: R_PAREN@22..24 ")" [] [Whitespace(" ")] - 3: JS_FUNCTION_BODY@24..26 + 4: JS_FUNCTION_BODY@24..26 0: L_CURLY@24..25 "{" [] [] 1: LIST@25..25 2: LIST@25..25 diff --git a/crates/rslint_parser/test_data/inline/ok/object_expr_generator_method.rast b/crates/rslint_parser/test_data/inline/ok/object_expr_generator_method.rast index 7d092e53597..eb1622907d7 100644 --- a/crates/rslint_parser/test_data/inline/ok/object_expr_generator_method.rast +++ b/crates/rslint_parser/test_data/inline/ok/object_expr_generator_method.rast @@ -16,14 +16,15 @@ 0: L_CURLY@8..10 "{" [] [Whitespace(" ")] 1: LIST@10..20 0: JS_METHOD_OBJECT_MEMBER@10..20 - 0: STAR@10..11 "*" [] [] - 1: JS_LITERAL_MEMBER_NAME@11..14 + 0: (empty) + 1: STAR@10..11 "*" [] [] + 2: JS_LITERAL_MEMBER_NAME@11..14 0: IDENT@11..14 "foo" [] [] - 2: JS_PARAMETER_LIST@14..17 + 3: JS_PARAMETER_LIST@14..17 0: L_PAREN@14..15 "(" [] [] 1: LIST@15..15 2: R_PAREN@15..17 ")" [] [Whitespace(" ")] - 3: JS_FUNCTION_BODY@17..20 + 4: JS_FUNCTION_BODY@17..20 0: L_CURLY@17..18 "{" [] [] 1: LIST@18..18 2: LIST@18..18 diff --git a/crates/rslint_parser/test_data/inline/ok/try_stmt.rast b/crates/rslint_parser/test_data/inline/ok/try_stmt.rast index 88995ce8f8f..3f3093006d7 100644 --- a/crates/rslint_parser/test_data/inline/ok/try_stmt.rast +++ b/crates/rslint_parser/test_data/inline/ok/try_stmt.rast @@ -10,7 +10,8 @@ 2: R_CURLY@5..7 "}" [] [Whitespace(" ")] 2: JS_CATCH_CLAUSE@7..15 0: CATCH_KW@7..13 "catch" [] [Whitespace(" ")] - 1: JS_BLOCK_STATEMENT@13..15 + 1: (empty) + 2: JS_BLOCK_STATEMENT@13..15 0: L_CURLY@13..14 "{" [] [] 1: LIST@14..14 2: R_CURLY@14..15 "}" [] [] @@ -40,7 +41,8 @@ 2: R_CURLY@41..43 "}" [] [Whitespace(" ")] 2: JS_CATCH_CLAUSE@43..52 0: CATCH_KW@43..49 "catch" [] [Whitespace(" ")] - 1: JS_BLOCK_STATEMENT@49..52 + 1: (empty) + 2: JS_BLOCK_STATEMENT@49..52 0: L_CURLY@49..50 "{" [] [] 1: LIST@50..50 2: R_CURLY@50..52 "}" [] [Whitespace(" ")] @@ -80,7 +82,8 @@ 0: L_CURLY@98..99 "{" [] [] 1: LIST@99..99 2: R_CURLY@99..101 "}" [] [Whitespace(" ")] - 2: JS_FINALLY_CLAUSE@101..111 + 2: (empty) + 3: JS_FINALLY_CLAUSE@101..111 0: FINALLY_KW@101..109 "finally" [] [Whitespace(" ")] 1: JS_BLOCK_STATEMENT@109..111 0: L_CURLY@109..110 "{" [] []