mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(linter): implement eslint/no-labels (#8131)
Implements [eslint/no-labels](https://eslint.org/docs/latest/rules/no-labels) rule. Part of #479
This commit is contained in:
parent
8fb71f518f
commit
1c5db72986
3 changed files with 511 additions and 0 deletions
|
|
@ -89,6 +89,7 @@ mod eslint {
|
|||
pub mod no_irregular_whitespace;
|
||||
pub mod no_iterator;
|
||||
pub mod no_label_var;
|
||||
pub mod no_labels;
|
||||
pub mod no_loss_of_precision;
|
||||
pub mod no_magic_numbers;
|
||||
pub mod no_multi_str;
|
||||
|
|
@ -534,6 +535,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
eslint::max_classes_per_file,
|
||||
eslint::max_lines,
|
||||
eslint::max_params,
|
||||
eslint::no_labels,
|
||||
eslint::no_restricted_imports,
|
||||
eslint::no_object_constructor,
|
||||
eslint::no_duplicate_imports,
|
||||
|
|
|
|||
272
crates/oxc_linter/src/rules/eslint/no_labels.rs
Normal file
272
crates/oxc_linter/src/rules/eslint/no_labels.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
use oxc_ast::{
|
||||
ast::{LabelIdentifier, Statement},
|
||||
AstKind,
|
||||
};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_semantic::NodeId;
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
fn no_labels_diagnostic(message: &'static str, label_span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn(message).with_label(label_span)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoLabels {
|
||||
allow_loop: bool,
|
||||
allow_switch: bool,
|
||||
}
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Disallow labeled statements.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Labeled statements in JavaScript are used in conjunction with `break` and `continue` to control flow around multiple loops. For example:
|
||||
/// ```js
|
||||
/// outer:
|
||||
/// while (true) {
|
||||
/// while (true) {
|
||||
/// break outer;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// The `break outer` statement ensures that this code will not result in an infinite loop because control is returned to the next statement after the `outer` label was applied. If this statement was changed to be just `break`, control would flow back to the outer `while` statement and an infinite loop would result.
|
||||
/// While convenient in some cases, labels tend to be used only rarely and are frowned upon by some as a remedial form of flow control that is more error prone and harder to understand.
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// Examples of **incorrect** code for this rule:
|
||||
/// ```js
|
||||
/// label:
|
||||
/// while(true) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// label:
|
||||
/// while(true) {
|
||||
/// break label;
|
||||
/// }
|
||||
///
|
||||
/// label:
|
||||
/// while(true) {
|
||||
/// continue label;
|
||||
/// }
|
||||
///
|
||||
/// label:
|
||||
/// switch (a) {
|
||||
/// case 0:
|
||||
/// break label;
|
||||
/// }
|
||||
///
|
||||
/// label:
|
||||
/// {
|
||||
/// break label;
|
||||
/// }
|
||||
///
|
||||
/// label:
|
||||
/// if (a) {
|
||||
/// break label;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Examples of **correct** code for this rule:
|
||||
/// ```js
|
||||
/// var f = {
|
||||
/// label: "foo"
|
||||
/// };
|
||||
///
|
||||
/// while (true) {
|
||||
/// break;
|
||||
/// }
|
||||
///
|
||||
/// while (true) {
|
||||
/// continue;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Options
|
||||
///
|
||||
/// The options allow labels with loop or switch statements:
|
||||
/// * `"allowLoop"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to loop statements.
|
||||
/// * `"allowSwitch"` (`boolean`, default is `false`) - If this option was set `true`, this rule ignores labels which are sticking to switch statements.
|
||||
///
|
||||
/// Actually labeled statements in JavaScript can be used with other than loop and switch statements.
|
||||
/// However, this way is ultra rare, not well-known, so this would be confusing developers.
|
||||
///
|
||||
/// #### allowLoop
|
||||
///
|
||||
/// Examples of **correct** code for the `{ "allowLoop": true }` option:
|
||||
/// ```js
|
||||
/// label:
|
||||
/// while (true) {
|
||||
/// break label;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// #### allowSwitch
|
||||
///
|
||||
/// Examples of **correct** code for the `{ "allowSwitch": true }` option:
|
||||
/// ```js
|
||||
/// label:
|
||||
/// switch (a) {
|
||||
/// case 0:
|
||||
/// break label;
|
||||
/// }
|
||||
/// ```
|
||||
NoLabels,
|
||||
style,
|
||||
);
|
||||
|
||||
impl Rule for NoLabels {
|
||||
fn from_configuration(value: serde_json::Value) -> Self {
|
||||
let allow_loop = value
|
||||
.get(0)
|
||||
.and_then(|config| config.get("allowLoop"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
|
||||
let allow_switch = value
|
||||
.get(0)
|
||||
.and_then(|config| config.get("allowSwitch"))
|
||||
.and_then(serde_json::Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
|
||||
Self { allow_loop, allow_switch }
|
||||
}
|
||||
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
if let AstKind::LabeledStatement(labeled_stmt) = node.kind() {
|
||||
if !self.is_allowed(&labeled_stmt.body) {
|
||||
let label_span = labeled_stmt.label.span;
|
||||
ctx.diagnostic(no_labels_diagnostic(
|
||||
"Labeled statement is not allowed",
|
||||
label_span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let AstKind::BreakStatement(break_stmt) = node.kind() {
|
||||
let Some(label) = &break_stmt.label else { return };
|
||||
|
||||
if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) {
|
||||
ctx.diagnostic(no_labels_diagnostic(
|
||||
"Label in break statement is not allowed",
|
||||
label.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let AstKind::ContinueStatement(cont_stmt) = node.kind() {
|
||||
let Some(label) = &cont_stmt.label else { return };
|
||||
|
||||
if !self.is_allowed_in_break_or_continue(label, node.id(), ctx) {
|
||||
ctx.diagnostic(no_labels_diagnostic(
|
||||
"Label in continue statement is not allowed",
|
||||
label.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoLabels {
|
||||
fn is_allowed(&self, stmt: &Statement) -> bool {
|
||||
match stmt {
|
||||
stmt if stmt.is_iteration_statement() => self.allow_loop,
|
||||
Statement::SwitchStatement(_) => self.allow_switch,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the `label` in break/continue statement is allowed.
|
||||
fn is_allowed_in_break_or_continue<'a>(
|
||||
&self,
|
||||
label: &LabelIdentifier<'a>,
|
||||
stmt_node_id: NodeId,
|
||||
ctx: &LintContext<'a>,
|
||||
) -> bool {
|
||||
let nodes = ctx.nodes();
|
||||
for ancestor_kind in nodes.ancestor_kinds(stmt_node_id) {
|
||||
if let AstKind::LabeledStatement(labeled_stmt) = ancestor_kind {
|
||||
if label.name == labeled_stmt.label.name {
|
||||
return self.is_allowed(&labeled_stmt.body);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
("var f = { label: foo ()}", None),
|
||||
("while (true) {}", None),
|
||||
("while (true) { break; }", None),
|
||||
("while (true) { continue; }", None),
|
||||
("A: while (a) { break A; }", Some(serde_json::json!([{ "allowLoop": true }]))),
|
||||
(
|
||||
"A: do { if (b) { break A; } } while (a);",
|
||||
Some(serde_json::json!([{ "allowLoop": true }])),
|
||||
),
|
||||
(
|
||||
"A: for (var a in obj) { for (;;) { switch (a) { case 0: continue A; } } }",
|
||||
Some(serde_json::json!([{ "allowLoop": true }])),
|
||||
),
|
||||
("A: switch (a) { case 0: break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
("label: while(true) {}", None),
|
||||
("label: while (true) { break label; }", None),
|
||||
("label: while (true) { continue label; }", None),
|
||||
("A: var foo = 0;", None),
|
||||
("A: break A;", None),
|
||||
("A: { if (foo()) { break A; } bar(); };", None),
|
||||
("A: if (a) { if (foo()) { break A; } bar(); };", None),
|
||||
("A: switch (a) { case 0: break A; default: break; };", None),
|
||||
("A: switch (a) { case 0: B: { break A; } default: break; };", None),
|
||||
("A: var foo = 0;", Some(serde_json::json!([{ "allowLoop": true }]))),
|
||||
("A: break A;", Some(serde_json::json!([{ "allowLoop": true }]))),
|
||||
(
|
||||
"A: { if (foo()) { break A; } bar(); };",
|
||||
Some(serde_json::json!([{ "allowLoop": true }])),
|
||||
),
|
||||
(
|
||||
"A: if (a) { if (foo()) { break A; } bar(); };",
|
||||
Some(serde_json::json!([{ "allowLoop": true }])),
|
||||
),
|
||||
(
|
||||
"A: switch (a) { case 0: break A; default: break; };",
|
||||
Some(serde_json::json!([{ "allowLoop": true }])),
|
||||
),
|
||||
("A: var foo = 0;", Some(serde_json::json!([{ "allowSwitch": true }]))),
|
||||
("A: break A;", Some(serde_json::json!([{ "allowSwitch": true }]))),
|
||||
(
|
||||
"A: { if (foo()) { break A; } bar(); };",
|
||||
Some(serde_json::json!([{ "allowSwitch": true }])),
|
||||
),
|
||||
(
|
||||
"A: if (a) { if (foo()) { break A; } bar(); };",
|
||||
Some(serde_json::json!([{ "allowSwitch": true }])),
|
||||
),
|
||||
("A: while (a) { break A; }", Some(serde_json::json!([{ "allowSwitch": true }]))),
|
||||
(
|
||||
"A: do { if (b) { break A; } } while (a);",
|
||||
Some(serde_json::json!([{ "allowSwitch": true }])),
|
||||
),
|
||||
(
|
||||
"A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } }",
|
||||
Some(serde_json::json!([{ "allowSwitch": true }])),
|
||||
),
|
||||
];
|
||||
|
||||
Tester::new(NoLabels::NAME, NoLabels::CATEGORY, pass, fail).test_and_snapshot();
|
||||
}
|
||||
237
crates/oxc_linter/src/snapshots/eslint_no_labels.snap
Normal file
237
crates/oxc_linter/src/snapshots/eslint_no_labels.snap
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ label: while(true) {}
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ label: while (true) { break label; }
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:29]
|
||||
1 │ label: while (true) { break label; }
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ label: while (true) { continue label; }
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in continue statement is not allowed
|
||||
╭─[no_labels.tsx:1:32]
|
||||
1 │ label: while (true) { continue label; }
|
||||
· ─────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: var foo = 0;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:10]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:25]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:32]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: switch (a) { case 0: break A; default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:31]
|
||||
1 │ A: switch (a) { case 0: break A; default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: switch (a) { case 0: B: { break A; } default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:25]
|
||||
1 │ A: switch (a) { case 0: B: { break A; } default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:36]
|
||||
1 │ A: switch (a) { case 0: B: { break A; } default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: var foo = 0;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:10]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:25]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:32]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: switch (a) { case 0: break A; default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:31]
|
||||
1 │ A: switch (a) { case 0: break A; default: break; };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: var foo = 0;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:10]
|
||||
1 │ A: break A;
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:25]
|
||||
1 │ A: { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:32]
|
||||
1 │ A: if (a) { if (foo()) { break A; } bar(); };
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: while (a) { break A; }
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:22]
|
||||
1 │ A: while (a) { break A; }
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: do { if (b) { break A; } } while (a);
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:24]
|
||||
1 │ A: do { if (b) { break A; } } while (a);
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Labeled statement is not allowed
|
||||
╭─[no_labels.tsx:1:1]
|
||||
1 │ A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } }
|
||||
· ─
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-labels): Label in break statement is not allowed
|
||||
╭─[no_labels.tsx:1:63]
|
||||
1 │ A: for (var a in obj) { for (;;) { switch (a) { case 0: break A; } } }
|
||||
· ─
|
||||
╰────
|
||||
Loading…
Reference in a new issue