Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions crates/oxc_linter/src/rules/unicorn/prefer_type_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,16 @@ fn test() {
// throw new TypeError;
// }
// "#,
r#"
r"
if (typeof foo == 'Foo' || 'Foo' === typeof foo) {
throw new Error();
}
r#"
",
r"
if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
throw new Error();
}
"#,
",
r"
if (wrapper.n.isFinite(foo) && wrapper.n.isSafeInteger(foo) && wrapper.n.isInteger(foo)) {
throw new Error();
Expand Down
23 changes: 12 additions & 11 deletions crates/oxc_linter/src/snapshots/unicorn_prefer_type_error.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,23 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Change to `throw new TypeError(...)`

× Invalid Character `"`
╭─[prefer_type_error.tsx:5:11]
⚠ eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
╭─[prefer_type_error.tsx:3:27]
2 │ if (typeof foo == 'Foo' || 'Foo' === typeof foo) {
3 │ throw new Error();
· ─────
4 │ }
5 │ r#"
· ─
6 │ if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
╰────
help: Change to `throw new TypeError(...)`

× Expected a semicolon or an implicit semicolon after a statement, but found none
╭─[prefer_type_error.tsx:5:10]
⚠ eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
╭─[prefer_type_error.tsx:3:27]
2 │ if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
3 │ throw new Error();
· ─────
4 │ }
5 │ r#"
· ▲
6 │ if (Number.isFinite(foo) && Number.isSafeInteger(foo) && Number.isInteger(foo)) {
╰────
help: Try insert a semicolon here
help: Change to `throw new TypeError(...)`

⚠ eslint-plugin-unicorn(prefer-type-error): Prefer throwing a `TypeError` over a generic `Error` after a type checking if-statement
╭─[prefer_type_error.tsx:3:27]
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_parser/examples/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ fn main() -> Result<(), String> {
for error in ret.errors {
let error = error.with_source_code(source_text.clone());
println!("{error:?}");
println!("Parsed with Errors.");
}
println!("Parsed with Errors.");
}

Ok(())
Expand Down
25 changes: 16 additions & 9 deletions crates/oxc_parser/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ use oxc_span::{GetSpan, Span};

use crate::{
Context, ParserImpl, diagnostics,
error_handler::FatalError,
lexer::{Kind, LexerCheckpoint, LexerContext, Token},
};

#[derive(Clone, Copy)]
#[derive(Clone)]
pub struct ParserCheckpoint<'a> {
lexer: LexerCheckpoint<'a>,
cur_token: Token,
prev_span_end: u32,
errors_pos: usize,
fatal_error: Option<FatalError>,
}

