From bf2781fbdeb9ca7039a8f14aee20844612e2a66c Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Thu, 26 Sep 2024 08:50:58 -0500 Subject: [PATCH 01/32] Update tests --- Cargo.lock | 1 + crates/biome_grit_formatter/Cargo.toml | 1 + crates/biome_grit_formatter/tests/language.rs | 15 ++++++++++++--- .../tests/specs/grit/file_node.grit.snap | 18 ++++++++++++++++++ .../tests/specs/grit/options.json | 1 + .../tests/specs/grit/patterns/if_pattern.grit | 7 +++++++ .../tests/specs/grit/patterns/options.json | 1 + 7 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 crates/biome_grit_formatter/tests/specs/grit/options.json create mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit create mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/options.json diff --git a/Cargo.lock b/Cargo.lock index 21c97694cef7..4bf01c37bd43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -600,6 +600,7 @@ dependencies = [ "biome_configuration", "biome_formatter", "biome_formatter_test", + "biome_fs", "biome_grit_factory", "biome_grit_parser", "biome_grit_syntax", diff --git a/crates/biome_grit_formatter/Cargo.toml b/crates/biome_grit_formatter/Cargo.toml index 399c4eda2216..2312d61a2347 100644 --- a/crates/biome_grit_formatter/Cargo.toml +++ b/crates/biome_grit_formatter/Cargo.toml @@ -20,6 +20,7 @@ biome_rowan = { workspace = true } [dev-dependencies] biome_configuration = { path = "../biome_configuration" } biome_formatter_test = { path = "../biome_formatter_test" } +biome_fs = { workspace = true } biome_grit_factory = { path = "../biome_grit_factory" } biome_grit_parser = { path = "../biome_grit_parser" } biome_parser = { path = "../biome_parser" } diff --git a/crates/biome_grit_formatter/tests/language.rs b/crates/biome_grit_formatter/tests/language.rs index 24edd18515ba..e1499380f0c2 100644 --- a/crates/biome_grit_formatter/tests/language.rs +++ b/crates/biome_grit_formatter/tests/language.rs @@ -1,7 +1,9 @@ use biome_formatter_test::TestFormatLanguage; +use biome_fs::BiomePath; use biome_grit_formatter::{context::GritFormatContext, GritFormatLanguage}; use biome_grit_parser::parse_grit; use biome_grit_syntax::GritLanguage; +use biome_service::settings::ServiceLanguage; #[derive(Default)] pub struct GritTestFormatLanguage; @@ -17,9 +19,16 @@ impl TestFormatLanguage for GritTestFormatLanguage { fn to_format_language( &self, - _settings: &biome_service::settings::Settings, - _file_source: &biome_service::workspace::DocumentFileSource, + settings: &biome_service::settings::Settings, + file_source: &biome_service::workspace::DocumentFileSource, ) -> Self::FormatLanguage { - todo!() + let options = Self::ServiceLanguage::resolve_format_options( + Some(&settings.formatter), + Some(&settings.override_settings), + None, + &BiomePath::new(""), + file_source, + ); + GritFormatLanguage::new(options) } } diff --git a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap index 6af8139fa4cf..33c32ba631af 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap @@ -28,6 +28,24 @@ file(body = contains `console.$method` => `println`)``` +## Unimplemented nodes/tokens + +"file(body = contains `console.$method` => `println`)" => 0..52 +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +----- + +```grit +file(body = contains `console.$method` => `println`)``` + + + ## Unimplemented nodes/tokens "file(body = contains `console.$method` => `println`)" => 0..52 diff --git a/crates/biome_grit_formatter/tests/specs/grit/options.json b/crates/biome_grit_formatter/tests/specs/grit/options.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/crates/biome_grit_formatter/tests/specs/grit/options.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit new file mode 100644 index 000000000000..97bc4bb4f96f --- /dev/null +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit @@ -0,0 +1,7 @@ +`$method('$message')` where { + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +} diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json b/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json @@ -0,0 +1 @@ +{} \ No newline at end of file From c331c41ed0fe050c7da0d1938fe7ba8dba7e57ea Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Thu, 26 Sep 2024 10:02:06 -0500 Subject: [PATCH 02/32] Update Cargo.toml Co-authored-by: Emanuele Stoppa --- crates/biome_grit_formatter/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_grit_formatter/Cargo.toml b/crates/biome_grit_formatter/Cargo.toml index 2312d61a2347..eaebbafeaa7c 100644 --- a/crates/biome_grit_formatter/Cargo.toml +++ b/crates/biome_grit_formatter/Cargo.toml @@ -20,7 +20,7 @@ biome_rowan = { workspace = true } [dev-dependencies] biome_configuration = { path = "../biome_configuration" } biome_formatter_test = { path = "../biome_formatter_test" } -biome_fs = { workspace = true } +biome_fs = { path = "../biome_fs" } biome_grit_factory = { path = "../biome_grit_factory" } biome_grit_parser = { path = "../biome_grit_parser" } biome_parser = { path = "../biome_parser" } From 8faaa59a751d10e8b98079705013f83b9890b202 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Fri, 27 Sep 2024 21:59:30 -0500 Subject: [PATCH 03/32] fix test --- crates/biome_grit_formatter/src/context.rs | 21 +++++- .../biome_grit_formatter/tests/spec_tests.rs | 6 +- .../tests/specs/grit/file_node.grit.snap | 2 + .../tests/specs/grit/patterns/if_pattern.grit | 2 +- .../specs/grit/patterns/if_pattern.grit.snap | 71 +++++++++++++++++++ 5 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap diff --git a/crates/biome_grit_formatter/src/context.rs b/crates/biome_grit_formatter/src/context.rs index 43812f0b78e5..cbf6aacb53b8 100644 --- a/crates/biome_grit_formatter/src/context.rs +++ b/crates/biome_grit_formatter/src/context.rs @@ -62,6 +62,7 @@ pub struct GritFormatOptions { line_width: LineWidth, quote_style: QuoteStyle, attribute_position: AttributePosition, + bracket_spacing: BracketSpacing, } impl GritFormatOptions { @@ -73,8 +74,15 @@ impl GritFormatOptions { line_width: LineWidth::default(), quote_style: QuoteStyle::default(), attribute_position: AttributePosition::default(), + bracket_spacing: BracketSpacing::default(), } } + + pub fn with_bracket_spacing(mut self, bracket_spacing: BracketSpacing) -> Self { + self.bracket_spacing = bracket_spacing; + self + } + pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self { self.indent_style = indent_style; self @@ -100,6 +108,10 @@ impl GritFormatOptions { self } + pub fn set_bracket_spacing(&mut self, bracket_spacing: BracketSpacing) { + self.bracket_spacing = bracket_spacing; + } + pub fn set_indent_style(&mut self, indent_style: IndentStyle) { self.indent_style = indent_style; } @@ -120,6 +132,10 @@ impl GritFormatOptions { self.quote_style = quote_style; } + pub fn bracket_spacing(&self) -> BracketSpacing { + self.bracket_spacing + } + pub fn quote_style(&self) -> QuoteStyle { self.quote_style } @@ -135,6 +151,7 @@ impl Display for GritFormatOptions { writeln!(f, "Indent width: {}", self.indent_width.value())?; writeln!(f, "Line ending: {}", self.line_ending)?; writeln!(f, "Line width: {}", self.line_width.value())?; + writeln!(f, "Bracket spacing: {}", self.bracket_spacing.value())?; writeln!(f, "Attribute Position: {}", self.attribute_position) } } @@ -160,8 +177,8 @@ impl FormatOptions for GritFormatOptions { self.attribute_position } - fn bracket_spacing(&self) -> biome_formatter::BracketSpacing { - BracketSpacing::default() + fn bracket_spacing(&self) -> BracketSpacing { + self.bracket_spacing } fn as_print_options(&self) -> biome_formatter::prelude::PrinterOptions { diff --git a/crates/biome_grit_formatter/tests/spec_tests.rs b/crates/biome_grit_formatter/tests/spec_tests.rs index f68ad1f252f7..d2fd48314093 100644 --- a/crates/biome_grit_formatter/tests/spec_tests.rs +++ b/crates/biome_grit_formatter/tests/spec_tests.rs @@ -3,6 +3,10 @@ mod spec_test; mod formatter { mod grit_module { - tests_macros::gen_tests! {"tests/specs/grit/**/*.grit", crate::spec_test::run, ""} + tests_macros::gen_tests! {"tests/specs/grit/*.grit", crate::spec_test::run, ""} + } + + mod grit_patterns_module { + tests_macros::gen_tests! {"tests/specs/grit/patterns/*.grit", crate::spec_test::run, "patterns"} } } diff --git a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap index 33c32ba631af..4e1d3f1d4450 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap @@ -20,6 +20,7 @@ Indent style: Tab Indent width: 2 Line ending: LF Line width: 80 +Bracket spacing: true Attribute Position: Auto ----- @@ -38,6 +39,7 @@ Indent style: Tab Indent width: 2 Line ending: LF Line width: 80 +Bracket spacing: true Attribute Position: Auto ----- diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit index 97bc4bb4f96f..78bcb801d9cb 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit @@ -4,4 +4,4 @@ } else { $method => `console.warn` } -} +} \ No newline at end of file diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap new file mode 100644 index 000000000000..c792a2b76d7e --- /dev/null +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap @@ -0,0 +1,71 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: grit/patterns/if_pattern.grit +--- +# Input + +```grit +`$method('$message')` where { + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +} +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Bracket spacing: true +Attribute Position: Auto +----- + +```grit +`$method('$message')` where { + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +}``` + + + +## Unimplemented nodes/tokens + +"`$method('$message')` where {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 0..141 +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Bracket spacing: true +Attribute Position: Auto +----- + +```grit +`$method('$message')` where { + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +}``` + + + +## Unimplemented nodes/tokens + +"`$method('$message')` where {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 0..141 From 083e1bfd3fc55604c70be92018ae341107d08b37 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Sat, 28 Sep 2024 07:32:24 +0100 Subject: [PATCH 04/32] fix(js-api): update code (#4111) --- CHANGELOG.md | 4 ++++ packages/@biomejs/js-api/src/index.ts | 2 ++ packages/@biomejs/wasm-nodejs/package.json | 13 +++++-------- packages/aria-data/generate-aria-data.js | 6 +++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42dd50991f6d..f70bbb2d78ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### JavaScript API +#### Bug fixes + +- Fix [#3881](https://github.com/biomejs/biome/issues/3881), by updating the APIs to use the latest WASM changes. Contributed by @ematipico + ### Linter #### New features diff --git a/packages/@biomejs/js-api/src/index.ts b/packages/@biomejs/js-api/src/index.ts index 3cf806474c4c..edeceeae7c4e 100644 --- a/packages/@biomejs/js-api/src/index.ts +++ b/packages/@biomejs/js-api/src/index.ts @@ -158,6 +158,8 @@ export class Biome { return this.tryCatchWrapper(() => { const biomePath: BiomePath = { path, + was_written: false, + kind: "Handleable", }; this.workspace.openFile({ diff --git a/packages/@biomejs/wasm-nodejs/package.json b/packages/@biomejs/wasm-nodejs/package.json index 8ce2378006a3..9884da9cdad0 100644 --- a/packages/@biomejs/wasm-nodejs/package.json +++ b/packages/@biomejs/wasm-nodejs/package.json @@ -1,15 +1,14 @@ { - "name": "@biomejs/wasm-nodejs", + "name": "@biomejs/biome_wasm", "collaborators": [ "Biome Developers and Contributors" ], "description": "WebAssembly bindings to the Biome workspace API", - "version": "1.8.3", + "version": "1.7.3", "license": "MIT OR Apache-2.0", "repository": { "type": "git", - "url": "git+https://github.com/biomejs/biome.git", - "directory": "packages/@biomejs/biome/wasm-nodejs" + "url": "https://github.com/biomejs/biome" }, "files": [ "biome_wasm_bg.wasm", @@ -18,13 +17,11 @@ ], "main": "biome_wasm.js", "homepage": "https://biomejs.dev/", - "publishConfig": { - "provenance": true - }, "types": "biome_wasm.d.ts", "keywords": [ "parser", "linter", - "formatter" + "formatter", + "wasm" ] } \ No newline at end of file diff --git a/packages/aria-data/generate-aria-data.js b/packages/aria-data/generate-aria-data.js index bb7321727dd6..db33490f71dd 100644 --- a/packages/aria-data/generate-aria-data.js +++ b/packages/aria-data/generate-aria-data.js @@ -1,6 +1,6 @@ #!/usr/bin/node -import * as util from "node:util"; import * as process from "node:process"; +import * as util from "node:util"; import { Browser, BrowserErrorCaptureEnum } from "happy-dom"; function parseAriaSpec(doc, { url, version }) { @@ -174,7 +174,7 @@ function parseNamesFrom(node) { .map((name) => name.trim()) .filter( (name) => - name == "author" || name == "contents" || name == "prohibited", + name === "author" || name === "contents" || name === "prohibited", ) ?? [] ); } @@ -262,7 +262,7 @@ function parseConcept(spacedText, version) { module: module.toLowerCase(), }); } - if (result.length == 0) { + if (result.length === 0) { if (text.includes(" ")) { result.push({ type: "text", From 38fcbd3b9c53ca198f8c949c0ca0cd6cacdb9892 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 06:47:19 +0100 Subject: [PATCH 05/32] fix(deps): update rust crate libc to 0.2.159 (#4126) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/biome_cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85ac5b267bd9..16f48224d7cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,9 +2351,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libgit2-sys" diff --git a/crates/biome_cli/Cargo.toml b/crates/biome_cli/Cargo.toml index 01001244ac15..28c3030331ad 100644 --- a/crates/biome_cli/Cargo.toml +++ b/crates/biome_cli/Cargo.toml @@ -58,7 +58,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter", "json"] tracing-tree = "0.4.0" [target.'cfg(unix)'.dependencies] -libc = "0.2.158" +libc = "0.2.159" tokio = { workspace = true, features = ["process"] } [target.'cfg(windows)'.dependencies] From 4388e1ebb4d5f470e43f66258c9404f2c44fe3f8 Mon Sep 17 00:00:00 2001 From: Antony David Date: Mon, 30 Sep 2024 09:37:23 +0200 Subject: [PATCH 06/32] fix(bench): typo in `.prettierignore` (#4134) --- benchmark/.prettierignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/.prettierignore b/benchmark/.prettierignore index 617a767169c3..35b3f3fe880d 100644 --- a/benchmark/.prettierignore +++ b/benchmark/.prettierignore @@ -1,4 +1,4 @@ **/*.json **/*.css **/*.md -**/*.htnl +**/*.html From f7831309033c2915bc23c8d5c55b345737448042 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 30 Sep 2024 15:58:31 -0500 Subject: [PATCH 07/32] Resolve conflicts --- crates/biome_grit_formatter/src/cst.rs | 23 +++++++++- .../src/grit/auxiliary/root.rs | 26 +++++++++-- .../biome_grit_formatter/tests/quick_test.rs | 44 +++++++++++++++++++ .../tests/specs/grit/file_node.grit.snap | 3 +- 4 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 crates/biome_grit_formatter/tests/quick_test.rs diff --git a/crates/biome_grit_formatter/src/cst.rs b/crates/biome_grit_formatter/src/cst.rs index 58595b25d5a5..bc077d4ce78e 100644 --- a/crates/biome_grit_formatter/src/cst.rs +++ b/crates/biome_grit_formatter/src/cst.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use biome_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatResult}; -use biome_grit_syntax::{map_syntax_node, GritSyntaxNode}; +use biome_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatResult, FormatToken}; +use biome_grit_syntax::{map_syntax_node, GritSyntaxNode, GritSyntaxToken}; #[derive(Debug, Copy, Clone, Default)] pub struct FormatGritSyntaxNode; @@ -28,3 +28,22 @@ impl IntoFormat for GritSyntaxNode { FormatOwnedWithRule::new(self, FormatGritSyntaxNode) } } + +/// Format implementation specific to GritQL tokens. +pub(crate) type FormatGritSyntaxToken = FormatToken; + +impl AsFormat for GritSyntaxToken { + type Format<'a> = FormatRefWithRule<'a, GritSyntaxToken, FormatGritSyntaxToken>; + + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, FormatGritSyntaxToken::default()) + } +} + +impl IntoFormat for GritSyntaxToken { + type Format = FormatOwnedWithRule; + + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, FormatGritSyntaxToken::default()) + } +} diff --git a/crates/biome_grit_formatter/src/grit/auxiliary/root.rs b/crates/biome_grit_formatter/src/grit/auxiliary/root.rs index cbcded68cb44..9b29028b703b 100644 --- a/crates/biome_grit_formatter/src/grit/auxiliary/root.rs +++ b/crates/biome_grit_formatter/src/grit/auxiliary/root.rs @@ -1,10 +1,30 @@ use crate::prelude::*; -use biome_grit_syntax::GritRoot; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_grit_syntax::{GritRoot, GritRootFields}; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatGritRoot; + impl FormatNodeRule for FormatGritRoot { fn fmt_fields(&self, node: &GritRoot, f: &mut GritFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let GritRootFields { + bom_token, + version, + language, + definitions, + eof_token, + } = node.as_fields(); + + write!( + f, + [ + bom_token.format(), + version.format(), + language.format(), + definitions.format(), + hard_line_break(), + format_removed(&eof_token?), + ] + ) } } diff --git a/crates/biome_grit_formatter/tests/quick_test.rs b/crates/biome_grit_formatter/tests/quick_test.rs new file mode 100644 index 000000000000..d8605439ac83 --- /dev/null +++ b/crates/biome_grit_formatter/tests/quick_test.rs @@ -0,0 +1,44 @@ +use biome_formatter::{IndentStyle, LineWidth, QuoteStyle}; +use biome_formatter_test::check_reformat::CheckReformat; +use biome_grit_formatter::context::GritFormatOptions; +use biome_grit_formatter::{format_node, GritFormatLanguage}; +use biome_grit_parser::parse_grit; + +mod language { + include!("language.rs"); +} + +#[ignore] +#[test] +// use this test check if your snippet prints as you wish, without using a snapshot +fn quick_test() { + let src = r#" +`$method('$message')` where { + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +} +"#; + let tree = parse_grit(src); + let options = GritFormatOptions::new() + .with_indent_style(IndentStyle::Space) + .with_line_width(LineWidth::try_from(80).unwrap()) + .with_quote_style(QuoteStyle::Double); + + let doc = format_node(options.clone(), &tree.syntax()).unwrap(); + let result = doc.print().unwrap(); + + println!("{}", doc.into_document()); + eprintln!("{}", result.as_code()); + + CheckReformat::new( + &tree.syntax(), + result.as_code(), + "testing", + &language::GritTestFormatLanguage, + GritFormatLanguage::new(options), + ) + .check_reformat(); +} diff --git a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap index 4e1d3f1d4450..6256b4dc3f40 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap @@ -44,7 +44,8 @@ Attribute Position: Auto ----- ```grit -file(body = contains `console.$method` => `println`)``` +file(body = contains `console.$method` => `println`) +``` From 9b1757e9ce2682933b256391396292aca22b0609 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 30 Sep 2024 15:58:59 -0500 Subject: [PATCH 08/32] Update formatting for definition list --- .../src/grit/lists/definition_list.rs | 12 +++++++++++- .../src/grit/patterns/pattern_where.rs | 19 ++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs index 78a127a982c1..b71cc3f19418 100644 --- a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs @@ -5,6 +5,16 @@ pub(crate) struct FormatGritDefinitionList; impl FormatRule for FormatGritDefinitionList { type Context = GritFormatContext; fn fmt(&self, node: &GritDefinitionList, f: &mut GritFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let mut join = f.join_nodes_with_hardline(); + + for definition in node { + let def_clone = definition.clone().unwrap(); + join.entry( + definition?.syntax(), + &format_or_verbatim(def_clone.format()), + ); + } + + join.finish() } } diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index bbdfa34e0aec..f2f19502b638 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -1,10 +1,23 @@ use crate::prelude::*; -use biome_grit_syntax::GritPatternWhere; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_grit_syntax::{GritPatternWhere, GritPatternWhereFields}; + #[derive(Debug, Clone, Default)] pub(crate) struct FormatGritPatternWhere; impl FormatNodeRule for FormatGritPatternWhere { fn fmt_fields(&self, node: &GritPatternWhere, f: &mut GritFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let GritPatternWhereFields { + pattern, + side_condition, + where_token, + } = node.as_fields(); + write!( + f, + [ + pattern.format(), + side_condition.format(), + where_token.format() + ] + ) } } From 45cb896400bd52b566e63b8e7ee7064e39bafc64 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:11:33 +0200 Subject: [PATCH 09/32] chore(deps): update pnpm to v9.11.0 (#4132) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70f21cd43c93..96fa0253293a 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,5 @@ "keywords": [], "author": "Biome Developers and Contributors", "license": "MIT OR Apache-2.0", - "packageManager": "pnpm@9.9.0" + "packageManager": "pnpm@9.11.0" } From 00370400756f873d6c241c637f6c3ef100790f86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:13:45 +0200 Subject: [PATCH 10/32] chore(deps): update dependency eslint to v9.11.1 (#4130) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- benchmark/package.json | 2 +- pnpm-lock.yaml | 77 ++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 8dbf91a2ba6a..0944ab2c74a3 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "8.5.0", "dprint": "0.47.2", - "eslint": "9.10.0", + "eslint": "9.11.1", "prettier": "3.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 794bce3e5dea..c40f4dd8410a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,13 +12,13 @@ importers: devDependencies: '@typescript-eslint/eslint-plugin': specifier: 8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2) + version: 8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) dprint: specifier: 0.47.2 version: 0.47.2 eslint: - specifier: 9.10.0 - version: 9.10.0(jiti@1.21.0) + specifier: 9.11.1 + version: 9.11.1(jiti@1.21.0) prettier: specifier: 3.3.3 version: 3.3.3 @@ -123,20 +123,24 @@ packages: resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.6.0': + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.10.0': - resolution: {integrity: sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==} + '@eslint/js@9.11.1': + resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.1.0': - resolution: {integrity: sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==} + '@eslint/plugin-kit@0.2.0': + resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@humanwhocodes/module-importer@1.0.1': @@ -188,6 +192,12 @@ packages: '@types/bun@1.1.10': resolution: {integrity: sha512-76KYVSwrHwr9zsnk6oLXOGs9KvyBg3U066GLO4rk6JZk1ypEPGCUDZ5yOiESyIHWs9cx9iC8r01utYN32XdmgA==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@20.12.12': resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} @@ -448,8 +458,8 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.10.0: - resolution: {integrity: sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==} + eslint@9.11.1: + resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1063,9 +1073,9 @@ snapshots: '@dprint/win32-x64@0.47.2': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.10.0(jiti@1.21.0))': + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.1(jiti@1.21.0))': dependencies: - eslint: 9.10.0(jiti@1.21.0) + eslint: 9.11.1(jiti@1.21.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.0': {} @@ -1078,6 +1088,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/core@0.6.0': {} + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 @@ -1092,11 +1104,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.10.0': {} + '@eslint/js@9.11.1': {} '@eslint/object-schema@2.1.4': {} - '@eslint/plugin-kit@0.1.0': + '@eslint/plugin-kit@0.2.0': dependencies: levn: 0.4.1 @@ -1149,6 +1161,10 @@ snapshots: dependencies: bun-types: 1.1.29 + '@types/estree@1.0.6': {} + + '@types/json-schema@7.0.15': {} + '@types/node@20.12.12': dependencies: undici-types: 5.26.5 @@ -1163,15 +1179,15 @@ snapshots: dependencies: '@types/node': 20.14.10 - '@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2))(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 8.3.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/parser': 8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/type-utils': 8.5.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/type-utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) '@typescript-eslint/visitor-keys': 8.5.0 - eslint: 9.10.0(jiti@1.21.0) + eslint: 9.11.1(jiti@1.21.0) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -1181,14 +1197,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.3.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@typescript-eslint/scope-manager': 8.3.0 '@typescript-eslint/types': 8.3.0 '@typescript-eslint/typescript-estree': 8.3.0(typescript@5.6.2) '@typescript-eslint/visitor-keys': 8.3.0 debug: 4.3.6 - eslint: 9.10.0(jiti@1.21.0) + eslint: 9.11.1(jiti@1.21.0) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -1204,10 +1220,10 @@ snapshots: '@typescript-eslint/types': 8.5.0 '@typescript-eslint/visitor-keys': 8.5.0 - '@typescript-eslint/type-utils@8.5.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: @@ -1250,13 +1266,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.5.0(eslint@9.10.0(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/utils@8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0(jiti@1.21.0)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.0)) '@typescript-eslint/scope-manager': 8.5.0 '@typescript-eslint/types': 8.5.0 '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - eslint: 9.10.0(jiti@1.21.0) + eslint: 9.11.1(jiti@1.21.0) transitivePeerDependencies: - supports-color - typescript @@ -1424,17 +1440,20 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint@9.10.0(jiti@1.21.0): + eslint@9.11.1(jiti@1.21.0): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.10.0(jiti@1.21.0)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.0)) '@eslint-community/regexpp': 4.11.0 '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.10.0 - '@eslint/plugin-kit': 0.1.0 + '@eslint/js': 9.11.1 + '@eslint/plugin-kit': 0.2.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 From ab75e3dfa397c0ca249142edb95bd6017fa58c3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:15:17 +0200 Subject: [PATCH 11/32] chore(deps): update rust crate oxc_resolver to 1.12.0 (#4133) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16f48224d7cf..aa2bee91a455 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2649,9 +2649,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "oxc_resolver" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fe4d07afdfcf6b1d7fb952e6691d82692a54b71964a377cf49f3e47dac283d" +checksum = "6c20bb345f290c46058ba650fef7ca2b579612cf2786b927ebad7b8bec0845a7" dependencies = [ "cfg-if", "dashmap 6.1.0", diff --git a/Cargo.toml b/Cargo.toml index ff68ec4725f7..7d3a99c4d194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,7 +182,7 @@ ignore = "0.4.23" indexmap = { version = "2.5.0", features = ["serde"] } insta = "1.40.0" natord = "1.0.9" -oxc_resolver = "1.11.0" +oxc_resolver = "1.12.0" proc-macro2 = "1.0.86" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" From 0e853fce5e033f65895ee8806c9f69d52313aa60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:15:36 +0200 Subject: [PATCH 12/32] chore(deps): update actions/checkout action to v4.2.0 (#4127) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/bench_cli.yml | 10 +++++----- .github/workflows/benchmark.yml | 2 +- .github/workflows/main.yml | 12 ++++++------ .github/workflows/parser_conformance.yml | 6 +++--- .github/workflows/prepare_release.yml | 2 +- .github/workflows/pull_request.yml | 14 +++++++------- .github/workflows/pull_request_js.yml | 2 +- .github/workflows/release_cli.yml | 8 ++++---- .github/workflows/release_js_api.yml | 6 +++--- .github/workflows/release_knope.yml | 12 ++++++------ .github/workflows/repository_dispatch.yml | 2 +- 11 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/workflows/bench_cli.yml b/.github/workflows/bench_cli.yml index 7591f406e6e6..eb31ae002fe6 100644 --- a/.github/workflows/bench_cli.yml +++ b/.github/workflows/bench_cli.yml @@ -29,7 +29,7 @@ jobs: return response.data.head.sha; - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: ${{ steps.sha.outputs.result }} @@ -52,7 +52,7 @@ jobs: cp target/release/biome benchmark/target/biome_pr - name: Checkout Main Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: clean: false ref: main @@ -63,17 +63,17 @@ jobs: cp target/release/biome benchmark/target/biome_main - name: Checkout webpack - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: webpack/webpack path: benchmark/target/webpack - name: Checkout prettier - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: prettier/prettier path: benchmark/target/prettier - name: Checkout eslint - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: repository: eslint/eslint path: benchmark/target/eslint diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 59fa4af12250..977695e0dc09 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: ref: ${{ steps.sha.outputs.result }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c09ffc4b5fa..84369fdc8042 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Support longpaths run: git config core.longpaths true - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -91,7 +91,7 @@ jobs: - os: macos-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -116,7 +116,7 @@ jobs: if: matrix.os == 'windows-latest' run: git config --system core.longpaths true - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: submodules: recursive - name: Free Disk Space diff --git a/.github/workflows/parser_conformance.yml b/.github/workflows/parser_conformance.yml index 8257b0d54aaf..1d1d13bbdab7 100644 --- a/.github/workflows/parser_conformance.yml +++ b/.github/workflows/parser_conformance.yml @@ -25,13 +25,13 @@ jobs: steps: - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Support longpaths run: git config core.longpaths true - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: submodules: recursive @@ -54,7 +54,7 @@ jobs: run: cargo coverage --json > new_results.json - name: Checkout main Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: clean: false ref: main diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml index f475abe8f5d2..1e3f5f54998d 100644 --- a/.github/workflows/prepare_release.yml +++ b/.github/workflows/prepare_release.yml @@ -13,7 +13,7 @@ jobs: if: "!contains(github.event.head_commit.message, 'chore: prepare release')" # Skip merges from releases runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: fetch-depth: 0 - name: Configure Git diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 4ceed51424b5..d36dc709ef94 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -50,7 +50,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR Branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -90,7 +90,7 @@ jobs: - os: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -107,7 +107,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -153,7 +153,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -170,7 +170,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain diff --git a/.github/workflows/pull_request_js.yml b/.github/workflows/pull_request_js.yml index 0ea1f3e2a521..995a6205637e 100644 --- a/.github/workflows/pull_request_js.yml +++ b/.github/workflows/pull_request_js.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Cache pnpm modules diff --git a/.github/workflows/release_cli.yml b/.github/workflows/release_cli.yml index 121999d4e483..c357fe78d720 100644 --- a/.github/workflows/release_cli.yml +++ b/.github/workflows/release_cli.yml @@ -19,7 +19,7 @@ jobs: nightly: ${{ env.nightly }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Check nightly status id: nightly @@ -88,7 +88,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install Node.js uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -156,7 +156,7 @@ jobs: needs: check steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh @@ -189,7 +189,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download CLI artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 diff --git a/.github/workflows/release_js_api.yml b/.github/workflows/release_js_api.yml index ef9fa1fc4bc9..b9d33e1cefb6 100644 --- a/.github/workflows/release_js_api.yml +++ b/.github/workflows/release_js_api.yml @@ -19,7 +19,7 @@ jobs: nightly: ${{ env.nightly }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Check nightly status id: nightly @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install Node.js uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -118,7 +118,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download package artifact uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 diff --git a/.github/workflows/release_knope.yml b/.github/workflows/release_knope.yml index 82b8250a50a7..05cdd44840d1 100644 --- a/.github/workflows/release_knope.yml +++ b/.github/workflows/release_knope.yml @@ -15,7 +15,7 @@ jobs: version: ${{ env.version }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Check version changes uses: EndBug/version-check@d4be4219408b50d1bbbfd350a47cbcb126878692 # v2.1.4 @@ -69,7 +69,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install Node.js uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 @@ -143,7 +143,7 @@ jobs: needs: retrieve-version steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh @@ -176,7 +176,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download CLI artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -207,7 +207,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download CLI artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -241,7 +241,7 @@ jobs: needs: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install toolchain uses: moonrepo/setup-rust@e013866c4215f77c925f42f60257dec7dd18836e # v1.2.1 with: diff --git a/.github/workflows/repository_dispatch.yml b/.github/workflows/repository_dispatch.yml index 1048579c7127..333eb8ee3f9f 100644 --- a/.github/workflows/repository_dispatch.yml +++ b/.github/workflows/repository_dispatch.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Warm up wasm-pack cache id: cache-restore From 5dea2f50204741a04016f18b1c2838ba16dfe9ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:25:43 +0200 Subject: [PATCH 13/32] chore(deps): update rust:1.81.0 docker digest to a21d540 (#4123) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile.benchmark | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.benchmark b/Dockerfile.benchmark index 88b35de0b110..553ebcf2832b 100644 --- a/Dockerfile.benchmark +++ b/Dockerfile.benchmark @@ -1,4 +1,4 @@ -FROM rust:1.81.0@sha256:fcd390e0a3a6bfcf26969861efbe7b864df052aa71a361cf3cd7c5c585b1b413 +FROM rust:1.81.0@sha256:a21d54019c66e3a1e7512651e9a7de99b08f28d49b023ed7220b7fe4d3b9f24e WORKDIR /usr/src/ # https://github.com/nodesource/distributions From af988a2ce690f4326ce9183a76eeed6f02dfb819 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:26:00 +0200 Subject: [PATCH 14/32] chore(deps): update dependency @typescript-eslint/eslint-plugin to v8.7.0 (#4129) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- benchmark/package.json | 2 +- pnpm-lock.yaml | 74 +++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 0944ab2c74a3..db8ddaf88ff5 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -11,7 +11,7 @@ "node": ">20.0.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "8.5.0", + "@typescript-eslint/eslint-plugin": "8.7.0", "dprint": "0.47.2", "eslint": "9.11.1", "prettier": "3.3.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c40f4dd8410a..defb57e98554 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,8 +11,8 @@ importers: benchmark: devDependencies: '@typescript-eslint/eslint-plugin': - specifier: 8.5.0 - version: 8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) + specifier: 8.7.0 + version: 8.7.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) dprint: specifier: 0.47.2 version: 0.47.2 @@ -210,8 +210,8 @@ packages: '@types/ws@8.5.10': resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} - '@typescript-eslint/eslint-plugin@8.5.0': - resolution: {integrity: sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==} + '@typescript-eslint/eslint-plugin@8.7.0': + resolution: {integrity: sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -235,12 +235,12 @@ packages: resolution: {integrity: sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.5.0': - resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==} + '@typescript-eslint/scope-manager@8.7.0': + resolution: {integrity: sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.5.0': - resolution: {integrity: sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==} + '@typescript-eslint/type-utils@8.7.0': + resolution: {integrity: sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -252,8 +252,8 @@ packages: resolution: {integrity: sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.5.0': - resolution: {integrity: sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==} + '@typescript-eslint/types@8.7.0': + resolution: {integrity: sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.3.0': @@ -265,8 +265,8 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.5.0': - resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==} + '@typescript-eslint/typescript-estree@8.7.0': + resolution: {integrity: sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -274,8 +274,8 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.5.0': - resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==} + '@typescript-eslint/utils@8.7.0': + resolution: {integrity: sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -284,8 +284,8 @@ packages: resolution: {integrity: sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.5.0': - resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==} + '@typescript-eslint/visitor-keys@8.7.0': + resolution: {integrity: sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -1179,14 +1179,14 @@ snapshots: dependencies: '@types/node': 20.14.10 - '@typescript-eslint/eslint-plugin@8.5.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.0 '@typescript-eslint/parser': 8.3.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/type-utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.7.0 eslint: 9.11.1(jiti@1.21.0) graphemer: 1.4.0 ignore: 5.3.2 @@ -1215,15 +1215,15 @@ snapshots: '@typescript-eslint/types': 8.3.0 '@typescript-eslint/visitor-keys': 8.3.0 - '@typescript-eslint/scope-manager@8.5.0': + '@typescript-eslint/scope-manager@8.7.0': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 - '@typescript-eslint/type-utils@8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.7.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: @@ -1234,7 +1234,7 @@ snapshots: '@typescript-eslint/types@8.3.0': {} - '@typescript-eslint/types@8.5.0': {} + '@typescript-eslint/types@8.7.0': {} '@typescript-eslint/typescript-estree@8.3.0(typescript@5.6.2)': dependencies: @@ -1251,10 +1251,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.7.0(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 debug: 4.3.6 fast-glob: 3.3.2 is-glob: 4.0.3 @@ -1266,12 +1266,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.5.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': + '@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.0))(typescript@5.6.2)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.0)) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) eslint: 9.11.1(jiti@1.21.0) transitivePeerDependencies: - supports-color @@ -1282,9 +1282,9 @@ snapshots: '@typescript-eslint/types': 8.3.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@8.5.0': + '@typescript-eslint/visitor-keys@8.7.0': dependencies: - '@typescript-eslint/types': 8.5.0 + '@typescript-eslint/types': 8.7.0 eslint-visitor-keys: 3.4.3 acorn-jsx@5.3.2(acorn@8.12.1): From 2e0173ece8fca1a628b18b91c8c5e96af677b6b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:26:19 +0200 Subject: [PATCH 15/32] chore(deps): update @biomejs packages (#4124) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/@biomejs/backend-jsonrpc/package.json | 4 ++-- packages/@biomejs/js-api/package.json | 2 +- packages/aria-data/package.json | 2 +- packages/tailwindcss-config-analyzer/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/@biomejs/backend-jsonrpc/package.json b/packages/@biomejs/backend-jsonrpc/package.json index 0e09f93fa51b..a1e3d3327fe1 100644 --- a/packages/@biomejs/backend-jsonrpc/package.json +++ b/packages/@biomejs/backend-jsonrpc/package.json @@ -36,9 +36,9 @@ }, "license": "MIT OR Apache-2.0", "devDependencies": { - "@types/node": "20.16.5", + "@types/node": "20.16.10", "typescript": "5.6.2", - "vite": "5.4.7", + "vite": "5.4.8", "vitest": "1.6.0" }, "publishConfig": { diff --git a/packages/@biomejs/js-api/package.json b/packages/@biomejs/js-api/package.json index c9099db1751d..04927b025ce3 100644 --- a/packages/@biomejs/js-api/package.json +++ b/packages/@biomejs/js-api/package.json @@ -50,7 +50,7 @@ "@biomejs/wasm-nodejs": "link:../wasm-nodejs", "@biomejs/wasm-web": "link:../wasm-web", "typescript": "5.6.2", - "vite": "5.4.7", + "vite": "5.4.8", "vitest": "1.6.0" }, "peerDependencies": { diff --git a/packages/aria-data/package.json b/packages/aria-data/package.json index 75b8d177f05b..444141fabccd 100644 --- a/packages/aria-data/package.json +++ b/packages/aria-data/package.json @@ -6,7 +6,7 @@ "license": "MIT OR Apache-2.0", "author": "Victorien Elvinger", "engines": { - "node": ">=20.0.0" + "node": ">=20.17.0" }, "type": "module", "dependencies": { diff --git a/packages/tailwindcss-config-analyzer/package.json b/packages/tailwindcss-config-analyzer/package.json index ff445cd5d509..62e26b45fda5 100644 --- a/packages/tailwindcss-config-analyzer/package.json +++ b/packages/tailwindcss-config-analyzer/package.json @@ -7,7 +7,7 @@ "author": "Dani Guardiola", "type": "module", "dependencies": { - "tailwindcss": "^3.4.12" + "tailwindcss": "^3.4.13" }, "devDependencies": { "@types/bun": "1.1.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index defb57e98554..5eb3a9406307 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,8 +38,8 @@ importers: packages/tailwindcss-config-analyzer: dependencies: tailwindcss: - specifier: ^3.4.12 - version: 3.4.12 + specifier: ^3.4.13 + version: 3.4.13 devDependencies: '@types/bun': specifier: 1.1.10 @@ -938,8 +938,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@3.4.12: - resolution: {integrity: sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==} + tailwindcss@3.4.13: + resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==} engines: {node: '>=14.0.0'} hasBin: true @@ -1909,7 +1909,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.12: + tailwindcss@3.4.13: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 From aab92efca8a39021ecb21644f412542358f44da5 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Mon, 30 Sep 2024 13:37:05 +0200 Subject: [PATCH 16/32] fix(noMisleadingCharacterClass): properly handle escaping and improve diagnostics (#4112) Co-authored-by: Emanuele Stoppa --- CHANGELOG.md | 5 +- .../no_control_characters_in_regex.rs | 39 +- .../no_misleading_character_class.rs | 749 ++++++++++-------- .../noControlCharactersInRegex/valid.js | 2 + .../noControlCharactersInRegex/valid.js.snap | 2 + .../invalid.js.snap | 592 ++++++++------ .../noMisleadingCharacterClass/valid.js | 26 +- .../noMisleadingCharacterClass/valid.js.snap | 26 +- crates/biome_text_size/src/range.rs | 19 +- 9 files changed, 853 insertions(+), 607 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f70bbb2d78ce..10b3283d1cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,10 +78,13 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### New features - Add [noTemplateCurlyInString](https://biomejs.dev/linter/rules/no-template-curly-in-string/). Contributed by @fireairforce -- Add [NoOctalEscape](https://biomejs.dev/linter/rules/no-octal-escape/). Contributed by @fireairforce + +- Add [noOctalEscape](https://biomejs.dev/linter/rules/no-octal-escape/). Contributed by @fireairforce #### Bug fixes +- [noControlCharactersInRegex](https://www.biomejs.dev/linter/rules/no-control-characters-in-regex) no longer panics on regexes with incomplete escape sequences. Contributed by @Conaclos + - [noMisleadingCharacterClass](https://biomejs.dev/linter/rules/no-misleading-character-class/) no longer reports issues outside of character classes. The following code is no longer reported: diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs b/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs index dd80bb7f51e4..00403108a429 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_control_characters_in_regex.rs @@ -3,8 +3,8 @@ use biome_analyze::{ }; use biome_console::markup; use biome_js_syntax::{ - AnyJsExpression, JsCallArguments, JsCallExpression, JsNewExpression, JsRegexLiteralExpression, - JsStringLiteralExpression, + static_value::StaticValue, AnyJsExpression, JsCallArguments, JsCallExpression, JsNewExpression, + JsRegexLiteralExpression, }; use biome_rowan::{declare_node_union, AstNode, AstSeparatedList, TextRange, TextSize}; use core::str; @@ -69,7 +69,7 @@ declare_lint_rule! { } declare_node_union! { - pub RegexExpressionLike = JsNewExpression | JsCallExpression | JsRegexLiteralExpression + pub AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression } fn decode_hex(digits: &[u8]) -> Option { @@ -111,7 +111,7 @@ fn collect_control_characters( }; let hex_index = escaped_index + 1; match c { - b'x' => ( + b'x' if (hex_index + 2) <= bytes.len() => ( decode_hex(&bytes[hex_index..(hex_index + 2)]), hex_index + 2, ), @@ -129,7 +129,7 @@ fn collect_control_characters( ) } } - b'u' => ( + b'u' if (hex_index + 4) <= bytes.len() => ( decode_hex(&bytes[hex_index..(hex_index + 4)]), hex_index + 4, ), @@ -169,31 +169,32 @@ fn collect_control_characters_from_expression( .is_some_and(|name| name.has_name("RegExp")) { let mut args = js_call_arguments.args().iter(); - let Some(js_string_literal) = args + let Some(static_value) = args .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()) else { return Default::default(); }; - let Ok(pattern) = js_string_literal.inner_string_text() else { + let Some(pattern) = static_value.as_string_constant() else { return Default::default(); }; - let pattern_start = js_string_literal.range().start() + TextSize::from(1); + let pattern_start = static_value.range().start() + TextSize::from(1); let flags = args .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - collect_control_characters(pattern_start, &pattern, &flags, true).unwrap_or_default() + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()); + let flags = if let Some(StaticValue::String(flags)) = &flags { + flags.text() + } else { + "" + }; + collect_control_characters(pattern_start, pattern, flags, true).unwrap_or_default() } else { Vec::new() } } impl Rule for NoControlCharactersInRegex { - type Query = Ast; + type Query = Ast; type State = TextRange; type Signals = Vec; type Options = (); @@ -201,19 +202,19 @@ impl Rule for NoControlCharactersInRegex { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); match node { - RegexExpressionLike::JsNewExpression(new_expr) => { + AnyRegexExpression::JsNewExpression(new_expr) => { let (Ok(callee), Some(args)) = (new_expr.callee(), new_expr.arguments()) else { return Default::default(); }; collect_control_characters_from_expression(&callee, &args) } - RegexExpressionLike::JsCallExpression(call_expr) => { + AnyRegexExpression::JsCallExpression(call_expr) => { let (Ok(callee), Ok(args)) = (call_expr.callee(), call_expr.arguments()) else { return Default::default(); }; collect_control_characters_from_expression(&callee, &args) } - RegexExpressionLike::JsRegexLiteralExpression(regex_literal_expr) => { + AnyRegexExpression::JsRegexLiteralExpression(regex_literal_expr) => { let Ok((pattern, flags)) = regex_literal_expr.decompose() else { return Default::default(); }; diff --git a/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs b/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs index 6d3f45c629bb..63bb7430cf08 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/no_misleading_character_class.rs @@ -6,14 +6,15 @@ use biome_analyze::{ use biome_console::markup; use biome_js_factory::make; use biome_js_syntax::{ - global_identifier, AnyJsCallArgument, AnyJsExpression, AnyJsLiteralExpression, - AnyJsTemplateElement, JsCallArguments, JsCallExpression, JsNewExpression, - JsRegexLiteralExpression, JsStringLiteralExpression, JsSyntaxKind, JsSyntaxToken, T, + global_identifier, static_value::StaticValue, AnyJsCallArgument, AnyJsExpression, + AnyJsLiteralExpression, AnyJsTemplateElement, JsCallArguments, JsSyntaxKind, JsSyntaxToken, T, }; use biome_rowan::{ - declare_node_union, AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, - TriviaPieceKind, + AstNode, AstNodeList, AstSeparatedList, BatchMutationExt, TextRange, TriviaPieceKind, }; + +use super::no_control_characters_in_regex::AnyRegexExpression; + declare_lint_rule! { /// Disallow characters made with multiple code points in character class syntax. /// @@ -68,10 +69,6 @@ declare_lint_rule! { } } -declare_node_union! { - pub AnyRegexExpression = JsNewExpression | JsCallExpression | JsRegexLiteralExpression -} - pub enum Message { SurrogatePairWithoutUFlag, EmojiModifier, @@ -81,17 +78,31 @@ pub enum Message { } impl Message { - fn as_str(&self) -> &str { + fn diagnostic(&self) -> &str { match self { - Self::CombiningClassOrVs16 => "Unexpected combined character in the character class.", + Self::CombiningClassOrVs16 => "A character class cannot match a character and a combining character.", Self::SurrogatePairWithoutUFlag => { - "Unexpected surrogate pair in character class. Use the 'u' flag." + "A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them." } - Self::EmojiModifier => "Unexpected modified Emoji in the character class. ", + Self::EmojiModifier => "A character class cannot match an emoji with a skin tone modifier.", Self::RegionalIndicatorSymbol => { - "Regional indicator symbol characters should not be used in the character class." + "A character class cannot match a pair of regional indicator symbols." } - Self::JoinedCharSequence => "Unexpected joined character sequence in character class.", + Self::JoinedCharSequence => "A character class cannot match a joined character sequence.", + } + } + + fn note(&self) -> &str { + match self { + Self::CombiningClassOrVs16 => "A character and a combining character forms a new character. Replace the character class with an alternation.", + Self::SurrogatePairWithoutUFlag => { + "A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character." + } + Self::EmojiModifier => "Replace the character class with an alternation.", + Self::RegionalIndicatorSymbol => { + "A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation." + } + Self::JoinedCharSequence => "A zero width joiner composes several emojis into a new one. Replace the character class with an alternation.", } } } @@ -109,70 +120,52 @@ impl Rule for NoMisleadingCharacterClass { fn run(ctx: &RuleContext) -> Self::Signals { let regex = ctx.query(); - match regex { + let (callee, arguments) = match regex { AnyRegexExpression::JsRegexLiteralExpression(expr) => { - let Ok((pattern, flags)) = expr.decompose() else { - return None; - }; - let regex_pattern = replace_escaped_unicode(pattern.text()); - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, flags.text(), range); - } - - AnyRegexExpression::JsNewExpression(expr) => { - if is_regex_expr(expr.callee().ok()?)? { - let mut args = expr.arguments()?.args().iter(); - let raw_regex_pattern = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? - .to_string(); - - let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); - let regexp_flags = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, ®exp_flags, range); - } + let (pattern, flags) = expr.decompose().ok()?; + let RuleState { range, message } = + diagnostic_regex_pattern(pattern.text(), flags.text(), false)?; + return Some(RuleState { + range: range.checked_add(expr.range().start().checked_add(1.into())?)?, + message, + }); } + AnyRegexExpression::JsNewExpression(expr) => (expr.callee().ok()?, expr.arguments()?), AnyRegexExpression::JsCallExpression(expr) => { - if is_regex_expr(expr.callee().ok()?)? { - let mut args = expr.arguments().ok()?.args().iter(); - let raw_regex_pattern = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .and_then(|js_string_literal| js_string_literal.inner_string_text().ok())? - .to_string(); - - let regex_pattern = replace_escaped_unicode(raw_regex_pattern.as_str()); - - let regexp_flags = args - .next() - .and_then(|arg| arg.ok()) - .and_then(|arg| JsStringLiteralExpression::cast(arg.into_syntax())) - .map(|js_string_literal| js_string_literal.text()) - .unwrap_or_default(); - let range = expr.syntax().text_trimmed_range(); - return diagnostic_regex_pattern(®ex_pattern, ®exp_flags, range); - } + (expr.callee().ok()?, expr.arguments().ok()?) } + }; + if is_regex_expr(callee)? { + let mut args = arguments.args().iter(); + let pattern = args + .next()? + .ok()? + .as_any_js_expression()? + .as_static_value()?; + let pattern_range = pattern.range(); + let pattern = pattern.as_string_constant()?; + let flags = args + .next() + .and_then(|arg| arg.ok()?.as_any_js_expression()?.as_static_value()); + let flags = if let Some(StaticValue::String(flags)) = &flags { + flags.text() + } else { + "" + }; + let RuleState { range, message } = diagnostic_regex_pattern(pattern, flags, true)?; + return Some(RuleState { + range: range.checked_add(pattern_range.start().checked_add(1.into())?)?, + message, + }); } None } fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { - Some(RuleDiagnostic::new( - rule_category!(), - state.range, - state.message.as_str(), - )) + Some( + RuleDiagnostic::new(rule_category!(), state.range, state.message.diagnostic()) + .note(state.message.note()), + ) } fn action(ctx: &RuleContext, state: &Self::State) -> Option { @@ -189,10 +182,8 @@ impl Rule for NoMisleadingCharacterClass { [], [], ); - let mut mutation = ctx.root().begin(); mutation.replace_token(prev_token, next_token); - Some(JsRuleAction::new( ActionCategory::QuickFix, ctx.metadata().applicability(), @@ -201,14 +192,11 @@ impl Rule for NoMisleadingCharacterClass { mutation, )) } - AnyRegexExpression::JsNewExpression(expr) => { let prev_node = expr.arguments()?; let mut prev_args = prev_node.args().iter(); - let regex_pattern = prev_args.next().and_then(|a| a.ok())?; let flag = prev_args.next().and_then(|a| a.ok()); - match make_suggestion(regex_pattern, flag) { Some(suggest) => { let mut mutation = ctx.root().begin(); @@ -224,14 +212,11 @@ impl Rule for NoMisleadingCharacterClass { None => None, } } - AnyRegexExpression::JsCallExpression(expr) => { let prev_node = expr.arguments().ok()?; let mut prev_args = expr.arguments().ok()?.args().iter(); - let regex_pattern = prev_args.next().and_then(|a| a.ok())?; let flag = prev_args.next().and_then(|a| a.ok()); - match make_suggestion(regex_pattern, flag) { Some(suggest) => { let mut mutation = ctx.root().begin(); @@ -274,7 +259,7 @@ fn is_regex_expr(expr: AnyJsExpression) -> Option { fn diagnostic_regex_pattern( regex_pattern: &str, flags: &str, - range: TextRange, + is_in_string: bool, ) -> Option { if flags.contains('v') { return None; @@ -294,10 +279,13 @@ fn diagnostic_regex_pattern( } b']' => { let char_class = ®ex_pattern[i + 1..j]; - if let Some(diag) = - diagnostic_regex_class(char_class, has_u_flag, range) + if let Some(RuleState { range, message }) = + diagnostic_regex_class(char_class, has_u_flag, is_in_string) { - return Some(diag); + return Some(RuleState { + range: range.checked_add(((i + 1) as u32).into())?, + message, + }); } break; } @@ -311,57 +299,229 @@ fn diagnostic_regex_pattern( None } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum CharType { + CombiningOrVariationSelectorS16, + EmojiModifier, + None, + RegionalIndicator, + Regular, + ZeroWidthJoiner, +} + fn diagnostic_regex_class( char_class: &str, has_u_flag: bool, - range: TextRange, + is_in_string: bool, ) -> Option { - // FIXME: escape handling should be done in th entire class, not only at the start. - let char_class = if char_class.as_bytes().first() == Some(&b'\\') { - let start = 1 + char_class[1..].chars().next()?.len_utf8(); - if start >= char_class.len() { - return None; + let mut prev_char_index = 0; + let mut prev_char_type = CharType::None; + let mut iter = char_class.char_indices(); + while let Some((i, c)) = iter.next() { + let (codepoint, end) = if c == '\\' { + // Maybe unicode esccapes \u{XXX} \uXXXX + let Some((codepoint, len)) = decode_next_codepoint(&char_class[i..], is_in_string) + else { + prev_char_index = i; + continue; + }; + for _ in c.len_utf8()..len { + iter.next(); + } + (codepoint, i + len) + } else { + (c as u32, i + c.len_utf8()) + }; + match codepoint { + // Non-BMP characters are encoded as surrogate pairs in UTF-16 / UCS-2 + 0x10000.. if !has_u_flag => { + return Some(RuleState { + range: TextRange::new((i as u32).into(), (end as u32).into()), + message: Message::SurrogatePairWithoutUFlag, + }); + } + // Combining Diacritical Marks + 0x0300..=0x036F + // Mongolian Free Variation Selector (FVS1 to FVS4) + | 0x180B..=0x180D | 0x180F + // Combining Diacritical Marks Extended + | 0x1AB0..=0x1AFF + // Combining Diacritical Marks Supplement + | 0x1DC0..=0x1DFF + // Combining Diacritical Marks for Symbols + | 0x20D0..=0x20FF + // Variation Selector (VS1 to VS16) + | 0xFE00..=0xFE0F + // Combining Half Marks + | 0xFE20..=0xFE2F + // Variation Selectors Supplement (VS17 to VS256) + | 0xE0100..=0xE01EF => { + if prev_char_type == CharType::Regular { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::CombiningClassOrVs16, + }); + } + prev_char_type = CharType::CombiningOrVariationSelectorS16; + } + // Regional indicator + 0x1F1E6..=0x1F1FF => { + if matches!(prev_char_type, CharType::RegionalIndicator) { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::RegionalIndicatorSymbol, + }); + } + prev_char_type = CharType::RegionalIndicator; + } + // Emoji skin modifier + 0x1F3FB..=0x1F3FF => { + if prev_char_type == CharType::Regular { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), (end as u32).into()), + message: Message::EmojiModifier, + }); + } + prev_char_type = CharType::EmojiModifier; + } + // Zero Width Joiner (used to combine emoji) + 0x200D => { + if + !matches!(prev_char_type, CharType::None | CharType::ZeroWidthJoiner) + && end < char_class.len() + { + if let Some((c, len)) = decode_next_codepoint(&char_class[end..], is_in_string) { + if c != 0x200D { + return Some(RuleState { + range: TextRange::new((prev_char_index as u32).into(), ((end + len) as u32).into()), + message: Message::JoinedCharSequence, + }); + } + } + } + prev_char_type = CharType::ZeroWidthJoiner; + } + _ => { + prev_char_type = CharType::Regular; + } } - &char_class[start..] - } else { - char_class - }; - - if !has_u_flag && has_surrogate_pair(char_class) { - return Some(RuleState { - range, - message: Message::SurrogatePairWithoutUFlag, - }); + prev_char_index = i; } + None +} - if has_combining_class_or_vs16(char_class) { - return Some(RuleState { - range, - message: Message::CombiningClassOrVs16, - }); - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum UnicodeEscapeKind { + // "\u{XXX}" + // "\uXXXX" + String, + // /\u{XXX}/ and "\\u{XXX}" + RegexBraced, + // /\uXXX/ and "\\uXXX" + RegexPlain, +} - if has_regional_indicator_symbol(char_class) { - return Some(RuleState { - range, - message: Message::RegionalIndicatorSymbol, - }); - } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct UnicodeEscape { + codepoint: u32, + kind: UnicodeEscapeKind, + len: usize, +} - if has_emoji_modifier(char_class) { - return Some(RuleState { - range, - message: Message::EmojiModifier, - }); +/// Convert unicode escape sequence string to unicode character +/// - unicode escape sequences: \u{XXXX} +/// - unicode escape sequences without parenthesis: \uXXXX +/// - surrogate pair: \uXXXX\uXXXX +/// If the unicode escape sequence is not valid, it will be treated as a simple string. +/// +/// ```example +/// \uD83D\uDC4D -> 👍 +/// \u0041\u0301 -> Á +/// \uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66 -> 👨‍👩‍👦 +/// \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466} -> 👨‍👩‍👦 +/// \u899\uD83D\uDC4D -> \u899👍 +/// ```` +fn decode_next_codepoint(char_class: &str, is_in_string: bool) -> Option<(u32, usize)> { + let c = char_class.chars().next()?; + // `\u{XXX}` + // `\uXXXX` + let Some(UnicodeEscape { + kind, + codepoint, + len, + }) = decode_unicode_escape_sequence(char_class, is_in_string) + else { + // Ignore the escape sequence + return Some((c as u32, c.len_utf8())); + }; + if kind != UnicodeEscapeKind::RegexBraced + && matches!(codepoint, 0xD800..=0xDBFF) + && len <= char_class.len() + { + if let Some(UnicodeEscape { + kind: low_kind, + codepoint: low_codepoint, + len: low_len, + }) = decode_unicode_escape_sequence(&char_class[len..], is_in_string) + { + let (final_codepoint, final_len) = if kind == low_kind + && matches!(low_codepoint, 0xDC00..=0xDFFF) + { + let surrogate = ((codepoint - 0xD800) << 10) + (low_codepoint - 0xDC00) + 0x10000; + (surrogate, len + low_len) + } else { + (codepoint, len) + }; + Some((final_codepoint, final_len)) + } else { + Some((codepoint, len)) + } + } else { + Some((codepoint, len)) } +} - if zwj(char_class) { - return Some(RuleState { - range, - message: Message::JoinedCharSequence, - }); +fn decode_unicode_escape_sequence(s: &str, is_in_string: bool) -> Option { + let bytes = s.as_bytes(); + if bytes.len() < 5 || bytes[0] != b'\\' { + return None; + } + let (offset, is_regex_escape) = if is_in_string && bytes[1] == b'\\' { + (1, true) + } else { + (0, !is_in_string) + }; + if bytes[offset + 1] != b'u' { + return None; + } + if bytes[offset + 2] == b'{' { + let (end, _) = bytes + .iter() + .enumerate() + .skip(offset + 3) + .find(|(_, &c)| c == b'}')?; + Some(UnicodeEscape { + codepoint: u32::from_str_radix(&s[offset + 3..end], 16).ok()?, + kind: if is_regex_escape { + UnicodeEscapeKind::RegexBraced + } else { + UnicodeEscapeKind::String + }, + len: end + 1, + }) + } else if (offset + 6) <= bytes.len() { + Some(UnicodeEscape { + codepoint: u32::from_str_radix(&s[offset + 2..offset + 6], 16).ok()?, + kind: if is_regex_escape { + UnicodeEscapeKind::RegexPlain + } else { + UnicodeEscapeKind::String + }, + len: offset + 6, + }) + } else { + None } - None } fn make_suggestion( @@ -429,7 +589,6 @@ fn make_suggestion( AnyJsCallArgument::JsSpread(_) => None, }, }; - suggestion.map(|s| { make::js_call_arguments( make::token(T!['(']), @@ -442,233 +601,159 @@ fn make_suggestion( }) } -fn is_emoji_modifier(c: char) -> bool { - (0x1F3FB..=0x1F3FF).contains(&(c as u32)) -} - -fn has_emoji_modifier(chars: &str) -> bool { - let mut prev_is_emoji_modifier = true; - for c in chars.chars() { - if is_emoji_modifier(c) { - if !prev_is_emoji_modifier { - return true; - } - prev_is_emoji_modifier = true; - } else { - prev_is_emoji_modifier = false; - } - } - false -} - -fn is_regional_indicator_symbol(c: char) -> bool { - (0x1F1E6..=0x1F1FF).contains(&(c as u32)) -} - -fn has_regional_indicator_symbol(chars: &str) -> bool { - let mut iter = chars.chars(); - while let Some(c) = iter.next() { - if is_regional_indicator_symbol(c) { - if let Some(c) = iter.next() { - if is_regional_indicator_symbol(c) { - return true; - } - } - } - } - false -} - -fn is_combining_character(ch: char) -> bool { - match ch { - '\u{0300}'..='\u{036F}' | // Combining Diacritical Marks - '\u{1AB0}'..='\u{1AFF}' | // Combining Diacritical Marks Extended - '\u{1DC0}'..='\u{1DFF}' | // Combining Diacritical Marks Supplement - '\u{20D0}'..='\u{20FF}' | // Combining Diacritical Marks for Symbols - '\u{FE20}'..='\u{FE2F}' // Combining Half Marks - => true, - _ => false - } -} - -fn is_variation_selector_16(ch: char) -> bool { - ('\u{FE00}'..='\u{FE0F}').contains(&ch) -} - -fn has_combining_class_or_vs16(chars: &str) -> bool { - chars.chars().enumerate().any(|(i, c)| { - i != 0 - && (is_combining_character(c) || is_variation_selector_16(c)) - // SAFETY: index `i - 1` is not equal to zero. - && !(is_combining_character(chars.chars().nth(i - 1).unwrap()) - // SAFETY: index `i - 1` is not equal to zero. - || is_variation_selector_16(chars.chars().nth(i - 1).unwrap())) - }) -} - -fn zwj(chars: &str) -> bool { - let char_vec: Vec = chars.chars().collect(); - let last_index = char_vec.len() - 1; - char_vec.iter().enumerate().any(|(i, &c)| { - i != 0 - && i != last_index - && c as u32 == 0x200D - && char_vec[i - 1] as u32 != 0x200D - && char_vec[i + 1] as u32 != 0x200D - }) -} - -fn has_surrogate_pair(s: &str) -> bool { - s.chars().any(|c| c as u32 > 0xFFFF) -} +#[cfg(test)] +mod tests { + use super::*; -/// Convert unicode escape sequence string to unicode character -/// - unicode escape sequences: \u{XXXX} -/// - unicode escape sequences without parenthesis: \uXXXX -/// - surrogate pair: \uXXXX\uXXXX -/// If the unicode escape sequence is not valid, it will be treated as a simple string. -/// -/// ```example -/// \uD83D\uDC4D -> 👍 -/// \u0041\u0301 -> Á -/// \uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66 -> 👨‍👩‍👦 -/// \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466} -> 👨‍👩‍👦 -/// \u899\uD83D\uDC4D -> \u899👍 -/// ```` -fn replace_escaped_unicode(input: &str) -> String { - let mut result = String::new(); - let mut chars_iter = input.chars().peekable(); + #[test] + fn test_decode_unicode_escape_sequence() { + assert_eq!(decode_unicode_escape_sequence(r"", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\", true), None); + assert_eq!(decode_unicode_escape_sequence(r"\n", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\uZ", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{}", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\u{Z}", false), None); + assert_eq!(decode_unicode_escape_sequence(r"\\u{31}", false), None); - while let Some(ch) = chars_iter.next() { - if ch == '\\' { - match handle_escape_sequence(&mut chars_iter) { - Some(unicode_char) => result.push_str(&unicode_char), - None => result.push(ch), - } - } else { - result.push(ch); - } - } - result -} + assert_eq!( + decode_unicode_escape_sequence(r"\u0031 test", false), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexPlain, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\u0031 test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::String, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\\u0031 test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexPlain, + len: 7 + }) + ); -fn handle_escape_sequence(chars_iter: &mut std::iter::Peekable) -> Option { - if chars_iter.peek() != Some(&'u') { - return None; - } - chars_iter.next(); + assert_eq!( + decode_unicode_escape_sequence(r"\u{31} test", false), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexBraced, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\u{31} test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::String, + len: 6 + }) + ); + assert_eq!( + decode_unicode_escape_sequence(r"\\u{31} test", true), + Some(UnicodeEscape { + codepoint: 0x31, + kind: UnicodeEscapeKind::RegexBraced, + len: 7 + }) + ); - if chars_iter.peek() == Some(&'{') { - handle_braced_escape_sequence(chars_iter) - } else { - handle_simple_or_surrogate_escape_sequence(chars_iter) + assert_eq!( + decode_unicode_escape_sequence(r"\u{1} test", false), + Some(UnicodeEscape { + codepoint: 1, + kind: UnicodeEscapeKind::RegexBraced, + len: 5 + }) + ); } -} -fn handle_braced_escape_sequence( - chars_iter: &mut std::iter::Peekable, -) -> Option { - chars_iter.next(); - let mut codepoint_str = String::new(); - while let Some(&next_char) = chars_iter.peek() { - if next_char == '}' { - chars_iter.next(); - break; - } else { - codepoint_str.push(next_char); - chars_iter.next(); - } - } - u32::from_str_radix(&codepoint_str, 16) - .ok() - .and_then(char::from_u32) - .map(|c| c.to_string()) -} + #[test] + fn test_decode_next_codepoint() { + assert_eq!(decode_next_codepoint(r"", false), None); + assert_eq!(decode_next_codepoint(r"1 test", false), Some((0x31, 1))); -fn handle_simple_or_surrogate_escape_sequence( - chars_iter: &mut std::iter::Peekable, -) -> Option { - let mut invalid_pair = String::new(); - let mut high_surrogate_str = String::new(); + assert_eq!( + decode_next_codepoint(r"\u0031\u0031", false), + Some((0x31, 6)) + ); + assert_eq!(decode_next_codepoint(r"\u0031 test", true), Some((0x31, 6))); + assert_eq!( + decode_next_codepoint(r"\\u0031 test", true), + Some((0x31, 7)) + ); - for _ in 0..4 { - if let Some(&next_char) = chars_iter.peek() { - if next_char.is_ascii_hexdigit() { - high_surrogate_str.push(next_char); - chars_iter.next(); - } else { - // If the character is not a valid Unicode char, return as simple string. - return Some(format!("\\u{high_surrogate_str}")); - } - } else { - // If not enough characters, return as if it were a simple string. - return Some(format!("\\u{high_surrogate_str}")); - } - } + assert_eq!( + decode_next_codepoint(r"\u{31}\u{31}", false), + Some((0x31, 6)) + ); + assert_eq!(decode_next_codepoint(r"\u{31} test", true), Some((0x31, 6))); + assert_eq!( + decode_next_codepoint(r"\\u{31} test", true), + Some((0x31, 7)) + ); - if let Ok(high_surrogate) = u32::from_str_radix(&high_surrogate_str, 16) { - // Check if it is in the high surrogate range(0xD800-0xDBFF) in UTF-16. - if (0xD800..=0xDBFF).contains(&high_surrogate) { - // If we have a high surrogate, expect a low surrogate next - if chars_iter.next() == Some('\\') && chars_iter.next() == Some('u') { - let mut low_surrogate_str = String::new(); - for _ in 0..4 { - if let Some(next_char) = chars_iter.peek() { - if !next_char.is_ascii_hexdigit() { - // Return as a simple string - // - high surrogate on its own doesn't make sense - // - low surrogate is not a valid unicode codepoint - // e.g \uD83D\u333 - invalid_pair.push_str(&format!("\\u{high_surrogate_str}")); - invalid_pair.push_str(&format!("\\u{low_surrogate_str}")); - return Some(invalid_pair); - } - low_surrogate_str.push(*next_char); - chars_iter.next(); - } - } - if let Ok(low_surrogate) = u32::from_str_radix(&low_surrogate_str, 16) { - // Check if it is in the low surrogate range(0xDC00-0xDFFF) in UTF-16. - if (0xDC00..=0xDFFF).contains(&low_surrogate) { - // Calculate the codepoint from the surrogate pair - let codepoint = - ((high_surrogate - 0xD800) << 10) + (low_surrogate - 0xDC00) + 0x10000; - return char::from_u32(codepoint).map(|c| c.to_string()); - }; - } - } - } else { - match char::from_u32(high_surrogate) { - Some(c) => return Some(c.to_string()), - None => invalid_pair.push_str(&format!("\\u{high_surrogate_str}")), - } - } - } - Some(invalid_pair) -} + // Surrogate pairs + assert_eq!( + decode_next_codepoint(r"\uD83D\uDC4D", false), + Some(('👍' as u32, 12)) + ); + assert_eq!( + decode_next_codepoint(r"\uD83D\uDC4D", true), + Some(('👍' as u32, 12)) + ); + assert_eq!( + decode_next_codepoint(r"\\uD83D\\uDC4D", true), + Some(('👍' as u32, 14)) + ); + assert_eq!( + decode_next_codepoint(r"\u{D83D}\u{DC4D}", true), + Some(('👍' as u32, 16)) + ); + assert_eq!( + decode_next_codepoint(r"\uD83D\u{DC4D}", true), + Some(('👍' as u32, 14)) + ); + assert_eq!( + decode_next_codepoint(r"\u{D83D}\uDC4D", true), + Some(('👍' as u32, 14)) + ); -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_replace_escaped_unicode() { - assert_eq!(replace_escaped_unicode(r#"/[\uD83D\uDC4D]/"#), "/[👍]/"); - assert_eq!(replace_escaped_unicode(r#"/[\u0041\u0301]/"#), "/[Á]/"); + // Lone high surrogate + assert_eq!( + decode_next_codepoint(r"\u{D83D}\u{DC4D}", false), + Some((0xD83D, 8)) + ); + assert_eq!( + decode_next_codepoint(r"\\u{D83D}\\u{DC4D}", true), + Some((0xD83D, 9)) + ); assert_eq!( - replace_escaped_unicode("/[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u"), - "/[👨‍👩‍👦]/u" + decode_next_codepoint(r"\\uD83D\\u{DC4D}", true), + Some((0xD83D, 7)) ); assert_eq!( - replace_escaped_unicode(r#"/[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u"#), - "/[👨‍👩‍👦]/u" + decode_next_codepoint(r"\\u{D83D}\\uDC4D", true), + Some((0xD83D, 9)) ); assert_eq!( - replace_escaped_unicode(r#"/[\u899\uD83D\uDC4D]/"#), - r#"/[\u899👍]/"# + decode_next_codepoint(r"\u{D83D}\\uDC4D", true), + Some((0xD83D, 8)) ); assert_eq!( - replace_escaped_unicode(r#"/[\u899\uD83D\u899\uDC4D]/"#), - r#"/[\u899\uD83D\u899\uDC4D]/"# + decode_next_codepoint(r"\uD83D\\uDC4D", true), + Some((0xD83D, 6)) ); } } diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js index f24d34e5928c..042352d27bb7 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js @@ -17,5 +17,7 @@ new RegExp("\n"); /\u{1F}/g; /\t/; /\n/; +/\x/; +/\u/; new (function foo() {})("\\x1f"); /[\u200E\u2066-\u2069]/gu; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap index b6f2aad25d69..31c832935b63 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noControlCharactersInRegex/valid.js.snap @@ -23,6 +23,8 @@ new RegExp("\n"); /\u{1F}/g; /\t/; /\n/; +/\x/; +/\u/; new (function foo() {})("\\x1f"); /[\u200E\u2066-\u2069]/gu; ``` diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap index 353af434dc4a..aa59ade4826d 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/invalid.js.snap @@ -83,15 +83,17 @@ var r = globalThis.RegExp("[👍]", ""); # Diagnostics ``` -invalid.js:1:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:1:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. > 1 │ var r = /[👍]/; - │ ^^^^^^ + │ ^^ 2 │ var r = /[\uD83D\uDC4D]/; 3 │ var r = /[👍]\\a/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 1 │ var·r·=·/[👍]/u; @@ -100,16 +102,18 @@ invalid.js:1:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:2:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:2:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 1 │ var r = /[👍]/; > 2 │ var r = /[\uD83D\uDC4D]/; - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 3 │ var r = /[👍]\\a/; 4 │ var r = /(?<=[👍])/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 2 │ var·r·=·/[\uD83D\uDC4D]/u; @@ -118,17 +122,19 @@ invalid.js:2:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:3:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:3:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 1 │ var r = /[👍]/; 2 │ var r = /[\uD83D\uDC4D]/; > 3 │ var r = /[👍]\\a/; - │ ^^^^^^^^^ + │ ^^ 4 │ var r = /(?<=[👍])/; 5 │ var r = /[A�]/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 3 │ var·r·=·/[👍]\\a/u; @@ -137,17 +143,19 @@ invalid.js:3:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:4:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:4:15 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 2 │ var r = /[\uD83D\uDC4D]/; 3 │ var r = /[👍]\\a/; > 4 │ var r = /(?<=[👍])/; - │ ^^^^^^^^^^^ + │ ^^ 5 │ var r = /[A�]/; 6 │ var r = /[A�]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 4 │ var·r·=·/(?<=[👍])/u; @@ -156,167 +164,189 @@ invalid.js:4:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━ ``` ``` -invalid.js:5:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:5:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 3 │ var r = /[👍]\\a/; 4 │ var r = /(?<=[👍])/; > 5 │ var r = /[A�]/; - │ ^^^^^ + │ ^ 6 │ var r = /[A�]/u; 7 │ var r = /[\u0041\u0301]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:6:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:6:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 4 │ var r = /(?<=[👍])/; 5 │ var r = /[A�]/; > 6 │ var r = /[A�]/u; - │ ^^^^^^ + │ ^ 7 │ var r = /[\u0041\u0301]/; 8 │ var r = /[\u0041\u0301]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:7:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:7:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 5 │ var r = /[A�]/; 6 │ var r = /[A�]/u; > 7 │ var r = /[\u0041\u0301]/; - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 8 │ var r = /[\u0041\u0301]/u; 9 │ var r = /[\u{41}\u{301}]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:8:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:8:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 6 │ var r = /[A�]/u; 7 │ var r = /[\u0041\u0301]/; > 8 │ var r = /[\u0041\u0301]/u; - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 9 │ var r = /[\u{41}\u{301}]/u; 10 │ var r = /[❇�]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:9:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:9:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 7 │ var r = /[\u0041\u0301]/; 8 │ var r = /[\u0041\u0301]/u; > 9 │ var r = /[\u{41}\u{301}]/u; - │ ^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 10 │ var r = /[❇�]/; 11 │ var r = /[❇�]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:10:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:10:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 8 │ var r = /[\u0041\u0301]/u; 9 │ var r = /[\u{41}\u{301}]/u; > 10 │ var r = /[❇�]/; - │ ^^^^^ + │ ^ 11 │ var r = /[❇�]/u; 12 │ var r = /[\u2747\uFE0F]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:11:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:11:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 9 │ var r = /[\u{41}\u{301}]/u; 10 │ var r = /[❇�]/; > 11 │ var r = /[❇�]/u; - │ ^^^^^^ + │ ^ 12 │ var r = /[\u2747\uFE0F]/; 13 │ var r = /[\u2747\uFE0F]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:12:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:12:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 10 │ var r = /[❇�]/; 11 │ var r = /[❇�]/u; > 12 │ var r = /[\u2747\uFE0F]/; - │ ^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 13 │ var r = /[\u2747\uFE0F]/u; 14 │ var r = /[\u{2747}\u{FE0F}]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:13:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:13:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 11 │ var r = /[❇�]/u; 12 │ var r = /[\u2747\uFE0F]/; > 13 │ var r = /[\u2747\uFE0F]/u; - │ ^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 14 │ var r = /[\u{2747}\u{FE0F}]/u; 15 │ var r = /[👶🏻]/; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:14:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:14:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 12 │ var r = /[\u2747\uFE0F]/; 13 │ var r = /[\u2747\uFE0F]/u; > 14 │ var r = /[\u{2747}\u{FE0F}]/u; - │ ^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 15 │ var r = /[👶🏻]/; 16 │ var r = /[👶🏻]/u; + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:15:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:15:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 13 │ var r = /[\u2747\uFE0F]/u; 14 │ var r = /[\u{2747}\u{FE0F}]/u; > 15 │ var r = /[👶🏻]/; - │ ^^^^^^^^ + │ ^^ 16 │ var r = /[👶🏻]/u; 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 15 │ var·r·=·/[👶🏻]/u; @@ -325,62 +355,70 @@ invalid.js:15:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:16:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:16:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 14 │ var r = /[\u{2747}\u{FE0F}]/u; 15 │ var r = /[👶🏻]/; > 16 │ var r = /[👶🏻]/u; - │ ^^^^^^^^^ + │ ^^^^ 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; + i Replace the character class with an alternation. + ``` ``` -invalid.js:17:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:17:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 15 │ var r = /[👶🏻]/; 16 │ var r = /[👶🏻]/u; > 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^ 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; 19 │ var r = /[🇯🇵]/; + i Replace the character class with an alternation. + ``` ``` -invalid.js:18:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:18:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 16 │ var r = /[👶🏻]/u; 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; > 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^ 19 │ var r = /[🇯🇵]/; 20 │ var r = /[🇯🇵]/i; + i Replace the character class with an alternation. + ``` ``` -invalid.js:19:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:19:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 17 │ var r = /[\uD83D\uDC76\uD83C\uDFFB]/u; 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; > 19 │ var r = /[🇯🇵]/; - │ ^^^^^^ + │ ^ 20 │ var r = /[🇯🇵]/i; 21 │ var r = /[🇯🇵]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 19 │ var·r·=·/[🇯🇵]/u; @@ -389,17 +427,19 @@ invalid.js:19:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:20:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:20:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; 19 │ var r = /[🇯🇵]/; > 20 │ var r = /[🇯🇵]/i; - │ ^^^^^^^ + │ ^ 21 │ var r = /[🇯🇵]/u; 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 18 18 │ var r = /[\u{1F476}\u{1F3FB}]/u; @@ -413,62 +453,70 @@ invalid.js:20:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:21:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:21:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 19 │ var r = /[🇯🇵]/; 20 │ var r = /[🇯🇵]/i; > 21 │ var r = /[🇯🇵]/u; - │ ^^^^^^^ + │ ^^ 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:22:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:22:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 20 │ var r = /[🇯🇵]/i; 21 │ var r = /[🇯🇵]/u; > 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^ 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; 24 │ var r = /[👨�👩�👦]/; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:23:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:23:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 21 │ var r = /[🇯🇵]/u; 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; > 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^ 24 │ var r = /[👨�👩�👦]/; 25 │ var r = /[👨�👩�👦]/u; + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:24:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:24:11 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 22 │ var r = /[\uD83C\uDDEF\uD83C\uDDF5]/u; 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; > 24 │ var r = /[👨�👩�👦]/; - │ ^^^^^^^^^^ + │ ^^ 25 │ var r = /[👨�👩�👦]/u; 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 24 │ var·r·=·/[👨�👩�👦]/u; @@ -477,62 +525,70 @@ invalid.js:24:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:25:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:25:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 23 │ var r = /[\u{1F1EF}\u{1F1F5}]/u; 24 │ var r = /[👨�👩�👦]/; > 25 │ var r = /[👨�👩�👦]/u; - │ ^^^^^^^^^^^ + │ ^^^^ 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:26:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:26:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 24 │ var r = /[👨�👩�👦]/; 25 │ var r = /[👨�👩�👦]/u; > 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; 28 │ var r = new RegExp("[👍]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:27:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:27:11 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 25 │ var r = /[👨�👩�👦]/u; 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; > 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 │ var r = new RegExp("[👍]", ""); 29 │ var r = new RegExp('[👍]', ``); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:28:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:28:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 26 │ var r = /[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]/u; 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; > 28 │ var r = new RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 29 │ var r = new RegExp('[👍]', ``); 30 │ var r = new RegExp("[👍]", flags); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 28 │ var·r·=·new·RegExp("[👍]",·"u"); @@ -541,17 +597,19 @@ invalid.js:28:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:29:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:29:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 27 │ var r = /[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]/u; 28 │ var r = new RegExp("[👍]", ""); > 29 │ var r = new RegExp('[👍]', ``); - │ ^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 30 │ var r = new RegExp("[👍]", flags); 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 29 │ var·r·=·new·RegExp('[👍]',·`u`); @@ -560,32 +618,36 @@ invalid.js:29:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:30:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:30:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 28 │ var r = new RegExp("[👍]", ""); 29 │ var r = new RegExp('[👍]', ``); > 30 │ var r = new RegExp("[👍]", flags); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); 32 │ var r = new RegExp("/(?<=[👍])", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + ``` ``` -invalid.js:31:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:31:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 29 │ var r = new RegExp('[👍]', ``); 30 │ var r = new RegExp("[👍]", flags); > 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 32 │ var r = new RegExp("/(?<=[👍])", ""); 33 │ var r = new RegExp("[A�]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 31 │ var·r·=·new·RegExp("[\uD83D\uDC4D]",·"u"); @@ -594,17 +656,19 @@ invalid.js:31:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:32:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:32:27 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 30 │ var r = new RegExp("[👍]", flags); 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); > 32 │ var r = new RegExp("/(?<=[👍])", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 33 │ var r = new RegExp("[A�]", ""); 34 │ var r = new RegExp("[A�]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 32 │ var·r·=·new·RegExp("/(?<=[👍])",·"u"); @@ -613,167 +677,189 @@ invalid.js:32:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:33:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:33:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 31 │ var r = new RegExp("[\uD83D\uDC4D]", ""); 32 │ var r = new RegExp("/(?<=[👍])", ""); > 33 │ var r = new RegExp("[A�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^ + │ ^ 34 │ var r = new RegExp("[A�]", "u"); 35 │ var r = new RegExp("[\u0041\u0301]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:34:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:34:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 32 │ var r = new RegExp("/(?<=[👍])", ""); 33 │ var r = new RegExp("[A�]", ""); > 34 │ var r = new RegExp("[A�]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 35 │ var r = new RegExp("[\u0041\u0301]", ""); 36 │ var r = new RegExp("[\u0041\u0301]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:35:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:35:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 33 │ var r = new RegExp("[A�]", ""); 34 │ var r = new RegExp("[A�]", "u"); > 35 │ var r = new RegExp("[\u0041\u0301]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 36 │ var r = new RegExp("[\u0041\u0301]", "u"); 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:36:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:36:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 34 │ var r = new RegExp("[A�]", "u"); 35 │ var r = new RegExp("[\u0041\u0301]", ""); > 36 │ var r = new RegExp("[\u0041\u0301]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); 38 │ var r = new RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:37:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:37:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 35 │ var r = new RegExp("[\u0041\u0301]", ""); 36 │ var r = new RegExp("[\u0041\u0301]", "u"); > 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^ 38 │ var r = new RegExp("[❇�]", ""); 39 │ var r = new RegExp("[❇�]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:38:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:38:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 36 │ var r = new RegExp("[\u0041\u0301]", "u"); 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); > 38 │ var r = new RegExp("[❇�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^ + │ ^ 39 │ var r = new RegExp("[❇�]", "u"); 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:39:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:39:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 37 │ var r = new RegExp("[\u{41}\u{301}]", "u"); 38 │ var r = new RegExp("[❇�]", ""); > 39 │ var r = new RegExp("[❇�]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:40:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:40:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 38 │ var r = new RegExp("[❇�]", ""); 39 │ var r = new RegExp("[❇�]", "u"); > 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:41:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:41:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 39 │ var r = new RegExp("[❇�]", "u"); 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); > 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^ 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); 43 │ var r = new RegExp("[👶🏻]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:42:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:42:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 40 │ var r = new RegExp("[\u2747\uFE0F]", ""); 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); > 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^ 43 │ var r = new RegExp("[👶🏻]", ""); 44 │ var r = new RegExp("[👶🏻]", "u"); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:43:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:43:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 41 │ var r = new RegExp("[\u2747\uFE0F]", "u"); 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); > 43 │ var r = new RegExp("[👶🏻]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 44 │ var r = new RegExp("[👶🏻]", "u"); 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 43 │ var·r·=·new·RegExp("[👶🏻]",·"u"); @@ -782,62 +868,70 @@ invalid.js:43:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:44:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:44:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 42 │ var r = new RegExp("[\u{2747}\u{FE0F}]", "u"); 43 │ var r = new RegExp("[👶🏻]", ""); > 44 │ var r = new RegExp("[👶🏻]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^ 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:45:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:45:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 43 │ var r = new RegExp("[👶🏻]", ""); 44 │ var r = new RegExp("[👶🏻]", "u"); > 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^ 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); 47 │ var r = new RegExp("[🇯🇵]", ""); + i Replace the character class with an alternation. + ``` ``` -invalid.js:46:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:46:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 44 │ var r = new RegExp("[👶🏻]", "u"); 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); > 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^ 47 │ var r = new RegExp("[🇯🇵]", ""); 48 │ var r = new RegExp("[🇯🇵]", "i"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:47:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:47:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 45 │ var r = new RegExp("[\uD83D\uDC76\uD83C\uDFFB]", "u"); 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); > 47 │ var r = new RegExp("[🇯🇵]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 48 │ var r = new RegExp("[🇯🇵]", "i"); 49 │ var r = new RegExp('[🇯🇵]', `i`); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 47 │ var·r·=·new·RegExp("[🇯🇵]",·"u"); @@ -846,17 +940,19 @@ invalid.js:47:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:48:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:48:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); 47 │ var r = new RegExp("[🇯🇵]", ""); > 48 │ var r = new RegExp("[🇯🇵]", "i"); - │ ^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 49 │ var r = new RegExp('[🇯🇵]', `i`); 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 46 46 │ var r = new RegExp("[\u{1F476}\u{1F3FB}]", "u"); @@ -870,17 +966,19 @@ invalid.js:48:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:49:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:49:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 47 │ var r = new RegExp("[🇯🇵]", ""); 48 │ var r = new RegExp("[🇯🇵]", "i"); > 49 │ var r = new RegExp('[🇯🇵]', `i`); - │ ^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); 51 │ var r = new RegExp("[🇯🇵]"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 47 47 │ var r = new RegExp("[🇯🇵]", ""); @@ -894,17 +992,19 @@ invalid.js:49:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:50:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:50:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 48 │ var r = new RegExp("[🇯🇵]", "i"); 49 │ var r = new RegExp('[🇯🇵]', `i`); > 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 51 │ var r = new RegExp("[🇯🇵]"); 52 │ var r = new RegExp("[🇯🇵]",); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 50 │ var·r·=·new·RegExp('[🇯🇵]',·`${foo}u`); @@ -913,17 +1013,19 @@ invalid.js:50:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:51:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:51:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 49 │ var r = new RegExp('[🇯🇵]', `i`); 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); > 51 │ var r = new RegExp("[🇯🇵]"); - │ ^^^^^^^^^^^^^^^^^^ + │ ^ 52 │ var r = new RegExp("[🇯🇵]",); 53 │ var r = new RegExp("[🇯🇵]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 51 │ var·r·=·new·RegExp("[🇯🇵]",·"u"); @@ -932,17 +1034,19 @@ invalid.js:51:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:52:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:52:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 50 │ var r = new RegExp('[🇯🇵]', `${foo}`); 51 │ var r = new RegExp("[🇯🇵]"); > 52 │ var r = new RegExp("[🇯🇵]",); - │ ^^^^^^^^^^^^^^^^^^^ + │ ^ 53 │ var r = new RegExp("[🇯🇵]", "u"); 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 52 │ var·r·=·new·RegExp("[🇯🇵]",·"u"); @@ -951,62 +1055,70 @@ invalid.js:52:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:53:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:53:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 51 │ var r = new RegExp("[🇯🇵]"); 52 │ var r = new RegExp("[🇯🇵]",); > 53 │ var r = new RegExp("[🇯🇵]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:54:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:54:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 52 │ var r = new RegExp("[🇯🇵]",); 53 │ var r = new RegExp("[🇯🇵]", "u"); > 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^ 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); 56 │ var r = new RegExp("[👨�👩�👦]", ""); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:55:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:55:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Regional indicator symbol characters should not be used in the character class. + ! A character class cannot match a pair of regional indicator symbols. 53 │ var r = new RegExp("[🇯🇵]", "u"); 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); > 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^ 56 │ var r = new RegExp("[👨�👩�👦]", ""); 57 │ var r = new RegExp("[👨�👩�👦]", "u"); + i A pair of regional indicator symbols encodes a country code. Replace the character class with an alternation. + ``` ``` -invalid.js:56:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:56:22 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 54 │ var r = new RegExp("[\uD83C\uDDEF\uD83C\uDDF5]", "u"); 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); > 56 │ var r = new RegExp("[👨�👩�👦]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 57 │ var r = new RegExp("[👨�👩�👦]", "u"); 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 56 │ var·r·=·new·RegExp("[👨�👩�👦]",·"u"); @@ -1015,92 +1127,104 @@ invalid.js:56:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:57:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:57:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 55 │ var r = new RegExp("[\u{1F1EF}\u{1F1F5}]", "u"); 56 │ var r = new RegExp("[👨�👩�👦]", ""); > 57 │ var r = new RegExp("[👨�👩�👦]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^ 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:58:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:58:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 56 │ var r = new RegExp("[👨�👩�👦]", ""); 57 │ var r = new RegExp("[👨�👩�👦]", "u"); > 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 60 │ var r = new globalThis.RegExp("[❇�]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:59:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:59:22 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 57 │ var r = new RegExp("[👨�👩�👦]", "u"); 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); > 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 60 │ var r = new globalThis.RegExp("[❇�]", ""); 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:60:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:60:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 58 │ var r = new RegExp("[\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC66]", "u"); 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); > 60 │ var r = new globalThis.RegExp("[❇�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:61:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:61:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected modified Emoji in the character class. + ! A character class cannot match an emoji with a skin tone modifier. 59 │ var r = new RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 60 │ var r = new globalThis.RegExp("[❇�]", ""); > 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^ 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); + i Replace the character class with an alternation. + ``` ``` -invalid.js:62:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:62:33 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 60 │ var r = new globalThis.RegExp("[❇�]", ""); 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); > 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 64 │ var r = new window.RegExp("[❇�]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 62 │ var·r·=·new·globalThis.RegExp("[🇯🇵]",·"u"); @@ -1109,47 +1233,53 @@ invalid.js:62:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:63:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:63:33 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected joined character sequence in character class. + ! A character class cannot match a joined character sequence. 61 │ var r = new globalThis.RegExp("[👶🏻]", "u"); 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); > 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ 64 │ var r = new window.RegExp("[❇�]", ""); 65 │ var r = new window.RegExp("[👍]", ""); + i A zero width joiner composes several emojis into a new one. Replace the character class with an alternation. + ``` ``` -invalid.js:64:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:64:29 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 62 │ var r = new globalThis.RegExp("[🇯🇵]", ""); 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); > 64 │ var r = new window.RegExp("[❇�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 65 │ var r = new window.RegExp("[👍]", ""); 66 │ var r = new global.RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:65:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:65:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 63 │ var r = new globalThis.RegExp("[\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}]", "u"); 64 │ var r = new window.RegExp("[❇�]", ""); > 65 │ var r = new window.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 66 │ var r = new global.RegExp("[❇�]", ""); 67 │ var r = new global.RegExp("[👍]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 65 │ var·r·=·new·window.RegExp("[👍]",·"u"); @@ -1158,32 +1288,36 @@ invalid.js:65:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:66:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:66:29 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 64 │ var r = new window.RegExp("[❇�]", ""); 65 │ var r = new window.RegExp("[👍]", ""); > 66 │ var r = new global.RegExp("[❇�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 67 │ var r = new global.RegExp("[👍]", ""); 68 │ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:67:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:67:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 65 │ var r = new window.RegExp("[👍]", ""); 66 │ var r = new global.RegExp("[❇�]", ""); > 67 │ var r = new global.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 68 │ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); 69 │ var r = new globalThis.globalThis.globalThis.RegExp("[👍]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 67 │ var·r·=·new·global.RegExp("[👍]",·"u"); @@ -1192,32 +1326,36 @@ invalid.js:67:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:68:9 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:68:55 lint/suspicious/noMisleadingCharacterClass ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected combined character in the character class. + ! A character class cannot match a character and a combining character. 66 │ var r = new global.RegExp("[❇�]", ""); 67 │ var r = new global.RegExp("[👍]", ""); > 68 │ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^ 69 │ var r = new globalThis.globalThis.globalThis.RegExp("[👍]", ""); 70 │ var r = RegExp("[👍]", ""); + i A character and a combining character forms a new character. Replace the character class with an alternation. + ``` ``` -invalid.js:69:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:69:55 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 67 │ var r = new global.RegExp("[👍]", ""); 68 │ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); > 69 │ var r = new globalThis.globalThis.globalThis.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 70 │ var r = RegExp("[👍]", ""); 71 │ var r = window.RegExp("[👍]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 69 │ var·r·=·new·globalThis.globalThis.globalThis.RegExp("[👍]",·"u"); @@ -1226,17 +1364,19 @@ invalid.js:69:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:70:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:70:18 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 68 │ var r = new globalThis.globalThis.globalThis.RegExp("[❇�]", ""); 69 │ var r = new globalThis.globalThis.globalThis.RegExp("[👍]", ""); > 70 │ var r = RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^ + │ ^^ 71 │ var r = window.RegExp("[👍]", ""); 72 │ var r = global.RegExp("[👍]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 70 │ var·r·=·RegExp("[👍]",·"u"); @@ -1245,17 +1385,19 @@ invalid.js:70:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:71:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:71:25 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 69 │ var r = new globalThis.globalThis.globalThis.RegExp("[👍]", ""); 70 │ var r = RegExp("[👍]", ""); > 71 │ var r = window.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 72 │ var r = global.RegExp("[👍]", ""); 73 │ var r = globalThis.RegExp("[👍]", ""); + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 71 │ var·r·=·window.RegExp("[👍]",·"u"); @@ -1264,17 +1406,19 @@ invalid.js:71:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:72:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:72:25 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 70 │ var r = RegExp("[👍]", ""); 71 │ var r = window.RegExp("[👍]", ""); > 72 │ var r = global.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 73 │ var r = globalThis.RegExp("[👍]", ""); 74 │ + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 72 │ var·r·=·global.RegExp("[👍]",·"u"); @@ -1283,17 +1427,19 @@ invalid.js:72:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:73:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:73:29 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 71 │ var r = window.RegExp("[👍]", ""); 72 │ var r = global.RegExp("[👍]", ""); > 73 │ var r = globalThis.RegExp("[👍]", ""); - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ ^^ 74 │ 75 │ /[\]👍]/; + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. + i Safe fix: Add unicode u flag to regex 73 │ var·r·=·globalThis.RegExp("[👍]",·"u"); @@ -1302,14 +1448,16 @@ invalid.js:73:9 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━ ``` ``` -invalid.js:75:1 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:75:5 lint/suspicious/noMisleadingCharacterClass FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! Unexpected surrogate pair in character class. Use the 'u' flag. + ! A character class cannot match a surrogate pair. Add the 'u' unicode flag to match against them. 73 │ var r = globalThis.RegExp("[👍]", ""); 74 │ > 75 │ /[\]👍]/; - │ ^^^^^^^^ + │ ^^ + + i A surrogate pair forms a single codepoint, but is encoded as a pair of two characters. Without the unicode flag, the regex matches a single surrogate character. i Safe fix: Add unicode u flag to regex diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js index a7f00f0ba6e5..9b07d4bfaa1f 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js @@ -1,6 +1,6 @@ var r = /[👍]/u; -var r = /[\\uD83D\\uDC4D]/u; -var r = /[\\u{1F44D}]/u; +var r = /[\uD83D\uDC4D]/u; +var r = /[\u{1F44D}]/u; var r = /❇️/; var r = /Á/; var r = /[❇]/; @@ -11,19 +11,19 @@ var r = /[JP]/; var r = /👨‍👩‍👦/; // Ignore solo lead/tail surrogate. -var r = /[\\uD83D]/; -var r = /[\\uDC4D]/; -var r = /[\\uD83D]/u; -var r = /[\\uDC4D]/u; +var r = /[\uD83D]/; +var r = /[\uDC4D]/; +var r = /[\uD83D]/u; +var r = /[\uDC4D]/u; // Ignore solo combining char. -var r = /[\\u0301]/; -var r = /[\\uFE0F]/; -var r = /[\\u0301]/u; -var r = /[\\uFE0F]/u; +var r = /[\u0301]/; +var r = /[\uFE0F]/; +var r = /[\u0301]/u; +var r = /[\uFE0F]/u; // Ignore solo emoji modifier. -var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; var r = /[\u{1F3FB}]/u; // Ignore solo regional indicator symbol. @@ -31,8 +31,8 @@ var r = /[🇯]/u; var r = /[🇵]/u; // Ignore solo ZWJ. -var r = /[\\u200D]/; -var r = /[\\u200D]/u; +var r = /[\u200D]/; +var r = /[\u200D]/u; // don't report and don't crash on invalid regex // FIXME: need to ecma regex parser to handle this case diff --git a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap index 5e3bf1d09cf5..7818bf658a01 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/noMisleadingCharacterClass/valid.js.snap @@ -5,8 +5,8 @@ expression: valid.js # Input ```jsx var r = /[👍]/u; -var r = /[\\uD83D\\uDC4D]/u; -var r = /[\\u{1F44D}]/u; +var r = /[\uD83D\uDC4D]/u; +var r = /[\u{1F44D}]/u; var r = /❇️/; var r = /Á/; var r = /[❇]/; @@ -17,19 +17,19 @@ var r = /[JP]/; var r = /👨‍👩‍👦/; // Ignore solo lead/tail surrogate. -var r = /[\\uD83D]/; -var r = /[\\uDC4D]/; -var r = /[\\uD83D]/u; -var r = /[\\uDC4D]/u; +var r = /[\uD83D]/; +var r = /[\uDC4D]/; +var r = /[\uD83D]/u; +var r = /[\uDC4D]/u; // Ignore solo combining char. -var r = /[\\u0301]/; -var r = /[\\uFE0F]/; -var r = /[\\u0301]/u; -var r = /[\\uFE0F]/u; +var r = /[\u0301]/; +var r = /[\uFE0F]/; +var r = /[\u0301]/u; +var r = /[\uFE0F]/u; // Ignore solo emoji modifier. -var r = /[\\u{1F3FB}]/u; +var r = /[\u{1F3FB}]/u; var r = /[\u{1F3FB}]/u; // Ignore solo regional indicator symbol. @@ -37,8 +37,8 @@ var r = /[🇯]/u; var r = /[🇵]/u; // Ignore solo ZWJ. -var r = /[\\u200D]/; -var r = /[\\u200D]/u; +var r = /[\u200D]/; +var r = /[\u200D]/u; // don't report and don't crash on invalid regex // FIXME: need to ecma regex parser to handle this case diff --git a/crates/biome_text_size/src/range.rs b/crates/biome_text_size/src/range.rs index 9c65f8d4da56..d71303946c6a 100644 --- a/crates/biome_text_size/src/range.rs +++ b/crates/biome_text_size/src/range.rs @@ -62,8 +62,8 @@ impl TextRange { /// assert_eq!(range.len(), end - start); /// ``` #[inline] - pub fn new(start: TextSize, end: TextSize) -> TextRange { - assert!(start <= end); + pub const fn new(start: TextSize, end: TextSize) -> TextRange { + assert!(start.raw <= end.raw); TextRange { start, end } } @@ -83,8 +83,13 @@ impl TextRange { /// assert_eq!(&text[range], "23456") /// ``` #[inline] - pub fn at(offset: TextSize, len: TextSize) -> TextRange { - TextRange::new(offset, offset + len) + pub const fn at(offset: TextSize, len: TextSize) -> TextRange { + TextRange { + start: offset, + end: TextSize { + raw: offset.raw + len.raw, + }, + } } /// Create a zero-length range at the specified offset (`offset..offset`). @@ -100,7 +105,7 @@ impl TextRange { /// assert_eq!(range, TextRange::new(point, point)); /// ``` #[inline] - pub fn empty(offset: TextSize) -> TextRange { + pub const fn empty(offset: TextSize) -> TextRange { TextRange { start: offset, end: offset, @@ -122,9 +127,9 @@ impl TextRange { /// assert_eq!(range, TextRange::at(0.into(), point)); /// ``` #[inline] - pub fn up_to(end: TextSize) -> TextRange { + pub const fn up_to(end: TextSize) -> TextRange { TextRange { - start: 0.into(), + start: TextSize { raw: 0 }, end, } } From 71d547ea460adb95c178c242e182dc41c52b6668 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 30 Sep 2024 13:18:51 +0100 Subject: [PATCH 17/32] ci: update codspeed action (#4139) --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 977695e0dc09..8459dcf98060 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -51,7 +51,7 @@ jobs: run: cargo codspeed build --features codspeed -p xtask_bench - name: Run the benchmarks - uses: CodSpeedHQ/action@9481de2550a3d73cdac24ddd54ee6b9ee31f7ec2 # v2.4.5 + uses: CodSpeedHQ/action@v3 timeout-minutes: 30 with: run: cargo codspeed run From 4a22da8f4d0ab2b5b250a91f37ae6221fb1c576b Mon Sep 17 00:00:00 2001 From: matsuura <79092292+tunamaguro@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:22:14 +0900 Subject: [PATCH 18/32] feat(biome_css_analyze): implement `noDescendingSpecificity` (#4097) --- .../src/analyzer/linter/rules.rs | 164 ++++++++------ crates/biome_css_analyze/src/lint/nursery.rs | 2 + .../lint/nursery/no_descending_specificity.rs | 203 ++++++++++++++++++ crates/biome_css_analyze/src/options.rs | 1 + .../complex_selector.invalid.css | 7 + .../complex_selector.invalid.css.snap | 40 ++++ .../function_pseudo_selector.invalid.css | 15 ++ .../function_pseudo_selector.invalid.css.snap | 73 +++++++ .../nested.invalid.css | 9 + .../nested.invalid.css.snap | 42 ++++ .../simple_pseudo_selector.invalid.css | 7 + .../simple_pseudo_selector.invalid.css.snap | 40 ++++ .../nursery/noDescendingSpecificity/valid.css | 51 +++++ .../noDescendingSpecificity/valid.css.snap | 58 +++++ crates/biome_css_semantic/src/events.rs | 49 ++++- .../src/semantic_model/builder.rs | 20 +- .../src/semantic_model/mod.rs | 1 + .../src/semantic_model/model.rs | 46 +++- .../src/semantic_model/specificity.rs | 183 ++++++++++++++++ .../src/categories.rs | 1 + .../@biomejs/backend-jsonrpc/src/workspace.ts | 5 + .../@biomejs/biome/configuration_schema.json | 7 + 22 files changed, 939 insertions(+), 85 deletions(-) create mode 100644 crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css create mode 100644 crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap create mode 100644 crates/biome_css_semantic/src/semantic_model/specificity.rs diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index 7f9d335f686c..b7cac6a2ab55 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -3274,6 +3274,10 @@ pub struct Nursery { #[doc = "Disallow use of CommonJs module system in favor of ESM style imports."] #[serde(skip_serializing_if = "Option::is_none")] pub no_common_js: Option>, + #[doc = "Disallow a lower specificity selector from coming after a higher specificity selector."] + #[serde(skip_serializing_if = "Option::is_none")] + pub no_descending_specificity: + Option>, #[doc = "Disallow duplicate custom properties within declaration blocks."] #[serde(skip_serializing_if = "Option::is_none")] pub no_duplicate_custom_properties: @@ -3417,6 +3421,7 @@ impl Nursery { const GROUP_NAME: &'static str = "nursery"; pub(crate) const GROUP_RULES: &'static [&'static str] = &[ "noCommonJs", + "noDescendingSpecificity", "noDuplicateCustomProperties", "noDuplicateElseIf", "noDuplicatedFields", @@ -3452,6 +3457,7 @@ impl Nursery { "useValidAutocomplete", ]; const RECOMMENDED_RULES: &'static [&'static str] = &[ + "noDescendingSpecificity", "noDuplicateCustomProperties", "noDuplicateElseIf", "noDuplicatedFields", @@ -3468,14 +3474,15 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27]), - RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), ]; const ALL_RULES_AS_FILTERS: &'static [RuleFilter<'static>] = &[ RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0]), @@ -3512,6 +3519,7 @@ impl Nursery { RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32]), RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33]), + RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34]), ]; #[doc = r" Retrieves the recommended rules"] pub(crate) fn is_recommended_true(&self) -> bool { @@ -3533,171 +3541,176 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { + if let Some(rule) = self.no_descending_specificity.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_else_if.as_ref() { + if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_else_if.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { + if let Some(rule) = self.no_duplicated_fields.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_enum.as_ref() { + if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_exported_imports.as_ref() { + if let Some(rule) = self.no_enum.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_irregular_whitespace.as_ref() { + if let Some(rule) = self.no_exported_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_missing_var_function.as_ref() { + if let Some(rule) = self.no_irregular_whitespace.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_nested_ternary.as_ref() { + if let Some(rule) = self.no_missing_var_function.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_octal_escape.as_ref() { + if let Some(rule) = self.no_nested_ternary.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_process_env.as_ref() { + if let Some(rule) = self.no_octal_escape.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_process_env.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_restricted_types.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_template_curly_in_string.as_ref() { + if let Some(rule) = self.no_substr.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { + if let Some(rule) = self.no_template_curly_in_string.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.use_component_export_only_modules.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_component_export_only_modules.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_deprecated_reason.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_explicit_function_return_type.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_explicit_function_return_type.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_sorted_classes.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[30])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_enabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_enabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); + } + } index_set } pub(crate) fn get_disabled_rules(&self) -> FxHashSet> { @@ -3707,171 +3720,176 @@ impl Nursery { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[0])); } } - if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { + if let Some(rule) = self.no_descending_specificity.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[1])); } } - if let Some(rule) = self.no_duplicate_else_if.as_ref() { + if let Some(rule) = self.no_duplicate_custom_properties.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[2])); } } - if let Some(rule) = self.no_duplicated_fields.as_ref() { + if let Some(rule) = self.no_duplicate_else_if.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[3])); } } - if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { + if let Some(rule) = self.no_duplicated_fields.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[4])); } } - if let Some(rule) = self.no_enum.as_ref() { + if let Some(rule) = self.no_dynamic_namespace_import_access.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[5])); } } - if let Some(rule) = self.no_exported_imports.as_ref() { + if let Some(rule) = self.no_enum.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[6])); } } - if let Some(rule) = self.no_irregular_whitespace.as_ref() { + if let Some(rule) = self.no_exported_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[7])); } } - if let Some(rule) = self.no_missing_var_function.as_ref() { + if let Some(rule) = self.no_irregular_whitespace.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[8])); } } - if let Some(rule) = self.no_nested_ternary.as_ref() { + if let Some(rule) = self.no_missing_var_function.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[9])); } } - if let Some(rule) = self.no_octal_escape.as_ref() { + if let Some(rule) = self.no_nested_ternary.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[10])); } } - if let Some(rule) = self.no_process_env.as_ref() { + if let Some(rule) = self.no_octal_escape.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[11])); } } - if let Some(rule) = self.no_restricted_imports.as_ref() { + if let Some(rule) = self.no_process_env.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[12])); } } - if let Some(rule) = self.no_restricted_types.as_ref() { + if let Some(rule) = self.no_restricted_imports.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[13])); } } - if let Some(rule) = self.no_secrets.as_ref() { + if let Some(rule) = self.no_restricted_types.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[14])); } } - if let Some(rule) = self.no_static_element_interactions.as_ref() { + if let Some(rule) = self.no_secrets.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[15])); } } - if let Some(rule) = self.no_substr.as_ref() { + if let Some(rule) = self.no_static_element_interactions.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[16])); } } - if let Some(rule) = self.no_template_curly_in_string.as_ref() { + if let Some(rule) = self.no_substr.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[17])); } } - if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { + if let Some(rule) = self.no_template_curly_in_string.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[18])); } } - if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_class.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[19])); } } - if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { + if let Some(rule) = self.no_unknown_pseudo_element.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[20])); } } - if let Some(rule) = self.no_value_at_rule.as_ref() { + if let Some(rule) = self.no_useless_escape_in_regex.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[21])); } } - if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { + if let Some(rule) = self.no_value_at_rule.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[22])); } } - if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { + if let Some(rule) = self.use_adjacent_overload_signatures.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[23])); } } - if let Some(rule) = self.use_component_export_only_modules.as_ref() { + if let Some(rule) = self.use_aria_props_supported_by_role.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[24])); } } - if let Some(rule) = self.use_consistent_curly_braces.as_ref() { + if let Some(rule) = self.use_component_export_only_modules.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[25])); } } - if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { + if let Some(rule) = self.use_consistent_curly_braces.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[26])); } } - if let Some(rule) = self.use_deprecated_reason.as_ref() { + if let Some(rule) = self.use_consistent_member_accessibility.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[27])); } } - if let Some(rule) = self.use_explicit_function_return_type.as_ref() { + if let Some(rule) = self.use_deprecated_reason.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[28])); } } - if let Some(rule) = self.use_import_restrictions.as_ref() { + if let Some(rule) = self.use_explicit_function_return_type.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[29])); } } - if let Some(rule) = self.use_sorted_classes.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[30])); } } - if let Some(rule) = self.use_strict_mode.as_ref() { + if let Some(rule) = self.use_sorted_classes.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[31])); } } - if let Some(rule) = self.use_trim_start_end.as_ref() { + if let Some(rule) = self.use_strict_mode.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[32])); } } - if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if let Some(rule) = self.use_trim_start_end.as_ref() { if rule.is_disabled() { index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[33])); } } + if let Some(rule) = self.use_valid_autocomplete.as_ref() { + if rule.is_disabled() { + index_set.insert(RuleFilter::Rule(Self::GROUP_NAME, Self::GROUP_RULES[34])); + } + } index_set } #[doc = r" Checks if, given a rule name, matches one of the rules contained in this category"] @@ -3912,6 +3930,10 @@ impl Nursery { .no_common_js .as_ref() .map(|conf| (conf.level(), conf.get_options())), + "noDescendingSpecificity" => self + .no_descending_specificity + .as_ref() + .map(|conf| (conf.level(), conf.get_options())), "noDuplicateCustomProperties" => self .no_duplicate_custom_properties .as_ref() diff --git a/crates/biome_css_analyze/src/lint/nursery.rs b/crates/biome_css_analyze/src/lint/nursery.rs index da0d81727c08..7910f2e76559 100644 --- a/crates/biome_css_analyze/src/lint/nursery.rs +++ b/crates/biome_css_analyze/src/lint/nursery.rs @@ -2,6 +2,7 @@ use biome_analyze::declare_lint_group; +pub mod no_descending_specificity; pub mod no_duplicate_custom_properties; pub mod no_irregular_whitespace; pub mod no_missing_var_function; @@ -13,6 +14,7 @@ declare_lint_group! { pub Nursery { name : "nursery" , rules : [ + self :: no_descending_specificity :: NoDescendingSpecificity , self :: no_duplicate_custom_properties :: NoDuplicateCustomProperties , self :: no_irregular_whitespace :: NoIrregularWhitespace , self :: no_missing_var_function :: NoMissingVarFunction , diff --git a/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs b/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs new file mode 100644 index 000000000000..94da4204046e --- /dev/null +++ b/crates/biome_css_analyze/src/lint/nursery/no_descending_specificity.rs @@ -0,0 +1,203 @@ +use rustc_hash::{FxHashMap, FxHashSet}; + +use biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic, RuleSource}; +use biome_console::markup; +use biome_css_semantic::model::{Rule as CssSemanticRule, RuleId, SemanticModel, Specificity}; +use biome_css_syntax::{AnyCssSelector, CssRoot}; +use biome_rowan::TextRange; + +use biome_rowan::AstNode; + +use crate::services::semantic::Semantic; + +declare_lint_rule! { + /// Disallow a lower specificity selector from coming after a higher specificity selector. + /// + /// This rule prohibits placing selectors with lower specificity after selectors with higher specificity. + /// By maintaining the order of the source and specificity as consistently as possible, it enhances readability. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```css,expect_diagnostic + /// b a { color: red; } + /// a { color: red; } + /// ``` + /// + /// ```css,expect_diagnostic + /// a { + /// & > b { color: red; } + /// } + /// b { color: red; } + /// ``` + /// + /// ```css,expect_diagnostic + /// :root input { + /// color: red; + /// } + /// html input { + /// color: red; + /// } + /// ``` + /// + /// + /// ### Valid + /// + /// ```css + /// a { color: red; } + /// b a { color: red; } + /// ``` + /// + /// ```css + /// b { color: red; } + /// a { + /// & > b { color: red; } + /// } + /// ``` + /// + /// ```css + /// a:hover { color: red; } + /// a { color: red; } + /// ``` + /// + /// ```css + /// a b { + /// color: red; + /// } + /// /* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */ + /// :where(a) :is(b) { + /// color: blue; + /// } + /// ``` + /// + pub NoDescendingSpecificity { + version: "next", + name: "noDescendingSpecificity", + language: "css", + recommended: true, + sources: &[RuleSource::Stylelint("no-descending-specificity")], + } +} + +#[derive(Debug)] +pub struct DescendingSelector { + high: (TextRange, Specificity), + low: (TextRange, Specificity), +} +/// find tail selector +/// ```css +/// a b:hover { +/// ^^^^^^^ +/// } +/// ``` +fn find_tail_selector(selector: &AnyCssSelector) -> Option { + match selector { + AnyCssSelector::CssCompoundSelector(s) => { + let simple = s + .simple_selector() + .map_or(String::new(), |s| s.syntax().text_trimmed().to_string()); + let sub = s.sub_selectors().syntax().text_trimmed().to_string(); + + let last_selector = [simple, sub].join(""); + Some(last_selector) + } + AnyCssSelector::CssComplexSelector(s) => { + s.right().as_ref().ok().and_then(find_tail_selector) + } + _ => None, + } +} + +/// This function traverses the CSS rules starting from the given rule and checks for selectors that have the same tail selector. +/// For each selector, it compares its specificity with the previously encountered specificity of the same tail selector. +/// If a lower specificity selector is found after a higher specificity selector with the same tail selector, it records this as a descending selector. +fn find_descending_selector( + rule: &CssSemanticRule, + model: &SemanticModel, + visited_rules: &mut FxHashSet, + visited_selectors: &mut FxHashMap, + descending_selectors: &mut Vec, +) { + if visited_rules.contains(&rule.id) { + return; + } else { + visited_rules.insert(rule.id); + }; + + for selector in &rule.selectors { + let tail_selector = if let Some(s) = find_tail_selector(&selector.original) { + s + } else { + continue; + }; + + if let Some((last_textrange, last_specificity)) = visited_selectors.get(&tail_selector) { + if last_specificity > &selector.specificity { + descending_selectors.push(DescendingSelector { + high: (*last_textrange, last_specificity.clone()), + low: (selector.range, selector.specificity.clone()), + }); + } + } else { + visited_selectors.insert( + tail_selector, + (selector.range, selector.specificity.clone()), + ); + } + } + + for child_id in &rule.child_ids { + if let Some(child_rule) = model.get_rule_by_id(*child_id) { + find_descending_selector( + child_rule, + model, + visited_rules, + visited_selectors, + descending_selectors, + ); + } + } +} + +impl Rule for NoDescendingSpecificity { + type Query = Semantic; + type State = DescendingSelector; + type Signals = Vec; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let model = ctx.model(); + let mut visited_rules = FxHashSet::default(); + let mut visited_selectors = FxHashMap::default(); + let mut descending_selectors = Vec::new(); + for rule in model.rules() { + find_descending_selector( + rule, + model, + &mut visited_rules, + &mut visited_selectors, + &mut descending_selectors, + ); + } + + descending_selectors + } + + fn diagnostic(_: &RuleContext, node: &Self::State) -> Option { + Some( + RuleDiagnostic::new( + rule_category!(), + node.low.0, + markup! { + "Descending specificity selector found. This selector specificity is "{node.low.1.to_string()} + }, + ).detail(node.high.0, markup!( + "This selector specificity is "{node.high.1.to_string()} + )) + .note(markup! { + "Descending specificity selector may not applied. Consider rearranging the order of the selectors. See ""MDN web docs"" for more details." + }), + ) + } +} diff --git a/crates/biome_css_analyze/src/options.rs b/crates/biome_css_analyze/src/options.rs index 8f09248e25d6..c91337050f9b 100644 --- a/crates/biome_css_analyze/src/options.rs +++ b/crates/biome_css_analyze/src/options.rs @@ -2,6 +2,7 @@ use crate::lint; +pub type NoDescendingSpecificity = < lint :: nursery :: no_descending_specificity :: NoDescendingSpecificity as biome_analyze :: Rule > :: Options ; pub type NoDuplicateAtImportRules = < lint :: suspicious :: no_duplicate_at_import_rules :: NoDuplicateAtImportRules as biome_analyze :: Rule > :: Options ; pub type NoDuplicateCustomProperties = < lint :: nursery :: no_duplicate_custom_properties :: NoDuplicateCustomProperties as biome_analyze :: Rule > :: Options ; pub type NoDuplicateFontNames = < lint :: suspicious :: no_duplicate_font_names :: NoDuplicateFontNames as biome_analyze :: Rule > :: Options ; diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css new file mode 100644 index 000000000000..dabe1edd13c1 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css @@ -0,0 +1,7 @@ +b a { + color: red; +} + +a { + color: red; +} diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap new file mode 100644 index 000000000000..e860dea23937 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/complex_selector.invalid.css.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: complex_selector.invalid.css +--- +# Input +```css +b a { + color: red; +} + +a { + color: red; +} + +``` + +# Diagnostics +``` +complex_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Descending specificity selector found. This selector specificity is (0, 0, 1) + + 3 │ } + 4 │ + > 5 │ a { + │ ^ + 6 │ color: red; + 7 │ } + + i This selector specificity is (0, 0, 2) + + > 1 │ b a { + │ ^^^ + 2 │ color: red; + 3 │ } + + i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css new file mode 100644 index 000000000000..f4b9ff63a876 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css @@ -0,0 +1,15 @@ +:is(#a, a) f { + color: red; +} + +:is(a, b, c, d) f { + color: red; +} + +:is(#fake#fake#fake#fake#fake#fake, *) g { + color: red; +} + +:where(*) g { + color: red; +} \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap new file mode 100644 index 000000000000..03b6d35bb478 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/function_pseudo_selector.invalid.css.snap @@ -0,0 +1,73 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: function_pseudo_selector.invalid.css +--- +# Input +```css +:is(#a, a) f { + color: red; +} + +:is(a, b, c, d) f { + color: red; +} + +:is(#fake#fake#fake#fake#fake#fake, *) g { + color: red; +} + +:where(*) g { + color: red; +} +``` + +# Diagnostics +``` +function_pseudo_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━ + + ! Descending specificity selector found. This selector specificity is (0, 0, 2) + + 3 │ } + 4 │ + > 5 │ :is(a, b, c, d) f { + │ ^^^^^^^^^^^^^^^^^ + 6 │ color: red; + 7 │ } + + i This selector specificity is (1, 0, 1) + + > 1 │ :is(#a, a) f { + │ ^^^^^^^^^^^^ + 2 │ color: red; + 3 │ } + + i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details. + + +``` + +``` +function_pseudo_selector.invalid.css:13:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━ + + ! Descending specificity selector found. This selector specificity is (0, 0, 1) + + 11 │ } + 12 │ + > 13 │ :where(*) g { + │ ^^^^^^^^^^^ + 14 │ color: red; + 15 │ } + + i This selector specificity is (6, 0, 1) + + 7 │ } + 8 │ + > 9 │ :is(#fake#fake#fake#fake#fake#fake, *) g { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ color: red; + 11 │ } + + i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css new file mode 100644 index 000000000000..f44130b1de57 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css @@ -0,0 +1,9 @@ +a { + & > b { + color: red; + } +} + +b { + color: red; +} \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap new file mode 100644 index 000000000000..8cadc52ec585 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/nested.invalid.css.snap @@ -0,0 +1,42 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: nested.invalid.css +--- +# Input +```css +a { + & > b { + color: red; + } +} + +b { + color: red; +} +``` + +# Diagnostics +``` +nested.invalid.css:7:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Descending specificity selector found. This selector specificity is (0, 0, 1) + + 5 │ } + 6 │ + > 7 │ b { + │ ^ + 8 │ color: red; + 9 │ } + + i This selector specificity is (0, 0, 2) + + 1 │ a { + > 2 │ & > b { + │ ^^^^^ + 3 │ color: red; + 4 │ } + + i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css new file mode 100644 index 000000000000..05a78fadd6eb --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css @@ -0,0 +1,7 @@ +a:hover #b { + color: red; +} + +a #b { + color: red; +} diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap new file mode 100644 index 000000000000..d33be5459b12 --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/simple_pseudo_selector.invalid.css.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: simple_pseudo_selector.invalid.css +--- +# Input +```css +a:hover #b { + color: red; +} + +a #b { + color: red; +} + +``` + +# Diagnostics +``` +simple_pseudo_selector.invalid.css:5:1 lint/nursery/noDescendingSpecificity ━━━━━━━━━━━━━━━━━━━━━━━━ + + ! Descending specificity selector found. This selector specificity is (1, 0, 1) + + 3 │ } + 4 │ + > 5 │ a #b { + │ ^^^^ + 6 │ color: red; + 7 │ } + + i This selector specificity is (1, 1, 1) + + > 1 │ a:hover #b { + │ ^^^^^^^^^^ + 2 │ color: red; + 3 │ } + + i Descending specificity selector may not applied. Consider rearranging the order of the selectors. See MDN web docs for more details. + + +``` diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css new file mode 100644 index 000000000000..730a7a0b314e --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css @@ -0,0 +1,51 @@ +a { + color: red; +} + +b a { + color: red; +} + +d { + color: red; +} + +c { + &>d { + color: red; + } +} + +e:hover { + color: red; +} + +e { + color: red; +} + +:is(a, b, c, d) f { + color: red; +} + +:is(#a, a) f { + color: red; +} + +:where(#fake#fake#fake#fake#fake#fake, *) g { + color: red; +} + +:where(*) g { + color: red; +} + + +#h h { + color: red; +} + +/* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */ +:where(#h) :is(h) { + color: red; +} \ No newline at end of file diff --git a/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap new file mode 100644 index 000000000000..78ddffc7cafb --- /dev/null +++ b/crates/biome_css_analyze/tests/specs/nursery/noDescendingSpecificity/valid.css.snap @@ -0,0 +1,58 @@ +--- +source: crates/biome_css_analyze/tests/spec_tests.rs +expression: valid.css +--- +# Input +```css +a { + color: red; +} + +b a { + color: red; +} + +d { + color: red; +} + +c { + &>d { + color: red; + } +} + +e:hover { + color: red; +} + +e { + color: red; +} + +:is(a, b, c, d) f { + color: red; +} + +:is(#a, a) f { + color: red; +} + +:where(#fake#fake#fake#fake#fake#fake, *) g { + color: red; +} + +:where(*) g { + color: red; +} + + +#h h { + color: red; +} + +/* This selector is overwritten by the one above it, but this is not an error because the rule only evaluates it as a compound selector */ +:where(#h) :is(h) { + color: red; +} +``` diff --git a/crates/biome_css_semantic/src/events.rs b/crates/biome_css_semantic/src/events.rs index 64b5dee7fedd..b2e6bb40c246 100644 --- a/crates/biome_css_semantic/src/events.rs +++ b/crates/biome_css_semantic/src/events.rs @@ -3,11 +3,12 @@ use std::{borrow::Cow, collections::VecDeque}; use biome_css_syntax::{ AnyCssSelector, CssDeclarationBlock, CssRelativeSelector, CssSyntaxKind::*, }; -use biome_rowan::{AstNode, SyntaxNodeCast, TextRange}; +use biome_rowan::{AstNode, SyntaxNodeCast, SyntaxNodeOptionExt, TextRange}; use crate::{ model::{CssProperty, CssValue}, semantic_model::model::Specificity, + specificity::{evaluate_complex_selector, evaluate_compound_selector}, }; const ROOT_SELECTOR: &str = ":root"; @@ -19,6 +20,7 @@ pub enum SemanticEvent { SelectorDeclaration { name: String, range: TextRange, + original: AnyCssSelector, specificity: Specificity, }, PropertyDeclaration { @@ -70,11 +72,23 @@ impl SemanticEventExtractor { self.current_rule_stack.push(range); } CSS_SELECTOR_LIST => { + if !matches!( + node.parent().kind(), + Some(CSS_QUALIFIED_RULE | CSS_NESTED_QUALIFIED_RULE) + ) { + return; + }; node.children() .filter_map(AnyCssSelector::cast) .for_each(|s| self.process_selector(s)); } CSS_RELATIVE_SELECTOR_LIST => { + if !matches!( + node.parent().kind(), + Some(CSS_QUALIFIED_RULE | CSS_NESTED_QUALIFIED_RULE) + ) { + return; + }; node.children() .filter_map(CssRelativeSelector::cast) .filter_map(|s| s.selector().ok()) @@ -108,20 +122,28 @@ impl SemanticEventExtractor { fn process_selector(&mut self, selector: AnyCssSelector) { match selector { AnyCssSelector::CssComplexSelector(s) => { - if let Ok(l) = s.left() { - self.add_selector_event(Cow::Borrowed(&l.text()), l.range()); - } - if let Ok(r) = s.right() { - self.add_selector_event(Cow::Borrowed(&r.text()), r.range()); - } + let specificity = evaluate_complex_selector(&s); + self.add_selector_event( + Cow::Borrowed(&s.text()), + s.range(), + AnyCssSelector::CssComplexSelector(s), + specificity, + ); } + AnyCssSelector::CssCompoundSelector(selector) => { let selector_text = selector.text(); if selector_text == ROOT_SELECTOR { self.stash.push_back(SemanticEvent::RootSelectorStart); self.is_in_root_selector = true; } - self.add_selector_event(Cow::Borrowed(&selector_text), selector.range()) + let specificity = evaluate_compound_selector(&selector); + self.add_selector_event( + Cow::Borrowed(&selector_text), + selector.range(), + AnyCssSelector::CssCompoundSelector(selector), + specificity, + ) } _ => {} } @@ -198,11 +220,18 @@ impl SemanticEventExtractor { }); } - fn add_selector_event(&mut self, name: Cow, range: TextRange) { + fn add_selector_event( + &mut self, + name: Cow, + range: TextRange, + original: AnyCssSelector, + specificity: Specificity, + ) { self.stash.push_back(SemanticEvent::SelectorDeclaration { name: name.into_owned(), range, - specificity: Specificity(0, 0, 0), // TODO: Implement this + original, + specificity, }); } diff --git a/crates/biome_css_semantic/src/semantic_model/builder.rs b/crates/biome_css_semantic/src/semantic_model/builder.rs index ec263240e54f..864c169707aa 100644 --- a/crates/biome_css_semantic/src/semantic_model/builder.rs +++ b/crates/biome_css_semantic/src/semantic_model/builder.rs @@ -6,7 +6,7 @@ use rustc_hash::FxHashMap; use super::model::{ CssDeclaration, CssGlobalCustomVariable, Rule, RuleId, Selector, SemanticModel, - SemanticModelData, + SemanticModelData, Specificity, }; use crate::events::SemanticEvent; @@ -66,6 +66,7 @@ impl SemanticModelBuilder { range, parent_id, child_ids: Vec::new(), + specificity: Specificity::default(), }; if let Some(&parent_id) = self.current_rule_stack.last() { @@ -95,15 +96,28 @@ impl SemanticModelBuilder { SemanticEvent::SelectorDeclaration { name, range, + original, specificity, } => { - if let Some(current_rule) = self.current_rule_stack.last_mut() { + let parent_specificity = self + .current_rule_stack + .last() + .and_then(|rule_id| self.rules_by_id.get(rule_id)) + .and_then(|rule| rule.parent_id) + .and_then(|parent_id| self.rules_by_id.get(&parent_id)) + .map(|parent| parent.specificity.clone()) + .unwrap_or_default(); + + if let Some(current_rule) = self.current_rule_stack.last() { let current_rule = self.rules_by_id.get_mut(current_rule).unwrap(); current_rule.selectors.push(Selector { name, range, - specificity, + original, + specificity: parent_specificity + specificity.clone(), }); + + current_rule.specificity += specificity; } } SemanticEvent::PropertyDeclaration { diff --git a/crates/biome_css_semantic/src/semantic_model/mod.rs b/crates/biome_css_semantic/src/semantic_model/mod.rs index 4372a8fdfae3..2038b0177447 100644 --- a/crates/biome_css_semantic/src/semantic_model/mod.rs +++ b/crates/biome_css_semantic/src/semantic_model/mod.rs @@ -1,5 +1,6 @@ pub mod builder; pub mod model; +pub mod specificity; use biome_css_syntax::CssRoot; use biome_rowan::AstNode; diff --git a/crates/biome_css_semantic/src/semantic_model/model.rs b/crates/biome_css_semantic/src/semantic_model/model.rs index 8dc2a2e3a562..f65e9e75f125 100644 --- a/crates/biome_css_semantic/src/semantic_model/model.rs +++ b/crates/biome_css_semantic/src/semantic_model/model.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeMap, rc::Rc}; -use biome_css_syntax::CssRoot; +use biome_css_syntax::{AnyCssSelector, CssRoot}; use biome_rowan::{TextRange, TextSize}; use rustc_hash::FxHashMap; @@ -108,6 +108,9 @@ pub struct Rule { pub child_ids: Vec, /// The text range of this rule in the source document. pub range: TextRange, + /// Specificity context of this rule + /// See https://drafts.csswg.org/selectors-4/#specificity-rules + pub specificity: Specificity, } /// Represents a CSS selector. @@ -123,6 +126,7 @@ pub struct Selector { pub name: String, /// The text range of the selector in the source document. pub range: TextRange, + pub original: AnyCssSelector, /// The specificity of the selector. pub specificity: Specificity, } @@ -135,6 +139,46 @@ pub struct Selector { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Specificity(pub u32, pub u32, pub u32); +/// In CSS, when selectors are combined (e.g., in a compound selector), their specificities are summed. +/// This implementation mirrors that behavior by adding the ID, class, and type selector counts separately. +/// +/// Consider the following selector. +/// ```css +/// #id .class {} +/// ``` +/// +/// The specificity of each component is as follows: +/// - `#id` has a specificity of `Specificity(1, 0, 0)` +/// - `.class` has a specificity of `Specificity(0, 1, 0)` +/// +/// Therefore, the combined selector `#id .class` has a specificity of: +/// - `Specificity(1 + 0, 0 + 1, 0 + 0) = Specificity(1, 1, 0)` +/// +/// More details https://drafts.csswg.org/selectors/#example-d97bd125 +impl std::ops::Add for Specificity { + type Output = Specificity; + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2) + } +} + +impl std::ops::AddAssign for Specificity { + fn add_assign(&mut self, rhs: Self) { + self.0 = rhs.0; + self.1 = rhs.1; + self.2 = rhs.2; + } +} + +/// Formats the `Specificity` instance to match the notation used in the official CSS specification. +/// +/// More details https://www.w3.org/TR/selectors-4/#specificity-rules +impl std::fmt::Display for Specificity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {}, {})", self.0, self.1, self.2) + } +} + /// Represents a CSS declaration (property-value pair). /// ```css /// a { diff --git a/crates/biome_css_semantic/src/semantic_model/specificity.rs b/crates/biome_css_semantic/src/semantic_model/specificity.rs new file mode 100644 index 000000000000..041713d67e42 --- /dev/null +++ b/crates/biome_css_semantic/src/semantic_model/specificity.rs @@ -0,0 +1,183 @@ +use crate::semantic_model::model::Specificity; + +use biome_css_syntax::{ + AnyCssCompoundSelector, AnyCssPseudoClass, AnyCssRelativeSelector, AnyCssSelector, + AnyCssSimpleSelector, AnyCssSubSelector, CssComplexSelector, CssCompoundSelector, + CssPseudoClassSelector, +}; + +use biome_rowan::{AstNodeList, AstSeparatedList}; + +const ID_SPECIFICITY: Specificity = Specificity(1, 0, 0); +const CLASS_SPECIFICITY: Specificity = Specificity(0, 1, 0); +const TYPE_SPECIFICITY: Specificity = Specificity(0, 0, 1); +const ZERO_SPECIFICITY: Specificity = Specificity(0, 0, 0); + +fn evaluate_any_simple_selector(selector: &AnyCssSimpleSelector) -> Specificity { + match selector { + AnyCssSimpleSelector::CssTypeSelector(_) => TYPE_SPECIFICITY, + AnyCssSimpleSelector::CssUniversalSelector(_) => ZERO_SPECIFICITY, + } +} + +/// See https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity#the_is_not_has_and_css_nesting_exceptions +const fn evaluate_pseudo_function_selector(name: &str) -> Option { + match name.as_bytes() { + b"where" => None, + b"is" | b"not" | b"has" | b"matches" => Some(ZERO_SPECIFICITY), + _ => Some(CLASS_SPECIFICITY), + } +} + +fn evaluate_any_pseudo_class(class: &AnyCssPseudoClass) -> Specificity { + // https://www.w3.org/TR/selectors-4/#specificity-rules + match class { + AnyCssPseudoClass::CssBogusPseudoClass(_) => ZERO_SPECIFICITY, + AnyCssPseudoClass::CssPseudoClassFunctionCompoundSelector(selector) => { + CLASS_SPECIFICITY + + selector + .selector() + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_compound_selector(&s)) + } + AnyCssPseudoClass::CssPseudoClassFunctionCompoundSelectorList(selector_list) => { + let list_max = selector_list + .compound_selectors() + .iter() + .map(|s| s.map_or(ZERO_SPECIFICITY, |s| evaluate_any_compound_selector(&s))) + .reduce(|acc, e| acc.max(e)) + .unwrap_or(ZERO_SPECIFICITY); + + CLASS_SPECIFICITY + list_max + } + AnyCssPseudoClass::CssPseudoClassFunctionIdentifier(_) => CLASS_SPECIFICITY, + AnyCssPseudoClass::CssPseudoClassFunctionNth(_) => CLASS_SPECIFICITY, + AnyCssPseudoClass::CssPseudoClassFunctionRelativeSelectorList(selector_list) => { + if let Some(base) = selector_list + .name_token() + .ok() + .and_then(|name| evaluate_pseudo_function_selector(name.text())) + { + let list_max = selector_list + .relative_selectors() + .iter() + .map(|relative_selector| { + relative_selector + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_relative_selector(&s)) + }) + .reduce(|acc, e| acc.max(e)) + .unwrap_or(ZERO_SPECIFICITY); + base + list_max + } else { + ZERO_SPECIFICITY + } + } + AnyCssPseudoClass::CssPseudoClassFunctionSelector(s) => { + if let Some(base) = s + .name() + .ok() + .and_then(|name| evaluate_pseudo_function_selector(name.text())) + { + base + s.selector().map_or(ZERO_SPECIFICITY, |selector| { + evaluate_any_selector(&selector) + }) + } else { + ZERO_SPECIFICITY + } + } + AnyCssPseudoClass::CssPseudoClassFunctionSelectorList(selector_list) => { + if let Some(base) = selector_list + .name() + .ok() + .and_then(|name| evaluate_pseudo_function_selector(name.text())) + { + let list_max = selector_list + .selectors() + .iter() + .map(|selector| { + selector.map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s)) + }) + .reduce(|acc, e| acc.max(e)) + .unwrap_or(ZERO_SPECIFICITY); + base + list_max + } else { + ZERO_SPECIFICITY + } + } + AnyCssPseudoClass::CssPseudoClassFunctionValueList(_) => CLASS_SPECIFICITY, + AnyCssPseudoClass::CssPseudoClassIdentifier(_) => CLASS_SPECIFICITY, + } +} + +fn evaluate_pseudo_selector(selector: &CssPseudoClassSelector) -> Specificity { + match selector.class() { + Ok(any_pseudo_class) => evaluate_any_pseudo_class(&any_pseudo_class), + Err(_) => ZERO_SPECIFICITY, + } +} + +fn evaluate_any_subselector(selector: &AnyCssSubSelector) -> Specificity { + // https://www.w3.org/TR/selectors-4/#typedef-subclass-selector + match selector { + AnyCssSubSelector::CssIdSelector(_) => ID_SPECIFICITY, + AnyCssSubSelector::CssClassSelector(_) => CLASS_SPECIFICITY, + AnyCssSubSelector::CssAttributeSelector(_) => CLASS_SPECIFICITY, + AnyCssSubSelector::CssPseudoClassSelector(s) => evaluate_pseudo_selector(s), + AnyCssSubSelector::CssPseudoElementSelector(_) => TYPE_SPECIFICITY, + AnyCssSubSelector::CssBogusSubSelector(_) => ZERO_SPECIFICITY, + } +} + +pub fn evaluate_compound_selector(selector: &CssCompoundSelector) -> Specificity { + let nested_specificity = ZERO_SPECIFICITY; // TODO: Implement this + + let simple_specificity = selector + .simple_selector() + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_simple_selector(&s)); + let subselector_specificity = selector + .sub_selectors() + .iter() + .map(|s| evaluate_any_subselector(&s)) + .reduce(|acc, e| acc + e) + .unwrap_or(ZERO_SPECIFICITY); + + nested_specificity + simple_specificity + subselector_specificity +} + +fn evaluate_any_compound_selector(selector: &AnyCssCompoundSelector) -> Specificity { + match selector { + AnyCssCompoundSelector::CssBogusSelector(_) => ZERO_SPECIFICITY, + AnyCssCompoundSelector::CssCompoundSelector(s) => evaluate_compound_selector(s), + } +} + +pub fn evaluate_complex_selector(selector: &CssComplexSelector) -> Specificity { + let left_specificity = selector + .left() + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s)); + let right_specificity = selector + .right() + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s)); + + left_specificity + right_specificity +} + +pub fn evaluate_any_selector(selector: &AnyCssSelector) -> Specificity { + match selector { + AnyCssSelector::CssCompoundSelector(s) => evaluate_compound_selector(s), + AnyCssSelector::CssComplexSelector(s) => evaluate_complex_selector(s), + AnyCssSelector::CssBogusSelector(_) => ZERO_SPECIFICITY, + AnyCssSelector::CssMetavariable(_) => { + // TODO: Implement this + ZERO_SPECIFICITY + } + } +} + +fn evaluate_any_relative_selector(selector: &AnyCssRelativeSelector) -> Specificity { + match selector { + AnyCssRelativeSelector::CssBogusSelector(_) => ZERO_SPECIFICITY, + AnyCssRelativeSelector::CssRelativeSelector(s) => s + .selector() + .map_or(ZERO_SPECIFICITY, |s| evaluate_any_selector(&s)), + } +} diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index d39031092aa9..c8321d685ad8 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -136,6 +136,7 @@ define_categories! { "lint/nursery/noColorInvalidHex": "https://biomejs.dev/linter/rules/no-color-invalid-hex", "lint/nursery/noCommonJs": "https://biomejs.dev/linter/rules/no-common-js", "lint/nursery/noConsole": "https://biomejs.dev/linter/rules/no-console", + "lint/nursery/noDescendingSpecificity": "https://biomejs.dev/linter/rules/no-descending-specificity", "lint/nursery/noDoneCallback": "https://biomejs.dev/linter/rules/no-done-callback", "lint/nursery/noDuplicateAtImportRules": "https://biomejs.dev/linter/rules/no-duplicate-at-import-rules", "lint/nursery/noDuplicateCustomProperties": "https://biomejs.dev/linter/rules/no-duplicate-custom-properties", diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 81fa5a018fc5..d10410948d90 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -1222,6 +1222,10 @@ export interface Nursery { * Disallow use of CommonJs module system in favor of ESM style imports. */ noCommonJs?: RuleConfiguration_for_Null; + /** + * Disallow a lower specificity selector from coming after a higher specificity selector. + */ + noDescendingSpecificity?: RuleConfiguration_for_Null; /** * Disallow duplicate custom properties within declaration blocks. */ @@ -2833,6 +2837,7 @@ export type Category = | "lint/nursery/noColorInvalidHex" | "lint/nursery/noCommonJs" | "lint/nursery/noConsole" + | "lint/nursery/noDescendingSpecificity" | "lint/nursery/noDoneCallback" | "lint/nursery/noDuplicateAtImportRules" | "lint/nursery/noDuplicateCustomProperties" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index b45098e9db28..9f725c5c0a2e 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -2083,6 +2083,13 @@ { "type": "null" } ] }, + "noDescendingSpecificity": { + "description": "Disallow a lower specificity selector from coming after a higher specificity selector.", + "anyOf": [ + { "$ref": "#/definitions/RuleConfiguration" }, + { "type": "null" } + ] + }, "noDuplicateCustomProperties": { "description": "Disallow duplicate custom properties within declaration blocks.", "anyOf": [ From 69faa783f8374e11cf621b1e96b605a416a873fc Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Wed, 2 Oct 2024 08:05:00 -0500 Subject: [PATCH 19/32] Add formatting for predicate list and pattern where --- .../src/grit/lists/predicate_list.rs | 12 ++++++++++- .../src/grit/patterns/pattern_where.rs | 12 ++++------- .../src/grit/predicates/predicate_and.rs | 21 ++++++++++++++++--- .../specs/grit/patterns/if_pattern.grit.snap | 6 ++++-- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs index 0a93e84841eb..5a76e9f9c77f 100644 --- a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs @@ -5,6 +5,16 @@ pub(crate) struct FormatGritPredicateList; impl FormatRule for FormatGritPredicateList { type Context = GritFormatContext; fn fmt(&self, node: &GritPredicateList, f: &mut GritFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let mut join = f.join_nodes_with_hardline(); + + for predicate in node { + let pred_clone = predicate.clone().unwrap(); + join.entry( + predicate?.syntax(), + &format_or_verbatim(pred_clone.format()), + ); + } + + join.finish() } } diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index f2f19502b638..4864ee666396 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -11,13 +11,9 @@ impl FormatNodeRule for FormatGritPatternWhere { side_condition, where_token, } = node.as_fields(); - write!( - f, - [ - pattern.format(), - side_condition.format(), - where_token.format() - ] - ) + let pattern_format = pattern.format(); + let side_condition = side_condition.format(); + let where_token = where_token.format(); + write!(f, [pattern_format, side_condition, where_token,]) } } diff --git a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs index d48416ef1524..0e783024bde3 100644 --- a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs +++ b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs @@ -1,10 +1,25 @@ use crate::prelude::*; -use biome_grit_syntax::GritPredicateAnd; -use biome_rowan::AstNode; +use biome_formatter::write; +use biome_grit_syntax::{GritPredicateAnd, GritPredicateAndFields}; #[derive(Debug, Clone, Default)] pub(crate) struct FormatGritPredicateAnd; impl FormatNodeRule for FormatGritPredicateAnd { fn fmt_fields(&self, node: &GritPredicateAnd, f: &mut GritFormatter) -> FormatResult<()> { - format_verbatim_node(node.syntax()).fmt(f) + let GritPredicateAndFields { + and_token, + l_curly_token, + predicates, + r_curly_token, + } = node.as_fields(); + + write!( + f, + [ + and_token.format(), + l_curly_token.format(), + predicates.format(), + r_curly_token.format() + ] + ) } } diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap index c792a2b76d7e..88bf9ca6a559 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap @@ -37,7 +37,8 @@ Attribute Position: Auto } else { $method => `console.warn` } -}``` +} +``` @@ -62,7 +63,8 @@ Attribute Position: Auto } else { $method => `console.warn` } -}``` +} +``` From fe7667d24249fc07dff9f84fe872a469539e3d41 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Thu, 3 Oct 2024 18:00:46 -0500 Subject: [PATCH 20/32] Reorder tokens --- crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index 4864ee666396..dbe74d0a3e02 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -14,6 +14,6 @@ impl FormatNodeRule for FormatGritPatternWhere { let pattern_format = pattern.format(); let side_condition = side_condition.format(); let where_token = where_token.format(); - write!(f, [pattern_format, side_condition, where_token,]) + write!(f, [pattern_format, where_token, side_condition,]) } } From 29ca5c43df129dde9596bb50b899500df0de6249 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Fri, 4 Oct 2024 21:51:28 -0500 Subject: [PATCH 21/32] Add where predicate --- .../src/grit/patterns/pattern_where.rs | 12 ++++++- .../src/grit/predicates/predicate_and.rs | 32 +++++++++-------- .../specs/grit/patterns/if_pattern.grit.snap | 34 +++++++++++++++++-- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index dbe74d0a3e02..c19c4948efb6 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -14,6 +14,16 @@ impl FormatNodeRule for FormatGritPatternWhere { let pattern_format = pattern.format(); let side_condition = side_condition.format(); let where_token = where_token.format(); - write!(f, [pattern_format, where_token, side_condition,]) + write!( + f, + [ + space(), + pattern_format, + space(), + where_token, + side_condition, + hard_line_break(), + ] + ) } } diff --git a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs index 0e783024bde3..5a4403150a0a 100644 --- a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs +++ b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs @@ -5,21 +5,23 @@ use biome_grit_syntax::{GritPredicateAnd, GritPredicateAndFields}; pub(crate) struct FormatGritPredicateAnd; impl FormatNodeRule for FormatGritPredicateAnd { fn fmt_fields(&self, node: &GritPredicateAnd, f: &mut GritFormatter) -> FormatResult<()> { - let GritPredicateAndFields { - and_token, - l_curly_token, - predicates, - r_curly_token, - } = node.as_fields(); + // let GritPredicateAndFields { + // and_token, + // l_curly_token, + // predicates, + // r_curly_token, + // } = node.as_fields(); - write!( - f, - [ - and_token.format(), - l_curly_token.format(), - predicates.format(), - r_curly_token.format() - ] - ) + // write!( + // f, + // [ + // and_token.format(), + // l_curly_token.format(), + // predicates.format(), + // r_curly_token.format() + // ] + // ) + + format_verbatim_node(node.syntax()).fmt(f) } } diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap index 7a6e27ffb1f9..e581e38f7cec 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap @@ -12,6 +12,7 @@ info: grit/patterns/if_pattern.grit $method => `console.warn` } } + ``` @@ -26,11 +27,39 @@ Indent style: Tab Indent width: 2 Line ending: LF Line width: 80 +Bracket spacing: true Attribute Position: Auto ----- ```grit -`$method('$message')` where { +`$method('$message')` where{ + if ($message <: r"Hello, .*!") { + $method => `console.info` + } else { + $method => `console.warn` + } +} +``` + + + +## Unimplemented nodes/tokens + +"`$method('$message')` " => 0..22 +"{\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 27..140 +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Bracket spacing: true +Attribute Position: Auto +----- + +```grit +`$method('$message')` where{ if ($message <: r"Hello, .*!") { $method => `console.info` } else { @@ -43,4 +72,5 @@ Attribute Position: Auto ## Unimplemented nodes/tokens -"`$method('$message')` where {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 0..141 +"`$method('$message')` " => 0..22 +"{\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 27..140 From de984317f5276805e967137ea131ef74b9e550d0 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Fri, 4 Oct 2024 21:51:45 -0500 Subject: [PATCH 22/32] Remove comments --- .../src/grit/predicates/predicate_and.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs index 5a4403150a0a..b64a59efc1db 100644 --- a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs +++ b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs @@ -5,23 +5,6 @@ use biome_grit_syntax::{GritPredicateAnd, GritPredicateAndFields}; pub(crate) struct FormatGritPredicateAnd; impl FormatNodeRule for FormatGritPredicateAnd { fn fmt_fields(&self, node: &GritPredicateAnd, f: &mut GritFormatter) -> FormatResult<()> { - // let GritPredicateAndFields { - // and_token, - // l_curly_token, - // predicates, - // r_curly_token, - // } = node.as_fields(); - - // write!( - // f, - // [ - // and_token.format(), - // l_curly_token.format(), - // predicates.format(), - // r_curly_token.format() - // ] - // ) - format_verbatim_node(node.syntax()).fmt(f) } } From 3cb4ad4c285576cec24a53727ce6fc3b47fe9a2a Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Fri, 4 Oct 2024 22:08:23 -0500 Subject: [PATCH 23/32] Add simple whitespace --- .../src/grit/patterns/pattern_where.rs | 2 +- .../src/grit/predicates/predicate_and.rs | 3 +-- .../tests/specs/grit/patterns/if_pattern.grit | 2 +- .../tests/specs/grit/patterns/if_pattern.grit.snap | 14 +++++++------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index c19c4948efb6..58d0952d02ae 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -21,8 +21,8 @@ impl FormatNodeRule for FormatGritPatternWhere { pattern_format, space(), where_token, + space(), side_condition, - hard_line_break(), ] ) } diff --git a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs index b64a59efc1db..890c07c23a7c 100644 --- a/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs +++ b/crates/biome_grit_formatter/src/grit/predicates/predicate_and.rs @@ -1,6 +1,5 @@ use crate::prelude::*; -use biome_formatter::write; -use biome_grit_syntax::{GritPredicateAnd, GritPredicateAndFields}; +use biome_grit_syntax::GritPredicateAnd; #[derive(Debug, Clone, Default)] pub(crate) struct FormatGritPredicateAnd; impl FormatNodeRule for FormatGritPredicateAnd { diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit index 97bc4bb4f96f..fcbab39e4a64 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit @@ -1,4 +1,4 @@ -`$method('$message')` where { +`$method('$message')`where{ if ($message <: r"Hello, .*!") { $method => `console.info` } else { diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap index e581e38f7cec..a229d07a98b9 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap @@ -5,7 +5,7 @@ info: grit/patterns/if_pattern.grit # Input ```grit -`$method('$message')` where { +`$method('$message')`where{ if ($message <: r"Hello, .*!") { $method => `console.info` } else { @@ -32,7 +32,7 @@ Attribute Position: Auto ----- ```grit -`$method('$message')` where{ +`$method('$message')` where { if ($message <: r"Hello, .*!") { $method => `console.info` } else { @@ -45,8 +45,8 @@ Attribute Position: Auto ## Unimplemented nodes/tokens -"`$method('$message')` " => 0..22 -"{\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 27..140 +"`$method('$message')`" => 0..21 +" {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n" => 27..140 ## Output 1 ----- @@ -59,7 +59,7 @@ Attribute Position: Auto ----- ```grit -`$method('$message')` where{ +`$method('$message')` where { if ($message <: r"Hello, .*!") { $method => `console.info` } else { @@ -72,5 +72,5 @@ Attribute Position: Auto ## Unimplemented nodes/tokens -"`$method('$message')` " => 0..22 -"{\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n}" => 27..140 +"`$method('$message')`" => 0..21 +" {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n" => 27..140 From 73bb6910a407012ec8f9a405a247b81a2e786606 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Sat, 5 Oct 2024 10:47:40 -0500 Subject: [PATCH 24/32] Create specific test for where pattern --- .../src/grit/patterns/pattern_where.rs | 10 ++++---- .../tests/specs/grit/patterns/if_pattern.grit | 7 ------ .../specs/grit/patterns/where_pattern.grit | 3 +++ ...tern.grit.snap => where_pattern.grit.snap} | 24 +++++-------------- 4 files changed, 13 insertions(+), 31 deletions(-) delete mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit create mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit rename crates/biome_grit_formatter/tests/specs/grit/patterns/{if_pattern.grit.snap => where_pattern.grit.snap} (51%) diff --git a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs index 58d0952d02ae..5f24ffec270e 100644 --- a/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs +++ b/crates/biome_grit_formatter/src/grit/patterns/pattern_where.rs @@ -11,18 +11,16 @@ impl FormatNodeRule for FormatGritPatternWhere { side_condition, where_token, } = node.as_fields(); - let pattern_format = pattern.format(); - let side_condition = side_condition.format(); - let where_token = where_token.format(); + write!( f, [ space(), - pattern_format, + pattern.format(), space(), - where_token, + where_token.format(), space(), - side_condition, + side_condition.format(), ] ) } diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit b/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit deleted file mode 100644 index fcbab39e4a64..000000000000 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit +++ /dev/null @@ -1,7 +0,0 @@ -`$method('$message')`where{ - if ($message <: r"Hello, .*!") { - $method => `console.info` - } else { - $method => `console.warn` - } -} diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit new file mode 100644 index 000000000000..f23458480d4c --- /dev/null +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit @@ -0,0 +1,3 @@ +`$method('$message')`where{ + +} diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap similarity index 51% rename from crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap rename to crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap index a229d07a98b9..fe900ed23c93 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/if_pattern.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap @@ -1,16 +1,12 @@ --- source: crates/biome_formatter_test/src/snapshot_builder.rs -info: grit/patterns/if_pattern.grit +info: grit/patterns/where_pattern.grit --- # Input ```grit `$method('$message')`where{ - if ($message <: r"Hello, .*!") { - $method => `console.info` - } else { - $method => `console.warn` - } + } ``` @@ -33,11 +29,7 @@ Attribute Position: Auto ```grit `$method('$message')` where { - if ($message <: r"Hello, .*!") { - $method => `console.info` - } else { - $method => `console.warn` - } + } ``` @@ -46,7 +38,7 @@ Attribute Position: Auto ## Unimplemented nodes/tokens "`$method('$message')`" => 0..21 -" {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n" => 27..140 +" {\n\n" => 27..31 ## Output 1 ----- @@ -60,11 +52,7 @@ Attribute Position: Auto ```grit `$method('$message')` where { - if ($message <: r"Hello, .*!") { - $method => `console.info` - } else { - $method => `console.warn` - } + } ``` @@ -73,4 +61,4 @@ Attribute Position: Auto ## Unimplemented nodes/tokens "`$method('$message')`" => 0..21 -" {\n if ($message <: r\"Hello, .*!\") {\n $method => `console.info`\n } else {\n $method => `console.warn`\n }\n" => 27..140 +" {\n\n" => 27..31 From 9d60af709e934b5e83fd3e1d9f9b1909c145243f Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Sat, 5 Oct 2024 12:05:11 -0500 Subject: [PATCH 25/32] Undo change to package.json --- packages/@biomejs/wasm-nodejs/package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/@biomejs/wasm-nodejs/package.json b/packages/@biomejs/wasm-nodejs/package.json index 6276db7ba28c..1ab3959cbd0d 100644 --- a/packages/@biomejs/wasm-nodejs/package.json +++ b/packages/@biomejs/wasm-nodejs/package.json @@ -1,14 +1,15 @@ { - "name": "@biomejs/biome_wasm", + "name": "@biomejs/wasm-nodejs", "collaborators": [ "Biome Developers and Contributors" ], "description": "WebAssembly bindings to the Biome workspace API", - "version": "1.7.3", + "version": "1.8.3", "license": "MIT OR Apache-2.0", "repository": { "type": "git", - "url": "https://github.com/biomejs/biome" + "url": "git+https://github.com/biomejs/biome.git", + "directory": "packages/@biomejs/biome/wasm-nodejs" }, "files": [ "biome_wasm_bg.wasm", From a6593fa5e1e524bc2859fb6916cfd3404dba40c6 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Sun, 6 Oct 2024 12:26:57 -0500 Subject: [PATCH 26/32] Add missing changes to get grit formatter options read in --- crates/biome_grit_formatter/src/context.rs | 27 ++------ crates/biome_grit_formatter/tests/language.rs | 3 +- .../biome_grit_formatter/tests/quick_test.rs | 2 +- .../biome_grit_formatter/tests/spec_tests.rs | 2 +- .../tests/specs/grit/file_node.grit.snap | 21 ------ .../tests/specs/grit/options.json | 1 - .../tests/specs/grit/patterns/options.json | 1 - .../grit/patterns/where_pattern.grit.snap | 24 ------- .../biome_service/src/file_handlers/grit.rs | 68 ++++++++++++++++--- crates/biome_service/src/file_handlers/mod.rs | 7 ++ crates/biome_service/src/settings.rs | 48 +++++++++++++ 11 files changed, 126 insertions(+), 78 deletions(-) delete mode 100644 crates/biome_grit_formatter/tests/specs/grit/options.json delete mode 100644 crates/biome_grit_formatter/tests/specs/grit/patterns/options.json diff --git a/crates/biome_grit_formatter/src/context.rs b/crates/biome_grit_formatter/src/context.rs index cbf6aacb53b8..6180be622d39 100644 --- a/crates/biome_grit_formatter/src/context.rs +++ b/crates/biome_grit_formatter/src/context.rs @@ -4,6 +4,7 @@ use biome_formatter::{ AttributePosition, BracketSpacing, CstFormatContext, FormatContext, FormatOptions, IndentStyle, IndentWidth, LineEnding, LineWidth, QuoteStyle, TransformSourceMap, }; +use biome_grit_syntax::file_source::GritFileSource; use biome_grit_syntax::GritLanguage; use std::fmt::Display; use std::rc::Rc; @@ -54,7 +55,7 @@ impl CstFormatContext for GritFormatContext { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct GritFormatOptions { indent_style: IndentStyle, indent_width: IndentWidth, @@ -62,27 +63,22 @@ pub struct GritFormatOptions { line_width: LineWidth, quote_style: QuoteStyle, attribute_position: AttributePosition, - bracket_spacing: BracketSpacing, + _file_source: GritFileSource, } impl GritFormatOptions { - pub fn new() -> Self { + pub fn new(file_source: GritFileSource) -> Self { Self { + _file_source: file_source, indent_style: IndentStyle::default(), indent_width: IndentWidth::default(), line_ending: LineEnding::default(), line_width: LineWidth::default(), quote_style: QuoteStyle::default(), attribute_position: AttributePosition::default(), - bracket_spacing: BracketSpacing::default(), } } - pub fn with_bracket_spacing(mut self, bracket_spacing: BracketSpacing) -> Self { - self.bracket_spacing = bracket_spacing; - self - } - pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self { self.indent_style = indent_style; self @@ -108,10 +104,6 @@ impl GritFormatOptions { self } - pub fn set_bracket_spacing(&mut self, bracket_spacing: BracketSpacing) { - self.bracket_spacing = bracket_spacing; - } - pub fn set_indent_style(&mut self, indent_style: IndentStyle) { self.indent_style = indent_style; } @@ -132,10 +124,6 @@ impl GritFormatOptions { self.quote_style = quote_style; } - pub fn bracket_spacing(&self) -> BracketSpacing { - self.bracket_spacing - } - pub fn quote_style(&self) -> QuoteStyle { self.quote_style } @@ -151,7 +139,6 @@ impl Display for GritFormatOptions { writeln!(f, "Indent width: {}", self.indent_width.value())?; writeln!(f, "Line ending: {}", self.line_ending)?; writeln!(f, "Line width: {}", self.line_width.value())?; - writeln!(f, "Bracket spacing: {}", self.bracket_spacing.value())?; writeln!(f, "Attribute Position: {}", self.attribute_position) } } @@ -177,8 +164,8 @@ impl FormatOptions for GritFormatOptions { self.attribute_position } - fn bracket_spacing(&self) -> BracketSpacing { - self.bracket_spacing + fn bracket_spacing(&self) -> biome_formatter::BracketSpacing { + BracketSpacing::default() } fn as_print_options(&self) -> biome_formatter::prelude::PrinterOptions { diff --git a/crates/biome_grit_formatter/tests/language.rs b/crates/biome_grit_formatter/tests/language.rs index e1499380f0c2..13f278bb367e 100644 --- a/crates/biome_grit_formatter/tests/language.rs +++ b/crates/biome_grit_formatter/tests/language.rs @@ -22,10 +22,11 @@ impl TestFormatLanguage for GritTestFormatLanguage { settings: &biome_service::settings::Settings, file_source: &biome_service::workspace::DocumentFileSource, ) -> Self::FormatLanguage { + let language_settings = &settings.languages.grit.formatter; let options = Self::ServiceLanguage::resolve_format_options( Some(&settings.formatter), Some(&settings.override_settings), - None, + Some(language_settings), &BiomePath::new(""), file_source, ); diff --git a/crates/biome_grit_formatter/tests/quick_test.rs b/crates/biome_grit_formatter/tests/quick_test.rs index d8605439ac83..86b6cbe515af 100644 --- a/crates/biome_grit_formatter/tests/quick_test.rs +++ b/crates/biome_grit_formatter/tests/quick_test.rs @@ -22,7 +22,7 @@ fn quick_test() { } "#; let tree = parse_grit(src); - let options = GritFormatOptions::new() + let options = GritFormatOptions::default() .with_indent_style(IndentStyle::Space) .with_line_width(LineWidth::try_from(80).unwrap()) .with_quote_style(QuoteStyle::Double); diff --git a/crates/biome_grit_formatter/tests/spec_tests.rs b/crates/biome_grit_formatter/tests/spec_tests.rs index d2fd48314093..02214d4bd54e 100644 --- a/crates/biome_grit_formatter/tests/spec_tests.rs +++ b/crates/biome_grit_formatter/tests/spec_tests.rs @@ -7,6 +7,6 @@ mod formatter { } mod grit_patterns_module { - tests_macros::gen_tests! {"tests/specs/grit/patterns/*.grit", crate::spec_test::run, "patterns"} + tests_macros::gen_tests! {"tests/specs/grit/patterns/**/*.grit", crate::spec_test::run, "patterns"} } } diff --git a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap index 48e8c27a9e5a..8637af7ec1b5 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/file_node.grit.snap @@ -20,27 +20,6 @@ Indent style: Tab Indent width: 2 Line ending: LF Line width: 80 -Bracket spacing: true -Attribute Position: Auto ------ - -```grit -file(body = contains `console.$method` => `println`) -``` - - - -## Unimplemented nodes/tokens - -"file(body = contains `console.$method` => `println`)" => 0..52 -## Output 1 - ------ -Indent style: Tab -Indent width: 2 -Line ending: LF -Line width: 80 -Bracket spacing: true Attribute Position: Auto ----- diff --git a/crates/biome_grit_formatter/tests/specs/grit/options.json b/crates/biome_grit_formatter/tests/specs/grit/options.json deleted file mode 100644 index 9e26dfeeb6e6..000000000000 --- a/crates/biome_grit_formatter/tests/specs/grit/options.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json b/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json deleted file mode 100644 index 9e26dfeeb6e6..000000000000 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/options.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap index fe900ed23c93..f3db11c84cac 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/patterns/where_pattern.grit.snap @@ -23,30 +23,6 @@ Indent style: Tab Indent width: 2 Line ending: LF Line width: 80 -Bracket spacing: true -Attribute Position: Auto ------ - -```grit -`$method('$message')` where { - -} -``` - - - -## Unimplemented nodes/tokens - -"`$method('$message')`" => 0..21 -" {\n\n" => 27..31 -## Output 1 - ------ -Indent style: Tab -Indent width: 2 -Line ending: LF -Line width: 80 -Bracket spacing: true Attribute Position: Auto ----- diff --git a/crates/biome_service/src/file_handlers/grit.rs b/crates/biome_service/src/file_handlers/grit.rs index afb9529d2fe2..2b1548be43f0 100644 --- a/crates/biome_service/src/file_handlers/grit.rs +++ b/crates/biome_service/src/file_handlers/grit.rs @@ -3,7 +3,7 @@ use crate::{ WorkspaceError, }; use biome_analyze::{AnalyzerConfiguration, AnalyzerOptions}; -use biome_formatter::Printed; +use biome_formatter::{IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, QuoteStyle}; use biome_fs::BiomePath; use biome_grit_formatter::{context::GritFormatOptions, format_node}; use biome_grit_parser::parse_grit_with_cache; @@ -16,8 +16,32 @@ use super::{ FormatterCapabilities, ParseResult, ParserCapabilities, SearchCapabilities, }; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +pub struct GritFormatterSettings { + pub line_ending: Option, + pub line_width: Option, + pub indent_width: Option, + pub indent_style: Option, + pub quote_style: Option, + pub enabled: Option, +} + +impl Default for GritFormatterSettings { + fn default() -> Self { + Self { + enabled: Some(false), + indent_style: Default::default(), + indent_width: Default::default(), + line_ending: Default::default(), + line_width: Default::default(), + quote_style: Default::default(), + } + } +} + impl ServiceLanguage for GritLanguage { - type FormatterSettings = (); + type FormatterSettings = GritFormatterSettings; type LinterSettings = (); type OrganizeImportsSettings = (); type FormatOptions = GritFormatOptions; @@ -30,13 +54,41 @@ impl ServiceLanguage for GritLanguage { } fn resolve_format_options( - _global: Option<&crate::settings::FormatSettings>, - _overrides: Option<&crate::settings::OverrideSettings>, - _language: Option<&Self::FormatterSettings>, - _path: &biome_fs::BiomePath, - _file_source: &super::DocumentFileSource, + global: Option<&crate::settings::FormatSettings>, + overrides: Option<&crate::settings::OverrideSettings>, + language: Option<&Self::FormatterSettings>, + path: &biome_fs::BiomePath, + file_source: &super::DocumentFileSource, ) -> Self::FormatOptions { - GritFormatOptions::default() + let indent_style = language + .and_then(|l| l.indent_style) + .or(global.and_then(|g| g.indent_style)) + .unwrap_or_default(); + let line_width = language + .and_then(|l| l.line_width) + .or(global.and_then(|g| g.line_width)) + .unwrap_or_default(); + let indent_width = language + .and_then(|l| l.indent_width) + .or(global.and_then(|g| g.indent_width)) + .unwrap_or_default(); + + let line_ending = language + .and_then(|l| l.line_ending) + .or(global.and_then(|g| g.line_ending)) + .unwrap_or_default(); + + let options = GritFormatOptions::new(file_source.to_grit_file_source().unwrap_or_default()) + .with_indent_style(indent_style) + .with_indent_width(indent_width) + .with_line_width(line_width) + .with_line_ending(line_ending) + .with_quote_style(language.and_then(|l| l.quote_style).unwrap_or_default()); + if let Some(overrides) = overrides { + overrides.to_override_grit_format_options(path, options) + } else { + options + } } fn resolve_analyzer_options( diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs index eb249430a9b6..adbc67c57024 100644 --- a/crates/biome_service/src/file_handlers/mod.rs +++ b/crates/biome_service/src/file_handlers/mod.rs @@ -317,6 +317,13 @@ impl DocumentFileSource { } } + pub fn to_grit_file_source(&self) -> Option { + match self { + DocumentFileSource::Grit(grit) => Some(*grit), + _ => None, + } + } + pub fn to_css_file_source(&self) -> Option { match self { DocumentFileSource::Css(css) => Some(*css), diff --git a/crates/biome_service/src/settings.rs b/crates/biome_service/src/settings.rs index e484d9e4134c..4f4c9ef59b81 100644 --- a/crates/biome_service/src/settings.rs +++ b/crates/biome_service/src/settings.rs @@ -23,6 +23,7 @@ use biome_formatter::{ use biome_fs::BiomePath; use biome_graphql_formatter::context::GraphqlFormatOptions; use biome_graphql_syntax::GraphqlLanguage; +use biome_grit_formatter::context::GritFormatOptions; use biome_grit_syntax::GritLanguage; use biome_html_formatter::HtmlFormatOptions; use biome_html_syntax::HtmlLanguage; @@ -1005,6 +1006,19 @@ impl OverrideSettings { options } + pub fn to_override_grit_format_options( + &self, + path: &Path, + mut options: GritFormatOptions, + ) -> GritFormatOptions { + for pattern in self.patterns.iter() { + if pattern.include.matches_path(path) && !pattern.exclude.matches_path(path) { + pattern.apply_overrides_to_grit_format_options(&mut options); + } + } + options + } + pub fn to_override_html_format_options( &self, path: &Path, @@ -1168,6 +1182,7 @@ pub struct OverrideSettingPattern { pub(crate) cached_js_format_options: RwLock>, pub(crate) cached_json_format_options: RwLock>, pub(crate) cached_css_format_options: RwLock>, + pub(crate) cached_grit_format_options: RwLock>, pub(crate) cached_graphql_format_options: RwLock>, pub(crate) cached_html_format_options: RwLock>, pub(crate) cached_js_parser_options: RwLock>, @@ -1339,6 +1354,39 @@ impl OverrideSettingPattern { } } + fn apply_overrides_to_grit_format_options(&self, options: &mut GritFormatOptions) { + if let Ok(readonly_cache) = self.cached_grit_format_options.read() { + if let Some(cached_options) = readonly_cache.as_ref() { + *options = cached_options.clone(); + return; + } + } + + let grit_formatter = &self.languages.grit.formatter; + let formatter = &self.formatter; + + if let Some(indent_style) = grit_formatter.indent_style.or(formatter.indent_style) { + options.set_indent_style(indent_style); + } + if let Some(indent_width) = grit_formatter.indent_width.or(formatter.indent_width) { + options.set_indent_width(indent_width) + } + if let Some(line_ending) = grit_formatter.line_ending.or(formatter.line_ending) { + options.set_line_ending(line_ending); + } + if let Some(line_width) = grit_formatter.line_width.or(formatter.line_width) { + options.set_line_width(line_width); + } + if let Some(quote_style) = grit_formatter.quote_style { + options.set_quote_style(quote_style); + } + + if let Ok(mut writeonly_cache) = self.cached_grit_format_options.write() { + let options = options.clone(); + let _ = writeonly_cache.insert(options); + } + } + fn apply_overrides_to_html_format_options(&self, options: &mut HtmlFormatOptions) { if let Ok(readonly_cache) = self.cached_html_format_options.read() { if let Some(cached_options) = readonly_cache.as_ref() { From 47c4f8ad031613f5f1cf0184380b5948551ab2cd Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Sun, 6 Oct 2024 18:12:56 -0500 Subject: [PATCH 27/32] Add basic error handling for predicate and definition lists --- .../src/grit/lists/definition_list.rs | 18 ++++++++++++------ .../src/grit/lists/predicate_list.rs | 12 +++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs index b71cc3f19418..47418c75e664 100644 --- a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs @@ -5,14 +5,20 @@ pub(crate) struct FormatGritDefinitionList; impl FormatRule for FormatGritDefinitionList { type Context = GritFormatContext; fn fmt(&self, node: &GritDefinitionList, f: &mut GritFormatter) -> FormatResult<()> { - let mut join = f.join_nodes_with_hardline(); + let mut join: JoinNodesBuilder<'_, '_, Line, GritFormatContext> = + f.join_nodes_with_hardline(); + // TODO: Add separator for definition in node { - let def_clone = definition.clone().unwrap(); - join.entry( - definition?.syntax(), - &format_or_verbatim(def_clone.format()), - ); + match definition { + Ok(definition) => { + join.entry( + definition.syntax(), + &format_or_verbatim(definition.format()), + ); + } + Err(_) => (), + } } join.finish() diff --git a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs index 5a76e9f9c77f..95887a5d8d0d 100644 --- a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs @@ -7,12 +7,14 @@ impl FormatRule for FormatGritPredicateList { fn fmt(&self, node: &GritPredicateList, f: &mut GritFormatter) -> FormatResult<()> { let mut join = f.join_nodes_with_hardline(); + // TODO: Add separator for predicate in node { - let pred_clone = predicate.clone().unwrap(); - join.entry( - predicate?.syntax(), - &format_or_verbatim(pred_clone.format()), - ); + match predicate { + Ok(predicate) => { + join.entry(predicate.syntax(), &format_or_verbatim(predicate.format())); + } + Err(_) => (), + } } join.finish() From 07eb7bb63d2b366951a41be56e85c4470d108602 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 7 Oct 2024 07:33:19 -0500 Subject: [PATCH 28/32] Revert test change --- crates/biome_grit_formatter/tests/spec_tests.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/biome_grit_formatter/tests/spec_tests.rs b/crates/biome_grit_formatter/tests/spec_tests.rs index 02214d4bd54e..f68ad1f252f7 100644 --- a/crates/biome_grit_formatter/tests/spec_tests.rs +++ b/crates/biome_grit_formatter/tests/spec_tests.rs @@ -3,10 +3,6 @@ mod spec_test; mod formatter { mod grit_module { - tests_macros::gen_tests! {"tests/specs/grit/*.grit", crate::spec_test::run, ""} - } - - mod grit_patterns_module { - tests_macros::gen_tests! {"tests/specs/grit/patterns/**/*.grit", crate::spec_test::run, "patterns"} + tests_macros::gen_tests! {"tests/specs/grit/**/*.grit", crate::spec_test::run, ""} } } From 092662f7f3ccdbbd72186ca943783c132c623d48 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 7 Oct 2024 07:38:29 -0500 Subject: [PATCH 29/32] Address PR comments and remove quote style --- crates/biome_grit_formatter/src/context.rs | 17 +---------------- .../src/grit/lists/definition_list.rs | 16 ++++++---------- .../src/grit/lists/predicate_list.rs | 8 ++------ crates/biome_grit_formatter/tests/quick_test.rs | 5 ++--- crates/biome_service/src/file_handlers/grit.rs | 7 ++----- crates/biome_service/src/settings.rs | 3 --- 6 files changed, 13 insertions(+), 43 deletions(-) diff --git a/crates/biome_grit_formatter/src/context.rs b/crates/biome_grit_formatter/src/context.rs index 6180be622d39..3b4040af3b61 100644 --- a/crates/biome_grit_formatter/src/context.rs +++ b/crates/biome_grit_formatter/src/context.rs @@ -2,7 +2,7 @@ use crate::comments::{FormatGritLeadingComment, GritCommentStyle, GritComments}; use biome_formatter::printer::PrinterOptions; use biome_formatter::{ AttributePosition, BracketSpacing, CstFormatContext, FormatContext, FormatOptions, IndentStyle, - IndentWidth, LineEnding, LineWidth, QuoteStyle, TransformSourceMap, + IndentWidth, LineEnding, LineWidth, TransformSourceMap, }; use biome_grit_syntax::file_source::GritFileSource; use biome_grit_syntax::GritLanguage; @@ -61,7 +61,6 @@ pub struct GritFormatOptions { indent_width: IndentWidth, line_ending: LineEnding, line_width: LineWidth, - quote_style: QuoteStyle, attribute_position: AttributePosition, _file_source: GritFileSource, } @@ -74,7 +73,6 @@ impl GritFormatOptions { indent_width: IndentWidth::default(), line_ending: LineEnding::default(), line_width: LineWidth::default(), - quote_style: QuoteStyle::default(), attribute_position: AttributePosition::default(), } } @@ -99,11 +97,6 @@ impl GritFormatOptions { self } - pub fn with_quote_style(mut self, quote_style: QuoteStyle) -> Self { - self.quote_style = quote_style; - self - } - pub fn set_indent_style(&mut self, indent_style: IndentStyle) { self.indent_style = indent_style; } @@ -120,14 +113,6 @@ impl GritFormatOptions { self.line_width = line_width; } - pub fn set_quote_style(&mut self, quote_style: QuoteStyle) { - self.quote_style = quote_style; - } - - pub fn quote_style(&self) -> QuoteStyle { - self.quote_style - } - pub fn attribute_position(&self) -> AttributePosition { self.attribute_position } diff --git a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs index 47418c75e664..e35542e1e852 100644 --- a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs @@ -8,17 +8,13 @@ impl FormatRule for FormatGritDefinitionList { let mut join: JoinNodesBuilder<'_, '_, Line, GritFormatContext> = f.join_nodes_with_hardline(); - // TODO: Add separator for definition in node { - match definition { - Ok(definition) => { - join.entry( - definition.syntax(), - &format_or_verbatim(definition.format()), - ); - } - Err(_) => (), - } + let definition = definition?; + + join.entry( + definition.syntax(), + &format_or_verbatim(definition.format()), + ); } join.finish() diff --git a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs index 95887a5d8d0d..55b94764eee2 100644 --- a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs @@ -9,12 +9,8 @@ impl FormatRule for FormatGritPredicateList { // TODO: Add separator for predicate in node { - match predicate { - Ok(predicate) => { - join.entry(predicate.syntax(), &format_or_verbatim(predicate.format())); - } - Err(_) => (), - } + let predicate = predicate?; + join.entry(predicate.syntax(), &format_or_verbatim(predicate.format())); } join.finish() diff --git a/crates/biome_grit_formatter/tests/quick_test.rs b/crates/biome_grit_formatter/tests/quick_test.rs index 86b6cbe515af..41c9ea68e749 100644 --- a/crates/biome_grit_formatter/tests/quick_test.rs +++ b/crates/biome_grit_formatter/tests/quick_test.rs @@ -1,4 +1,4 @@ -use biome_formatter::{IndentStyle, LineWidth, QuoteStyle}; +use biome_formatter::{IndentStyle, LineWidth}; use biome_formatter_test::check_reformat::CheckReformat; use biome_grit_formatter::context::GritFormatOptions; use biome_grit_formatter::{format_node, GritFormatLanguage}; @@ -24,8 +24,7 @@ fn quick_test() { let tree = parse_grit(src); let options = GritFormatOptions::default() .with_indent_style(IndentStyle::Space) - .with_line_width(LineWidth::try_from(80).unwrap()) - .with_quote_style(QuoteStyle::Double); + .with_line_width(LineWidth::try_from(80).unwrap()); let doc = format_node(options.clone(), &tree.syntax()).unwrap(); let result = doc.print().unwrap(); diff --git a/crates/biome_service/src/file_handlers/grit.rs b/crates/biome_service/src/file_handlers/grit.rs index 2b1548be43f0..cf56af7a4596 100644 --- a/crates/biome_service/src/file_handlers/grit.rs +++ b/crates/biome_service/src/file_handlers/grit.rs @@ -3,7 +3,7 @@ use crate::{ WorkspaceError, }; use biome_analyze::{AnalyzerConfiguration, AnalyzerOptions}; -use biome_formatter::{IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, QuoteStyle}; +use biome_formatter::{IndentStyle, IndentWidth, LineEnding, LineWidth, Printed}; use biome_fs::BiomePath; use biome_grit_formatter::{context::GritFormatOptions, format_node}; use biome_grit_parser::parse_grit_with_cache; @@ -23,7 +23,6 @@ pub struct GritFormatterSettings { pub line_width: Option, pub indent_width: Option, pub indent_style: Option, - pub quote_style: Option, pub enabled: Option, } @@ -35,7 +34,6 @@ impl Default for GritFormatterSettings { indent_width: Default::default(), line_ending: Default::default(), line_width: Default::default(), - quote_style: Default::default(), } } } @@ -82,8 +80,7 @@ impl ServiceLanguage for GritLanguage { .with_indent_style(indent_style) .with_indent_width(indent_width) .with_line_width(line_width) - .with_line_ending(line_ending) - .with_quote_style(language.and_then(|l| l.quote_style).unwrap_or_default()); + .with_line_ending(line_ending); if let Some(overrides) = overrides { overrides.to_override_grit_format_options(path, options) } else { diff --git a/crates/biome_service/src/settings.rs b/crates/biome_service/src/settings.rs index 4f4c9ef59b81..77089af01bcb 100644 --- a/crates/biome_service/src/settings.rs +++ b/crates/biome_service/src/settings.rs @@ -1377,9 +1377,6 @@ impl OverrideSettingPattern { if let Some(line_width) = grit_formatter.line_width.or(formatter.line_width) { options.set_line_width(line_width); } - if let Some(quote_style) = grit_formatter.quote_style { - options.set_quote_style(quote_style); - } if let Ok(mut writeonly_cache) = self.cached_grit_format_options.write() { let options = options.clone(); From a984aac196f0436bb2454617ea5e3c2389725373 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 7 Oct 2024 07:40:37 -0500 Subject: [PATCH 30/32] Update crates/biome_grit_formatter/src/grit/lists/definition_list.rs Co-authored-by: Emanuele Stoppa --- crates/biome_grit_formatter/src/grit/lists/definition_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs index e35542e1e852..004203468c3b 100644 --- a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs @@ -5,7 +5,7 @@ pub(crate) struct FormatGritDefinitionList; impl FormatRule for FormatGritDefinitionList { type Context = GritFormatContext; fn fmt(&self, node: &GritDefinitionList, f: &mut GritFormatter) -> FormatResult<()> { - let mut join: JoinNodesBuilder<'_, '_, Line, GritFormatContext> = + let mut join = f.join_nodes_with_hardline(); for definition in node { From d44ea046c5694b8f6229b4e49ecbb4ccb1ec4d4a Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 7 Oct 2024 07:40:54 -0500 Subject: [PATCH 31/32] Remove comment --- crates/biome_grit_formatter/src/grit/lists/predicate_list.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs index 55b94764eee2..bc94f77bd127 100644 --- a/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/predicate_list.rs @@ -7,7 +7,6 @@ impl FormatRule for FormatGritPredicateList { fn fmt(&self, node: &GritPredicateList, f: &mut GritFormatter) -> FormatResult<()> { let mut join = f.join_nodes_with_hardline(); - // TODO: Add separator for predicate in node { let predicate = predicate?; join.entry(predicate.syntax(), &format_or_verbatim(predicate.format())); From 834f3401e0923a43b47e6a1ffb707b6a8aa23312 Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 7 Oct 2024 12:31:50 -0500 Subject: [PATCH 32/32] format --- crates/biome_grit_formatter/src/grit/lists/definition_list.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs index 004203468c3b..fc6c963ff715 100644 --- a/crates/biome_grit_formatter/src/grit/lists/definition_list.rs +++ b/crates/biome_grit_formatter/src/grit/lists/definition_list.rs @@ -5,8 +5,7 @@ pub(crate) struct FormatGritDefinitionList; impl FormatRule for FormatGritDefinitionList { type Context = GritFormatContext; fn fmt(&self, node: &GritDefinitionList, f: &mut GritFormatter) -> FormatResult<()> { - let mut join = - f.join_nodes_with_hardline(); + let mut join = f.join_nodes_with_hardline(); for definition in node { let definition = definition?;