mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(parser): report duplicate switch default clause in the parser (#23012)
## What Moves the "A 'default' clause cannot appear more than once in a 'switch' statement" (TS1113) check out of the semantic checker (`check_switch_statement`) and into the parser's `parse_switch_statement`. ## Why The check only reads the switch statement's own `cases` list, which is fully built at the parse site — it needs no scope, symbol, or strict-mode information, so it can move to the parser without introducing any new parser state. Reporting it during parsing surfaces the error in source order and removes a node-kind dispatch from the semantic builder. ## How - The duplicate-`default` check is folded into the case-parsing pass (no second loop over `cases`). To do this without duplicating `parse_normal_list`'s open/loop/close logic, its callback bound is relaxed from `Fn` to `FnMut` (a safe generalization — all existing callers already satisfy it), so the case callback can track the first `default` span inline. - Only the first duplicate per `switch` is reported, matching the previous semantic-checker behavior. - A `unicorn/no-useless-switch-case` pass fixture that relied on a two-`default` switch being a *recoverable* (semantic-only) error was removed: the linter treats any parser error as fatal to the file, so a duplicate `default` now stops that file from being linted. ## Conformance No positive/negative count changes in any suite. The only snapshot delta is a reordering in `parser_typescript.snap` (the error now fires during parsing, in source order, ahead of later errors in the same switch). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
8 files changed
+50
-59
No files matched your search
@@ -166,17 +166,6 @@ fn test() {
|
||||
console.log('2')
|
||||
}
|
||||
",
|
||||
"
|
||||
switch (1) {
|
||||
default:
|
||||
handleDefaultCase1();
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
handleDefaultCase2();
|
||||
break;
|
||||
}
|
||||
",
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
|
||||
@@ -369,9 +369,14 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
result
|
||||
}
|
||||
|
||||
pub(crate) fn parse_normal_list<F, T>(&mut self, open: Kind, close: Kind, f: F) -> Vec<'a, T>
|
||||
pub(crate) fn parse_normal_list<F, T>(
|
||||
&mut self,
|
||||
open: Kind,
|
||||
close: Kind,
|
||||
mut f: F,
|
||||
) -> Vec<'a, T>
|
||||
where
|
||||
F: Fn(&mut Self) -> T,
|
||||
F: FnMut(&mut Self) -> T,
|
||||
{
|
||||
let opening_span = self.cur_token().span();
|
||||
self.expect(open);
|
||||
|
||||
@@ -881,6 +881,15 @@ pub fn new_target_outside_function(span: Span) -> OxcDiagnostic {
|
||||
.with_label(span)
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn switch_multiple_default_clause(first_default: Span, other_default: Span) -> OxcDiagnostic {
|
||||
ts_error("1113", "A 'default' clause cannot appear more than once in a 'switch' statement.")
|
||||
.with_labels(vec![
|
||||
first_default.label("First 'default' clause is here."),
|
||||
other_default.label("Another 'default' clause cannot appear here."),
|
||||
])
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn import_meta(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::error("Unexpected import.meta expression")
|
||||
|
||||
@@ -693,7 +693,24 @@ impl<'a, C: Config> ParserImpl<'a, C> {
|
||||
let span = self.start_span();
|
||||
self.bump_any(); // advance `switch`
|
||||
let discriminant = self.parse_paren_expression();
|
||||
let cases = self.parse_normal_list(Kind::LCurly, Kind::RCurly, Self::parse_switch_case);
|
||||
// A `switch` may contain at most one `default` clause. Track the first one
|
||||
// and report only the first duplicate, all in the single parsing pass.
|
||||
let mut first_default: Option<Span> = None;
|
||||
let mut reported_duplicate = false;
|
||||
let cases = self.parse_normal_list(Kind::LCurly, Kind::RCurly, |p| {
|
||||
let case = p.parse_switch_case();
|
||||
if case.test.is_none() {
|
||||
match first_default {
|
||||
None => first_default = Some(case.span),
|
||||
Some(first_span) if !reported_duplicate => {
|
||||
p.error(diagnostics::switch_multiple_default_clause(first_span, case.span));
|
||||
reported_duplicate = true;
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
case
|
||||
});
|
||||
self.ast.statement_switch(self.end_span(span), discriminant, cases)
|
||||
}
|
||||
|
||||
|
||||
@@ -771,22 +771,6 @@ pub fn check_with_statement(stmt: &WithStatement, ctx: &SemanticBuilder<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_switch_statement<'a>(stmt: &SwitchStatement<'a>, ctx: &SemanticBuilder<'a>) {
|
||||
let mut previous_default: Option<Span> = None;
|
||||
for case in &stmt.cases {
|
||||
if case.test.is_none() {
|
||||
if let Some(previous_span) = previous_default {
|
||||
ctx.error(diagnostics::switch_stmt_cannot_have_multiple_default_case(
|
||||
previous_span,
|
||||
case.span,
|
||||
));
|
||||
break;
|
||||
}
|
||||
previous_default.replace(case.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_break_statement(stmt: &BreakStatement, ctx: &SemanticBuilder<'_>) {
|
||||
// It is a Syntax Error if this BreakStatement is not nested, directly or indirectly (but not crossing function or static initialization block boundaries), within an IterationStatement or a SwitchStatement.
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ pub fn check<'a>(kind: AstKind<'a>, ctx: &SemanticBuilder<'a>) {
|
||||
js::check_function_declaration(&stmt.body, false, ctx);
|
||||
js::check_with_statement(stmt, ctx);
|
||||
}
|
||||
AstKind::SwitchStatement(stmt) => js::check_switch_statement(stmt, ctx),
|
||||
AstKind::BreakStatement(stmt) => js::check_break_statement(stmt, ctx),
|
||||
AstKind::ContinueStatement(stmt) => js::check_continue_statement(stmt, ctx),
|
||||
AstKind::LabeledStatement(stmt) => {
|
||||
|
||||
@@ -456,15 +456,3 @@ pub fn ts_export_assignment_cannot_be_used_with_other_exports(span: Span) -> Oxc
|
||||
.with_label(span)
|
||||
.with_help("If you want to use `export =`, remove other `export`s and put all of them to the right hand value of `export =`. If you want to use `export`s, remove `export =` statement.")
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn switch_stmt_cannot_have_multiple_default_case(
|
||||
first_default: Span,
|
||||
other_default: Span,
|
||||
) -> OxcDiagnostic {
|
||||
ts_error("1113", "A 'default' clause cannot appear more than once in a 'switch' statement.")
|
||||
.with_labels(vec![
|
||||
first_default.label("First 'default' clause is here."),
|
||||
other_default.label("Another 'default' clause cannot appear here."),
|
||||
])
|
||||
}
|
||||
@@ -12973,22 +12973,6 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
|
||||
12 │ }
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults.ts:25:13]
|
||||
24 │ default: // Error, third 'default' clause
|
||||
25 │ def\u0061ult: // Error, fourth 'default' clause.
|
||||
· ────────────
|
||||
26 │ // Errors on fifth-seventh
|
||||
╰────
|
||||
|
||||
× TS(1108): A 'return' statement can only be used within a function body.
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults.ts:27:22]
|
||||
26 │ // Errors on fifth-seventh
|
||||
27 │ default: return;
|
||||
· ──────
|
||||
28 │ default: default:
|
||||
╰────
|
||||
|
||||
× TS(1113): A 'default' clause cannot appear more than once in a 'switch' statement.
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults.ts:6:5]
|
||||
5 │ case 2:
|
||||
@@ -13013,6 +12997,22 @@ Expect Syntax Error: tasks/coverage/typescript/tests/cases/conformance/types/wit
|
||||
22 │ case 10000:
|
||||
╰────
|
||||
|
||||
× Keywords cannot contain escape characters
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults.ts:25:13]
|
||||
24 │ default: // Error, third 'default' clause
|
||||
25 │ def\u0061ult: // Error, fourth 'default' clause.
|
||||
· ────────────
|
||||
26 │ // Errors on fifth-seventh
|
||||
╰────
|
||||
|
||||
× TS(1108): A 'return' statement can only be used within a function body.
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults.ts:27:22]
|
||||
26 │ // Errors on fifth-seventh
|
||||
27 │ default: return;
|
||||
· ──────
|
||||
28 │ default: default:
|
||||
╰────
|
||||
|
||||
× TS(1113): A 'default' clause cannot appear more than once in a 'switch' statement.
|
||||
╭─[typescript/tests/cases/compiler/switchStatementsWithMultipleDefaults1.ts:6:9]
|
||||
5 │ case 2:
|
||||
|
||||
Reference in new issue
Block a user