impl<'a> ParserImpl<'a> {
Expand Down Expand Up @@ -169,7 +171,8 @@ impl<'a> ParserImpl<'a> {
pub(crate) fn asi(&mut self) -> Result<()> {
if !self.can_insert_semicolon() {
let span = Span::new(self.prev_token_end, self.prev_token_end);
return Err(diagnostics::auto_semicolon_insertion(span));
let error = diagnostics::auto_semicolon_insertion(span);
return Err(self.set_fatal_error(error));
}
if self.at(Kind::Semicolon) {
self.advance(Kind::Semicolon);
Expand All @@ -186,10 +189,11 @@ impl<'a> ParserImpl<'a> {
}

/// # Errors
pub(crate) fn expect_without_advance(&self, kind: Kind) -> Result<()> {
pub(crate) fn expect_without_advance(&mut self, kind: Kind) -> Result<()> {
if !self.at(kind) {
let range = self.cur_token().span();
return Err(diagnostics::expect_token(kind.to_str(), self.cur_kind().to_str(), range));
let error = diagnostics::expect_token(kind.to_str(), self.cur_kind().to_str(), range);
return Err(self.set_fatal_error(error));
}
Ok(())
}
Expand Down Expand Up @@ -271,22 +275,25 @@ impl<'a> ParserImpl<'a> {
}
}

pub(crate) fn checkpoint(&self) -> ParserCheckpoint<'a> {
pub(crate) fn checkpoint(&mut self) -> ParserCheckpoint<'a> {
ParserCheckpoint {
lexer: self.lexer.checkpoint(),
cur_token: self.token,
prev_span_end: self.prev_token_end,
errors_pos: self.errors.len(),
fatal_error: self.fatal_error.take(),
}
}

pub(crate) fn rewind(&mut self, checkpoint: ParserCheckpoint<'a>) {
let ParserCheckpoint { lexer, cur_token, prev_span_end, errors_pos } = checkpoint;
let ParserCheckpoint { lexer, cur_token, prev_span_end, errors_pos, fatal_error } =
checkpoint;

self.lexer.rewind(lexer);
self.token = cur_token;
self.prev_token_end = prev_span_end;
self.errors.truncate(errors_pos);
self.fatal_error = fatal_error;
}

/// # Errors
Expand Down Expand Up @@ -343,7 +350,7 @@ impl<'a> ParserImpl<'a> {
let mut list = self.ast.vec();
loop {
let kind = self.cur_kind();
if kind == close || kind == Kind::Eof {
if kind == close || self.has_fatal_error() {
break;
}
match f(self)? {
Expand Down Expand Up @@ -373,7 +380,7 @@ impl<'a> ParserImpl<'a> {
let mut first = true;
loop {
let kind = self.cur_kind();
if kind == close || kind == Kind::Eof {
if kind == close || self.has_fatal_error() {
break;
}
if first {
Expand Down Expand Up @@ -408,7 +415,7 @@ impl<'a> ParserImpl<'a> {
let mut first = true;
loop {
let kind = self.cur_kind();
if kind == close || kind == Kind::Eof {
if kind == close || self.has_fatal_error() {
break;
}
if first {
Expand Down
5 changes: 5 additions & 0 deletions crates/oxc_parser/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ pub fn a_rest_element_cannot_have_an_initializer(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error("A rest element cannot have an initializer.").with_label(span)
}

#[cold]
pub fn import_requires_a_specifier(span: Span) -> OxcDiagnostic {
OxcDiagnostic::error("import() requires a specifier.").with_label(span)
}

// ================================= MODIFIERS =================================

#[cold]
Expand Down
70 changes: 70 additions & 0 deletions crates/oxc_parser/src/error_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Code related to error handling.

use oxc_allocator::Dummy;
use oxc_diagnostics::OxcDiagnostic;

use crate::{ParserImpl, diagnostics, lexer::Kind};

/// Fatal parsing error.
#[derive(Debug, Clone)]
pub struct FatalError {
/// The fatal error
pub error: OxcDiagnostic,
/// Length of `errors` at time fatal error is recorded
#[expect(unused)]
pub errors_len: usize,
}

impl<'a> ParserImpl<'a> {
pub(crate) fn set_unexpected(&mut self) -> OxcDiagnostic {
// The lexer should have reported a more meaningful diagnostic
// when it is a undetermined kind.
if matches!(self.cur_kind(), Kind::Eof | Kind::Undetermined) {
if let Some(error) = self.lexer.errors.pop() {
return self.set_fatal_error(error);
}
}
let error = diagnostics::unexpected_token(self.cur_token().span());
self.set_fatal_error(error)
}

/// Return error info at current token
///
/// # Panics
///
/// * The lexer did not push a diagnostic when `Kind::Undetermined` is returned
pub(crate) fn unexpected(&mut self) -> OxcDiagnostic {
self.set_unexpected()
// Dummy::dummy(self.ast.allocator)
}

/// Push a Syntax Error
pub(crate) fn error(&mut self, error: OxcDiagnostic) {
self.errors.push(error);
}

/// Count of all parser and lexer errors.
pub(crate) fn errors_count(&self) -> usize {
self.errors.len() + self.lexer.errors.len()
}

/// Advance lexer's cursor to end of file.
pub(crate) fn set_fatal_error(&mut self, error: OxcDiagnostic) -> OxcDiagnostic {
if self.fatal_error.is_none() {
self.lexer.advance_to_end();
self.fatal_error =
Some(FatalError { error: error.clone(), errors_len: self.errors.len() });
}
error
}

#[expect(unused)]
pub(crate) fn fatal_error<T: Dummy<'a>>(&mut self, error: OxcDiagnostic) -> T {
let _ = self.set_fatal_error(error);
Dummy::dummy(self.ast.allocator)
}

pub(crate) fn has_fatal_error(&self) -> bool {
matches!(self.cur_kind(), Kind::Eof | Kind::Undetermined) || self.fatal_error.is_some()
}
}
3 changes: 2 additions & 1 deletion crates/oxc_parser/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ impl<'a> ParserImpl<'a> {
)?;
if let Some(rest) = &rest {
if !matches!(&rest.argument.kind, BindingPatternKind::BindingIdentifier(_)) {
return Err(diagnostics::invalid_binding_rest_element(rest.argument.span()));
let error = diagnostics::invalid_binding_rest_element(rest.argument.span());
return Err(self.set_fatal_error(error));
}
}
self.expect(Kind::RCurly)?;
Expand Down
19 changes: 11 additions & 8 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ impl<'a> ParserImpl<'a> {
pub(crate) fn parse_binding_identifier(&mut self) -> Result<BindingIdentifier<'a>> {
let cur = self.cur_kind();
if !cur.is_binding_identifier() {
let err = if cur.is_reserved_keyword() {
diagnostics::identifier_reserved_word(self.cur_token().span(), cur.to_str())
return Err(if cur.is_reserved_keyword() {
let error =
diagnostics::identifier_reserved_word(self.cur_token().span(), cur.to_str());
self.set_fatal_error(error)
} else {
self.unexpected()
};
return Err(err);
});
}
let (span, name) = self.parse_identifier_kind(Kind::Ident);
self.check_identifier(span, &name);
Expand Down Expand Up @@ -211,7 +212,8 @@ impl<'a> ParserImpl<'a> {

if expressions.is_empty() {
self.expect(Kind::RParen)?;
return Err(diagnostics::empty_parenthesized_expression(self.end_span(span)));
let error = diagnostics::empty_parenthesized_expression(self.end_span(span));
return Err(self.set_fatal_error(error));
}

let expr_span = self.end_span(expr_span);
Expand Down Expand Up @@ -477,14 +479,14 @@ impl<'a> ParserImpl<'a> {
self.re_lex_template_substitution_tail();
loop {
match self.cur_kind() {
Kind::Eof => self.expect(Kind::TemplateTail)?,
Kind::TemplateTail => {
quasis.push(self.parse_template_element(tagged));
break;
}
Kind::TemplateMiddle => {
quasis.push(self.parse_template_element(tagged));
}
_ if self.has_fatal_error() => self.expect(Kind::TemplateTail)?,
_ => {
// TemplateMiddle Expression[+In, ?Yield, ?Await]
let expr =
Expand Down Expand Up @@ -737,7 +739,7 @@ impl<'a> ParserImpl<'a> {
| Kind::TemplateHead
| Kind::LBrack => break,
_ => {
return Err(diagnostics::unexpected_token(self.cur_token().span()));
return Err(self.unexpected());
}
}
}
Expand Down Expand Up @@ -1050,7 +1052,8 @@ impl<'a> ParserImpl<'a> {
self.expect(Kind::In)?;
let right = self.parse_binary_expression_or_higher(Precedence::Lowest)?;
if let Expression::PrivateInExpression(private_in_expr) = right {
return Err(diagnostics::private_in_private(private_in_expr.span));
let error = diagnostics::private_in_private(private_in_expr.span);
return Err(self.set_fatal_error(error));
}
self.ast.expression_private_in(self.end_span(lhs_span), left, right)
} else {
Expand Down
3 changes: 2 additions & 1 deletion crates/oxc_parser/src/js/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ impl<'a> CoverGrammar<'a, ArrayExpression<'a>> for ArrayAssignmentTarget<'a> {
p.error(diagnostics::binding_rest_element_trailing_comma(*span));
}
} else {
return Err(diagnostics::spread_last_element(elem.span));
let error = diagnostics::spread_last_element(elem.span);
return Err(p.set_fatal_error(error));
}
}
ArrayExpressionElement::Elision(_) => elements.push(None),
Expand Down
7 changes: 4 additions & 3 deletions crates/oxc_parser/src/js/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ impl<'a> ParserImpl<'a> {
) -> Result<Expression<'a>> {
self.expect(Kind::LParen)?;
if self.eat(Kind::RParen) {
return Err(oxc_diagnostics::OxcDiagnostic::error("import() requires a specifier.")
.with_label(self.end_span(span)));
let error = diagnostics::import_requires_a_specifier(self.end_span(span));
return Err(self.set_fatal_error(error));
}
let has_in = self.ctx.has_in();
self.ctx = self.ctx.and_in(true);
Expand All @@ -31,7 +31,8 @@ impl<'a> ParserImpl<'a> {
// Allow trailing comma
self.bump(Kind::Comma);
if !self.eat(Kind::RParen) {
return Err(diagnostics::import_arguments(self.end_span(span)));
let error = diagnostics::import_arguments(self.end_span(span));
return Err(self.set_fatal_error(error));
}
self.ctx = self.ctx.and_in(has_in);
let expr =
Expand Down
8 changes: 5 additions & 3 deletions crates/oxc_parser/src/js/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<'a> ParserImpl<'a> {
let mut statements = self.ast.vec();

let mut expecting_directives = true;
while !self.at(Kind::Eof) {
while !self.has_fatal_error() {
if !is_top_level && self.at(Kind::RCurly) {
break;
}
Expand Down Expand Up @@ -220,7 +220,7 @@ impl<'a> ParserImpl<'a> {
let span = self.start_span();
self.expect(Kind::LCurly)?;
let mut body = self.ast.vec();
while !self.at(Kind::RCurly) && !self.at(Kind::Eof) {
while !self.at(Kind::RCurly) && !self.has_fatal_error() {
let stmt = self.parse_statement_list_item(StatementContext::StatementList)?;
body.push(stmt);
}
Expand Down Expand Up @@ -553,7 +553,9 @@ impl<'a> ParserImpl<'a> {
};
self.expect(Kind::Colon)?;
let mut consequent = self.ast.vec();
while !matches!(self.cur_kind(), Kind::Case | Kind::Default | Kind::RCurly | Kind::Eof) {
while !matches!(self.cur_kind(), Kind::Case | Kind::Default | Kind::RCurly)
&& !self.has_fatal_error()
{
let stmt = self.parse_statement_list_item(StatementContext::StatementList)?;
consequent.push(stmt);
}
Expand Down
Loading
Loading