This repository has been archived by the owner on Aug 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 665
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(rslint_parser): ParsedSyntax, ConditionalParsedSyntax, and Inval…
…idParsedSyntax Introduces the new `ParsedSyntax`, `ConditionalParsedSyntax`, and `InvalidParsedSyntax` that all require explicit error handling. See #1815
- Loading branch information
1 parent
cbbdd0e
commit 22cd68b
Showing
30 changed files
with
1,307 additions
and
385 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<usize>) -> 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<usize>) -> ExpectedNodeDiagnosticBuilder { | ||
ExpectedNodeDiagnosticBuilder::with_any(names, range) | ||
} | ||
|
||
pub trait ToDiagnostic { | ||
fn to_diagnostic(&self, p: &Parser) -> Diagnostic; | ||
} | ||
|
||
pub struct ExpectedNodeDiagnosticBuilder { | ||
names: String, | ||
range: Range<usize>, | ||
} | ||
|
||
impl ExpectedNodeDiagnosticBuilder { | ||
fn with_single_node(name: &str, range: Range<usize>) -> Self { | ||
ExpectedNodeDiagnosticBuilder { | ||
names: format!("{} {}", article_for(name), name), | ||
range, | ||
} | ||
} | ||
|
||
fn with_any(names: &[&str], range: Range<usize>) -> Self { | ||
debug_assert!(names.len() > 1, "Requires at least 2 names"); | ||
|
||
if names.len() < 2 { | ||
return Self::with_single_node(names.first().unwrap_or(&"<missing>"), 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", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CompletedMarker, RecoveryError>; | ||
|
||
/// 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)) | ||
} | ||
} |
Oops, something went wrong.