From ab0d96b116e82c5f4e2d2c2168226c3d479227b4 Mon Sep 17 00:00:00 2001 From: Antony David Date: Mon, 16 Feb 2026 22:49:19 +0100 Subject: [PATCH] perf(lint): optimize noIrregularWhitespace rule --- .changeset/new-dolls-flow.md | 5 ++ .../suspicious/no_irregular_whitespace.rs | 52 +++++++------- .../suspicious/no_irregular_whitespace.rs | 69 +++++++++---------- 3 files changed, 64 insertions(+), 62 deletions(-) create mode 100644 .changeset/new-dolls-flow.md diff --git a/.changeset/new-dolls-flow.md b/.changeset/new-dolls-flow.md new file mode 100644 index 000000000000..39de8d19336f --- /dev/null +++ b/.changeset/new-dolls-flow.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Slightly improved performance of [`noIrregularWhitespace`](https://biomejs.dev/linter/rules/no-irregular-whitespace/) by adding early return optimization and simplifying character detection logic. diff --git a/crates/biome_css_analyze/src/lint/suspicious/no_irregular_whitespace.rs b/crates/biome_css_analyze/src/lint/suspicious/no_irregular_whitespace.rs index 6679b8fe01bc..39bf5934766d 100644 --- a/crates/biome_css_analyze/src/lint/suspicious/no_irregular_whitespace.rs +++ b/crates/biome_css_analyze/src/lint/suspicious/no_irregular_whitespace.rs @@ -2,17 +2,11 @@ use biome_analyze::{ Ast, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; -use biome_css_syntax::{AnyCssRule, CssLanguage}; +use biome_css_syntax::{AnyCssRule, CssSyntaxNode}; use biome_diagnostics::Severity; -use biome_rowan::{AstNode, Direction, SyntaxToken, TextRange}; +use biome_rowan::{AstNode, Direction, TextRange}; use biome_rule_options::no_irregular_whitespace::NoIrregularWhitespaceOptions; -const IRREGULAR_WHITESPACES: &[char; 22] = &[ - '\u{c}', '\u{b}', '\u{85}', '\u{feff}', '\u{a0}', '\u{1680}', '\u{180e}', '\u{2000}', - '\u{2001}', '\u{2002}', '\u{2003}', '\u{2004}', '\u{2005}', '\u{2006}', '\u{2007}', '\u{2008}', - '\u{2009}', '\u{200a}', '\u{200b}', '\u{202f}', '\u{205f}', '\u{3000}', -]; - declare_lint_rule! { /// Disallows the use of irregular whitespace characters. /// @@ -53,8 +47,7 @@ impl Rule for NoIrregularWhitespace { type Options = NoIrregularWhitespaceOptions; fn run(ctx: &RuleContext) -> Self::Signals { - let node = ctx.query(); - get_irregular_whitespace(node).into_boxed_slice() + get_irregular_whitespace(ctx.query().syntax()).into_boxed_slice() } fn diagnostic(_: &RuleContext, range: &Self::State) -> Option { @@ -73,24 +66,31 @@ impl Rule for NoIrregularWhitespace { } } -fn get_irregular_whitespace(node: &AnyCssRule) -> Vec { - let syntax = node.syntax(); - let mut all_whitespaces_token: Vec = vec![]; - let matches_irregular_whitespace = |token: &SyntaxToken| { - !token.has_leading_comments() - && !token.has_trailing_comments() - && token.text().chars().any(|char| { - IRREGULAR_WHITESPACES - .iter() - .any(|irregular_whitespace| &char == irregular_whitespace) - }) - }; +fn is_irregular_whitespace(c: char) -> bool { + matches!( + c, + '\u{000B}' | '\u{000C}' | '\u{0085}' | '\u{00A0}' | '\u{1680}' | '\u{180E}' | '\u{2000}' + ..='\u{200B}' | '\u{202F}' | '\u{205F}' | '\u{3000}' | '\u{FEFF}' + ) +} +fn get_irregular_whitespace(syntax: &CssSyntaxNode) -> Vec { + if !syntax + .text_with_trivia() + .chars() + .any(is_irregular_whitespace) + { + return vec![]; + } + + let mut results = vec![]; for token in syntax.descendants_tokens(Direction::Next) { - if matches_irregular_whitespace(&token) { - all_whitespaces_token.push(token.text_range()); + if !token.has_leading_comments() + && !token.has_trailing_comments() + && token.text().chars().any(is_irregular_whitespace) + { + results.push(token.text_range()); } } - - all_whitespaces_token + results } diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_irregular_whitespace.rs b/crates/biome_js_analyze/src/lint/suspicious/no_irregular_whitespace.rs index 493f4d09ae95..2708cc89b91a 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_irregular_whitespace.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_irregular_whitespace.rs @@ -2,16 +2,10 @@ use biome_analyze::RuleSource; use biome_analyze::{Ast, Rule, RuleDiagnostic, context::RuleContext, declare_lint_rule}; use biome_console::markup; use biome_diagnostics::Severity; -use biome_js_syntax::{AnyJsRoot, JsLanguage, JsSyntaxNode}; -use biome_rowan::{AstNode, Direction, SyntaxTriviaPiece, TextRange}; +use biome_js_syntax::{AnyJsRoot, JsSyntaxNode}; +use biome_rowan::{AstNode, Direction, TextRange}; use biome_rule_options::no_irregular_whitespace::NoIrregularWhitespaceOptions; -const IRREGULAR_WHITESPACES: &[char; 22] = &[ - '\u{c}', '\u{b}', '\u{85}', '\u{feff}', '\u{a0}', '\u{1680}', '\u{180e}', '\u{2000}', - '\u{2001}', '\u{2002}', '\u{2003}', '\u{2004}', '\u{2005}', '\u{2006}', '\u{2007}', '\u{2008}', - '\u{2009}', '\u{200a}', '\u{200b}', '\u{202f}', '\u{205f}', '\u{3000}', -]; - declare_lint_rule! { /// Disallows the use of irregular whitespace characters. /// @@ -78,38 +72,41 @@ impl Rule for NoIrregularWhitespace { } } +fn is_irregular_whitespace(c: char) -> bool { + matches!( + c, + '\u{000B}' + | '\u{000C}' + | '\u{0085}' + | '\u{00A0}' + | '\u{1680}' + | '\u{180E}' + | '\u{2000}'..='\u{200B}' + | '\u{202F}' + | '\u{205F}' + | '\u{3000}' + | '\u{FEFF}' + ) +} + fn get_irregular_whitespace(syntax: &JsSyntaxNode) -> Vec { - let mut all_whitespaces_trivia: Vec> = vec![]; - let is_whitespace = |trivia: &SyntaxTriviaPiece| { - trivia.is_whitespace() && !trivia.text().replace(' ', "").is_empty() - }; + if !syntax.text_with_trivia().chars().any(is_irregular_whitespace) { + return vec![]; + } + let mut results = vec![]; for token in syntax.descendants_tokens(Direction::Next) { - let leading_trivia_pieces = token.leading_trivia().pieces(); - let trailing_trivia_pieces = token.trailing_trivia().pieces(); - - for trivia in leading_trivia_pieces { - if is_whitespace(&trivia) { - all_whitespaces_trivia.push(trivia); - } - } - - for trivia in trailing_trivia_pieces { - if is_whitespace(&trivia) { - all_whitespaces_trivia.push(trivia); + for trivia in token + .leading_trivia() + .pieces() + .chain(token.trailing_trivia().pieces()) + { + if trivia.is_whitespace() + && trivia.text().chars().any(is_irregular_whitespace) + { + results.push(trivia.text_range()); } } } - - all_whitespaces_trivia - .iter() - .filter_map(|trivia| { - let has_irregular_whitespace = trivia.text().chars().any(|char| { - IRREGULAR_WHITESPACES - .iter() - .any(|irregular_whitespace| &char == irregular_whitespace) - }); - has_irregular_whitespace.then(|| trivia.text_range()) - }) - .collect::>() + results }