mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter): implement no-lone-blocks rule (#8145)
implement: https://eslint.org/docs/latest/rules/no-lone-blocks
This commit is contained in:
parent
372eb09569
commit
457aa31d92
3 changed files with 609 additions and 0 deletions
|
|
@ -92,6 +92,7 @@ mod eslint {
|
|||
pub mod no_iterator;
|
||||
pub mod no_label_var;
|
||||
pub mod no_labels;
|
||||
pub mod no_lone_blocks;
|
||||
pub mod no_loss_of_precision;
|
||||
pub mod no_magic_numbers;
|
||||
pub mod no_multi_assign;
|
||||
|
|
@ -548,6 +549,7 @@ oxc_macros::declare_all_lint_rules! {
|
|||
eslint::no_multi_assign,
|
||||
eslint::no_nested_ternary,
|
||||
eslint::no_labels,
|
||||
eslint::no_lone_blocks,
|
||||
eslint::no_restricted_imports,
|
||||
eslint::no_object_constructor,
|
||||
eslint::no_duplicate_imports,
|
||||
|
|
|
|||
376
crates/oxc_linter/src/rules/eslint/no_lone_blocks.rs
Normal file
376
crates/oxc_linter/src/rules/eslint/no_lone_blocks.rs
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
use oxc_ast::ast::{Declaration, VariableDeclarationKind};
|
||||
use oxc_ast::AstKind;
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::{GetSpan, Span};
|
||||
|
||||
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
fn no_lone_blocks_diagnostic(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("Block is unnecessary.").with_label(span)
|
||||
}
|
||||
|
||||
fn no_nested_lone_blocks_diagnostic(span: Span) -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("Nested block is redundant.").with_label(span)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct NoLoneBlocks;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
///
|
||||
/// Disallows unnecessary standalone block statements.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
///
|
||||
/// Standalone blocks can be confusing as they do not provide any meaningful purpose when used unnecessarily.
|
||||
/// They may introduce extra nesting, reducing code readability, and can mislead readers about scope or intent.
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// Examples of **incorrect** code for this rule:
|
||||
/// ```js
|
||||
/// {
|
||||
/// var x = 1;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Examples of **correct** code for this rule:
|
||||
/// ```js
|
||||
/// if (condition) {
|
||||
/// var x = 1;
|
||||
/// }
|
||||
///
|
||||
/// {
|
||||
/// let x = 1; // Used to create a valid block scope.
|
||||
/// }
|
||||
/// ```
|
||||
NoLoneBlocks,
|
||||
eslint,
|
||||
style,
|
||||
);
|
||||
|
||||
impl Rule for NoLoneBlocks {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let AstKind::BlockStatement(stmt) = node.kind() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(parent_node) = ctx.nodes().parent_node(node.id()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let body = &stmt.body;
|
||||
if body.is_empty() {
|
||||
report(ctx, node, parent_node);
|
||||
return;
|
||||
}
|
||||
|
||||
if body.len() == 1
|
||||
&& matches!(parent_node.kind(), AstKind::FunctionBody(parent) if parent.statements.len() == 1)
|
||||
{
|
||||
report(ctx, node, parent_node);
|
||||
}
|
||||
|
||||
let mut lone_blocks = Vec::new();
|
||||
if is_lone_block(node, parent_node) {
|
||||
lone_blocks.push(node);
|
||||
}
|
||||
|
||||
for child in &stmt.body {
|
||||
match child.as_declaration() {
|
||||
Some(Declaration::VariableDeclaration(decl))
|
||||
if decl.kind != VariableDeclarationKind::Var =>
|
||||
{
|
||||
mark_lone_block(node, &mut lone_blocks);
|
||||
}
|
||||
Some(Declaration::ClassDeclaration(_) | Declaration::FunctionDeclaration(_)) => {
|
||||
mark_lone_block(node, &mut lone_blocks);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last) = lone_blocks.last() {
|
||||
if last.id() == node.id() {
|
||||
lone_blocks.pop();
|
||||
report(ctx, node, parent_node);
|
||||
}
|
||||
} else {
|
||||
match parent_node.kind() {
|
||||
AstKind::BlockStatement(parent_statement) => {
|
||||
if parent_statement.body.len() == 1 {
|
||||
report(ctx, node, parent_node);
|
||||
}
|
||||
}
|
||||
AstKind::StaticBlock(parent_statement) => {
|
||||
if parent_statement.body.len() == 1 {
|
||||
report(ctx, node, parent_node);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn report(ctx: &LintContext, node: &AstNode, parent_node: &AstNode) {
|
||||
match parent_node.kind() {
|
||||
AstKind::BlockStatement(_) | AstKind::StaticBlock(_) => {
|
||||
ctx.diagnostic(no_nested_lone_blocks_diagnostic(node.span()));
|
||||
}
|
||||
_ => ctx.diagnostic(no_lone_blocks_diagnostic(node.span())),
|
||||
};
|
||||
}
|
||||
|
||||
fn is_lone_block(node: &AstNode, parent_node: &AstNode) -> bool {
|
||||
match parent_node.kind() {
|
||||
AstKind::BlockStatement(_) | AstKind::StaticBlock(_) | AstKind::Program(_) => true,
|
||||
AstKind::SwitchCase(parent_node) => {
|
||||
let consequent = &parent_node.consequent;
|
||||
if consequent.len() != 1 {
|
||||
return true;
|
||||
}
|
||||
let node_span = node.span();
|
||||
let consequent_span = consequent[0].span();
|
||||
node_span.start != consequent_span.start || node_span.end != consequent_span.end
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_lone_block(parent_node: &AstNode, lone_blocks: &mut Vec<&AstNode>) {
|
||||
if lone_blocks.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if lone_blocks.last().is_some_and(|last| last.id() == parent_node.id()) {
|
||||
lone_blocks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
"if (foo) { if (bar) { baz(); } }",
|
||||
"do { bar(); } while (foo)",
|
||||
"function foo() { while (bar) { baz() } }",
|
||||
"{ let x = 1; }", // { "ecmaVersion": 6 },
|
||||
"{ const x = 1; }", // { "ecmaVersion": 6 },
|
||||
"'use strict'; { function bar() {} }", // { "ecmaVersion": 6 },
|
||||
"{ function bar() {} }", // { "ecmaVersion": 6, "parserOptions": { "ecmaFeatures": { "impliedStrict": true } } },
|
||||
"{ class Bar {} }", // { "ecmaVersion": 6 },
|
||||
"{ {let y = 1;} let x = 1; }", // { "ecmaVersion": 6 },
|
||||
"
|
||||
switch (foo) {
|
||||
case bar: {
|
||||
baz;
|
||||
}
|
||||
}
|
||||
",
|
||||
"
|
||||
switch (foo) {
|
||||
case bar: {
|
||||
baz;
|
||||
}
|
||||
case qux: {
|
||||
boop;
|
||||
}
|
||||
}
|
||||
",
|
||||
"
|
||||
switch (foo) {
|
||||
case bar:
|
||||
{
|
||||
baz;
|
||||
}
|
||||
}
|
||||
",
|
||||
"function foo() { { const x = 4 } const x = 3 }", // { "ecmaVersion": 6 },
|
||||
"class C { static {} }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { foo; } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { if (foo) { block; } } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { lbl: { block; } } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { { let block; } something; } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { something; { const block = 1; } } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { { function block(){} } something; } }", // { "ecmaVersion": 2022 },
|
||||
"class C { static { something; { class block {} } } }", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
{
|
||||
using x = makeDisposable();
|
||||
}", // { "parser": require(parser("typescript-parsers/no-lone-blocks/using")), "ecmaVersion": 2022 },
|
||||
"
|
||||
{
|
||||
await using x = makeDisposable();
|
||||
}", // { "parser": require(parser("typescript-parsers/no-lone-blocks/await-using")), "ecmaVersion": 2022 }
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
"{}",
|
||||
"{var x = 1;}",
|
||||
"foo(); {} bar();",
|
||||
"if (foo) { bar(); {} baz(); }",
|
||||
"{
|
||||
{ } }",
|
||||
"function foo() { bar(); {} baz(); }",
|
||||
"while (foo) { {} }",
|
||||
// MEMO: Currently, this rule always analyzes in strict mode (as it cannot retrieve ecmaFeatures).
|
||||
// "{ function bar() {} }", // { "ecmaVersion": 6 },
|
||||
"{var x = 1;}", // { "ecmaVersion": 6 },
|
||||
"{
|
||||
{var x = 1;}
|
||||
let y = 2; } {let z = 1;}", // { "ecmaVersion": 6 },
|
||||
"{
|
||||
{let x = 1;}
|
||||
var y = 2; } {let z = 1;}", // { "ecmaVersion": 6 },
|
||||
"{
|
||||
{var x = 1;}
|
||||
var y = 2; }
|
||||
{var z = 1;}", // { "ecmaVersion": 6 },
|
||||
"
|
||||
switch (foo) {
|
||||
case 1:
|
||||
foo();
|
||||
{
|
||||
bar;
|
||||
}
|
||||
}
|
||||
",
|
||||
"
|
||||
switch (foo) {
|
||||
case 1:
|
||||
{
|
||||
bar;
|
||||
}
|
||||
foo();
|
||||
}
|
||||
",
|
||||
"
|
||||
function foo () {
|
||||
{
|
||||
const x = 4;
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 6 },
|
||||
"
|
||||
function foo () {
|
||||
{
|
||||
var x = 4;
|
||||
}
|
||||
}
|
||||
",
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
if (foo) {
|
||||
{
|
||||
let block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
if (foo) {
|
||||
{
|
||||
block;
|
||||
}
|
||||
something;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
block;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
let block;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
const block = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
function block() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
class block {}
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
var block;
|
||||
}
|
||||
something;
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
something;
|
||||
{
|
||||
var block;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
{
|
||||
block;
|
||||
}
|
||||
something;
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 },
|
||||
"
|
||||
class C {
|
||||
static {
|
||||
something;
|
||||
{
|
||||
block;
|
||||
}
|
||||
}
|
||||
}
|
||||
", // { "ecmaVersion": 2022 }
|
||||
];
|
||||
|
||||
Tester::new(NoLoneBlocks::NAME, NoLoneBlocks::PLUGIN, pass, fail).test_and_snapshot();
|
||||
}
|
||||
231
crates/oxc_linter/src/snapshots/eslint_no_lone_blocks.snap
Normal file
231
crates/oxc_linter/src/snapshots/eslint_no_lone_blocks.snap
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ {}
|
||||
· ──
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ {var x = 1;}
|
||||
· ────────────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:8]
|
||||
1 │ foo(); {} bar();
|
||||
· ──
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:1:19]
|
||||
1 │ if (foo) { bar(); {} baz(); }
|
||||
· ──
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ ╭─▶ {
|
||||
2 │ ╰─▶ { } }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:2:4]
|
||||
1 │ {
|
||||
2 │ { } }
|
||||
· ───
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:25]
|
||||
1 │ function foo() { bar(); {} baz(); }
|
||||
· ──
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:1:15]
|
||||
1 │ while (foo) { {} }
|
||||
· ──
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ {var x = 1;}
|
||||
· ────────────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:2:4]
|
||||
1 │ {
|
||||
2 │ {var x = 1;}
|
||||
· ────────────
|
||||
3 │ let y = 2; } {let z = 1;}
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ ╭─▶ {
|
||||
2 │ │ {let x = 1;}
|
||||
3 │ ╰─▶ var y = 2; } {let z = 1;}
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:1:1]
|
||||
1 │ ╭─▶ {
|
||||
2 │ │ {var x = 1;}
|
||||
3 │ ╰─▶ var y = 2; }
|
||||
4 │ {var z = 1;}
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:2:4]
|
||||
1 │ {
|
||||
2 │ {var x = 1;}
|
||||
· ────────────
|
||||
3 │ var y = 2; }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:4:5]
|
||||
3 │ var y = 2; }
|
||||
4 │ {var z = 1;}
|
||||
· ────────────
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:5:24]
|
||||
4 │ foo();
|
||||
5 │ ╭─▶ {
|
||||
6 │ │ bar;
|
||||
7 │ ╰─▶ }
|
||||
8 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:4:20]
|
||||
3 │ case 1:
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ bar;
|
||||
6 │ ╰─▶ }
|
||||
7 │ foo();
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:3:20]
|
||||
2 │ function foo () {
|
||||
3 │ ╭─▶ {
|
||||
4 │ │ const x = 4;
|
||||
5 │ ╰─▶ }
|
||||
6 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Block is unnecessary.
|
||||
╭─[no_lone_blocks.tsx:3:20]
|
||||
2 │ function foo () {
|
||||
3 │ ╭─▶ {
|
||||
4 │ │ var x = 4;
|
||||
5 │ ╰─▶ }
|
||||
6 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:5:24]
|
||||
4 │ if (foo) {
|
||||
5 │ ╭─▶ {
|
||||
6 │ │ let block;
|
||||
7 │ ╰─▶ }
|
||||
8 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:5:24]
|
||||
4 │ if (foo) {
|
||||
5 │ ╭─▶ {
|
||||
6 │ │ block;
|
||||
7 │ ╰─▶ }
|
||||
8 │ something;
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ block;
|
||||
6 │ ╰─▶ }
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ let block;
|
||||
6 │ ╰─▶ }
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ const block = 1;
|
||||
6 │ ╰─▶ }
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ function block() {}
|
||||
6 │ ╰─▶ }
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ class block {}
|
||||
6 │ ╰─▶ }
|
||||
7 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ var block;
|
||||
6 │ ╰─▶ }
|
||||
7 │ something;
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:5:22]
|
||||
4 │ something;
|
||||
5 │ ╭─▶ {
|
||||
6 │ │ var block;
|
||||
7 │ ╰─▶ }
|
||||
8 │ }
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:4:22]
|
||||
3 │ static {
|
||||
4 │ ╭─▶ {
|
||||
5 │ │ block;
|
||||
6 │ ╰─▶ }
|
||||
7 │ something;
|
||||
╰────
|
||||
|
||||
⚠ eslint(no-lone-blocks): Nested block is redundant.
|
||||
╭─[no_lone_blocks.tsx:5:22]
|
||||
4 │ something;
|
||||
5 │ ╭─▶ {
|
||||
6 │ │ block;
|
||||
7 │ ╰─▶ }
|
||||
8 │ }
|
||||
╰────
|
||||
Loading…
Reference in a new issue