diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index c8393e67a4601..7d22d39989537 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1091,6 +1091,19 @@ pub enum Statement<'a> { Declaration(Declaration<'a>), } +impl<'a> Statement<'a> { + pub fn is_iteration_statement(&self) -> bool { + matches!( + self, + Statement::DoWhileStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) + | Statement::ForStatement(_) + | Statement::WhileStatement(_) + ) + } +} + /// Directive Prologue #[derive(Debug, Hash)] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] diff --git a/crates/oxc_semantic/src/checker/javascript.rs b/crates/oxc_semantic/src/checker/javascript.rs index ed4b5a62aa2eb..d52a869acdc74 100644 --- a/crates/oxc_semantic/src/checker/javascript.rs +++ b/crates/oxc_semantic/src/checker/javascript.rs @@ -594,10 +594,32 @@ struct InvalidLabelJumpTarget(#[label] Span); #[diagnostic()] struct InvalidLabelTarget(#[label("This label is used, but not defined")] Span); -fn check_label(label: &LabelIdentifier, ctx: &SemanticBuilder) { +fn check_label(label: &LabelIdentifier, ctx: &SemanticBuilder, is_continue: bool) { + #[derive(Debug, Error, Diagnostic)] + #[error( + "A `{0}` statement can only jump to a label of an enclosing `for`, `while` or `do while` statement." + )] + #[diagnostic()] + struct InvalidLabelNonIteration( + &'static str, + #[label("This is an non-iteration statement")] Span, + #[label("for this label")] Span, + ); + if ctx.label_builder.is_inside_labeled_statement() { for labeled in ctx.label_builder.get_accessible_labels() { if label.name == labeled.name { + if is_continue + && matches!(ctx.nodes.kind(labeled.id), AstKind::LabeledStatement(stmt) if { + let mut body = &stmt.body; + while let Statement::LabeledStatement(stmt) = body { + body = &stmt.body; + } + !body.is_iteration_statement() + }) + { + ctx.error(InvalidLabelNonIteration("continue", labeled.span, label.span)); + } return; } } @@ -617,7 +639,7 @@ fn check_break_statement<'a>(stmt: &BreakStatement, node: &AstNode<'a>, ctx: &Se struct InvalidBreak(#[label] Span); if let Some(label) = &stmt.label { - return check_label(label, ctx); + return check_label(label, ctx, false); } // 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. @@ -649,53 +671,16 @@ fn check_continue_statement<'a>( ))] struct InvalidContinue(#[label] Span); - #[derive(Debug, Error, Diagnostic)] - #[error( - "A `{0}` statement can only jump to a label of an enclosing `for`, `while` or `do while` statement." - )] - #[diagnostic()] - struct InvalidLabelNonIteration( - &'static str, - #[label("This is an non-iteration statement")] Span, - #[label("for this label")] Span, - ); + if let Some(label) = &stmt.label { + return check_label(label, ctx, true); + } // It is a Syntax Error if this ContinueStatement is not nested, directly or indirectly (but not crossing function or static initialization block boundaries), within an IterationStatement. for node_id in ctx.nodes.ancestors(node.id()).skip(1) { match ctx.nodes.kind(node_id) { - AstKind::Program(_) => { - return stmt.label.as_ref().map_or_else( - || ctx.error(InvalidContinue(stmt.span)), - |label| ctx.error(InvalidLabelTarget(label.span)), - ); - } - AstKind::Function(_) | AstKind::StaticBlock(_) => { - return stmt.label.as_ref().map_or_else( - || ctx.error(InvalidContinue(stmt.span)), - |label| ctx.error(InvalidLabelJumpTarget(label.span)), - ); + AstKind::Program(_) | AstKind::Function(_) | AstKind::StaticBlock(_) => { + ctx.error(InvalidContinue(stmt.span)); } - AstKind::LabeledStatement(labeled_statement) => match &stmt.label { - Some(label) if label.name == labeled_statement.label.name => { - if matches!( - labeled_statement.body, - Statement::LabeledStatement(_) - | Statement::DoWhileStatement(_) - | Statement::WhileStatement(_) - | Statement::ForStatement(_) - | Statement::ForInStatement(_) - | Statement::ForOfStatement(_) - ) { - break; - } - return ctx.error(InvalidLabelNonIteration( - "continue", - labeled_statement.label.span, - label.span, - )); - } - _ => {} - }, kind if kind.is_iteration_statement() && stmt.label.is_none() => break, _ => {} } diff --git a/crates/oxc_semantic/src/label.rs b/crates/oxc_semantic/src/label.rs index b1ec9ed67112e..fb74da22f6ab3 100644 --- a/crates/oxc_semantic/src/label.rs +++ b/crates/oxc_semantic/src/label.rs @@ -6,7 +6,7 @@ use crate::AstNodeId; #[derive(Debug)] pub struct Label<'a> { - id: AstNodeId, + pub id: AstNodeId, pub name: &'a str, pub span: Span, used: bool, diff --git a/tasks/coverage/parser_test262.snap b/tasks/coverage/parser_test262.snap index f81cca4120829..df877bd7feb17 100644 --- a/tasks/coverage/parser_test262.snap +++ b/tasks/coverage/parser_test262.snap @@ -28292,11 +28292,12 @@ Expect Syntax Error: "language/import/import-attributes/json-named-bindings.js" 23 │ } ╰──── - × Jump target cannot cross function boundary. + × Use of undefined label ╭─[language/statements/class/static-init-invalid-undefined-continue-target.js:21:1] 21 │ x: while (false) { 22 │ continue y; - · ─ + · ┬ + · ╰── This label is used, but not defined 23 │ } ╰──── diff --git a/tasks/coverage/parser_typescript.snap b/tasks/coverage/parser_typescript.snap index a213fe4b9c28f..4738f23a6ff42 100644 --- a/tasks/coverage/parser_typescript.snap +++ b/tasks/coverage/parser_typescript.snap @@ -6688,6 +6688,15 @@ Expect to Parse: "conformance/salsa/plainJSRedeclare3.ts" ╰──── help: A `continue` statement can only be used within an enclosing `for`, `while` or `do while` + × Illegal continue statement: no surrounding iteration statement + ╭─[compiler/invalidContinueInDownlevelAsync.ts:2:1] + 2 │ if (true) { + 3 │ continue; + · ───────── + 4 │ } + ╰──── + help: A `continue` statement can only be used within an enclosing `for`, `while` or `do while` + × Expected `;` but found `[` ╭─[compiler/invalidLetInForOfAndForIn_ES5.ts:5:1] 5 │ var let = 10;