From 6e4ae149f4a96aed990521a2043b7a0e20831b05 Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Wed, 4 Mar 2026 11:24:24 -0500 Subject: [PATCH] perf(noEmptyBlockStatements): short circuit to avoid traversing descendants for comments --- .changeset/nine-sides-wish.md | 5 +++ .../suspicious/no_empty_block_statements.rs | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 .changeset/nine-sides-wish.md diff --git a/.changeset/nine-sides-wish.md b/.changeset/nine-sides-wish.md new file mode 100644 index 000000000000..8426c0dc8b92 --- /dev/null +++ b/.changeset/nine-sides-wish.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Improved performance of [`noEmptyBlockStatements`](https://biomejs.dev/linter/rules/no-empty-block-statements/). The rule is now smarter about short-circuiting its logic. diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_empty_block_statements.rs b/crates/biome_js_analyze/src/lint/suspicious/no_empty_block_statements.rs index 0683a158d08c..52387efcb5f3 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_empty_block_statements.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_empty_block_statements.rs @@ -83,12 +83,17 @@ impl Rule for NoEmptyBlockStatements { fn run(ctx: &RuleContext) -> Self::Signals { let query = ctx.query(); - let is_empty = is_empty(query); - let has_comments = query.syntax().has_comments_descendants(); - let is_constructor_with_ts_param_props_or_private = - is_constructor_with_ts_param_props_or_private(query); + if !is_empty(query) { + return None; + } + if is_constructor_with_ts_param_props_or_private(query) { + return None; + } + if query.syntax().has_comments_descendants() { + return None; + } - (is_empty && !has_comments && !is_constructor_with_ts_param_props_or_private).then_some(()) + Some(()) } fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { @@ -111,10 +116,10 @@ impl Rule for NoEmptyBlockStatements { 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, + JsFunctionBody(body) => body.directives().is_empty() && body.statements().is_empty(), + JsBlockStatement(block) => block.statements().is_empty(), + JsStaticInitializationBlockClassMember(block) => block.statements().is_empty(), + JsSwitchStatement(statement) => statement.cases().is_empty(), } } @@ -141,9 +146,11 @@ fn is_constructor_with_ts_param_props_or_private(query: &Query) -> bool { .parameters() .into_iter() .any(|param| matches!(param, Ok(AnyJsConstructorParameter::TsPropertyParameter(_)))); - let is_private = constructor + if is_param_props { + return true; + } + constructor .modifiers() .into_iter() - .any(|modifier| modifier.is_private() || modifier.is_protected()); - is_param_props || is_private + .any(|modifier| modifier.is_private() || modifier.is_protected()) }