diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index d9a11911ad35..547d0b6f2d0b 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -92,6 +92,7 @@ define_categories! { "lint/nursery/noApproximativeNumericConstant": "https://biomejs.dev/lint/rules/no-approximative-numeric-constant", "lint/nursery/noConfusingVoidType": "https://biomejs.dev/linter/rules/no-confusing-void-type", "lint/nursery/noDuplicateJsonKeys": "https://biomejs.dev/linter/rules/no-duplicate-json-keys", + "lint/nursery/noEmptyBlockStatements": "https://biomejs.dev/lint/rules/no-empty-block-statements", "lint/nursery/noEmptyCharacterClassInRegex": "https://biomejs.dev/lint/rules/no-empty-character-class-in-regex", "lint/nursery/noExcessiveComplexity": "https://biomejs.dev/linter/rules/no-excessive-complexity", "lint/nursery/noFallthroughSwitchClause": "https://biomejs.dev/linter/rules/no-fallthrough-switch-clause", diff --git a/crates/biome_js_analyze/src/analyzers/nursery.rs b/crates/biome_js_analyze/src/analyzers/nursery.rs index 14ab06897314..6a248b7fb40d 100644 --- a/crates/biome_js_analyze/src/analyzers/nursery.rs +++ b/crates/biome_js_analyze/src/analyzers/nursery.rs @@ -4,6 +4,7 @@ use biome_analyze::declare_group; pub(crate) mod no_approximative_numeric_constant; pub(crate) mod no_confusing_void_type; +pub(crate) mod no_empty_block_statements; pub(crate) mod no_empty_character_class_in_regex; pub(crate) mod no_excessive_complexity; pub(crate) mod no_fallthrough_switch_clause; @@ -25,6 +26,7 @@ declare_group! { rules : [ self :: no_approximative_numeric_constant :: NoApproximativeNumericConstant , self :: no_confusing_void_type :: NoConfusingVoidType , + self :: no_empty_block_statements :: NoEmptyBlockStatements , self :: no_empty_character_class_in_regex :: NoEmptyCharacterClassInRegex , self :: no_excessive_complexity :: NoExcessiveComplexity , self :: no_fallthrough_switch_clause :: NoFallthroughSwitchClause , diff --git a/crates/biome_js_analyze/src/analyzers/nursery/no_empty_block_statements.rs b/crates/biome_js_analyze/src/analyzers/nursery/no_empty_block_statements.rs new file mode 100644 index 000000000000..6c4f10f471df --- /dev/null +++ b/crates/biome_js_analyze/src/analyzers/nursery/no_empty_block_statements.rs @@ -0,0 +1,107 @@ +use biome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use biome_console::markup; +use biome_js_syntax::{ + JsBlockStatement, JsFunctionBody, JsStaticInitializationBlockClassMember, JsSwitchStatement, +}; +use biome_rowan::{declare_node_union, AstNode, AstNodeList}; + +declare_rule! { + /// Disallow empty block statements and static blocks. + /// + /// Empty static blocks and block statements, while not technically errors, usually occur due to refactoring that wasn’t completed. They can cause confusion when reading code. + /// + /// This rule disallows empty block statements and static blocks. + /// This rule ignores block statements or static blocks which contain a comment (for example, in an empty catch or finally block of a try statement to indicate that execution should continue regardless of errors). + /// + /// Source: https://eslint.org/docs/latest/rules/no-empty-static-block/ + /// Source: https://eslint.org/docs/latest/rules/no-empty/ + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// function emptyFunctionBody () {} + /// ``` + /// + /// ```js,expect_diagnostic + /// try { + /// doSomething(); + /// } catch(ex) { + /// + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// class Foo { + /// static {} + /// } + /// ``` + /// + /// ## Valid + /// + /// ```js + /// function foo () { + /// doSomething(); + /// } + /// ``` + /// + /// ```js + /// try { + /// doSomething(); + /// } catch (ex) { + /// // continue regardless of error + /// } + /// ``` + /// + pub(crate) NoEmptyBlockStatements { + version: "next", + name: "noEmptyBlockStatements", + recommended: false, + } +} + +declare_node_union! { + pub(crate) Query = JsBlockStatement | JsFunctionBody | JsStaticInitializationBlockClassMember | JsSwitchStatement +} + +impl Rule for NoEmptyBlockStatements { + type Query = Ast<Query>; + type State = (); + type Signals = Option<Self::State>; + type Options = (); + + fn run(ctx: &RuleContext<Self>) -> Self::Signals { + let query = ctx.query(); + let is_empty = is_empty(query); + let has_comments = query.syntax().has_comments_descendants(); + + (is_empty && !has_comments).then_some(()) + } + + fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> { + let query = ctx.query(); + Some( + RuleDiagnostic::new( + rule_category!(), + query.range(), + markup! { + "Unexpected empty block." + }, + ) + .note(markup! { + "Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional." + }), + ) + } +} + +fn is_empty(query: &Query) -> bool { + use Query::*; + match query { + JsFunctionBody(body) => body.directives().len() == 0 && body.statements().len() == 0, + JsBlockStatement(block) => block.statements().len() == 0, + JsStaticInitializationBlockClassMember(block) => block.statements().len() == 0, + JsSwitchStatement(statement) => statement.cases().len() == 0, + } +} diff --git a/crates/biome_js_analyze/src/options.rs b/crates/biome_js_analyze/src/options.rs index 67040c65d53f..df477f1ab4d7 100644 --- a/crates/biome_js_analyze/src/options.rs +++ b/crates/biome_js_analyze/src/options.rs @@ -134,7 +134,6 @@ impl PossibleOptions { options.visit_map(key.syntax(), value.syntax(), diagnostics)?; *self = PossibleOptions::RestrictedGlobals(options); } - _ => (), } } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js new file mode 100644 index 000000000000..548120a96ab0 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js @@ -0,0 +1,69 @@ +function foo() {} + +const bar = () => {}; + +function fooWithNestedEmptyFnBlock() { + let a = 1; + + function shouldFail(){} + + return a +} + + +const barWithNestedEmptyFnBlock = () => { + let a = 1; + + const shouldFail = () => {} + + return a +} + +let someVar; +if (someVar) { +} + +while (someVar) { +} + +switch(someVar) { +} + +try { + doSomething(); +} catch(ex) { + +} finally { + +} + +class Foo { + static {} +} + +for(let i; i>0; i++){} + +const ob = {} +for (key in ob) {} + +const ar = [] +for (val of ar) {} + +function fooWithInternalEmptyBlocks(){ + let someVar; + if (someVar) {} + + while (someVar) { + } + + switch(someVar) { + } + + try { + doSomething(); + } catch(ex) { + + } finally { + + } +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js.snap new file mode 100644 index 000000000000..f7cbb2b499ca --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.js.snap @@ -0,0 +1,400 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.js +--- +# Input +```js +function foo() {} + +const bar = () => {}; + +function fooWithNestedEmptyFnBlock() { + let a = 1; + + function shouldFail(){} + + return a +} + + +const barWithNestedEmptyFnBlock = () => { + let a = 1; + + const shouldFail = () => {} + + return a +} + +let someVar; +if (someVar) { +} + +while (someVar) { +} + +switch(someVar) { +} + +try { + doSomething(); +} catch(ex) { + +} finally { + +} + +class Foo { + static {} +} + +for(let i; i>0; i++){} + +const ob = {} +for (key in ob) {} + +const ar = [] +for (val of ar) {} + +function fooWithInternalEmptyBlocks(){ + let someVar; + if (someVar) {} + + while (someVar) { + } + + switch(someVar) { + } + + try { + doSomething(); + } catch(ex) { + + } finally { + + } +} +``` + +# Diagnostics +``` +invalid.js:1:16 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + > 1 │ function foo() {} + │ ^^ + 2 │ + 3 │ const bar = () => {}; + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:3:19 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 1 │ function foo() {} + 2 │ + > 3 │ const bar = () => {}; + │ ^^ + 4 │ + 5 │ function fooWithNestedEmptyFnBlock() { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:8:24 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 6 │ let a = 1; + 7 │ + > 8 │ function shouldFail(){} + │ ^^ + 9 │ + 10 │ return a + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:17:28 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 15 │ let a = 1; + 16 │ + > 17 │ const shouldFail = () => {} + │ ^^ + 18 │ + 19 │ return a + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:23:14 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 22 │ let someVar; + > 23 │ if (someVar) { + │ ^ + > 24 │ } + │ ^ + 25 │ + 26 │ while (someVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:26:17 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 24 │ } + 25 │ + > 26 │ while (someVar) { + │ ^ + > 27 │ } + │ ^ + 28 │ + 29 │ switch(someVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:29:1 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 27 │ } + 28 │ + > 29 │ switch(someVar) { + │ ^^^^^^^^^^^^^^^^^ + > 30 │ } + │ ^ + 31 │ + 32 │ try { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:34:13 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 32 │ try { + 33 │ doSomething(); + > 34 │ } catch(ex) { + │ ^ + > 35 │ + > 36 │ } finally { + │ ^ + 37 │ + 38 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:36:11 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 34 │ } catch(ex) { + 35 │ + > 36 │ } finally { + │ ^ + > 37 │ + > 38 │ } + │ ^ + 39 │ + 40 │ class Foo { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:41:3 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 40 │ class Foo { + > 41 │ static {} + │ ^^^^^^^^^ + 42 │ } + 43 │ + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:44:21 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 42 │ } + 43 │ + > 44 │ for(let i; i>0; i++){} + │ ^^ + 45 │ + 46 │ const ob = {} + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:47:17 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 46 │ const ob = {} + > 47 │ for (key in ob) {} + │ ^^ + 48 │ + 49 │ const ar = [] + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:50:17 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 49 │ const ar = [] + > 50 │ for (val of ar) {} + │ ^^ + 51 │ + 52 │ function fooWithInternalEmptyBlocks(){ + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:54:16 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 52 │ function fooWithInternalEmptyBlocks(){ + 53 │ let someVar; + > 54 │ if (someVar) {} + │ ^^ + 55 │ + 56 │ while (someVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:56:19 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 54 │ if (someVar) {} + 55 │ + > 56 │ while (someVar) { + │ ^ + > 57 │ } + │ ^ + 58 │ + 59 │ switch(someVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:59:3 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 57 │ } + 58 │ + > 59 │ switch(someVar) { + │ ^^^^^^^^^^^^^^^^^ + > 60 │ } + │ ^ + 61 │ + 62 │ try { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:64:15 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 62 │ try { + 63 │ doSomething(); + > 64 │ } catch(ex) { + │ ^ + > 65 │ + > 66 │ } finally { + │ ^ + 67 │ + 68 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.js:66:13 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 64 │ } catch(ex) { + 65 │ + > 66 │ } finally { + │ ^ + > 67 │ + > 68 │ } + │ ^ + 69 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts new file mode 100644 index 000000000000..00f3253ca85c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts @@ -0,0 +1,70 @@ +function fooEmptyTs() {} + +const barEmptyTs = () => {}; + +function fooWithNestedEmptyFnBlockTs() { + let a = 1; + + function shouldFail(){} + + return a +} + + +const barWithNestedEmptyFnBlockTs = () => { + let a = 1; + + const shouldFail = () => {} + + return a +} + +const someVarTs: string = ''; +if (someVarTs) { +} + +while (someVarTs) { +} + +switch(someVarTs) { +} + +const doSomething = () => null; +try { + doSomething(); +} catch(ex) { + +} finally { + +} + +class FooEmptyStaticTs { + static {} +} + +for(let i; i>0; i++){} + +const obTs = {} +for (const key in obTs) {} + +const arTs = [] +for (const val of arTs) {} + +function fooWithInternalEmptyBlocksTs(){ + let someOtherVar: string = ''; + if (someOtherVar) {} + + while (someOtherVar) { + } + + switch(someOtherVar) { + } + + try { + doSomething(); + } catch(ex) { + + } finally { + + } +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts.snap new file mode 100644 index 000000000000..5b4b353bf9c7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/invalid.ts.snap @@ -0,0 +1,401 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.ts +--- +# Input +```js +function fooEmptyTs() {} + +const barEmptyTs = () => {}; + +function fooWithNestedEmptyFnBlockTs() { + let a = 1; + + function shouldFail(){} + + return a +} + + +const barWithNestedEmptyFnBlockTs = () => { + let a = 1; + + const shouldFail = () => {} + + return a +} + +const someVarTs: string = ''; +if (someVarTs) { +} + +while (someVarTs) { +} + +switch(someVarTs) { +} + +const doSomething = () => null; +try { + doSomething(); +} catch(ex) { + +} finally { + +} + +class FooEmptyStaticTs { + static {} +} + +for(let i; i>0; i++){} + +const obTs = {} +for (const key in obTs) {} + +const arTs = [] +for (const val of arTs) {} + +function fooWithInternalEmptyBlocksTs(){ + let someOtherVar: string = ''; + if (someOtherVar) {} + + while (someOtherVar) { + } + + switch(someOtherVar) { + } + + try { + doSomething(); + } catch(ex) { + + } finally { + + } +} +``` + +# Diagnostics +``` +invalid.ts:1:23 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + > 1 │ function fooEmptyTs() {} + │ ^^ + 2 │ + 3 │ const barEmptyTs = () => {}; + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:3:26 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 1 │ function fooEmptyTs() {} + 2 │ + > 3 │ const barEmptyTs = () => {}; + │ ^^ + 4 │ + 5 │ function fooWithNestedEmptyFnBlockTs() { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:8:24 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 6 │ let a = 1; + 7 │ + > 8 │ function shouldFail(){} + │ ^^ + 9 │ + 10 │ return a + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:17:28 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 15 │ let a = 1; + 16 │ + > 17 │ const shouldFail = () => {} + │ ^^ + 18 │ + 19 │ return a + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:23:16 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 22 │ const someVarTs: string = ''; + > 23 │ if (someVarTs) { + │ ^ + > 24 │ } + │ ^ + 25 │ + 26 │ while (someVarTs) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:26:19 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 24 │ } + 25 │ + > 26 │ while (someVarTs) { + │ ^ + > 27 │ } + │ ^ + 28 │ + 29 │ switch(someVarTs) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:29:1 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 27 │ } + 28 │ + > 29 │ switch(someVarTs) { + │ ^^^^^^^^^^^^^^^^^^^ + > 30 │ } + │ ^ + 31 │ + 32 │ const doSomething = () => null; + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:35:13 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 33 │ try { + 34 │ doSomething(); + > 35 │ } catch(ex) { + │ ^ + > 36 │ + > 37 │ } finally { + │ ^ + 38 │ + 39 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:37:11 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 35 │ } catch(ex) { + 36 │ + > 37 │ } finally { + │ ^ + > 38 │ + > 39 │ } + │ ^ + 40 │ + 41 │ class FooEmptyStaticTs { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:42:3 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 41 │ class FooEmptyStaticTs { + > 42 │ static {} + │ ^^^^^^^^^ + 43 │ } + 44 │ + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:45:21 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 43 │ } + 44 │ + > 45 │ for(let i; i>0; i++){} + │ ^^ + 46 │ + 47 │ const obTs = {} + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:48:25 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 47 │ const obTs = {} + > 48 │ for (const key in obTs) {} + │ ^^ + 49 │ + 50 │ const arTs = [] + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:51:25 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 50 │ const arTs = [] + > 51 │ for (const val of arTs) {} + │ ^^ + 52 │ + 53 │ function fooWithInternalEmptyBlocksTs(){ + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:55:21 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 53 │ function fooWithInternalEmptyBlocksTs(){ + 54 │ let someOtherVar: string = ''; + > 55 │ if (someOtherVar) {} + │ ^^ + 56 │ + 57 │ while (someOtherVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:57:24 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 55 │ if (someOtherVar) {} + 56 │ + > 57 │ while (someOtherVar) { + │ ^ + > 58 │ } + │ ^ + 59 │ + 60 │ switch(someOtherVar) { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:60:3 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 58 │ } + 59 │ + > 60 │ switch(someOtherVar) { + │ ^^^^^^^^^^^^^^^^^^^^^^ + > 61 │ } + │ ^ + 62 │ + 63 │ try { + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:65:15 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 63 │ try { + 64 │ doSomething(); + > 65 │ } catch(ex) { + │ ^ + > 66 │ + > 67 │ } finally { + │ ^ + 68 │ + 69 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + +``` +invalid.ts:67:13 lint/nursery/noEmptyBlockStatements ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Unexpected empty block. + + 65 │ } catch(ex) { + 66 │ + > 67 │ } finally { + │ ^ + > 68 │ + > 69 │ } + │ ^ + 70 │ } + + i Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional. + + +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs new file mode 100644 index 000000000000..d826dcb0c71f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs @@ -0,0 +1,67 @@ +/* should not generate diagnostics */ +function foo() { + let a; +} + +const bar = () => { + let b; +} + + +function fooWithComment() { + // should work +} + +const barWithComment = () => { + // should work +} + +function fooWithMultilineComment() { + /** + * this should also work + */ +} + +const barWithMultilineComment = () => { + /** + * this should also work + */ +} + + +if (foo) { + // empty +} + +while (foo) { + /* empty */ +} + +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomething(); +} finally { + /* continue regardless of error */ +} + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFail() { + +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs.snap b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs.snap new file mode 100644 index 000000000000..bf68c75b8207 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.cjs.snap @@ -0,0 +1,76 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.cjs +--- +# Input +```js +/* should not generate diagnostics */ +function foo() { + let a; +} + +const bar = () => { + let b; +} + + +function fooWithComment() { + // should work +} + +const barWithComment = () => { + // should work +} + +function fooWithMultilineComment() { + /** + * this should also work + */ +} + +const barWithMultilineComment = () => { + /** + * this should also work + */ +} + + +if (foo) { + // empty +} + +while (foo) { + /* empty */ +} + +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomething(); +} finally { + /* continue regardless of error */ +} + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFail() { + +} +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js new file mode 100644 index 000000000000..d826dcb0c71f --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js @@ -0,0 +1,67 @@ +/* should not generate diagnostics */ +function foo() { + let a; +} + +const bar = () => { + let b; +} + + +function fooWithComment() { + // should work +} + +const barWithComment = () => { + // should work +} + +function fooWithMultilineComment() { + /** + * this should also work + */ +} + +const barWithMultilineComment = () => { + /** + * this should also work + */ +} + + +if (foo) { + // empty +} + +while (foo) { + /* empty */ +} + +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomething(); +} finally { + /* continue regardless of error */ +} + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFail() { + +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js.snap new file mode 100644 index 000000000000..ad4ba867b365 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.js.snap @@ -0,0 +1,76 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.js +--- +# Input +```js +/* should not generate diagnostics */ +function foo() { + let a; +} + +const bar = () => { + let b; +} + + +function fooWithComment() { + // should work +} + +const barWithComment = () => { + // should work +} + +function fooWithMultilineComment() { + /** + * this should also work + */ +} + +const barWithMultilineComment = () => { + /** + * this should also work + */ +} + + +if (foo) { + // empty +} + +while (foo) { + /* empty */ +} + +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomething(); +} finally { + /* continue regardless of error */ +} + +class Foo { + static { + bar(); + } +} + +class Foo { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFail() { + +} +``` + + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts new file mode 100644 index 000000000000..cefcd5d4d65d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts @@ -0,0 +1,68 @@ +/* should not generate diagnostics */ +function fooTs() { + let a; +} + +const barTs = () => { + let b; +} + + +function fooWithCommentTS() { + // should work +} + +const barWithCommentTs = () => { + // should work +} + +function fooWithMultilineCommentTS() { + /** + * this should also work + */ +} + +const barWithMultilineCommentTs = () => { + /** + * this should also work + */ +} + +let fooVarTs; +if (fooVarTs) { + // empty +} + +while (fooVarTs) { + /* empty */ +} + +const doSomethingTs = () => null; +try { + doSomethingTs(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomethingTs(); +} finally { + /* continue regardless of error */ +} + +class FooTs { + static { + bar(); + } +} + +class FoozTs { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFailTs() { + +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts.snap b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts.snap new file mode 100644 index 000000000000..8e70c1c1f43c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noEmptyBlockStatements/valid.ts.snap @@ -0,0 +1,77 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.ts +--- +# Input +```js +/* should not generate diagnostics */ +function fooTs() { + let a; +} + +const barTs = () => { + let b; +} + + +function fooWithCommentTS() { + // should work +} + +const barWithCommentTs = () => { + // should work +} + +function fooWithMultilineCommentTS() { + /** + * this should also work + */ +} + +const barWithMultilineCommentTs = () => { + /** + * this should also work + */ +} + +let fooVarTs; +if (fooVarTs) { + // empty +} + +while (fooVarTs) { + /* empty */ +} + +const doSomethingTs = () => null; +try { + doSomethingTs(); +} catch (ex) { + // continue regardless of error +} + +try { + doSomethingTs(); +} finally { + /* continue regardless of error */ +} + +class FooTs { + static { + bar(); + } +} + +class FoozTs { + static { + // comment + } +} + +// biome-ignore lint/nursery/noEmptyBlockStatements: this should be allowed +function shouldNotFailTs() { + +} +``` + + diff --git a/crates/biome_service/src/configuration/linter/rules.rs b/crates/biome_service/src/configuration/linter/rules.rs index ab8a9b900425..dca7c4c64ca5 100644 --- a/crates/biome_service/src/configuration/linter/rules.rs +++ b/crates/biome_service/src/configuration/linter/rules.rs @@ -2184,6 +2184,15 @@ pub struct Nursery { )] #[serde(skip_serializing_if = "Option::is_none")] pub no_duplicate_json_keys: Option<RuleConfiguration>, + #[doc = "Disallow empty block statements and static blocks."] + #[bpaf( + long("no-empty-block-statements"), + argument("on|off|warn"), + optional, + hide + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_empty_block_statements: Option<RuleConfiguration>, #[doc = "Disallow empty character classes in regular expression literals."] #[bpaf( long("no-empty-character-class-in-regex"), @@ -2344,11 +2353,12 @@ pub struct Nursery { } impl Nursery { const GROUP_NAME: &'static str = "nursery"; - pub(crate) const GROUP_RULES: [&'static str; 27] = [ + pub(crate) const GROUP_RULES: [&'static str; 28] = [ "noAccumulatingSpread", "noApproximativeNumericConstant", "noConfusingVoidType", "noDuplicateJsonKeys", + "noEmptyBlockStatements", "noEmptyCharacterClassInRegex", "noExcessiveComplexity", "noFallthroughSwitchClause", @@ -2389,19 +2399,19 @@ impl Nursery { ]; const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 12] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), ]; - const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 27] = [ + const ALL_RULES_AS_FILTERS: [RuleFilter<'static>; 28] = [ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), @@ -2429,6 +2439,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended(&self) -> bool { @@ -2465,121 +2476,126 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { + if let Some(rule) = self.no_empty_block_statements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_excessive_complexity.as_ref() { + if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { + if let Some(rule) = self.no_excessive_complexity.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_is_finite.as_ref() { + if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_global_is_nan.as_ref() { + if let Some(rule) = self.no_global_is_finite.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_interactive_element_to_noninteractive_role.as_ref() { + if let Some(rule) = self.no_global_is_nan.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_invalid_new_builtin.as_ref() { + if let Some(rule) = self.no_interactive_element_to_noninteractive_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_misrefactored_shorthand_assign.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misrefactored_shorthand_assign.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.use_aria_activedescendant_with_tabindex.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.use_aria_activedescendant_with_tabindex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.use_as_const_assertion.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_as_const_assertion.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_shorthand_assign.as_ref() { + if let Some(rule) = self.use_is_array.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } + if let Some(rule) = self.use_shorthand_assign.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> IndexSet<RuleFilter> { @@ -2604,121 +2620,126 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { + if let Some(rule) = self.no_empty_block_statements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_excessive_complexity.as_ref() { + if let Some(rule) = self.no_empty_character_class_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { + if let Some(rule) = self.no_excessive_complexity.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_global_is_finite.as_ref() { + if let Some(rule) = self.no_fallthrough_switch_clause.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_global_is_nan.as_ref() { + if let Some(rule) = self.no_global_is_finite.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_interactive_element_to_noninteractive_role.as_ref() { + if let Some(rule) = self.no_global_is_nan.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_invalid_new_builtin.as_ref() { + if let Some(rule) = self.no_interactive_element_to_noninteractive_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_misleading_instantiator.as_ref() { + if let Some(rule) = self.no_invalid_new_builtin.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_misrefactored_shorthand_assign.as_ref() { + if let Some(rule) = self.no_misleading_instantiator.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_unused_imports.as_ref() { + if let Some(rule) = self.no_misrefactored_shorthand_assign.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_useless_else.as_ref() { + if let Some(rule) = self.no_unused_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { + if let Some(rule) = self.no_useless_else.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_void.as_ref() { + if let Some(rule) = self.no_useless_lone_block_statements.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.use_aria_activedescendant_with_tabindex.as_ref() { + if let Some(rule) = self.no_void.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.use_arrow_function.as_ref() { + if let Some(rule) = self.use_aria_activedescendant_with_tabindex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.use_as_const_assertion.as_ref() { + if let Some(rule) = self.use_arrow_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.use_collapsed_else_if.as_ref() { + if let Some(rule) = self.use_as_const_assertion.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { + if let Some(rule) = self.use_collapsed_else_if.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_grouped_type_import.as_ref() { + if let Some(rule) = self.use_exhaustive_dependencies.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_hook_at_top_level.as_ref() { + if let Some(rule) = self.use_grouped_type_import.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_hook_at_top_level.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.use_is_array.as_ref() { + if let Some(rule) = self.use_import_restrictions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_shorthand_assign.as_ref() { + if let Some(rule) = self.use_is_array.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } + if let Some(rule) = self.use_shorthand_assign.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -2732,7 +2753,7 @@ impl Nursery { pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 12] { Self::RECOMMENDED_RULES_AS_FILTERS } - pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 27] { + pub(crate) fn all_rules_as_filters() -> [RuleFilter<'static>; 28] { Self::ALL_RULES_AS_FILTERS } #[doc = r" Select preset rules"] @@ -2759,6 +2780,7 @@ impl Nursery { "noApproximativeNumericConstant" => self.no_approximative_numeric_constant.as_ref(), "noConfusingVoidType" => self.no_confusing_void_type.as_ref(), "noDuplicateJsonKeys" => self.no_duplicate_json_keys.as_ref(), + "noEmptyBlockStatements" => self.no_empty_block_statements.as_ref(), "noEmptyCharacterClassInRegex" => self.no_empty_character_class_in_regex.as_ref(), "noExcessiveComplexity" => self.no_excessive_complexity.as_ref(), "noFallthroughSwitchClause" => self.no_fallthrough_switch_clause.as_ref(), diff --git a/crates/biome_service/src/configuration/parse/json/rules.rs b/crates/biome_service/src/configuration/parse/json/rules.rs index 55ae32ece711..aa34a6dc57e4 100644 --- a/crates/biome_service/src/configuration/parse/json/rules.rs +++ b/crates/biome_service/src/configuration/parse/json/rules.rs @@ -2027,6 +2027,7 @@ impl VisitNode<JsonLanguage> for Nursery { "noApproximativeNumericConstant", "noConfusingVoidType", "noDuplicateJsonKeys", + "noEmptyBlockStatements", "noEmptyCharacterClassInRegex", "noExcessiveComplexity", "noFallthroughSwitchClause", @@ -2161,6 +2162,29 @@ impl VisitNode<JsonLanguage> for Nursery { )); } }, + "noEmptyBlockStatements" => match value { + AnyJsonValue::JsonStringValue(_) => { + let mut configuration = RuleConfiguration::default(); + self.map_to_known_string(&value, name_text, &mut configuration, diagnostics)?; + self.no_empty_block_statements = Some(configuration); + } + AnyJsonValue::JsonObjectValue(_) => { + let mut rule_configuration = RuleConfiguration::default(); + rule_configuration.map_rule_configuration( + &value, + name_text, + "noEmptyBlockStatements", + diagnostics, + )?; + self.no_empty_block_statements = Some(rule_configuration); + } + _ => { + diagnostics.push(DeserializationDiagnostic::new_incorrect_type( + "object or string", + value.range(), + )); + } + }, "noEmptyCharacterClassInRegex" => match value { AnyJsonValue::JsonStringValue(_) => { let mut configuration = RuleConfiguration::default(); diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index 911a5b2ad25c..4f20250e6e8a 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -1026,6 +1026,13 @@ { "type": "null" } ] }, + "noEmptyBlockStatements": { + "description": "Disallow empty block statements and static blocks.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noEmptyCharacterClassInRegex": { "description": "Disallow empty character classes in regular expression literals.", "anyOf": [ diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 0841f4683206..d9c8b0a9f8c0 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -697,6 +697,10 @@ export interface Nursery { * Disallow two keys with the same name inside a JSON object. */ noDuplicateJsonKeys?: RuleConfiguration; + /** + * Disallow empty block statements and static blocks. + */ + noEmptyBlockStatements?: RuleConfiguration; /** * Disallow empty character classes in regular expression literals. */ @@ -1347,6 +1351,7 @@ export type Category = | "lint/nursery/noApproximativeNumericConstant" | "lint/nursery/noConfusingVoidType" | "lint/nursery/noDuplicateJsonKeys" + | "lint/nursery/noEmptyBlockStatements" | "lint/nursery/noEmptyCharacterClassInRegex" | "lint/nursery/noExcessiveComplexity" | "lint/nursery/noFallthroughSwitchClause" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 911a5b2ad25c..4f20250e6e8a 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1026,6 +1026,13 @@ { "type": "null" } ] }, + "noEmptyBlockStatements": { + "description": "Disallow empty block statements and static blocks.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noEmptyCharacterClassInRegex": { "description": "Disallow empty character classes in regular expression literals.", "anyOf": [ diff --git a/website/src/components/generated/NumberOfRules.astro b/website/src/components/generated/NumberOfRules.astro index 75b3faa7ebd5..6b9300bc0694 100644 --- a/website/src/components/generated/NumberOfRules.astro +++ b/website/src/components/generated/NumberOfRules.astro @@ -1,2 +1,2 @@ <!-- this file is auto generated, use `cargo lintdoc` to update it --> - <p>Biome's linter has a total of <strong><a href='/linter/rules'>169 rules</a></strong><p> \ No newline at end of file + <p>Biome's linter has a total of <strong><a href='/linter/rules'>170 rules</a></strong><p> \ No newline at end of file diff --git a/website/src/content/docs/linter/rules/index.mdx b/website/src/content/docs/linter/rules/index.mdx index 741148fac47c..a2e02d15af7c 100644 --- a/website/src/content/docs/linter/rules/index.mdx +++ b/website/src/content/docs/linter/rules/index.mdx @@ -208,6 +208,7 @@ Rules that belong to this group <strong>are not subject to semantic version</str | [noApproximativeNumericConstant](/linter/rules/no-approximative-numeric-constant) | Usually, the definition in the standard library is more precise than what people come up with or the used constant exceeds the maximum precision of the number type. | | | [noConfusingVoidType](/linter/rules/no-confusing-void-type) | Disallow <code>void</code> type outside of generic or return types. | | | [noDuplicateJsonKeys](/linter/rules/no-duplicate-json-keys) | Disallow two keys with the same name inside a JSON object. | | +| [noEmptyBlockStatements](/linter/rules/no-empty-block-statements) | Disallow empty block statements and static blocks. | | | [noEmptyCharacterClassInRegex](/linter/rules/no-empty-character-class-in-regex) | Disallow empty character classes in regular expression literals. | | | [noExcessiveComplexity](/linter/rules/no-excessive-complexity) | Disallow functions that exceed a given Cognitive Complexity score. | | | [noFallthroughSwitchClause](/linter/rules/no-fallthrough-switch-clause) | Disallow fallthrough of <code>switch</code> clauses. | | diff --git a/website/src/content/docs/linter/rules/no-empty-block-statements.md b/website/src/content/docs/linter/rules/no-empty-block-statements.md new file mode 100644 index 000000000000..8e67c4c2f7d1 --- /dev/null +++ b/website/src/content/docs/linter/rules/no-empty-block-statements.md @@ -0,0 +1,105 @@ +--- +title: noEmptyBlockStatements (since vnext) +--- + +**Diagnostic Category: `lint/nursery/noEmptyBlockStatements`** + +:::caution +This rule is part of the [nursery](/linter/rules/#nursery) group. +::: + +Disallow empty block statements and static blocks. + +Empty static blocks and block statements, while not technically errors, usually occur due to refactoring that wasn’t completed. They can cause confusion when reading code. + +This rule disallows empty block statements and static blocks. +This rule ignores block statements or static blocks which contain a comment (for example, in an empty catch or finally block of a try statement to indicate that execution should continue regardless of errors). + +Source: https://eslint.org/docs/latest/rules/no-empty-static-block/ +Source: https://eslint.org/docs/latest/rules/no-empty/ + +## Examples + +### Invalid + +```jsx +function emptyFunctionBody () {} +``` + +<pre class="language-text"><code class="language-text">nursery/noEmptyBlockStatements.js:1:31 <a href="https://biomejs.dev/lint/rules/no-empty-block-statements">lint/nursery/noEmptyBlockStatements</a> ━━━━━━━━━━━━━━━━━━━━━━━━━ + +<strong><span style="color: Orange;"> </span></strong><strong><span style="color: Orange;">⚠</span></strong> <span style="color: Orange;">Unexpected empty block.</span> + +<strong><span style="color: Tomato;"> </span></strong><strong><span style="color: Tomato;">></span></strong> <strong>1 │ </strong>function emptyFunctionBody () {} + <strong> │ </strong> <strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong> + <strong>2 │ </strong> + +<strong><span style="color: lightgreen;"> </span></strong><strong><span style="color: lightgreen;">ℹ</span></strong> <span style="color: lightgreen;">Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional.</span> + +</code></pre> + +```jsx +try { + doSomething(); +} catch(ex) { + +} +``` + +<pre class="language-text"><code class="language-text">nursery/noEmptyBlockStatements.js:3:13 <a href="https://biomejs.dev/lint/rules/no-empty-block-statements">lint/nursery/noEmptyBlockStatements</a> ━━━━━━━━━━━━━━━━━━━━━━━━━ + +<strong><span style="color: Orange;"> </span></strong><strong><span style="color: Orange;">⚠</span></strong> <span style="color: Orange;">Unexpected empty block.</span> + + <strong>1 │ </strong>try { + <strong>2 │ </strong> doSomething(); +<strong><span style="color: Tomato;"> </span></strong><strong><span style="color: Tomato;">></span></strong> <strong>3 │ </strong>} catch(ex) { + <strong> │ </strong> <strong><span style="color: Tomato;">^</span></strong> +<strong><span style="color: Tomato;"> </span></strong><strong><span style="color: Tomato;">></span></strong> <strong>4 │ </strong> +<strong><span style="color: Tomato;"> </span></strong><strong><span style="color: Tomato;">></span></strong> <strong>5 │ </strong>} + <strong> │ </strong><strong><span style="color: Tomato;">^</span></strong> + <strong>6 │ </strong> + +<strong><span style="color: lightgreen;"> </span></strong><strong><span style="color: lightgreen;">ℹ</span></strong> <span style="color: lightgreen;">Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional.</span> + +</code></pre> + +```jsx +class Foo { + static {} +} +``` + +<pre class="language-text"><code class="language-text">nursery/noEmptyBlockStatements.js:2:3 <a href="https://biomejs.dev/lint/rules/no-empty-block-statements">lint/nursery/noEmptyBlockStatements</a> ━━━━━━━━━━━━━━━━━━━━━━━━━━ + +<strong><span style="color: Orange;"> </span></strong><strong><span style="color: Orange;">⚠</span></strong> <span style="color: Orange;">Unexpected empty block.</span> + + <strong>1 │ </strong>class Foo { +<strong><span style="color: Tomato;"> </span></strong><strong><span style="color: Tomato;">></span></strong> <strong>2 │ </strong> static {} + <strong> │ </strong> <strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong><strong><span style="color: Tomato;">^</span></strong> + <strong>3 │ </strong>} + <strong>4 │ </strong> + +<strong><span style="color: lightgreen;"> </span></strong><strong><span style="color: lightgreen;">ℹ</span></strong> <span style="color: lightgreen;">Empty blocks are usually the result of an incomplete refactoring. Remove the empty block or add a comment inside it if it is intentional.</span> + +</code></pre> + +## Valid + +```jsx +function foo () { + doSomething(); +} +``` + +```jsx +try { + doSomething(); +} catch (ex) { + // continue regardless of error +} +``` + +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)