From 957a9d4b3345004b4ef4dde17e0d2974a5037951 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:43:36 +0200 Subject: [PATCH 01/27] Update utils to accept custom comparator --- crates/biome_analyze/src/utils.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index f982f3457fec..986baa2537c3 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use biome_rowan::{ AstNode, AstSeparatedElement, AstSeparatedList, Language, SyntaxError, SyntaxNode, SyntaxToken, chain_trivia_pieces, @@ -17,8 +18,13 @@ pub fn is_separated_list_sorted_by< >( list: &impl AstSeparatedList, get_key: impl Fn(&N) -> Option, + comparator: Option Ordering> ) -> Result { let mut is_sorted = true; + let cmp = comparator.unwrap_or_else(|| |a:&Key, b: &Key| { + if a > b { Ordering::Greater } else if a < b {Ordering::Less} else { Ordering::Equal} + }); + if list.len() > 1 { let mut previous_key: Option = None; for AstSeparatedElement { @@ -29,7 +35,7 @@ pub fn is_separated_list_sorted_by< // We have to check if the separator is not buggy. let _separator = trailing_separator?; previous_key = if let Some(key) = get_key(&node?) { - if previous_key.is_some_and(|previous_key| previous_key > key) { + if previous_key.is_some_and(|previous_key| cmp(&previous_key, &key) != Ordering::Equal) { // We don't return early because we want to return the error if we met one. is_sorted = false; } @@ -55,11 +61,15 @@ pub fn sorted_separated_list_by<'a, L: Language + 'a, List, Node, Key: Ord>( list: &List, get_key: impl Fn(&Node) -> Option, make_separator: fn() -> SyntaxToken, + comparator: Option Ordering> ) -> Result where List: AstSeparatedList + AstNode + 'a, Node: AstNode + 'a, { + let cmp = comparator.unwrap_or_else(|| |a:&Key, b: &Key| { + if a > b { Ordering::Greater } else if a < b {Ordering::Less} else { Ordering::Equal} + }); let mut elements = Vec::with_capacity(list.len()); for AstSeparatedElement { node, @@ -74,7 +84,14 @@ where // Iterate over chunks of node with a key for slice in elements.split_mut(|(key, _, _)| key.is_none()) { let last_has_separator = slice.last().is_some_and(|(_, _, sep)| sep.is_some()); - slice.sort_by(|(key1, _, _), (key2, _, _)| key1.cmp(key2)); + slice.sort_by(|(key1, _, _), (key2, _, _)| { + match (key1, key2) { + (Some(k1), Some(k2)) => cmp(k1, k2), + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => Ordering::Equal, + } + }); fix_separators( slice.iter_mut().map(|(_, node, sep)| (node, sep)), last_has_separator, From afc8b9dd3afd8e79a92a6ab07e1d858867d155fa Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:44:00 +0200 Subject: [PATCH 02/27] Update string_case crate to have flexible sorting methods --- .../biome_string_case/src/comparable_token.rs | 8 ++++++++ crates/biome_string_case/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/biome_string_case/src/comparable_token.rs b/crates/biome_string_case/src/comparable_token.rs index 569efcfe0121..f16596747210 100644 --- a/crates/biome_string_case/src/comparable_token.rs +++ b/crates/biome_string_case/src/comparable_token.rs @@ -13,6 +13,14 @@ impl ComparableToken { pub const fn new(token: TokenText) -> Self { Self { token } } + + pub fn ascii_nat_cmp(&self, other: &Self) -> Ordering { + self.token.text().ascii_nat_cmp(other.token.text()) + } + + pub fn lexicographic_cmp(&self, other: &Self) -> Ordering { + self.token.text().lexicographic_cmp(other.token.text()) + } } impl From for ComparableToken { fn from(value: TokenText) -> Self { diff --git a/crates/biome_string_case/src/lib.rs b/crates/biome_string_case/src/lib.rs index f138f77ae63d..4a6df82b8400 100644 --- a/crates/biome_string_case/src/lib.rs +++ b/crates/biome_string_case/src/lib.rs @@ -649,6 +649,11 @@ pub trait StrLikeExtension: ToOwned { /// Uppercase letters come first (e.g. `A` < `a` < `B` < `b`) /// and number are compared in a human way (e.g. `9` < `10`). fn ascii_nat_cmp(&self, other: &Self) -> Ordering; + + /// Compare two strings using lexicographically by their byte values. + /// + /// This orders Unicode code points based on their positions in the code charts. + fn lexicographic_cmp(&self, other: &Self) -> Ordering; } pub trait StrOnlyExtension: ToOwned { @@ -672,6 +677,10 @@ impl StrLikeExtension for str { fn ascii_nat_cmp(&self, other: &Self) -> Ordering { self.as_bytes().ascii_nat_cmp(other.as_bytes()) } + + fn lexicographic_cmp(&self, other: &Self) -> Ordering { + self.as_bytes().lexicographic_cmp(other.as_bytes()) + } } impl StrOnlyExtension for str { @@ -704,6 +713,10 @@ impl StrLikeExtension for std::ffi::OsStr { self.as_encoded_bytes() .ascii_nat_cmp(other.as_encoded_bytes()) } + + fn lexicographic_cmp(&self, other: &Self) -> Ordering { + self.as_encoded_bytes().cmp(other.as_encoded_bytes()) + } } impl StrLikeExtension for [u8] { @@ -719,6 +732,10 @@ impl StrLikeExtension for [u8] { fn ascii_nat_cmp(&self, other: &Self) -> Ordering { CldrAsciiCollator.cmp(self.iter().copied(), other.iter().copied()) } + + fn lexicographic_cmp(&self, other: &Self) -> Ordering { + self.iter().copied().cmp(other.iter().copied()) + } } // TODO: Once trait-alias are stabilized it would be enough to `use` this trait instead of individual ones. From 713864946424538025587d89f2d9ef09f8d219f0 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:44:58 +0200 Subject: [PATCH 03/27] Add sortMode to useSortedKeys rule --- .../src/assist/source/use_sorted_keys.rs | 19 +++++-- .../useSortedKeys/sorted-lexicographic.js | 7 +++ .../sorted-lexicographic.js.snap | 52 ++++++++++++++++++ .../sorted-lexicographic.options.json | 15 ++++++ .../source/useSortedKeys/sorted-natural.js | 7 +++ .../useSortedKeys/sorted-natural.js.snap | 52 ++++++++++++++++++ .../useSortedKeys/sorted-natural.options.json | 15 ++++++ .../source/useSortedKeys/sorted.js.snap.new | 53 +++++++++++++++++++ .../src/assist/source/use_sorted_keys.rs | 18 ++++++- crates/biome_json_analyze/tests/spec_tests.rs | 5 ++ .../useSortedKeys/sorted-lexicographic.json | 7 +++ .../sorted-lexicographic.json.snap | 52 ++++++++++++++++++ .../sorted-lexicographic.options.json | 15 ++++++ .../source/useSortedKeys/sorted-natural.json | 7 +++ .../useSortedKeys/sorted-natural.json.snap | 52 ++++++++++++++++++ .../useSortedKeys/sorted-natural.options.json | 15 ++++++ crates/biome_rule_options/src/shared/mod.rs | 1 + .../src/shared/sort_mode.rs | 9 ++++ .../biome_rule_options/src/use_sorted_keys.rs | 6 ++- 19 files changed, 401 insertions(+), 6 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json.snap create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json.snap create mode 100644 crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json create mode 100644 crates/biome_rule_options/src/shared/sort_mode.rs diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index 1b02d2f36383..ac7ee711a5c3 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -12,7 +12,7 @@ use biome_diagnostics::{Applicability, category}; use biome_js_factory::make; use biome_js_syntax::{JsObjectExpression, JsObjectMemberList, T}; use biome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind}; -use biome_rule_options::use_sorted_keys::UseSortedKeysOptions; +use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortMode}; use biome_string_case::comparable_token::ComparableToken; use crate::JsRuleAction; @@ -89,7 +89,14 @@ impl Rule for UseSortedKeys { type Options = UseSortedKeysOptions; fn run(ctx: &RuleContext) -> Self::Signals { - is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new)) + let options = ctx.options(); + let sort_mode = options.sort_mode; + let comparator = match sort_mode { + SortMode::Natural => ComparableToken::ascii_nat_cmp, + SortMode::Lexicographic => ComparableToken::lexicographic_cmp + }; + + is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new), Some(comparator)) .ok()? .not() .then_some(()) @@ -115,12 +122,18 @@ impl Rule for UseSortedKeys { fn action(ctx: &RuleContext, _: &Self::State) -> Option { let list = ctx.query(); + let options = ctx.options(); + let sort_mode = options.sort_mode; + let comparator = match sort_mode { + SortMode::Natural => ComparableToken::ascii_nat_cmp, + SortMode::Lexicographic => ComparableToken::lexicographic_cmp + }; let new_list = sorted_separated_list_by( list, |node| node.name().map(ComparableToken::new), || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - ) + Some(comparator)) .ok()?; let mut mutation = ctx.root().begin(); diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js new file mode 100644 index 000000000000..738bbd017fa5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js @@ -0,0 +1,7 @@ +const obj = { + val13: 1, + val1: 1, + val2: 1, + val21: 1, + val11: 1, +}; diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js.snap b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js.snap new file mode 100644 index 000000000000..e343786e34d7 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.js.snap @@ -0,0 +1,52 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: sorted-lexicographic.js +--- +# Input +```js +const obj = { + val13: 1, + val1: 1, + val2: 1, + val21: 1, + val11: 1, +}; + +``` + +# Diagnostics +``` +sorted-lexicographic.js:2:2 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The object properties are not sorted by key. + + 1 │ const obj = { + > 2 │ val13: 1, + │ ^^^^^^^^^ + > 3 │ val1: 1, + > 4 │ val2: 1, + > 5 │ val21: 1, + > 6 │ val11: 1, + │ ^^^^^^^^^ + 7 │ }; + 8 │ + + i Safe fix: Sort the object properties by key. + + 1 1 │ const obj = { + 2 │ - → val13:·1, + 3 │ - → val1:·1, + 4 │ - → val2:·1, + 5 │ - → val21:·1, + 6 │ - → val11:·1, + 2 │ + → val1:·1, + 3 │ + → val11:·1, + 4 │ + → val13:·1, + 5 │ + → val2:·1, + 6 │ + → val21:·1, + 7 7 │ }; + 8 8 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json new file mode 100644 index 000000000000..2c980a3f2196 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedKeys": { + "level": "on", + "options": { + "sortMode": "lexicographic" + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js new file mode 100644 index 000000000000..738bbd017fa5 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js @@ -0,0 +1,7 @@ +const obj = { + val13: 1, + val1: 1, + val2: 1, + val21: 1, + val11: 1, +}; diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js.snap b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js.snap new file mode 100644 index 000000000000..5e7ff6ff488c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.js.snap @@ -0,0 +1,52 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: sorted-natural.js +--- +# Input +```js +const obj = { + val13: 1, + val1: 1, + val2: 1, + val21: 1, + val11: 1, +}; + +``` + +# Diagnostics +``` +sorted-natural.js:2:2 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The object properties are not sorted by key. + + 1 │ const obj = { + > 2 │ val13: 1, + │ ^^^^^^^^^ + > 3 │ val1: 1, + > 4 │ val2: 1, + > 5 │ val21: 1, + > 6 │ val11: 1, + │ ^^^^^^^^^ + 7 │ }; + 8 │ + + i Safe fix: Sort the object properties by key. + + 1 1 │ const obj = { + 2 │ - → val13:·1, + 3 │ - → val1:·1, + 4 │ - → val2:·1, + 5 │ - → val21:·1, + 6 │ - → val11:·1, + 2 │ + → val1:·1, + 3 │ + → val2:·1, + 4 │ + → val11:·1, + 5 │ + → val13:·1, + 6 │ + → val21:·1, + 7 7 │ }; + 8 8 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json new file mode 100644 index 000000000000..37a11c0ccb1c --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedKeys": { + "level": "on", + "options": { + "sortMode": "natural" + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new new file mode 100644 index 000000000000..3461ea390964 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new @@ -0,0 +1,53 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: sorted.js +--- +# Input +```js +const obj = { + get aab() { + return this._aab; + }, + set aac(v) { + this._aac = v; + }, + w: 1, + x: 1, + ...g, + get aaa() { + return ""; + }, + u: 1, + v: 1, + [getProp()]: 2, + o: 1, + p: 1, + q: 1, +}; + +``` + +# Diagnostics +``` +sorted.js:2:2 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The object properties are not sorted by key. + + 1 │ const obj = { + > 2 │ get aab() { + │ ^^^^^^^^^^^ + > 3 │ return this._aab; + > 4 │ }, + ... + > 18 │ p: 1, + > 19 │ q: 1, + │ ^^^^^ + 20 │ }; + 21 │ + + i Safe fix: Sort the object properties by key. + + + +``` diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index fa8bc6fa8fe5..b488391b09b8 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -8,7 +8,7 @@ use biome_diagnostics::category; use biome_json_factory::make; use biome_json_syntax::{JsonMemberList, JsonObjectValue, T, TextRange}; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_keys::UseSortedKeysOptions; +use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortMode}; use biome_string_case::comparable_token::ComparableToken; use std::ops::Not; @@ -41,13 +41,20 @@ impl Rule for UseSortedKeys { type Options = UseSortedKeysOptions; fn run(ctx: &RuleContext) -> Option { + let options = ctx.options(); + let sort_mode = options.sort_mode; + let comparator = match sort_mode { + SortMode::Natural => ComparableToken::ascii_nat_cmp, + SortMode::Lexicographic => ComparableToken::lexicographic_cmp + }; + is_separated_list_sorted_by(ctx.query(), |node| { node.name() .ok()? .inner_string_text() .ok() .map(ComparableToken::new) - }) + }, Some(comparator)) .ok()? .not() .then_some(()) @@ -73,6 +80,12 @@ impl Rule for UseSortedKeys { fn action(ctx: &RuleContext, _state: &Self::State) -> Option { let list = ctx.query(); + let options = ctx.options(); + let sort_mode = options.sort_mode; + let comparator = match sort_mode { + SortMode::Natural => ComparableToken::ascii_nat_cmp, + SortMode::Lexicographic => ComparableToken::lexicographic_cmp + }; let new_list = sorted_separated_list_by( list, @@ -84,6 +97,7 @@ impl Rule for UseSortedKeys { .map(ComparableToken::new) }, || make::token(T![,]), + Some(comparator), ) .ok()?; diff --git a/crates/biome_json_analyze/tests/spec_tests.rs b/crates/biome_json_analyze/tests/spec_tests.rs index 5a460c38f2cb..2cf1f42b3170 100644 --- a/crates/biome_json_analyze/tests/spec_tests.rs +++ b/crates/biome_json_analyze/tests/spec_tests.rs @@ -22,6 +22,11 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) { let input_file = Utf8Path::new(input); let file_name = input_file.file_name().unwrap(); + // We should skip running test for .options.json as input_file + if file_name.ends_with(".options.json") || file_name.ends_with(".options.jsonc") { + return; + } + let parser_options = match input_file.extension() { Some("json") => JsonParserOptions::default(), Some("jsonc") => JsonParserOptions::default() diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json new file mode 100644 index 000000000000..6e11f9deb010 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json @@ -0,0 +1,7 @@ +{ + "val13": 1, + "val1": 1, + "val2": 1, + "val21": 1, + "val11": 1 +} diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json.snap b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json.snap new file mode 100644 index 000000000000..98e1baf06c08 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.json.snap @@ -0,0 +1,52 @@ +--- +source: crates/biome_json_analyze/tests/spec_tests.rs +assertion_line: 87 +expression: sorted-lexicographic.json +--- +# Input +```json +{ + "val13": 1, + "val1": 1, + "val2": 1, + "val21": 1, + "val11": 1 +} + +``` + +# Diagnostics +``` +sorted-lexicographic.json:1:1 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The members are not sorted by key. + + > 1 │ { + │ ^ + > 2 │ "val13": 1, + > 3 │ "val1": 1, + > 4 │ "val2": 1, + > 5 │ "val21": 1, + > 6 │ "val11": 1 + > 7 │ } + │ ^ + 8 │ + + i Safe fix: Sort the members by key. + + 1 1 │ { + 2 │ - → "val13":·1, + 3 │ - → "val1":·1, + 4 │ - → "val2":·1, + 5 │ - → "val21":·1, + 6 │ - → "val11":·1 + 2 │ + → "val1":·1, + 3 │ + → "val11":·1, + 4 │ + → "val13":·1, + 5 │ + → "val2":·1, + 6 │ + → "val21":·1 + 7 7 │ } + 8 8 │ + + +``` diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json new file mode 100644 index 000000000000..2c980a3f2196 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedKeys": { + "level": "on", + "options": { + "sortMode": "lexicographic" + } + } + } + } + } +} diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json new file mode 100644 index 000000000000..6e11f9deb010 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json @@ -0,0 +1,7 @@ +{ + "val13": 1, + "val1": 1, + "val2": 1, + "val21": 1, + "val11": 1 +} diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json.snap b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json.snap new file mode 100644 index 000000000000..0e21aab920d1 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.json.snap @@ -0,0 +1,52 @@ +--- +source: crates/biome_json_analyze/tests/spec_tests.rs +assertion_line: 87 +expression: sorted-natural.json +--- +# Input +```json +{ + "val13": 1, + "val1": 1, + "val2": 1, + "val21": 1, + "val11": 1 +} + +``` + +# Diagnostics +``` +sorted-natural.json:1:1 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The members are not sorted by key. + + > 1 │ { + │ ^ + > 2 │ "val13": 1, + > 3 │ "val1": 1, + > 4 │ "val2": 1, + > 5 │ "val21": 1, + > 6 │ "val11": 1 + > 7 │ } + │ ^ + 8 │ + + i Safe fix: Sort the members by key. + + 1 1 │ { + 2 │ - → "val13":·1, + 3 │ - → "val1":·1, + 4 │ - → "val2":·1, + 5 │ - → "val21":·1, + 6 │ - → "val11":·1 + 2 │ + → "val1":·1, + 3 │ + → "val2":·1, + 4 │ + → "val11":·1, + 5 │ + → "val13":·1, + 6 │ + → "val21":·1 + 7 7 │ } + 8 8 │ + + +``` diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json new file mode 100644 index 000000000000..1c1fbec78793 --- /dev/null +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedKeys": { + "level": "on", + "options": { + "sortMode": "natural" + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/biome_rule_options/src/shared/mod.rs b/crates/biome_rule_options/src/shared/mod.rs index e744bd2e321b..abb494d63bc6 100644 --- a/crates/biome_rule_options/src/shared/mod.rs +++ b/crates/biome_rule_options/src/shared/mod.rs @@ -1 +1,2 @@ pub mod restricted_regex; +pub mod sort_mode; \ No newline at end of file diff --git a/crates/biome_rule_options/src/shared/sort_mode.rs b/crates/biome_rule_options/src/shared/sort_mode.rs new file mode 100644 index 000000000000..f9beb1d07630 --- /dev/null +++ b/crates/biome_rule_options/src/shared/sort_mode.rs @@ -0,0 +1,9 @@ +#[derive( + Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, biome_deserialize_macros::Deserializable, +)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +pub enum SortMode { + #[default] + Natural, + Lexicographic, +} diff --git a/crates/biome_rule_options/src/use_sorted_keys.rs b/crates/biome_rule_options/src/use_sorted_keys.rs index 97aedbd23e26..f90e2e4e7cb7 100644 --- a/crates/biome_rule_options/src/use_sorted_keys.rs +++ b/crates/biome_rule_options/src/use_sorted_keys.rs @@ -1,6 +1,10 @@ use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; +pub use crate::shared::sort_mode::SortMode; + #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct UseSortedKeysOptions {} +pub struct UseSortedKeysOptions { + pub sort_mode: SortMode, +} From 5f1e1956410811fc2bdaaae6a607681c8dfd57fb Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:45:25 +0200 Subject: [PATCH 04/27] Add sortMode to useSortedAttributes rule --- .../assist/source/use_sorted_attributes.rs | 63 ++++++++++++++----- .../sorted-lexicographic.jsx | 3 + .../sorted-lexicographic.jsx.snap | 35 +++++++++++ .../sorted-lexicographic.options.json | 15 +++++ .../useSortedAttributes/sorted-natural.jsx | 3 + .../sorted-natural.jsx.snap | 35 +++++++++++ .../sorted-natural.options.json | 15 +++++ .../src/use_sorted_attributes.rs | 6 +- 8 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs index 294ee2530d5e..2a57a64458b1 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs @@ -11,7 +11,7 @@ use biome_js_syntax::{ AnyJsxAttribute, JsxAttribute, JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, }; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_attributes::UseSortedAttributesOptions; +use biome_rule_options::use_sorted_attributes::{UseSortedAttributesOptions, SortMode}; use biome_string_case::StrLikeExtension; use crate::JsRuleAction; @@ -58,6 +58,19 @@ impl Rule for UseSortedAttributes { let props = ctx.query(); let mut current_prop_group = PropGroup::default(); let mut prop_groups = Vec::new(); + let options= ctx.options(); + let sort_by = options.sort_mode; + + let comparator = match sort_by { + SortMode::Natural => PropElement::ascii_nat_cmp, + SortMode::Lexicographic => PropElement::lexicographic_cmp, + }; + + // Convert to boolean-based comparator for is_sorted_by + let boolean_comparator = |a: &PropElement, b: &PropElement| { + comparator(a, b) != Ordering::Greater + }; + for prop in props { match prop { AnyJsxAttribute::JsxAttribute(attr) => { @@ -65,7 +78,7 @@ impl Rule for UseSortedAttributes { } // spread prop reset sort order AnyJsxAttribute::JsxSpreadAttribute(_) => { - if !current_prop_group.is_empty() && !current_prop_group.is_sorted() { + if !current_prop_group.is_empty() && !current_prop_group.is_sorted(boolean_comparator) { prop_groups.push(current_prop_group); current_prop_group = PropGroup::default(); } else { @@ -76,7 +89,7 @@ impl Rule for UseSortedAttributes { AnyJsxAttribute::JsMetavariable(_) => {} } } - if !current_prop_group.is_empty() && !current_prop_group.is_sorted() { + if !current_prop_group.is_empty() && !current_prop_group.is_sorted(boolean_comparator) { prop_groups.push(current_prop_group); } prop_groups.into_boxed_slice() @@ -102,9 +115,16 @@ impl Rule for UseSortedAttributes { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); + let options= ctx.options(); + let sort_by = options.sort_mode; + + let comparator = match sort_by { + SortMode::Natural => PropElement::ascii_nat_cmp, + SortMode::Lexicographic => PropElement::lexicographic_cmp, + }; for (PropElement { prop }, PropElement { prop: sorted_prop }) in - zip(state.props.iter(), state.get_sorted_props()) + zip(state.props.iter(), state.get_sorted_props(comparator)) { mutation.replace_node(prop.clone(), sorted_prop); } @@ -123,8 +143,8 @@ pub struct PropElement { prop: JsxAttribute, } -impl Ord for PropElement { - fn cmp(&self, other: &Self) -> Ordering { +impl PropElement { + pub fn ascii_nat_cmp(&self, other: &Self) -> Ordering { let (Ok(self_name), Ok(other_name)) = (self.prop.name(), other.prop.name()) else { return Ordering::Equal; }; @@ -132,15 +152,18 @@ impl Ord for PropElement { return Ordering::Equal; }; - self_name - .text_trimmed() - .ascii_nat_cmp(other_name.text_trimmed()) + self_name.text_trimmed().ascii_nat_cmp(other_name.text_trimmed()) } -} -impl PartialOrd for PropElement { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + pub fn lexicographic_cmp(&self, other: &Self) -> Ordering { + let (Ok(self_name), Ok(other_name)) = (self.prop.name(), other.prop.name()) else { + return Ordering::Equal; + }; + let (Ok(self_name), Ok(other_name)) = (self_name.name(), other_name.name()) else { + return Ordering::Equal; + }; + + self_name.text_trimmed().lexicographic_cmp(other_name.text_trimmed()) } } @@ -154,13 +177,19 @@ impl PropGroup { self.props.is_empty() } - fn is_sorted(&self) -> bool { - self.props.is_sorted() + fn is_sorted(&self, comparator: F) -> bool + where + F: Fn(&PropElement, &PropElement) -> bool + { + self.props.is_sorted_by(comparator) } - fn get_sorted_props(&self) -> Vec { + fn get_sorted_props(&self, comparator: F,) -> Vec + where + F: FnMut(&PropElement, &PropElement) -> Ordering + { let mut new_props = self.props.clone(); - new_props.sort_unstable(); + new_props.sort_by(comparator); new_props } diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx new file mode 100644 index 000000000000..2c482de01d99 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx @@ -0,0 +1,3 @@ +; +; + diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx.snap b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx.snap new file mode 100644 index 000000000000..3540a39dec5d --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.jsx.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: sorted-lexicographic.jsx +--- +# Input +```jsx +; +; + + +``` + +# Diagnostics +``` +sorted-lexicographic.jsx:2:1 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The attributes are not sorted. + + 1 │ ; + > 2 │ ; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ + + i Safe fix: Sort the JSX props. + + 1 1 │ ; + 2 │ - ; + 2 │ + ; + 3 3 │ + 4 4 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json new file mode 100644 index 000000000000..6d94df677dcc --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedAttributes": { + "level": "on", + "options": { + "sortMode": "lexicographic" + } + } + } + } + } +} diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx new file mode 100644 index 000000000000..2c482de01d99 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx @@ -0,0 +1,3 @@ +; +; + diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx.snap b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx.snap new file mode 100644 index 000000000000..6e4007c2a719 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.jsx.snap @@ -0,0 +1,35 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: sorted-natural.jsx +--- +# Input +```jsx +; +; + + +``` + +# Diagnostics +``` +sorted-natural.jsx:2:1 assist/source/useSortedAttributes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The attributes are not sorted. + + 1 │ ; + > 2 │ ; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │ + + i Safe fix: Sort the JSX props. + + 1 1 │ ; + 2 │ - ; + 2 │ + ; + 3 3 │ + 4 4 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json new file mode 100644 index 000000000000..1719a0270a45 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "useSortedAttributes": { + "level": "on", + "options": { + "sortMode": "natural" + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/biome_rule_options/src/use_sorted_attributes.rs b/crates/biome_rule_options/src/use_sorted_attributes.rs index fee965f6015a..cb06bb6d5ae2 100644 --- a/crates/biome_rule_options/src/use_sorted_attributes.rs +++ b/crates/biome_rule_options/src/use_sorted_attributes.rs @@ -1,6 +1,10 @@ use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; +pub use crate::shared::sort_mode::SortMode; + #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct UseSortedAttributesOptions {} +pub struct UseSortedAttributesOptions { + pub sort_mode: SortMode, +} From 8c06689b0c7125c1e99f7a2eb166281965d629fc Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:45:46 +0200 Subject: [PATCH 05/27] Add sortMode to organizeImports rule --- .../src/assist/source/organize_imports.rs | 35 +++++--- .../organize_imports/specifiers_attributes.rs | 58 ++++++++++--- .../custom-sort-lexicographic.js | 20 +++++ .../custom-sort-lexicographic.js.snap | 87 +++++++++++++++++++ .../custom-sort-lexicographic.options.json | 15 ++++ .../organizeImports/custom-sort-natural.js | 20 +++++ .../custom-sort-natural.js.snap | 87 +++++++++++++++++++ .../custom-sort-natural.options.json | 15 ++++ .../src/organize_imports.rs | 2 + 9 files changed, 313 insertions(+), 26 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js.snap create mode 100644 crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index b004900ab806..bcb645fe991a 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -10,7 +10,7 @@ use biome_js_syntax::{ JsSyntaxKind, T, }; use biome_rowan::{AstNode, BatchMutationExt, TextRange, TriviaPieceKind, chain_trivia_pieces}; -use biome_rule_options::organize_imports::OrganizeImportsOptions; +use biome_rule_options::{organize_imports::OrganizeImportsOptions, sort_mode::SortMode}; use import_key::{ImportInfo, ImportKey}; use rustc_hash::FxHashMap; use specifiers_attributes::{ @@ -683,6 +683,7 @@ impl Rule for OrganizeImports { let root = ctx.query(); let mut result = Vec::new(); let options = ctx.options(); + let sort_mode = options.sort_mode; let mut chunk: Option = None; let mut prev_kind: Option = None; let mut prev_group = 0; @@ -702,10 +703,10 @@ impl Rule for OrganizeImports { let starts_chunk = chunk.is_none(); let leading_newline_count = leading_newlines(item.syntax()).count(); let are_specifiers_unsorted = - specifiers.is_some_and(|specifiers| !specifiers.are_sorted()); + specifiers.is_some_and(|specifiers| !specifiers.are_sorted(sort_mode)); let are_attributes_unsorted = attributes.is_some_and(|attributes| { // Assume the attributes are sorted if there are any bogus nodes. - !(are_import_attributes_sorted(&attributes).unwrap_or(true)) + !(are_import_attributes_sorted(&attributes, sort_mode).unwrap_or(true)) }); let newline_issue = if leading_newline_count == 1 // A chunk must start with a blank line (two newlines) @@ -792,6 +793,11 @@ impl Rule for OrganizeImports { } let options = ctx.options(); + let sort_mode = options.sort_mode; + // let comparator = match sort_mode { + // SortMode::Lexicographic => ComparableToken::lexicographic_cmp, + // SortMode::Natural => ComparableToken::ascii_nat_cmp + // }; let root = ctx.query(); let items = root.items().into_syntax(); let mut organized_items: FxHashMap = FxHashMap::default(); @@ -828,7 +834,7 @@ impl Rule for OrganizeImports { // Sort named specifiers if let AnyJsExportClause::JsExportNamedFromClause(cast) = &clause { if let Some(sorted_specifiers) = - sort_export_specifiers(&cast.specifiers()) + sort_export_specifiers(&cast.specifiers(), sort_mode) { clause = cast.clone().with_specifiers(sorted_specifiers).into(); @@ -837,7 +843,7 @@ impl Rule for OrganizeImports { } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(sort_attributes); + let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_mode)); clause = clause.with_attribute(sorted_attrs); } export.with_export_clause(clause).into() @@ -847,14 +853,14 @@ impl Rule for OrganizeImports { if *are_specifiers_unsorted { // Sort named specifiers if let Some(sorted_specifiers) = - clause.named_specifiers().and_then(sort_import_specifiers) + clause.named_specifiers().and_then(|specifiers| sort_import_specifiers(specifiers, sort_mode)) { clause = clause.with_named_specifiers(sorted_specifiers) } } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(sort_attributes); + let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_mode)); clause = clause.with_attribute(sorted_attrs); } import.with_import_clause(clause).into() @@ -903,8 +909,12 @@ impl Rule for OrganizeImports { ); // Sort imports based on their import key import_keys.sort_unstable_by( - |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| k1.cmp(k2), + |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| { + eprint!("%%%%% {} - {}\n", k1.source.inner().token, k2.source.inner().token); + k1.cmp(k2) + }, ); + // Merge imports/exports // We use `while` and indexing to allow both iteration and mutation of `import_keys`. let mut i = import_keys.len() - 1; @@ -916,7 +926,7 @@ impl Rule for OrganizeImports { } = &import_keys[i - 1]; let KeyedItem { key, item, .. } = &import_keys[i]; if prev_key.is_mergeable(key) { - if let Some(merged) = merge(prev_item.as_ref(), item.as_ref()) { + if let Some(merged) = merge(prev_item.as_ref(), item.as_ref(), sort_mode) { import_keys[i - 1].was_merged = true; import_keys[i - 1].item = Some(merged); import_keys[i].item = None; @@ -1055,6 +1065,7 @@ pub enum NewLineIssue { fn merge( item1: Option<&AnyJsModuleItem>, item2: Option<&AnyJsModuleItem>, + sort_mode: SortMode, ) -> Option { match (item1?, item2?) { (AnyJsModuleItem::JsExport(item1), AnyJsModuleItem::JsExport(item2)) => { @@ -1066,7 +1077,7 @@ fn merge( let clause2 = clause2.as_js_export_named_from_clause()?; let specifiers1 = clause1.specifiers(); let specifiers2 = clause2.specifiers(); - if let Some(meregd_specifiers) = merge_export_specifiers(&specifiers1, &specifiers2) { + if let Some(meregd_specifiers) = merge_export_specifiers(&specifiers1, &specifiers2, sort_mode) { let meregd_clause = clause1.with_specifiers(meregd_specifiers); let merged_item = item2.clone().with_export_clause(meregd_clause.into()); @@ -1132,7 +1143,7 @@ fn merge( }; let specifiers2 = clause2.named_specifiers().ok()?; if let Some(meregd_specifiers) = - merge_import_specifiers(specifiers1, &specifiers2) + merge_import_specifiers(specifiers1, &specifiers2, sort_mode) { let merged_clause = clause1.with_specifier(meregd_specifiers.into()); let merged_item = item2.clone().with_import_clause(merged_clause.into()); @@ -1155,7 +1166,7 @@ fn merge( let specifiers1 = clause1.named_specifiers().ok()?; let specifiers2 = clause2.named_specifiers().ok()?; if let Some(meregd_specifiers) = - merge_import_specifiers(specifiers1, &specifiers2) + merge_import_specifiers(specifiers1, &specifiers2, sort_mode) { let merged_clause = clause1.with_named_specifiers(meregd_specifiers); let merged_item = item2.clone().with_import_clause(merged_clause.into()); diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index 679740950bed..6b4f9f3922ef 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use biome_analyze::utils::{is_separated_list_sorted_by, sorted_separated_list_by}; use biome_js_factory::make; use biome_js_syntax::{ @@ -6,17 +7,20 @@ use biome_js_syntax::{ }; use biome_rowan::{AstNode, AstSeparatedElement, AstSeparatedList, TriviaPieceKind}; use biome_string_case::comparable_token::ComparableToken; +use biome_rule_options::organize_imports::SortMode; pub enum JsNamedSpecifiers { JsNamedImportSpecifiers(JsNamedImportSpecifiers), JsExportNamedFromSpecifierList(JsExportNamedFromSpecifierList), } impl JsNamedSpecifiers { - pub fn are_sorted(&self) -> bool { + pub fn are_sorted(&self, sort_mode: SortMode) -> bool { + + match self { - Self::JsNamedImportSpecifiers(specifeirs) => are_import_specifiers_sorted(specifeirs), + Self::JsNamedImportSpecifiers(specifeirs) => are_import_specifiers_sorted(specifeirs, sort_mode), Self::JsExportNamedFromSpecifierList(specifeirs) => { - are_export_specifiers_sorted(specifeirs) + are_export_specifiers_sorted(specifeirs, sort_mode) } } // Assume the import is already sorted if there are any bogus nodes, otherwise the `--write` @@ -25,7 +29,12 @@ impl JsNamedSpecifiers { } } -pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers) -> Option { +pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, sort_mode: SortMode) -> Option { + let comparator = match sort_mode { + SortMode::Lexicographic => ComparableToken::lexicographic_cmp, + SortMode::Natural => ComparableToken::ascii_nat_cmp, + }; + is_separated_list_sorted_by(&named_specifiers.specifiers(), |node| { let AnyJsBinding::JsIdentifierBinding(name) = node.local_name()? else { return None; @@ -33,13 +42,15 @@ pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers) Some(ComparableToken::new( name.name_token().ok()?.token_text_trimmed(), )) - }) + }, Some(comparator)) .ok() } pub fn sort_import_specifiers( named_specifiers: JsNamedImportSpecifiers, + sort_mode: SortMode, ) -> Option { + let comparator = get_comparator(sort_mode); let new_list = sorted_separated_list_by( &named_specifiers.specifiers(), |node| { @@ -51,6 +62,7 @@ pub fn sort_import_specifiers( )) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), + Some(comparator) ) .ok()?; Some(named_specifiers.with_specifiers(new_list)) @@ -59,6 +71,7 @@ pub fn sort_import_specifiers( pub fn merge_import_specifiers( named_specifiers1: JsNamedImportSpecifiers, named_specifiers2: &JsNamedImportSpecifiers, + sort_mode: SortMode, ) -> Option { let specifiers1 = named_specifiers1.specifiers(); let specifiers2 = named_specifiers2.specifiers(); @@ -91,23 +104,27 @@ pub fn merge_import_specifiers( } } let new_list = make::js_named_import_specifier_list(nodes, separators); - sort_import_specifiers(named_specifiers1.with_specifiers(new_list)) + sort_import_specifiers(named_specifiers1.with_specifiers(new_list), sort_mode) } -pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList) -> Option { +pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, sort_mode: SortMode) -> Option { + let comparator = get_comparator(sort_mode); + is_separated_list_sorted_by(specifiers, |node| { node.source_name() .ok()? .inner_string_text() .ok() .map(ComparableToken::new) - }) + }, Some(comparator)) .ok() } pub fn sort_export_specifiers( named_specifiers: &JsExportNamedFromSpecifierList, + sort_mode: SortMode, ) -> Option { + let comparator = get_comparator(sort_mode); let new_list = sorted_separated_list_by( named_specifiers, |node| { @@ -118,7 +135,7 @@ pub fn sort_export_specifiers( .map(ComparableToken::new) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - ) + Some(comparator)) .ok()?; Some(new_list) } @@ -126,6 +143,7 @@ pub fn sort_export_specifiers( pub fn merge_export_specifiers( specifiers1: &JsExportNamedFromSpecifierList, specifiers2: &JsExportNamedFromSpecifierList, + sort_mode: SortMode, ) -> Option { let mut nodes = Vec::with_capacity(specifiers1.len() + specifiers2.len()); let mut separators = Vec::with_capacity(specifiers1.len() + specifiers2.len()); @@ -156,21 +174,24 @@ pub fn merge_export_specifiers( } } sort_export_specifiers(&make::js_export_named_from_specifier_list( - nodes, separators, - )) + nodes, separators + ), sort_mode) } -pub fn are_import_attributes_sorted(attributes: &JsImportAssertion) -> Option { +pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_mode: SortMode) -> Option { + let comparator = get_comparator(sort_mode); is_separated_list_sorted_by(&attributes.assertions(), |node| { let AnyJsImportAssertionEntry::JsImportAssertionEntry(node) = node else { return None; }; Some(ComparableToken::new(inner_string_text(&node.key().ok()?))) - }) + }, Some(comparator)) .ok() } -pub fn sort_attributes(attributes: JsImportAssertion) -> Option { +pub fn sort_attributes(attributes: JsImportAssertion, sort_mode: SortMode) -> Option { + let comparator = get_comparator(sort_mode); + let new_list = sorted_separated_list_by( &attributes.assertions(), |node| { @@ -180,7 +201,16 @@ pub fn sort_attributes(attributes: JsImportAssertion) -> Option fn(&ComparableToken, &ComparableToken) -> Ordering { + let comparator = match sort_mode { + SortMode::Lexicographic => ComparableToken::lexicographic_cmp, + SortMode::Natural => ComparableToken::ascii_nat_cmp + }; + comparator +} diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js new file mode 100644 index 000000000000..78fce545f4f9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js @@ -0,0 +1,20 @@ +import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" +import { var1, var2, var21, var11, var12, var22 } from 'custom-package' +import { + BlindedBeaconBlock, + Attestation, + Epoch, + ProducedBlockSource, + Slot, + UintBn64, + ValidatorIndex, + altair, + BLSSignature, + phase0, + ssz, + Root, + sszTypesFor, + stringType, + CommitteeIndex, + BeaconBlockOrContents, +} from "@lodestar/types"; diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js.snap b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js.snap new file mode 100644 index 000000000000..b046ea7265ee --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.js.snap @@ -0,0 +1,87 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: custom-sort-lexicographic.js +--- +# Input +```js +import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" +import { var1, var2, var21, var11, var12, var22 } from 'custom-package' +import { + BlindedBeaconBlock, + Attestation, + Epoch, + ProducedBlockSource, + Slot, + UintBn64, + ValidatorIndex, + altair, + BLSSignature, + phase0, + ssz, + Root, + sszTypesFor, + stringType, + CommitteeIndex, + BeaconBlockOrContents, +} from "@lodestar/types"; + +``` + +# Diagnostics +``` +custom-sort-lexicographic.js:1:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The imports and exports are not sorted. + + > 1 │ import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ import { var1, var2, var21, var11, var12, var22 } from 'custom-package' + 3 │ import { + + i Safe fix: Organize Imports (Biome) + + 1 │ - import·{·Module,·module,·Module1,·Module2,·module2,·module1,·module21,·module1,·module2,·module11··}·from·"@scopeX/special" + 2 │ - import·{·var1,·var2,·var21,·var11,·var12,·var22·}·from·'custom-package' + 3 │ - import·{ + 4 │ - ··BlindedBeaconBlock,·· + 5 │ - ··Attestation,·· + 6 │ - ··Epoch, + 7 │ - ··ProducedBlockSource, + 8 │ - ··Slot, + 9 │ - ··UintBn64, + 10 │ - ··ValidatorIndex, + 11 │ - ··altair, + 12 │ - ··BLSSignature, + 13 │ - ··phase0, + 14 │ - ··ssz, + 15 │ - ··Root, + 16 │ - ··sszTypesFor, + 17 │ - ··stringType, + 18 │ - ··CommitteeIndex, + 19 │ - ··BeaconBlockOrContents, + 20 │ - }·from·"@lodestar/types"; + 1 │ + import·{ + 2 │ + ··Attestation,·· + 3 │ + ··BLSSignature, + 4 │ + ··BeaconBlockOrContents, + 5 │ + ··BlindedBeaconBlock,·· + 6 │ + ··CommitteeIndex, + 7 │ + ··Epoch, + 8 │ + ··ProducedBlockSource, + 9 │ + ··Root, + 10 │ + ··Slot, + 11 │ + ··UintBn64, + 12 │ + ··ValidatorIndex, + 13 │ + ··altair, + 14 │ + ··phase0, + 15 │ + ··ssz, + 16 │ + ··sszTypesFor, + 17 │ + ··stringType, + 18 │ + }·from·"@lodestar/types"; + 19 │ + import·{·Module,·Module1,·Module2,·module,·module1,·module2,·module21,·module1,·module2,·module11··}·from·"@scopeX/special" + 20 │ + import·{·var1,·var11,·var12,·var2,·var21,·var22·}·from·'custom-package' + 21 21 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json new file mode 100644 index 000000000000..2f33c29c2983 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "sortMode": "lexicographic" + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js new file mode 100644 index 000000000000..78fce545f4f9 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js @@ -0,0 +1,20 @@ +import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" +import { var1, var2, var21, var11, var12, var22 } from 'custom-package' +import { + BlindedBeaconBlock, + Attestation, + Epoch, + ProducedBlockSource, + Slot, + UintBn64, + ValidatorIndex, + altair, + BLSSignature, + phase0, + ssz, + Root, + sszTypesFor, + stringType, + CommitteeIndex, + BeaconBlockOrContents, +} from "@lodestar/types"; diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js.snap b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js.snap new file mode 100644 index 000000000000..157f650d4220 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.js.snap @@ -0,0 +1,87 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: custom-sort-natural.js +--- +# Input +```js +import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" +import { var1, var2, var21, var11, var12, var22 } from 'custom-package' +import { + BlindedBeaconBlock, + Attestation, + Epoch, + ProducedBlockSource, + Slot, + UintBn64, + ValidatorIndex, + altair, + BLSSignature, + phase0, + ssz, + Root, + sszTypesFor, + stringType, + CommitteeIndex, + BeaconBlockOrContents, +} from "@lodestar/types"; + +``` + +# Diagnostics +``` +custom-sort-natural.js:1:1 assist/source/organizeImports FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i The imports and exports are not sorted. + + > 1 │ import { Module, module, Module1, Module2, module2, module1, module21, module1, module2, module11 } from "@scopeX/special" + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 2 │ import { var1, var2, var21, var11, var12, var22 } from 'custom-package' + 3 │ import { + + i Safe fix: Organize Imports (Biome) + + 1 │ - import·{·Module,·module,·Module1,·Module2,·module2,·module1,·module21,·module1,·module2,·module11··}·from·"@scopeX/special" + 2 │ - import·{·var1,·var2,·var21,·var11,·var12,·var22·}·from·'custom-package' + 3 │ - import·{ + 4 │ - ··BlindedBeaconBlock,·· + 5 │ - ··Attestation,·· + 6 │ - ··Epoch, + 7 │ - ··ProducedBlockSource, + 8 │ - ··Slot, + 9 │ - ··UintBn64, + 10 │ - ··ValidatorIndex, + 11 │ - ··altair, + 12 │ - ··BLSSignature, + 13 │ - ··phase0, + 14 │ - ··ssz, + 15 │ - ··Root, + 16 │ - ··sszTypesFor, + 17 │ - ··stringType, + 18 │ - ··CommitteeIndex, + 19 │ - ··BeaconBlockOrContents, + 20 │ - }·from·"@lodestar/types"; + 1 │ + import·{ + 2 │ + ··Attestation,·· + 3 │ + ··altair, + 4 │ + ··BeaconBlockOrContents, + 5 │ + ··BLSSignature, + 6 │ + ··BlindedBeaconBlock,·· + 7 │ + ··CommitteeIndex, + 8 │ + ··Epoch, + 9 │ + ··ProducedBlockSource, + 10 │ + ··phase0, + 11 │ + ··Root, + 12 │ + ··Slot, + 13 │ + ··ssz, + 14 │ + ··sszTypesFor, + 15 │ + ··stringType, + 16 │ + ··UintBn64, + 17 │ + ··ValidatorIndex, + 18 │ + }·from·"@lodestar/types"; + 19 │ + import·{·Module,·Module1,·Module2,·module,·module1,·module2,·module21,·module1,·module2,·module11··}·from·"@scopeX/special" + 20 │ + import·{·var1,·var2,·var11,·var12,·var21,·var22·}·from·'custom-package' + 21 21 │ + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json new file mode 100644 index 000000000000..6e5e6e60c608 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json @@ -0,0 +1,15 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { + "sortMode": "natural" + } + } + } + } + } +} diff --git a/crates/biome_rule_options/src/organize_imports.rs b/crates/biome_rule_options/src/organize_imports.rs index 6f4ceddf0866..e0f253cfc582 100644 --- a/crates/biome_rule_options/src/organize_imports.rs +++ b/crates/biome_rule_options/src/organize_imports.rs @@ -4,10 +4,12 @@ pub mod import_source; use crate::organize_imports::import_groups::ImportGroups; use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; +pub use crate::shared::sort_mode::SortMode; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct OrganizeImportsOptions { pub groups: ImportGroups, + pub sort_mode: SortMode, } From a8b8a7d711de6b3ae1578916efac501e2b772015 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 2 Jul 2025 18:55:53 +0200 Subject: [PATCH 06/27] Update the configuration schema --- .../@biomejs/biome/configuration_schema.json | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 34fd78ceb207..b66adae5c3c6 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1837,6 +1837,11 @@ "type": "array", "items": { "$ref": "#/definitions/ImportGroup" } }, + "SortMode": { + "type": "string", + "enum": ["lexicographic", "natural"], + "default": "natural" + }, "ImportMatcher": { "type": "object", "properties": { @@ -5028,6 +5033,9 @@ "groups": { "default": [], "allOf": [{ "$ref": "#/definitions/ImportGroups" }] + }, + "sortMode": { + "$ref": "#/definitions/SortMode" } }, "additionalProperties": false @@ -13508,6 +13516,11 @@ }, "UseSortedAttributesOptions": { "type": "object", + "properties": { + "sortMode": { + "$ref": "#/definitions/SortMode" + } + }, "additionalProperties": false }, "UseSortedClassesConfiguration": { @@ -13532,7 +13545,15 @@ }, "additionalProperties": false }, - "UseSortedKeysOptions": { "type": "object", "additionalProperties": false }, + "UseSortedKeysOptions": { + "type": "object", + "properties": { + "sortMode": { + "$ref": "#/definitions/SortMode" + } + }, + "additionalProperties": false + }, "UseSortedPropertiesOptions": { "type": "object", "additionalProperties": false From e2d95a86d7dadbdbf43708bb6206d9532e4744ab Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 3 Jul 2025 14:33:29 +0200 Subject: [PATCH 07/27] Rename sort_mode to sort_order --- .../src/assist/source/organize_imports.rs | 30 +-- .../organize_imports/specifiers_attributes.rs | 52 +++--- .../assist/source/use_sorted_attributes.rs | 14 +- .../src/assist/source/use_sorted_keys.rs | 18 +- .../invalid_entropy_high.js.snap.new | 171 ++++++++++++++++++ .../noSecrets/invalid_entropy_low.js.snap.new | 119 ++++++++++++ .../custom-sort-lexicographic.options.json | 2 +- .../custom-sort-natural.options.json | 2 +- .../sorted-lexicographic.options.json | 2 +- .../sorted-natural.options.json | 2 +- .../sorted-lexicographic.options.json | 2 +- .../useSortedKeys/sorted-natural.options.json | 2 +- .../src/assist/source/use_sorted_keys.rs | 18 +- .../sorted-lexicographic.options.json | 2 +- .../useSortedKeys/sorted-natural.options.json | 2 +- .../src/organize_imports.rs | 4 +- crates/biome_rule_options/src/shared/mod.rs | 2 +- .../shared/{sort_mode.rs => sort_order.rs} | 2 +- .../src/use_sorted_attributes.rs | 4 +- .../biome_rule_options/src/use_sorted_keys.rs | 4 +- .../@biomejs/biome/configuration_schema.json | 14 +- 21 files changed, 379 insertions(+), 89 deletions(-) create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new create mode 100644 crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new rename crates/biome_rule_options/src/shared/{sort_mode.rs => sort_order.rs} (92%) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index bcb645fe991a..bcf4a088b087 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -10,7 +10,7 @@ use biome_js_syntax::{ JsSyntaxKind, T, }; use biome_rowan::{AstNode, BatchMutationExt, TextRange, TriviaPieceKind, chain_trivia_pieces}; -use biome_rule_options::{organize_imports::OrganizeImportsOptions, sort_mode::SortMode}; +use biome_rule_options::{organize_imports::OrganizeImportsOptions, sort_order::SortOrder}; use import_key::{ImportInfo, ImportKey}; use rustc_hash::FxHashMap; use specifiers_attributes::{ @@ -683,7 +683,7 @@ impl Rule for OrganizeImports { let root = ctx.query(); let mut result = Vec::new(); let options = ctx.options(); - let sort_mode = options.sort_mode; + let sort_order = options.sort_order; let mut chunk: Option = None; let mut prev_kind: Option = None; let mut prev_group = 0; @@ -703,10 +703,10 @@ impl Rule for OrganizeImports { let starts_chunk = chunk.is_none(); let leading_newline_count = leading_newlines(item.syntax()).count(); let are_specifiers_unsorted = - specifiers.is_some_and(|specifiers| !specifiers.are_sorted(sort_mode)); + specifiers.is_some_and(|specifiers| !specifiers.are_sorted(sort_order)); let are_attributes_unsorted = attributes.is_some_and(|attributes| { // Assume the attributes are sorted if there are any bogus nodes. - !(are_import_attributes_sorted(&attributes, sort_mode).unwrap_or(true)) + !(are_import_attributes_sorted(&attributes, sort_order).unwrap_or(true)) }); let newline_issue = if leading_newline_count == 1 // A chunk must start with a blank line (two newlines) @@ -793,8 +793,8 @@ impl Rule for OrganizeImports { } let options = ctx.options(); - let sort_mode = options.sort_mode; - // let comparator = match sort_mode { + let sort_order = options.sort_order; + // let comparator = match sort_order { // SortMode::Lexicographic => ComparableToken::lexicographic_cmp, // SortMode::Natural => ComparableToken::ascii_nat_cmp // }; @@ -834,7 +834,7 @@ impl Rule for OrganizeImports { // Sort named specifiers if let AnyJsExportClause::JsExportNamedFromClause(cast) = &clause { if let Some(sorted_specifiers) = - sort_export_specifiers(&cast.specifiers(), sort_mode) + sort_export_specifiers(&cast.specifiers(), sort_order) { clause = cast.clone().with_specifiers(sorted_specifiers).into(); @@ -843,7 +843,7 @@ impl Rule for OrganizeImports { } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_mode)); + let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_order)); clause = clause.with_attribute(sorted_attrs); } export.with_export_clause(clause).into() @@ -853,14 +853,14 @@ impl Rule for OrganizeImports { if *are_specifiers_unsorted { // Sort named specifiers if let Some(sorted_specifiers) = - clause.named_specifiers().and_then(|specifiers| sort_import_specifiers(specifiers, sort_mode)) + clause.named_specifiers().and_then(|specifiers| sort_import_specifiers(specifiers, sort_order)) { clause = clause.with_named_specifiers(sorted_specifiers) } } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_mode)); + let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_order)); clause = clause.with_attribute(sorted_attrs); } import.with_import_clause(clause).into() @@ -926,7 +926,7 @@ impl Rule for OrganizeImports { } = &import_keys[i - 1]; let KeyedItem { key, item, .. } = &import_keys[i]; if prev_key.is_mergeable(key) { - if let Some(merged) = merge(prev_item.as_ref(), item.as_ref(), sort_mode) { + if let Some(merged) = merge(prev_item.as_ref(), item.as_ref(), sort_order) { import_keys[i - 1].was_merged = true; import_keys[i - 1].item = Some(merged); import_keys[i].item = None; @@ -1065,7 +1065,7 @@ pub enum NewLineIssue { fn merge( item1: Option<&AnyJsModuleItem>, item2: Option<&AnyJsModuleItem>, - sort_mode: SortMode, + sort_order: SortOrder, ) -> Option { match (item1?, item2?) { (AnyJsModuleItem::JsExport(item1), AnyJsModuleItem::JsExport(item2)) => { @@ -1077,7 +1077,7 @@ fn merge( let clause2 = clause2.as_js_export_named_from_clause()?; let specifiers1 = clause1.specifiers(); let specifiers2 = clause2.specifiers(); - if let Some(meregd_specifiers) = merge_export_specifiers(&specifiers1, &specifiers2, sort_mode) { + if let Some(meregd_specifiers) = merge_export_specifiers(&specifiers1, &specifiers2, sort_order) { let meregd_clause = clause1.with_specifiers(meregd_specifiers); let merged_item = item2.clone().with_export_clause(meregd_clause.into()); @@ -1143,7 +1143,7 @@ fn merge( }; let specifiers2 = clause2.named_specifiers().ok()?; if let Some(meregd_specifiers) = - merge_import_specifiers(specifiers1, &specifiers2, sort_mode) + merge_import_specifiers(specifiers1, &specifiers2, sort_order) { let merged_clause = clause1.with_specifier(meregd_specifiers.into()); let merged_item = item2.clone().with_import_clause(merged_clause.into()); @@ -1166,7 +1166,7 @@ fn merge( let specifiers1 = clause1.named_specifiers().ok()?; let specifiers2 = clause2.named_specifiers().ok()?; if let Some(meregd_specifiers) = - merge_import_specifiers(specifiers1, &specifiers2, sort_mode) + merge_import_specifiers(specifiers1, &specifiers2, sort_order) { let merged_clause = clause1.with_named_specifiers(meregd_specifiers); let merged_item = item2.clone().with_import_clause(merged_clause.into()); diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index 6b4f9f3922ef..77625a19c6c9 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -7,20 +7,20 @@ use biome_js_syntax::{ }; use biome_rowan::{AstNode, AstSeparatedElement, AstSeparatedList, TriviaPieceKind}; use biome_string_case::comparable_token::ComparableToken; -use biome_rule_options::organize_imports::SortMode; +use biome_rule_options::organize_imports::SortOrder; pub enum JsNamedSpecifiers { JsNamedImportSpecifiers(JsNamedImportSpecifiers), JsExportNamedFromSpecifierList(JsExportNamedFromSpecifierList), } impl JsNamedSpecifiers { - pub fn are_sorted(&self, sort_mode: SortMode) -> bool { + pub fn are_sorted(&self, sort_order: SortOrder) -> bool { match self { - Self::JsNamedImportSpecifiers(specifeirs) => are_import_specifiers_sorted(specifeirs, sort_mode), + Self::JsNamedImportSpecifiers(specifeirs) => are_import_specifiers_sorted(specifeirs, sort_order), Self::JsExportNamedFromSpecifierList(specifeirs) => { - are_export_specifiers_sorted(specifeirs, sort_mode) + are_export_specifiers_sorted(specifeirs, sort_order) } } // Assume the import is already sorted if there are any bogus nodes, otherwise the `--write` @@ -29,10 +29,10 @@ impl JsNamedSpecifiers { } } -pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, sort_mode: SortMode) -> Option { - let comparator = match sort_mode { - SortMode::Lexicographic => ComparableToken::lexicographic_cmp, - SortMode::Natural => ComparableToken::ascii_nat_cmp, +pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, sort_order: SortOrder) -> Option { + let comparator = match sort_order { + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, + SortOrder::Natural => ComparableToken::ascii_nat_cmp, }; is_separated_list_sorted_by(&named_specifiers.specifiers(), |node| { @@ -48,9 +48,9 @@ pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, pub fn sort_import_specifiers( named_specifiers: JsNamedImportSpecifiers, - sort_mode: SortMode, + sort_order: SortOrder, ) -> Option { - let comparator = get_comparator(sort_mode); + let comparator = get_comparator(sort_order); let new_list = sorted_separated_list_by( &named_specifiers.specifiers(), |node| { @@ -71,7 +71,7 @@ pub fn sort_import_specifiers( pub fn merge_import_specifiers( named_specifiers1: JsNamedImportSpecifiers, named_specifiers2: &JsNamedImportSpecifiers, - sort_mode: SortMode, + sort_order: SortOrder, ) -> Option { let specifiers1 = named_specifiers1.specifiers(); let specifiers2 = named_specifiers2.specifiers(); @@ -104,11 +104,11 @@ pub fn merge_import_specifiers( } } let new_list = make::js_named_import_specifier_list(nodes, separators); - sort_import_specifiers(named_specifiers1.with_specifiers(new_list), sort_mode) + sort_import_specifiers(named_specifiers1.with_specifiers(new_list), sort_order) } -pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, sort_mode: SortMode) -> Option { - let comparator = get_comparator(sort_mode); +pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, sort_order: SortOrder) -> Option { + let comparator = get_comparator(sort_order); is_separated_list_sorted_by(specifiers, |node| { node.source_name() @@ -122,9 +122,9 @@ pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, pub fn sort_export_specifiers( named_specifiers: &JsExportNamedFromSpecifierList, - sort_mode: SortMode, + sort_order: SortOrder, ) -> Option { - let comparator = get_comparator(sort_mode); + let comparator = get_comparator(sort_order); let new_list = sorted_separated_list_by( named_specifiers, |node| { @@ -143,7 +143,7 @@ pub fn sort_export_specifiers( pub fn merge_export_specifiers( specifiers1: &JsExportNamedFromSpecifierList, specifiers2: &JsExportNamedFromSpecifierList, - sort_mode: SortMode, + sort_order: SortOrder, ) -> Option { let mut nodes = Vec::with_capacity(specifiers1.len() + specifiers2.len()); let mut separators = Vec::with_capacity(specifiers1.len() + specifiers2.len()); @@ -175,11 +175,11 @@ pub fn merge_export_specifiers( } sort_export_specifiers(&make::js_export_named_from_specifier_list( nodes, separators - ), sort_mode) + ), sort_order) } -pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_mode: SortMode) -> Option { - let comparator = get_comparator(sort_mode); +pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_order: SortOrder) -> Option { + let comparator = get_comparator(sort_order); is_separated_list_sorted_by(&attributes.assertions(), |node| { let AnyJsImportAssertionEntry::JsImportAssertionEntry(node) = node else { return None; @@ -189,8 +189,8 @@ pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_mode: S .ok() } -pub fn sort_attributes(attributes: JsImportAssertion, sort_mode: SortMode) -> Option { - let comparator = get_comparator(sort_mode); +pub fn sort_attributes(attributes: JsImportAssertion, sort_order: SortOrder) -> Option { + let comparator = get_comparator(sort_order); let new_list = sorted_separated_list_by( &attributes.assertions(), @@ -207,10 +207,10 @@ pub fn sort_attributes(attributes: JsImportAssertion, sort_mode: SortMode) -> Op Some(attributes.with_assertions(new_list)) } -pub fn get_comparator(sort_mode: SortMode) -> fn(&ComparableToken, &ComparableToken) -> Ordering { - let comparator = match sort_mode { - SortMode::Lexicographic => ComparableToken::lexicographic_cmp, - SortMode::Natural => ComparableToken::ascii_nat_cmp +pub fn get_comparator(sort_order: SortOrder) -> fn(&ComparableToken, &ComparableToken) -> Ordering { + let comparator = match sort_order { + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, + SortOrder::Natural => ComparableToken::ascii_nat_cmp }; comparator } diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs index 2a57a64458b1..51fb79107b1d 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs @@ -11,7 +11,7 @@ use biome_js_syntax::{ AnyJsxAttribute, JsxAttribute, JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, }; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_attributes::{UseSortedAttributesOptions, SortMode}; +use biome_rule_options::use_sorted_attributes::{UseSortedAttributesOptions, SortOrder}; use biome_string_case::StrLikeExtension; use crate::JsRuleAction; @@ -59,11 +59,11 @@ impl Rule for UseSortedAttributes { let mut current_prop_group = PropGroup::default(); let mut prop_groups = Vec::new(); let options= ctx.options(); - let sort_by = options.sort_mode; + let sort_by = options.sort_order; let comparator = match sort_by { - SortMode::Natural => PropElement::ascii_nat_cmp, - SortMode::Lexicographic => PropElement::lexicographic_cmp, + SortOrder::Natural => PropElement::ascii_nat_cmp, + SortOrder::Lexicographic => PropElement::lexicographic_cmp, }; // Convert to boolean-based comparator for is_sorted_by @@ -116,11 +116,11 @@ impl Rule for UseSortedAttributes { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); let options= ctx.options(); - let sort_by = options.sort_mode; + let sort_by = options.sort_order; let comparator = match sort_by { - SortMode::Natural => PropElement::ascii_nat_cmp, - SortMode::Lexicographic => PropElement::lexicographic_cmp, + SortOrder::Natural => PropElement::ascii_nat_cmp, + SortOrder::Lexicographic => PropElement::lexicographic_cmp, }; for (PropElement { prop }, PropElement { prop: sorted_prop }) in diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index ac7ee711a5c3..9df4a864b706 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -12,7 +12,7 @@ use biome_diagnostics::{Applicability, category}; use biome_js_factory::make; use biome_js_syntax::{JsObjectExpression, JsObjectMemberList, T}; use biome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind}; -use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortMode}; +use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortOrder}; use biome_string_case::comparable_token::ComparableToken; use crate::JsRuleAction; @@ -90,10 +90,10 @@ impl Rule for UseSortedKeys { fn run(ctx: &RuleContext) -> Self::Signals { let options = ctx.options(); - let sort_mode = options.sort_mode; - let comparator = match sort_mode { - SortMode::Natural => ComparableToken::ascii_nat_cmp, - SortMode::Lexicographic => ComparableToken::lexicographic_cmp + let sort_order = options.sort_order; + let comparator = match sort_order { + SortOrder::Natural => ComparableToken::ascii_nat_cmp, + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp }; is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new), Some(comparator)) @@ -123,10 +123,10 @@ impl Rule for UseSortedKeys { fn action(ctx: &RuleContext, _: &Self::State) -> Option { let list = ctx.query(); let options = ctx.options(); - let sort_mode = options.sort_mode; - let comparator = match sort_mode { - SortMode::Natural => ComparableToken::ascii_nat_cmp, - SortMode::Lexicographic => ComparableToken::lexicographic_cmp + let sort_order = options.sort_order; + let comparator = match sort_order { + SortOrder::Natural => ComparableToken::ascii_nat_cmp, + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp }; let new_list = sorted_separated_list_by( diff --git a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new new file mode 100644 index 000000000000..e134291d92d4 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new @@ -0,0 +1,171 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: invalid_entropy_high.js +--- +# Input +```js +// Completely random with symbols +const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; +const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; +const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; + +// Long random with symbols +const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; +const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; +const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; + +``` + +# Diagnostics +``` +invalid_entropy_high.options:10:25 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Found an unknown key `entropyThreshold`. + + 8 │ "level": "error", + 9 │ "options": { + > 10 │ "entropyThreshold": 80 + │ ^^^^^^^^^^^^^^^^^^ + 11 │ } + 12 │ } + + i Known keys: + + +``` + +``` +invalid_entropy_high.js:2:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 1 │ // Completely random with symbols + > 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; + 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_high.js:3:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 1 │ // Completely random with symbols + 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; + > 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; + 5 │ + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_high.js:4:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; + 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; + > 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ // Long random with symbols + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_high.js:7:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 6 │ // Long random with symbols + > 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; + 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_high.js:8:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 6 │ // Long random with symbols + 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; + > 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; + 10 │ + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_high.js:9:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; + 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; + > 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new new file mode 100644 index 000000000000..92f3563cc064 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new @@ -0,0 +1,119 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +assertion_line: 134 +expression: invalid_entropy_low.js +--- +# Input +```js +// These should trigger with low entropy threshold +const mediumEntropy1 = "abc123def456ghi"; +const mediumEntropy2 = "user2024Password"; +const mediumEntropy3 = "SecretKey123ABC"; +const mediumEntropy4 = "TokenXyZ98765"; +``` + +# Diagnostics +``` +invalid_entropy_low.options:10:25 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Found an unknown key `entropyThreshold`. + + 8 │ "level": "error", + 9 │ "options": { + > 10 │ "entropyThreshold": 20 + │ ^^^^^^^^^^^^^^^^^^ + 11 │ } + 12 │ } + + i Known keys: + + +``` + +``` +invalid_entropy_low.js:2:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 1 │ // These should trigger with low entropy threshold + > 2 │ const mediumEntropy1 = "abc123def456ghi"; + │ ^^^^^^^^^^^^^^^^^ + 3 │ const mediumEntropy2 = "user2024Password"; + 4 │ const mediumEntropy3 = "SecretKey123ABC"; + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_low.js:3:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 1 │ // These should trigger with low entropy threshold + 2 │ const mediumEntropy1 = "abc123def456ghi"; + > 3 │ const mediumEntropy2 = "user2024Password"; + │ ^^^^^^^^^^^^^^^^^^ + 4 │ const mediumEntropy3 = "SecretKey123ABC"; + 5 │ const mediumEntropy4 = "TokenXyZ98765"; + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_low.js:4:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 2 │ const mediumEntropy1 = "abc123def456ghi"; + 3 │ const mediumEntropy2 = "user2024Password"; + > 4 │ const mediumEntropy3 = "SecretKey123ABC"; + │ ^^^^^^^^^^^^^^^^^ + 5 │ const mediumEntropy4 = "TokenXyZ98765"; + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` + +``` +invalid_entropy_low.js:5:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential secret found. + + 3 │ const mediumEntropy2 = "user2024Password"; + 4 │ const mediumEntropy3 = "SecretKey123ABC"; + > 5 │ const mediumEntropy4 = "TokenXyZ98765"; + │ ^^^^^^^^^^^^^^^ + + i Type of secret detected: Detected high entropy string + + i Storing secrets in source code is a security risk. Consider the following steps: + 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. + 2. If needed, use environment variables or a secure secret management system to store sensitive data. + 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. + This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations + + +``` diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json index 2f33c29c2983..1d75175d47e3 100644 --- a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json @@ -6,7 +6,7 @@ "organizeImports": { "level": "on", "options": { - "sortMode": "lexicographic" + "sortOrder": "lexicographic" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json index 6e5e6e60c608..a4af86114383 100644 --- a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json @@ -6,7 +6,7 @@ "organizeImports": { "level": "on", "options": { - "sortMode": "natural" + "sortOrder": "natural" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json index 6d94df677dcc..0f678f1fbbf3 100644 --- a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-lexicographic.options.json @@ -6,7 +6,7 @@ "useSortedAttributes": { "level": "on", "options": { - "sortMode": "lexicographic" + "sortOrder": "lexicographic" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json index 1719a0270a45..083c09e37c77 100644 --- a/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json +++ b/crates/biome_js_analyze/tests/specs/source/useSortedAttributes/sorted-natural.options.json @@ -6,7 +6,7 @@ "useSortedAttributes": { "level": "on", "options": { - "sortMode": "natural" + "sortOrder": "natural" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json index 2c980a3f2196..a7bb7afbacad 100644 --- a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json @@ -6,7 +6,7 @@ "useSortedKeys": { "level": "on", "options": { - "sortMode": "lexicographic" + "sortOrder": "lexicographic" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json index 37a11c0ccb1c..afb39daa0929 100644 --- a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json +++ b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json @@ -6,7 +6,7 @@ "useSortedKeys": { "level": "on", "options": { - "sortMode": "natural" + "sortOrder": "natural" } } } diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index b488391b09b8..1afb12bcac53 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -8,7 +8,7 @@ use biome_diagnostics::category; use biome_json_factory::make; use biome_json_syntax::{JsonMemberList, JsonObjectValue, T, TextRange}; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortMode}; +use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortOrder}; use biome_string_case::comparable_token::ComparableToken; use std::ops::Not; @@ -42,10 +42,10 @@ impl Rule for UseSortedKeys { fn run(ctx: &RuleContext) -> Option { let options = ctx.options(); - let sort_mode = options.sort_mode; - let comparator = match sort_mode { - SortMode::Natural => ComparableToken::ascii_nat_cmp, - SortMode::Lexicographic => ComparableToken::lexicographic_cmp + let sort_order = options.sort_order; + let comparator = match sort_order { + SortOrder::Natural => ComparableToken::ascii_nat_cmp, + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp }; is_separated_list_sorted_by(ctx.query(), |node| { @@ -81,10 +81,10 @@ impl Rule for UseSortedKeys { fn action(ctx: &RuleContext, _state: &Self::State) -> Option { let list = ctx.query(); let options = ctx.options(); - let sort_mode = options.sort_mode; - let comparator = match sort_mode { - SortMode::Natural => ComparableToken::ascii_nat_cmp, - SortMode::Lexicographic => ComparableToken::lexicographic_cmp + let sort_order = options.sort_order; + let comparator = match sort_order { + SortOrder::Natural => ComparableToken::ascii_nat_cmp, + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp }; let new_list = sorted_separated_list_by( diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json index 2c980a3f2196..a7bb7afbacad 100644 --- a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-lexicographic.options.json @@ -6,7 +6,7 @@ "useSortedKeys": { "level": "on", "options": { - "sortMode": "lexicographic" + "sortOrder": "lexicographic" } } } diff --git a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json index 1c1fbec78793..b57696e649df 100644 --- a/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json +++ b/crates/biome_json_analyze/tests/specs/source/useSortedKeys/sorted-natural.options.json @@ -6,7 +6,7 @@ "useSortedKeys": { "level": "on", "options": { - "sortMode": "natural" + "sortOrder": "natural" } } } diff --git a/crates/biome_rule_options/src/organize_imports.rs b/crates/biome_rule_options/src/organize_imports.rs index e0f253cfc582..034d9828632b 100644 --- a/crates/biome_rule_options/src/organize_imports.rs +++ b/crates/biome_rule_options/src/organize_imports.rs @@ -4,12 +4,12 @@ pub mod import_source; use crate::organize_imports::import_groups::ImportGroups; use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_mode::SortMode; +pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct OrganizeImportsOptions { pub groups: ImportGroups, - pub sort_mode: SortMode, + pub sort_order: SortOrder, } diff --git a/crates/biome_rule_options/src/shared/mod.rs b/crates/biome_rule_options/src/shared/mod.rs index abb494d63bc6..524a97feab82 100644 --- a/crates/biome_rule_options/src/shared/mod.rs +++ b/crates/biome_rule_options/src/shared/mod.rs @@ -1,2 +1,2 @@ pub mod restricted_regex; -pub mod sort_mode; \ No newline at end of file +pub mod sort_order; \ No newline at end of file diff --git a/crates/biome_rule_options/src/shared/sort_mode.rs b/crates/biome_rule_options/src/shared/sort_order.rs similarity index 92% rename from crates/biome_rule_options/src/shared/sort_mode.rs rename to crates/biome_rule_options/src/shared/sort_order.rs index f9beb1d07630..844f740084c9 100644 --- a/crates/biome_rule_options/src/shared/sort_mode.rs +++ b/crates/biome_rule_options/src/shared/sort_order.rs @@ -2,7 +2,7 @@ Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, biome_deserialize_macros::Deserializable, )] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -pub enum SortMode { +pub enum SortOrder { #[default] Natural, Lexicographic, diff --git a/crates/biome_rule_options/src/use_sorted_attributes.rs b/crates/biome_rule_options/src/use_sorted_attributes.rs index cb06bb6d5ae2..524d9bc1d8be 100644 --- a/crates/biome_rule_options/src/use_sorted_attributes.rs +++ b/crates/biome_rule_options/src/use_sorted_attributes.rs @@ -1,10 +1,10 @@ use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_mode::SortMode; +pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct UseSortedAttributesOptions { - pub sort_mode: SortMode, + pub sort_order: SortOrder, } diff --git a/crates/biome_rule_options/src/use_sorted_keys.rs b/crates/biome_rule_options/src/use_sorted_keys.rs index f90e2e4e7cb7..ba1c4b557b2f 100644 --- a/crates/biome_rule_options/src/use_sorted_keys.rs +++ b/crates/biome_rule_options/src/use_sorted_keys.rs @@ -1,10 +1,10 @@ use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_mode::SortMode; +pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct UseSortedKeysOptions { - pub sort_mode: SortMode, + pub sort_order: SortOrder, } diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index b66adae5c3c6..9f1e39b82609 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1837,7 +1837,7 @@ "type": "array", "items": { "$ref": "#/definitions/ImportGroup" } }, - "SortMode": { + "SortOrder": { "type": "string", "enum": ["lexicographic", "natural"], "default": "natural" @@ -5034,8 +5034,8 @@ "default": [], "allOf": [{ "$ref": "#/definitions/ImportGroups" }] }, - "sortMode": { - "$ref": "#/definitions/SortMode" + "sortOrder": { + "$ref": "#/definitions/SortOrder" } }, "additionalProperties": false @@ -13517,8 +13517,8 @@ "UseSortedAttributesOptions": { "type": "object", "properties": { - "sortMode": { - "$ref": "#/definitions/SortMode" + "sortOrder": { + "$ref": "#/definitions/SortOrder" } }, "additionalProperties": false @@ -13548,8 +13548,8 @@ "UseSortedKeysOptions": { "type": "object", "properties": { - "sortMode": { - "$ref": "#/definitions/SortMode" + "sortOrder": { + "$ref": "#/definitions/SortOrder" } }, "additionalProperties": false From aa4f143539cd5a7613922752902bd3e5af8a0662 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 3 Jul 2025 14:42:31 +0200 Subject: [PATCH 08/27] Update sortOrder to identifierOrder for organizeImports --- .../src/assist/source/organize_imports.rs | 32 +++++++++++++------ .../custom-sort-lexicographic.options.json | 2 +- .../custom-sort-natural.options.json | 2 +- .../src/organize_imports.rs | 4 +-- .../@biomejs/biome/configuration_schema.json | 2 +- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index bcf4a088b087..5d3a723c599c 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -683,7 +683,7 @@ impl Rule for OrganizeImports { let root = ctx.query(); let mut result = Vec::new(); let options = ctx.options(); - let sort_order = options.sort_order; + let sort_order = options.identifier_order; let mut chunk: Option = None; let mut prev_kind: Option = None; let mut prev_group = 0; @@ -793,7 +793,7 @@ impl Rule for OrganizeImports { } let options = ctx.options(); - let sort_order = options.sort_order; + let sort_order = options.identifier_order; // let comparator = match sort_order { // SortMode::Lexicographic => ComparableToken::lexicographic_cmp, // SortMode::Natural => ComparableToken::ascii_nat_cmp @@ -843,7 +843,9 @@ impl Rule for OrganizeImports { } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_order)); + let sorted_attrs = clause + .attribute() + .and_then(|attrs| sort_attributes(attrs, sort_order)); clause = clause.with_attribute(sorted_attrs); } export.with_export_clause(clause).into() @@ -853,14 +855,18 @@ impl Rule for OrganizeImports { if *are_specifiers_unsorted { // Sort named specifiers if let Some(sorted_specifiers) = - clause.named_specifiers().and_then(|specifiers| sort_import_specifiers(specifiers, sort_order)) + clause.named_specifiers().and_then(|specifiers| { + sort_import_specifiers(specifiers, sort_order) + }) { clause = clause.with_named_specifiers(sorted_specifiers) } } if *are_attributes_unsorted { // Sort import attributes - let sorted_attrs = clause.attribute().and_then(|attrs| sort_attributes(attrs, sort_order)); + let sorted_attrs = clause + .attribute() + .and_then(|attrs| sort_attributes(attrs, sort_order)); clause = clause.with_attribute(sorted_attrs); } import.with_import_clause(clause).into() @@ -910,11 +916,15 @@ impl Rule for OrganizeImports { // Sort imports based on their import key import_keys.sort_unstable_by( |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| { - eprint!("%%%%% {} - {}\n", k1.source.inner().token, k2.source.inner().token); + eprint!( + "%%%%% {} - {}\n", + k1.source.inner().token, + k2.source.inner().token + ); k1.cmp(k2) }, ); - + // Merge imports/exports // We use `while` and indexing to allow both iteration and mutation of `import_keys`. let mut i = import_keys.len() - 1; @@ -926,7 +936,9 @@ impl Rule for OrganizeImports { } = &import_keys[i - 1]; let KeyedItem { key, item, .. } = &import_keys[i]; if prev_key.is_mergeable(key) { - if let Some(merged) = merge(prev_item.as_ref(), item.as_ref(), sort_order) { + if let Some(merged) = + merge(prev_item.as_ref(), item.as_ref(), sort_order) + { import_keys[i - 1].was_merged = true; import_keys[i - 1].item = Some(merged); import_keys[i].item = None; @@ -1077,7 +1089,9 @@ fn merge( let clause2 = clause2.as_js_export_named_from_clause()?; let specifiers1 = clause1.specifiers(); let specifiers2 = clause2.specifiers(); - if let Some(meregd_specifiers) = merge_export_specifiers(&specifiers1, &specifiers2, sort_order) { + if let Some(meregd_specifiers) = + merge_export_specifiers(&specifiers1, &specifiers2, sort_order) + { let meregd_clause = clause1.with_specifiers(meregd_specifiers); let merged_item = item2.clone().with_export_clause(meregd_clause.into()); diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json index 1d75175d47e3..b23868303464 100644 --- a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-lexicographic.options.json @@ -6,7 +6,7 @@ "organizeImports": { "level": "on", "options": { - "sortOrder": "lexicographic" + "identifierOrder": "lexicographic" } } } diff --git a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json index a4af86114383..f8ac89214ee8 100644 --- a/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json +++ b/crates/biome_js_analyze/tests/specs/source/organizeImports/custom-sort-natural.options.json @@ -6,7 +6,7 @@ "organizeImports": { "level": "on", "options": { - "sortOrder": "natural" + "identifierOrder": "natural" } } } diff --git a/crates/biome_rule_options/src/organize_imports.rs b/crates/biome_rule_options/src/organize_imports.rs index 034d9828632b..a44229597887 100644 --- a/crates/biome_rule_options/src/organize_imports.rs +++ b/crates/biome_rule_options/src/organize_imports.rs @@ -2,14 +2,14 @@ pub mod import_groups; pub mod import_source; use crate::organize_imports::import_groups::ImportGroups; +pub use crate::shared::sort_order::SortOrder; use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct OrganizeImportsOptions { pub groups: ImportGroups, - pub sort_order: SortOrder, + pub identifier_order: SortOrder, } diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 9f1e39b82609..dc8d2d14f216 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -5034,7 +5034,7 @@ "default": [], "allOf": [{ "$ref": "#/definitions/ImportGroups" }] }, - "sortOrder": { + "identifierOrder": { "$ref": "#/definitions/SortOrder" } }, From fd898bb5392af0d72e2776bae20ad41fd0da79a9 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 3 Jul 2025 14:45:52 +0200 Subject: [PATCH 09/27] Remove files accidently committed --- .../invalid_entropy_high.js.snap.new | 171 ------------------ .../noSecrets/invalid_entropy_low.js.snap.new | 119 ------------ 2 files changed, 290 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new delete mode 100644 crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new diff --git a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new deleted file mode 100644 index e134291d92d4..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_high.js.snap.new +++ /dev/null @@ -1,171 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 134 -expression: invalid_entropy_high.js ---- -# Input -```js -// Completely random with symbols -const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; -const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; -const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; - -// Long random with symbols -const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; -const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; -const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; - -``` - -# Diagnostics -``` -invalid_entropy_high.options:10:25 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Found an unknown key `entropyThreshold`. - - 8 │ "level": "error", - 9 │ "options": { - > 10 │ "entropyThreshold": 80 - │ ^^^^^^^^^^^^^^^^^^ - 11 │ } - 12 │ } - - i Known keys: - - -``` - -``` -invalid_entropy_high.js:2:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 1 │ // Completely random with symbols - > 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; - 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_high.js:3:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 1 │ // Completely random with symbols - 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; - > 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; - 5 │ - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_high.js:4:22 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 2 │ const maybeSecret1 = "k9$mP2#qR7!xN4vL8*zT3&yU6"; - 3 │ const maybeSecret2 = "H5hK9$mL#2pR!7nX&4vZ*8qT%3"; - > 4 │ const maybeSecret3 = "9g$K2m#P!5r&X*7n%L^4v(Z)8q"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 5 │ - 6 │ // Long random with symbols - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_high.js:7:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 6 │ // Long random with symbols - > 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; - 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_high.js:8:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 6 │ // Long random with symbols - 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; - > 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; - 10 │ - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_high.js:9:21 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 7 │ const privateKey1 = "X9$mK2#pL7!nR4@vH8*qT3&yB6^uF1%cG5~wZ0+jM9-eI2"; - 8 │ const privateKey2 = "P8#vR3!nQ7$mX2&kL9*tY4^uG6%wB1+hC5~zF0@jN8-eS4"; - > 9 │ const privateKey3 = "K7!mP9$qR2#nX5&vL8*yT4^uH6%wB3+gC1~zF0@jM7-eI9#kQ2$pL5"; - │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 10 │ - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new b/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new deleted file mode 100644 index 92f3563cc064..000000000000 --- a/crates/biome_js_analyze/tests/specs/nursery/noSecrets/invalid_entropy_low.js.snap.new +++ /dev/null @@ -1,119 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 134 -expression: invalid_entropy_low.js ---- -# Input -```js -// These should trigger with low entropy threshold -const mediumEntropy1 = "abc123def456ghi"; -const mediumEntropy2 = "user2024Password"; -const mediumEntropy3 = "SecretKey123ABC"; -const mediumEntropy4 = "TokenXyZ98765"; -``` - -# Diagnostics -``` -invalid_entropy_low.options:10:25 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - × Found an unknown key `entropyThreshold`. - - 8 │ "level": "error", - 9 │ "options": { - > 10 │ "entropyThreshold": 20 - │ ^^^^^^^^^^^^^^^^^^ - 11 │ } - 12 │ } - - i Known keys: - - -``` - -``` -invalid_entropy_low.js:2:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 1 │ // These should trigger with low entropy threshold - > 2 │ const mediumEntropy1 = "abc123def456ghi"; - │ ^^^^^^^^^^^^^^^^^ - 3 │ const mediumEntropy2 = "user2024Password"; - 4 │ const mediumEntropy3 = "SecretKey123ABC"; - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_low.js:3:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 1 │ // These should trigger with low entropy threshold - 2 │ const mediumEntropy1 = "abc123def456ghi"; - > 3 │ const mediumEntropy2 = "user2024Password"; - │ ^^^^^^^^^^^^^^^^^^ - 4 │ const mediumEntropy3 = "SecretKey123ABC"; - 5 │ const mediumEntropy4 = "TokenXyZ98765"; - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_low.js:4:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 2 │ const mediumEntropy1 = "abc123def456ghi"; - 3 │ const mediumEntropy2 = "user2024Password"; - > 4 │ const mediumEntropy3 = "SecretKey123ABC"; - │ ^^^^^^^^^^^^^^^^^ - 5 │ const mediumEntropy4 = "TokenXyZ98765"; - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` - -``` -invalid_entropy_low.js:5:24 lint/nursery/noSecrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i Potential secret found. - - 3 │ const mediumEntropy2 = "user2024Password"; - 4 │ const mediumEntropy3 = "SecretKey123ABC"; - > 5 │ const mediumEntropy4 = "TokenXyZ98765"; - │ ^^^^^^^^^^^^^^^ - - i Type of secret detected: Detected high entropy string - - i Storing secrets in source code is a security risk. Consider the following steps: - 1. Remove the secret from your code. If you've already committed it, consider removing the commit entirely from your git tree. - 2. If needed, use environment variables or a secure secret management system to store sensitive data. - 3. If this is a false positive, consider adding an inline disable comment, or tweak the entropy threshold. See options in our docs. - This rule only catches basic vulnerabilities. For more robust, proper solutions, check out our recommendations at: https://biomejs.dev/linter/rules/no-secrets/#recommendations - - -``` From 05d73015c4bd8be65d6dd3520ea2df544a7e4381 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 10:50:05 +0200 Subject: [PATCH 10/27] Update the code as per feedback --- crates/biome_analyze/src/utils.rs | 14 ++++---------- .../src/assist/source/organize_imports.rs | 5 ----- .../organize_imports/specifiers_attributes.rs | 12 ++++++------ .../src/assist/source/use_sorted_keys.rs | 4 ++-- .../src/assist/source/use_sorted_keys.rs | 4 ++-- crates/biome_rule_options/src/shared/sort_order.rs | 1 + 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index 986baa2537c3..2a6b4602aa7a 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -18,12 +18,9 @@ pub fn is_separated_list_sorted_by< >( list: &impl AstSeparatedList, get_key: impl Fn(&N) -> Option, - comparator: Option Ordering> + comparator: impl Fn(&Key, &Key) -> Ordering ) -> Result { let mut is_sorted = true; - let cmp = comparator.unwrap_or_else(|| |a:&Key, b: &Key| { - if a > b { Ordering::Greater } else if a < b {Ordering::Less} else { Ordering::Equal} - }); if list.len() > 1 { let mut previous_key: Option = None; @@ -35,7 +32,7 @@ pub fn is_separated_list_sorted_by< // We have to check if the separator is not buggy. let _separator = trailing_separator?; previous_key = if let Some(key) = get_key(&node?) { - if previous_key.is_some_and(|previous_key| cmp(&previous_key, &key) != Ordering::Equal) { + if previous_key.is_some_and(|previous_key| comparator(&previous_key, &key).is_ne()) { // We don't return early because we want to return the error if we met one. is_sorted = false; } @@ -61,15 +58,12 @@ pub fn sorted_separated_list_by<'a, L: Language + 'a, List, Node, Key: Ord>( list: &List, get_key: impl Fn(&Node) -> Option, make_separator: fn() -> SyntaxToken, - comparator: Option Ordering> + comparator: impl Fn(&Key, &Key) -> Ordering ) -> Result where List: AstSeparatedList + AstNode + 'a, Node: AstNode + 'a, { - let cmp = comparator.unwrap_or_else(|| |a:&Key, b: &Key| { - if a > b { Ordering::Greater } else if a < b {Ordering::Less} else { Ordering::Equal} - }); let mut elements = Vec::with_capacity(list.len()); for AstSeparatedElement { node, @@ -86,7 +80,7 @@ where let last_has_separator = slice.last().is_some_and(|(_, _, sep)| sep.is_some()); slice.sort_by(|(key1, _, _), (key2, _, _)| { match (key1, key2) { - (Some(k1), Some(k2)) => cmp(k1, k2), + (Some(k1), Some(k2)) => comparator(k1, k2), (Some(_), None) => Ordering::Less, (None, Some(_)) => Ordering::Greater, (None, None) => Ordering::Equal, diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index 5d3a723c599c..dac32b9dc75b 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -916,11 +916,6 @@ impl Rule for OrganizeImports { // Sort imports based on their import key import_keys.sort_unstable_by( |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| { - eprint!( - "%%%%% {} - {}\n", - k1.source.inner().token, - k2.source.inner().token - ); k1.cmp(k2) }, ); diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index 77625a19c6c9..e4a3a9eb325c 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -42,7 +42,7 @@ pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, Some(ComparableToken::new( name.name_token().ok()?.token_text_trimmed(), )) - }, Some(comparator)) + }, comparator) .ok() } @@ -62,7 +62,7 @@ pub fn sort_import_specifiers( )) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - Some(comparator) + comparator ) .ok()?; Some(named_specifiers.with_specifiers(new_list)) @@ -116,7 +116,7 @@ pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, .inner_string_text() .ok() .map(ComparableToken::new) - }, Some(comparator)) + }, comparator) .ok() } @@ -135,7 +135,7 @@ pub fn sort_export_specifiers( .map(ComparableToken::new) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - Some(comparator)) + comparator) .ok()?; Some(new_list) } @@ -185,7 +185,7 @@ pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_order: return None; }; Some(ComparableToken::new(inner_string_text(&node.key().ok()?))) - }, Some(comparator)) + }, comparator) .ok() } @@ -201,7 +201,7 @@ pub fn sort_attributes(attributes: JsImportAssertion, sort_order: SortOrder) -> Some(ComparableToken::new(inner_string_text(&node.key().ok()?))) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - Some(comparator), + comparator, ) .ok()?; Some(attributes.with_assertions(new_list)) diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index 9df4a864b706..53a1a9b379e6 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -96,7 +96,7 @@ impl Rule for UseSortedKeys { SortOrder::Lexicographic => ComparableToken::lexicographic_cmp }; - is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new), Some(comparator)) + is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new), comparator) .ok()? .not() .then_some(()) @@ -133,7 +133,7 @@ impl Rule for UseSortedKeys { list, |node| node.name().map(ComparableToken::new), || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - Some(comparator)) + comparator) .ok()?; let mut mutation = ctx.root().begin(); diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index 1afb12bcac53..e62bc3de196c 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -54,7 +54,7 @@ impl Rule for UseSortedKeys { .inner_string_text() .ok() .map(ComparableToken::new) - }, Some(comparator)) + }, comparator) .ok()? .not() .then_some(()) @@ -97,7 +97,7 @@ impl Rule for UseSortedKeys { .map(ComparableToken::new) }, || make::token(T![,]), - Some(comparator), + comparator, ) .ok()?; diff --git a/crates/biome_rule_options/src/shared/sort_order.rs b/crates/biome_rule_options/src/shared/sort_order.rs index 844f740084c9..7d5604649511 100644 --- a/crates/biome_rule_options/src/shared/sort_order.rs +++ b/crates/biome_rule_options/src/shared/sort_order.rs @@ -2,6 +2,7 @@ Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, biome_deserialize_macros::Deserializable, )] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] pub enum SortOrder { #[default] Natural, From 2cc1c766f108559e5116e94a95d3e869ae7677c1 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 10:56:17 +0200 Subject: [PATCH 11/27] Fix the sorting comaprision --- crates/biome_analyze/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index 2a6b4602aa7a..34d2a13b4c6b 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -32,7 +32,7 @@ pub fn is_separated_list_sorted_by< // We have to check if the separator is not buggy. let _separator = trailing_separator?; previous_key = if let Some(key) = get_key(&node?) { - if previous_key.is_some_and(|previous_key| comparator(&previous_key, &key).is_ne()) { + if previous_key.is_some_and(|previous_key| comparator(&previous_key, &key).is_gt()) { // We don't return early because we want to return the error if we met one. is_sorted = false; } From 842ebb92012f500c6f3f14b7a4074b1e4bc35e70 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 10:58:13 +0200 Subject: [PATCH 12/27] Update the constraint from Key --- crates/biome_analyze/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index 34d2a13b4c6b..4aaa0618526d 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -14,7 +14,7 @@ pub fn is_separated_list_sorted_by< 'a, L: Language + 'a, N: AstNode + 'a, - Key: Ord, + Key, >( list: &impl AstSeparatedList, get_key: impl Fn(&N) -> Option, @@ -54,7 +54,7 @@ pub fn is_separated_list_sorted_by< /// Chunks are sorted separately. /// /// This sort is stable (i.e., does not reorder equal elements). -pub fn sorted_separated_list_by<'a, L: Language + 'a, List, Node, Key: Ord>( +pub fn sorted_separated_list_by<'a, L: Language + 'a, List, Node, Key>( list: &List, get_key: impl Fn(&Node) -> Option, make_separator: fn() -> SyntaxToken, From 785ce1a756a258ba3a9e47919632bd7c5af8a98e Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 10:58:32 +0200 Subject: [PATCH 13/27] Remove unintended snapshot --- .../source/useSortedKeys/sorted.js.snap.new | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new diff --git a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new b/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new deleted file mode 100644 index 3461ea390964..000000000000 --- a/crates/biome_js_analyze/tests/specs/source/useSortedKeys/sorted.js.snap.new +++ /dev/null @@ -1,53 +0,0 @@ ---- -source: crates/biome_js_analyze/tests/spec_tests.rs -assertion_line: 134 -expression: sorted.js ---- -# Input -```js -const obj = { - get aab() { - return this._aab; - }, - set aac(v) { - this._aac = v; - }, - w: 1, - x: 1, - ...g, - get aaa() { - return ""; - }, - u: 1, - v: 1, - [getProp()]: 2, - o: 1, - p: 1, - q: 1, -}; - -``` - -# Diagnostics -``` -sorted.js:2:2 assist/source/useSortedKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - i The object properties are not sorted by key. - - 1 │ const obj = { - > 2 │ get aab() { - │ ^^^^^^^^^^^ - > 3 │ return this._aab; - > 4 │ }, - ... - > 18 │ p: 1, - > 19 │ q: 1, - │ ^^^^^ - 20 │ }; - 21 │ - - i Safe fix: Sort the object properties by key. - - - -``` From ec37c2a77e659cc30103eef2b8fedc68cbe7c498 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 11:00:37 +0200 Subject: [PATCH 14/27] Update comaparision of None vs Some --- crates/biome_analyze/src/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index 4aaa0618526d..d932e5486cd1 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -81,8 +81,8 @@ where slice.sort_by(|(key1, _, _), (key2, _, _)| { match (key1, key2) { (Some(k1), Some(k2)) => comparator(k1, k2), - (Some(_), None) => Ordering::Less, - (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, (None, None) => Ordering::Equal, } }); From 9725c0fb3c48def9a4d1da12f1681e1d4ef49b00 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 22:13:27 +0200 Subject: [PATCH 15/27] Cleanup the comments --- crates/biome_js_analyze/src/assist/source/organize_imports.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index dac32b9dc75b..3a36ecea354b 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -794,10 +794,6 @@ impl Rule for OrganizeImports { let options = ctx.options(); let sort_order = options.identifier_order; - // let comparator = match sort_order { - // SortMode::Lexicographic => ComparableToken::lexicographic_cmp, - // SortMode::Natural => ComparableToken::ascii_nat_cmp - // }; let root = ctx.query(); let items = root.items().into_syntax(); let mut organized_items: FxHashMap = FxHashMap::default(); From 1cacd4332c4f450054b06149a9bc8547f4ecdd22 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 7 Jul 2025 22:17:36 +0200 Subject: [PATCH 16/27] Clean up some duplicate code --- .../assist/source/organize_imports/specifiers_attributes.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index e4a3a9eb325c..e952d0f0ff14 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -30,10 +30,7 @@ impl JsNamedSpecifiers { } pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, sort_order: SortOrder) -> Option { - let comparator = match sort_order { - SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, - SortOrder::Natural => ComparableToken::ascii_nat_cmp, - }; + let comparator = get_comparator(sort_order); is_separated_list_sorted_by(&named_specifiers.specifiers(), |node| { let AnyJsBinding::JsIdentifierBinding(name) = node.local_name()? else { From 24399e6013b4f395f03705bcb2e3d65f6a6a2ae0 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 09:49:08 +0200 Subject: [PATCH 17/27] Fix formatting --- crates/biome_analyze/src/utils.rs | 28 +++--- .../src/assist/source/organize_imports.rs | 4 +- .../organize_imports/specifiers_attributes.rs | 98 ++++++++++++------- .../assist/source/use_sorted_attributes.rs | 33 ++++--- .../src/assist/source/use_sorted_keys.rs | 21 ++-- .../src/assist/source/use_sorted_keys.rs | 24 +++-- crates/biome_rule_options/src/shared/mod.rs | 2 +- .../src/shared/sort_order.rs | 10 +- .../src/use_sorted_attributes.rs | 4 +- .../biome_rule_options/src/use_sorted_keys.rs | 4 +- crates/biome_string_case/src/lib.rs | 2 +- 11 files changed, 135 insertions(+), 95 deletions(-) diff --git a/crates/biome_analyze/src/utils.rs b/crates/biome_analyze/src/utils.rs index d932e5486cd1..becffe35dccf 100644 --- a/crates/biome_analyze/src/utils.rs +++ b/crates/biome_analyze/src/utils.rs @@ -1,8 +1,8 @@ -use std::cmp::Ordering; use biome_rowan::{ AstNode, AstSeparatedElement, AstSeparatedList, Language, SyntaxError, SyntaxNode, SyntaxToken, chain_trivia_pieces, }; +use std::cmp::Ordering; /// Returns `true` if `list` is sorted by `get_key`. /// The function returns an error if we encounter a buggy node or separator. @@ -10,15 +10,10 @@ use biome_rowan::{ /// The list is divided into chunks of nodes with keys. /// Thus, a node without key acts as a chuck delimiter. /// Chunks are sorted separately. -pub fn is_separated_list_sorted_by< - 'a, - L: Language + 'a, - N: AstNode + 'a, - Key, ->( +pub fn is_separated_list_sorted_by<'a, L: Language + 'a, N: AstNode + 'a, Key>( list: &impl AstSeparatedList, get_key: impl Fn(&N) -> Option, - comparator: impl Fn(&Key, &Key) -> Ordering + comparator: impl Fn(&Key, &Key) -> Ordering, ) -> Result { let mut is_sorted = true; @@ -32,7 +27,8 @@ pub fn is_separated_list_sorted_by< // We have to check if the separator is not buggy. let _separator = trailing_separator?; previous_key = if let Some(key) = get_key(&node?) { - if previous_key.is_some_and(|previous_key| comparator(&previous_key, &key).is_gt()) { + if previous_key.is_some_and(|previous_key| comparator(&previous_key, &key).is_gt()) + { // We don't return early because we want to return the error if we met one. is_sorted = false; } @@ -58,7 +54,7 @@ pub fn sorted_separated_list_by<'a, L: Language + 'a, List, Node, Key>( list: &List, get_key: impl Fn(&Node) -> Option, make_separator: fn() -> SyntaxToken, - comparator: impl Fn(&Key, &Key) -> Ordering + comparator: impl Fn(&Key, &Key) -> Ordering, ) -> Result where List: AstSeparatedList + AstNode + 'a, @@ -78,13 +74,11 @@ where // Iterate over chunks of node with a key for slice in elements.split_mut(|(key, _, _)| key.is_none()) { let last_has_separator = slice.last().is_some_and(|(_, _, sep)| sep.is_some()); - slice.sort_by(|(key1, _, _), (key2, _, _)| { - match (key1, key2) { - (Some(k1), Some(k2)) => comparator(k1, k2), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => Ordering::Equal, - } + slice.sort_by(|(key1, _, _), (key2, _, _)| match (key1, key2) { + (Some(k1), Some(k2)) => comparator(k1, k2), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, }); fix_separators( slice.iter_mut().map(|(_, node, sep)| (node, sep)), diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index 3a36ecea354b..48741cb0860b 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -911,9 +911,7 @@ impl Rule for OrganizeImports { ); // Sort imports based on their import key import_keys.sort_unstable_by( - |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| { - k1.cmp(k2) - }, + |KeyedItem { key: k1, .. }, KeyedItem { key: k2, .. }| k1.cmp(k2), ); // Merge imports/exports diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index e952d0f0ff14..928f8d643d22 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use biome_analyze::utils::{is_separated_list_sorted_by, sorted_separated_list_by}; use biome_js_factory::make; use biome_js_syntax::{ @@ -6,8 +5,9 @@ use biome_js_syntax::{ JsNamedImportSpecifiers, T, inner_string_text, }; use biome_rowan::{AstNode, AstSeparatedElement, AstSeparatedList, TriviaPieceKind}; -use biome_string_case::comparable_token::ComparableToken; use biome_rule_options::organize_imports::SortOrder; +use biome_string_case::comparable_token::ComparableToken; +use std::cmp::Ordering; pub enum JsNamedSpecifiers { JsNamedImportSpecifiers(JsNamedImportSpecifiers), @@ -15,10 +15,10 @@ pub enum JsNamedSpecifiers { } impl JsNamedSpecifiers { pub fn are_sorted(&self, sort_order: SortOrder) -> bool { - - match self { - Self::JsNamedImportSpecifiers(specifeirs) => are_import_specifiers_sorted(specifeirs, sort_order), + Self::JsNamedImportSpecifiers(specifeirs) => { + are_import_specifiers_sorted(specifeirs, sort_order) + } Self::JsExportNamedFromSpecifierList(specifeirs) => { are_export_specifiers_sorted(specifeirs, sort_order) } @@ -29,17 +29,24 @@ impl JsNamedSpecifiers { } } -pub fn are_import_specifiers_sorted(named_specifiers: &JsNamedImportSpecifiers, sort_order: SortOrder) -> Option { +pub fn are_import_specifiers_sorted( + named_specifiers: &JsNamedImportSpecifiers, + sort_order: SortOrder, +) -> Option { let comparator = get_comparator(sort_order); - is_separated_list_sorted_by(&named_specifiers.specifiers(), |node| { - let AnyJsBinding::JsIdentifierBinding(name) = node.local_name()? else { - return None; - }; - Some(ComparableToken::new( - name.name_token().ok()?.token_text_trimmed(), - )) - }, comparator) + is_separated_list_sorted_by( + &named_specifiers.specifiers(), + |node| { + let AnyJsBinding::JsIdentifierBinding(name) = node.local_name()? else { + return None; + }; + Some(ComparableToken::new( + name.name_token().ok()?.token_text_trimmed(), + )) + }, + comparator, + ) .ok() } @@ -59,7 +66,7 @@ pub fn sort_import_specifiers( )) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - comparator + comparator, ) .ok()?; Some(named_specifiers.with_specifiers(new_list)) @@ -104,16 +111,23 @@ pub fn merge_import_specifiers( sort_import_specifiers(named_specifiers1.with_specifiers(new_list), sort_order) } -pub fn are_export_specifiers_sorted(specifiers: &JsExportNamedFromSpecifierList, sort_order: SortOrder) -> Option { +pub fn are_export_specifiers_sorted( + specifiers: &JsExportNamedFromSpecifierList, + sort_order: SortOrder, +) -> Option { let comparator = get_comparator(sort_order); - is_separated_list_sorted_by(specifiers, |node| { - node.source_name() - .ok()? - .inner_string_text() - .ok() - .map(ComparableToken::new) - }, comparator) + is_separated_list_sorted_by( + specifiers, + |node| { + node.source_name() + .ok()? + .inner_string_text() + .ok() + .map(ComparableToken::new) + }, + comparator, + ) .ok() } @@ -132,7 +146,8 @@ pub fn sort_export_specifiers( .map(ComparableToken::new) }, || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - comparator) + comparator, + ) .ok()?; Some(new_list) } @@ -170,23 +185,34 @@ pub fn merge_export_specifiers( separators.push(separator); } } - sort_export_specifiers(&make::js_export_named_from_specifier_list( - nodes, separators - ), sort_order) + sort_export_specifiers( + &make::js_export_named_from_specifier_list(nodes, separators), + sort_order, + ) } -pub fn are_import_attributes_sorted(attributes: &JsImportAssertion, sort_order: SortOrder) -> Option { +pub fn are_import_attributes_sorted( + attributes: &JsImportAssertion, + sort_order: SortOrder, +) -> Option { let comparator = get_comparator(sort_order); - is_separated_list_sorted_by(&attributes.assertions(), |node| { - let AnyJsImportAssertionEntry::JsImportAssertionEntry(node) = node else { - return None; - }; - Some(ComparableToken::new(inner_string_text(&node.key().ok()?))) - }, comparator) + is_separated_list_sorted_by( + &attributes.assertions(), + |node| { + let AnyJsImportAssertionEntry::JsImportAssertionEntry(node) = node else { + return None; + }; + Some(ComparableToken::new(inner_string_text(&node.key().ok()?))) + }, + comparator, + ) .ok() } -pub fn sort_attributes(attributes: JsImportAssertion, sort_order: SortOrder) -> Option { +pub fn sort_attributes( + attributes: JsImportAssertion, + sort_order: SortOrder, +) -> Option { let comparator = get_comparator(sort_order); let new_list = sorted_separated_list_by( @@ -207,7 +233,7 @@ pub fn sort_attributes(attributes: JsImportAssertion, sort_order: SortOrder) -> pub fn get_comparator(sort_order: SortOrder) -> fn(&ComparableToken, &ComparableToken) -> Ordering { let comparator = match sort_order { SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, - SortOrder::Natural => ComparableToken::ascii_nat_cmp + SortOrder::Natural => ComparableToken::ascii_nat_cmp, }; comparator } diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs index 51fb79107b1d..94b28842a236 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs @@ -11,7 +11,7 @@ use biome_js_syntax::{ AnyJsxAttribute, JsxAttribute, JsxAttributeList, JsxOpeningElement, JsxSelfClosingElement, }; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_attributes::{UseSortedAttributesOptions, SortOrder}; +use biome_rule_options::use_sorted_attributes::{SortOrder, UseSortedAttributesOptions}; use biome_string_case::StrLikeExtension; use crate::JsRuleAction; @@ -58,7 +58,7 @@ impl Rule for UseSortedAttributes { let props = ctx.query(); let mut current_prop_group = PropGroup::default(); let mut prop_groups = Vec::new(); - let options= ctx.options(); + let options = ctx.options(); let sort_by = options.sort_order; let comparator = match sort_by { @@ -66,10 +66,9 @@ impl Rule for UseSortedAttributes { SortOrder::Lexicographic => PropElement::lexicographic_cmp, }; - // Convert to boolean-based comparator for is_sorted_by - let boolean_comparator = |a: &PropElement, b: &PropElement| { - comparator(a, b) != Ordering::Greater - }; + // Convert to boolean-based comparator for is_sorted_by + let boolean_comparator = + |a: &PropElement, b: &PropElement| comparator(a, b) != Ordering::Greater; for prop in props { match prop { @@ -78,7 +77,9 @@ impl Rule for UseSortedAttributes { } // spread prop reset sort order AnyJsxAttribute::JsxSpreadAttribute(_) => { - if !current_prop_group.is_empty() && !current_prop_group.is_sorted(boolean_comparator) { + if !current_prop_group.is_empty() + && !current_prop_group.is_sorted(boolean_comparator) + { prop_groups.push(current_prop_group); current_prop_group = PropGroup::default(); } else { @@ -115,7 +116,7 @@ impl Rule for UseSortedAttributes { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); - let options= ctx.options(); + let options = ctx.options(); let sort_by = options.sort_order; let comparator = match sort_by { @@ -152,7 +153,9 @@ impl PropElement { return Ordering::Equal; }; - self_name.text_trimmed().ascii_nat_cmp(other_name.text_trimmed()) + self_name + .text_trimmed() + .ascii_nat_cmp(other_name.text_trimmed()) } pub fn lexicographic_cmp(&self, other: &Self) -> Ordering { @@ -163,7 +166,9 @@ impl PropElement { return Ordering::Equal; }; - self_name.text_trimmed().lexicographic_cmp(other_name.text_trimmed()) + self_name + .text_trimmed() + .lexicographic_cmp(other_name.text_trimmed()) } } @@ -177,16 +182,16 @@ impl PropGroup { self.props.is_empty() } - fn is_sorted(&self, comparator: F) -> bool + fn is_sorted(&self, comparator: F) -> bool where - F: Fn(&PropElement, &PropElement) -> bool + F: Fn(&PropElement, &PropElement) -> bool, { self.props.is_sorted_by(comparator) } - fn get_sorted_props(&self, comparator: F,) -> Vec + fn get_sorted_props(&self, comparator: F) -> Vec where - F: FnMut(&PropElement, &PropElement) -> Ordering + F: FnMut(&PropElement, &PropElement) -> Ordering, { let mut new_props = self.props.clone(); new_props.sort_by(comparator); diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index 53a1a9b379e6..022200bf8e8f 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -12,7 +12,7 @@ use biome_diagnostics::{Applicability, category}; use biome_js_factory::make; use biome_js_syntax::{JsObjectExpression, JsObjectMemberList, T}; use biome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind}; -use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortOrder}; +use biome_rule_options::use_sorted_keys::{SortOrder, UseSortedKeysOptions}; use biome_string_case::comparable_token::ComparableToken; use crate::JsRuleAction; @@ -93,13 +93,17 @@ impl Rule for UseSortedKeys { let sort_order = options.sort_order; let comparator = match sort_order { SortOrder::Natural => ComparableToken::ascii_nat_cmp, - SortOrder::Lexicographic => ComparableToken::lexicographic_cmp + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, }; - is_separated_list_sorted_by(ctx.query(), |node| node.name().map(ComparableToken::new), comparator) - .ok()? - .not() - .then_some(()) + is_separated_list_sorted_by( + ctx.query(), + |node| node.name().map(ComparableToken::new), + comparator, + ) + .ok()? + .not() + .then_some(()) } fn diagnostic(ctx: &RuleContext, _state: &Self::State) -> Option { @@ -126,14 +130,15 @@ impl Rule for UseSortedKeys { let sort_order = options.sort_order; let comparator = match sort_order { SortOrder::Natural => ComparableToken::ascii_nat_cmp, - SortOrder::Lexicographic => ComparableToken::lexicographic_cmp + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, }; let new_list = sorted_separated_list_by( list, |node| node.name().map(ComparableToken::new), || make::token(T![,]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - comparator) + comparator, + ) .ok()?; let mut mutation = ctx.root().begin(); diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index e62bc3de196c..35ad0fd7627f 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -8,7 +8,7 @@ use biome_diagnostics::category; use biome_json_factory::make; use biome_json_syntax::{JsonMemberList, JsonObjectValue, T, TextRange}; use biome_rowan::{AstNode, BatchMutationExt}; -use biome_rule_options::use_sorted_keys::{UseSortedKeysOptions, SortOrder}; +use biome_rule_options::use_sorted_keys::{SortOrder, UseSortedKeysOptions}; use biome_string_case::comparable_token::ComparableToken; use std::ops::Not; @@ -45,16 +45,20 @@ impl Rule for UseSortedKeys { let sort_order = options.sort_order; let comparator = match sort_order { SortOrder::Natural => ComparableToken::ascii_nat_cmp, - SortOrder::Lexicographic => ComparableToken::lexicographic_cmp + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, }; - is_separated_list_sorted_by(ctx.query(), |node| { - node.name() - .ok()? - .inner_string_text() - .ok() - .map(ComparableToken::new) - }, comparator) + is_separated_list_sorted_by( + ctx.query(), + |node| { + node.name() + .ok()? + .inner_string_text() + .ok() + .map(ComparableToken::new) + }, + comparator, + ) .ok()? .not() .then_some(()) @@ -84,7 +88,7 @@ impl Rule for UseSortedKeys { let sort_order = options.sort_order; let comparator = match sort_order { SortOrder::Natural => ComparableToken::ascii_nat_cmp, - SortOrder::Lexicographic => ComparableToken::lexicographic_cmp + SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, }; let new_list = sorted_separated_list_by( diff --git a/crates/biome_rule_options/src/shared/mod.rs b/crates/biome_rule_options/src/shared/mod.rs index 524a97feab82..a9965fbb4866 100644 --- a/crates/biome_rule_options/src/shared/mod.rs +++ b/crates/biome_rule_options/src/shared/mod.rs @@ -1,2 +1,2 @@ pub mod restricted_regex; -pub mod sort_order; \ No newline at end of file +pub mod sort_order; diff --git a/crates/biome_rule_options/src/shared/sort_order.rs b/crates/biome_rule_options/src/shared/sort_order.rs index 7d5604649511..058be1c11118 100644 --- a/crates/biome_rule_options/src/shared/sort_order.rs +++ b/crates/biome_rule_options/src/shared/sort_order.rs @@ -1,5 +1,13 @@ #[derive( - Clone, Copy, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, biome_deserialize_macros::Deserializable, + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + biome_deserialize_macros::Deserializable, )] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] diff --git a/crates/biome_rule_options/src/use_sorted_attributes.rs b/crates/biome_rule_options/src/use_sorted_attributes.rs index 524d9bc1d8be..a28f54f5e624 100644 --- a/crates/biome_rule_options/src/use_sorted_attributes.rs +++ b/crates/biome_rule_options/src/use_sorted_attributes.rs @@ -1,10 +1,10 @@ +pub use crate::shared::sort_order::SortOrder; use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct UseSortedAttributesOptions { - pub sort_order: SortOrder, + pub sort_order: SortOrder, } diff --git a/crates/biome_rule_options/src/use_sorted_keys.rs b/crates/biome_rule_options/src/use_sorted_keys.rs index ba1c4b557b2f..87fd6a69fecb 100644 --- a/crates/biome_rule_options/src/use_sorted_keys.rs +++ b/crates/biome_rule_options/src/use_sorted_keys.rs @@ -1,10 +1,10 @@ +pub use crate::shared::sort_order::SortOrder; use biome_deserialize_macros::Deserializable; use serde::{Deserialize, Serialize}; -pub use crate::shared::sort_order::SortOrder; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] pub struct UseSortedKeysOptions { - pub sort_order: SortOrder, + pub sort_order: SortOrder, } diff --git a/crates/biome_string_case/src/lib.rs b/crates/biome_string_case/src/lib.rs index 4a6df82b8400..601c24f464a8 100644 --- a/crates/biome_string_case/src/lib.rs +++ b/crates/biome_string_case/src/lib.rs @@ -651,7 +651,7 @@ pub trait StrLikeExtension: ToOwned { fn ascii_nat_cmp(&self, other: &Self) -> Ordering; /// Compare two strings using lexicographically by their byte values. - /// + /// /// This orders Unicode code points based on their positions in the code charts. fn lexicographic_cmp(&self, other: &Self) -> Ordering; } From 3001a47c9eaf9db994a44fec78a4458f03df7f63 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 09:53:18 +0200 Subject: [PATCH 18/27] Fix linting --- .../assist/source/organize_imports/specifiers_attributes.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs index 928f8d643d22..67100e53d01f 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports/specifiers_attributes.rs @@ -231,9 +231,8 @@ pub fn sort_attributes( } pub fn get_comparator(sort_order: SortOrder) -> fn(&ComparableToken, &ComparableToken) -> Ordering { - let comparator = match sort_order { + match sort_order { SortOrder::Lexicographic => ComparableToken::lexicographic_cmp, SortOrder::Natural => ComparableToken::ascii_nat_cmp, - }; - comparator + } } From b177951a257cdb5bf27224ea54ac2ad443ba3ce4 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 10:36:10 +0200 Subject: [PATCH 19/27] Add a changeset file --- .changeset/fancy-trains-happen.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .changeset/fancy-trains-happen.md diff --git a/.changeset/fancy-trains-happen.md b/.changeset/fancy-trains-happen.md new file mode 100644 index 000000000000..58969ba49f9b --- /dev/null +++ b/.changeset/fancy-trains-happen.md @@ -0,0 +1,19 @@ +--- +"@biomejs/biome": minor +--- + +Allow customization for the sort order for different sorting rules. These rules are supported with following options: + +- assist/source/useSortedKeys (sortOrder) +- assist/source/useSortedAttributes (sortOrder) +- assist/source/organizeImports (identifierOrder) + +Following options are supported for ordering: + +*1. Natural (default)* + +Compare two strings using a natural ASCII order. Uppercase letters come first (e.g. `A` < `a` < `B` < `b`) and number are compared in a human way (e.g. `9` < `10`). + +*2. Lexicographic* + +Strings are ordered lexicographically by their byte values. This orders Unicode code points based on their positions in the code charts. This is not necessarily the same as “alphabetical” order, which varies by language and locale. From b97422bda0d3494b74a8f6348137f2ccaa21e865 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 11:03:41 +0200 Subject: [PATCH 20/27] Add link for the rules in changeset --- .changeset/fancy-trains-happen.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/fancy-trains-happen.md b/.changeset/fancy-trains-happen.md index 58969ba49f9b..25f58d4f107e 100644 --- a/.changeset/fancy-trains-happen.md +++ b/.changeset/fancy-trains-happen.md @@ -4,9 +4,9 @@ Allow customization for the sort order for different sorting rules. These rules are supported with following options: -- assist/source/useSortedKeys (sortOrder) -- assist/source/useSortedAttributes (sortOrder) -- assist/source/organizeImports (identifierOrder) +- [assist/source/useSortedKeys](https://biomejs.dev/assist/actions/use-sorted-keys/) (sortOrder) +- [assist/source/useSortedAttributes](https://biomejs.dev/assist/actions/use-sorted-attributes/) (sortOrder) +- [assist/source/organizeImports](https://biomejs.dev/assist/actions/organize-imports/) (identifierOrder) Following options are supported for ordering: From 993452952514fb6b0df076949344ff6a542df7a6 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 11:34:22 +0200 Subject: [PATCH 21/27] Apply suggestions from code review Co-authored-by: Arend van Beelen jr. --- .changeset/fancy-trains-happen.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.changeset/fancy-trains-happen.md b/.changeset/fancy-trains-happen.md index 25f58d4f107e..bbaa74ea1179 100644 --- a/.changeset/fancy-trains-happen.md +++ b/.changeset/fancy-trains-happen.md @@ -2,18 +2,13 @@ "@biomejs/biome": minor --- -Allow customization for the sort order for different sorting rules. These rules are supported with following options: +Allow customization of the sort order for different sorting actions. These actions now support a sort option: -- [assist/source/useSortedKeys](https://biomejs.dev/assist/actions/use-sorted-keys/) (sortOrder) -- [assist/source/useSortedAttributes](https://biomejs.dev/assist/actions/use-sorted-attributes/) (sortOrder) -- [assist/source/organizeImports](https://biomejs.dev/assist/actions/organize-imports/) (identifierOrder) +- [`assist/source/useSortedKeys`](https://biomejs.dev/assist/actions/use-sorted-keys/) now has a `sortOrder` option +- [`assist/source/useSortedAttributes`](https://biomejs.dev/assist/actions/use-sorted-attributes/) now has a `sortOrder` option +- [`assist/source/organizeImports`](https://biomejs.dev/assist/actions/organize-imports/) now has an `identifierOrder` option -Following options are supported for ordering: +For each of these options, the supported values are the same: -*1. Natural (default)* - -Compare two strings using a natural ASCII order. Uppercase letters come first (e.g. `A` < `a` < `B` < `b`) and number are compared in a human way (e.g. `9` < `10`). - -*2. Lexicographic* - -Strings are ordered lexicographically by their byte values. This orders Unicode code points based on their positions in the code charts. This is not necessarily the same as “alphabetical” order, which varies by language and locale. +1. **`natural`**. Compares two strings using a natural ASCII order. Uppercase letters come first (e.g. `A` < `a` < `B` < `b`) and number are compared in a human way (e.g. `9` < `10`). This is the default value. +2. **`lexicographic`**. Strings are ordered lexicographically by their byte values. This orders Unicode code points based on their positions in the code charts. This is not necessarily the same as “alphabetical” order, which varies by language and locale. From f6a11020eae45dfc29f997318065fb1974d17dbf Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 15:40:58 +0200 Subject: [PATCH 22/27] Update the docs for update options --- .../src/assist/source/organize_imports.rs | 17 +++++++++++++++++ .../src/assist/source/use_sorted_attributes.rs | 14 ++++++++++++++ .../src/assist/source/use_sorted_keys.rs | 15 +++++++++++++++ .../src/assist/source/use_sorted_keys.rs | 15 +++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index 48741cb0860b..ae81ce671ea1 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -618,6 +618,23 @@ declare_source_rule! { /// } /// ``` /// + /// ## Options + /// This actions accepts following options + /// + /// ### `groups` + /// The usage of `groups` is explained in detail above. + /// + /// ### `identifier_order` + /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. This only applies to the named import/exports and not the source itself. + /// + /// ```json,options + /// { + /// "options": { + /// "identifier_order": "natural" + /// } + /// } + /// ``` + /// pub OrganizeImports { version: "1.0.0", name: "organizeImports", diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs index 94b28842a236..f0b597c2e94c 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs @@ -38,6 +38,20 @@ declare_source_rule! { /// ; /// ``` /// + /// ## Options + /// This actions accepts following options + /// + /// ### `sortOrder` + /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "natural" + /// } + /// } + /// ``` + /// pub UseSortedAttributes { version: "2.0.0", name: "useSortedAttributes", diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index 022200bf8e8f..68b7c0dc3a2c 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -72,6 +72,21 @@ declare_source_rule! { /// q: 1, /// } /// ``` + /// + /// ## Options + /// This actions accepts following options + /// + /// ### `sortOrder` + /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "natural" + /// } + /// } + /// ``` + /// pub UseSortedKeys { version: "2.0.0", name: "useSortedKeys", diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index 35ad0fd7627f..495a0b09b242 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -26,6 +26,21 @@ declare_source_rule! { /// } /// } /// ``` + /// + /// ## Options + /// This actions accepts following options + /// + /// ### `sortOrder` + /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "natural" + /// } + /// } + /// ``` + /// pub UseSortedKeys { version: "1.9.0", name: "useSortedKeys", From b9d4e0ad5c93af446f2256eb524c5c2d6cb79a9e Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 21:20:29 +0200 Subject: [PATCH 23/27] Fix codegen and rules check issue --- .../src/assist/source/organize_imports.rs | 4 ++-- .../@biomejs/biome/configuration_schema.json | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index ae81ce671ea1..78f6251424b6 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -624,13 +624,13 @@ declare_source_rule! { /// ### `groups` /// The usage of `groups` is explained in detail above. /// - /// ### `identifier_order` + /// ### `identifierOrder` /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. This only applies to the named import/exports and not the source itself. /// /// ```json,options /// { /// "options": { - /// "identifier_order": "natural" + /// "identifierOrder": "natural" /// } /// } /// ``` diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index dc8d2d14f216..1dfbc61ba93e 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -1837,11 +1837,6 @@ "type": "array", "items": { "$ref": "#/definitions/ImportGroup" } }, - "SortOrder": { - "type": "string", - "enum": ["lexicographic", "natural"], - "default": "natural" - }, "ImportMatcher": { "type": "object", "properties": { @@ -5035,7 +5030,8 @@ "allOf": [{ "$ref": "#/definitions/ImportGroups" }] }, "identifierOrder": { - "$ref": "#/definitions/SortOrder" + "default": "natural", + "allOf": [{ "$ref": "#/definitions/SortOrder" }] } }, "additionalProperties": false @@ -11379,6 +11375,7 @@ { "$ref": "#/definitions/Suspicious" } ] }, + "SortOrder": { "type": "string", "enum": ["natural", "lexicographic"] }, "Source": { "description": "A list of rules that belong to this group", "type": "object", @@ -13518,7 +13515,8 @@ "type": "object", "properties": { "sortOrder": { - "$ref": "#/definitions/SortOrder" + "default": "natural", + "allOf": [{ "$ref": "#/definitions/SortOrder" }] } }, "additionalProperties": false @@ -13549,10 +13547,11 @@ "type": "object", "properties": { "sortOrder": { - "$ref": "#/definitions/SortOrder" + "default": "natural", + "allOf": [{ "$ref": "#/definitions/SortOrder" }] } }, - "additionalProperties": false + "additionalProperties": false }, "UseSortedPropertiesOptions": { "type": "object", From 213ba0d80e1767bf6545b47265436aeeddc37545 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 8 Jul 2025 21:24:24 +0200 Subject: [PATCH 24/27] Fix config schema linting --- packages/@biomejs/biome/configuration_schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 1dfbc61ba93e..c8be08bc29d4 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -13543,8 +13543,8 @@ }, "additionalProperties": false }, - "UseSortedKeysOptions": { - "type": "object", + "UseSortedKeysOptions": { + "type": "object", "properties": { "sortOrder": { "default": "natural", From 55b5730fed6024333d4d412916c180e1dc257014 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 9 Jul 2025 13:22:32 +0200 Subject: [PATCH 25/27] Update the docs and added exampels for ne rule options --- .../src/assist/source/organize_imports.rs | 23 ++++++++++---- .../assist/source/use_sorted_attributes.rs | 18 +++++++++++ .../src/assist/source/use_sorted_keys.rs | 30 +++++++++++++++++++ .../src/assist/source/use_sorted_keys.rs | 30 +++++++++++++++++++ 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/crates/biome_js_analyze/src/assist/source/organize_imports.rs b/crates/biome_js_analyze/src/assist/source/organize_imports.rs index 78f6251424b6..8e561dcfeb04 100644 --- a/crates/biome_js_analyze/src/assist/source/organize_imports.rs +++ b/crates/biome_js_analyze/src/assist/source/organize_imports.rs @@ -618,14 +618,22 @@ declare_source_rule! { /// } /// ``` /// - /// ## Options - /// This actions accepts following options + /// ## Change the sorting of import identifiers to lexicographic sorting + /// This only applies to the named import/exports and not the source itself. /// - /// ### `groups` - /// The usage of `groups` is explained in detail above. + /// ```json,options + /// { + /// "options": { + /// "identifierOrder": "lexicographic" + /// } + /// } + /// ``` + /// ```js,use_options,expect_diagnostic + /// import { var1, var2, var21, var11, var12, var22 } from 'my-package' + /// ``` /// - /// ### `identifierOrder` - /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. This only applies to the named import/exports and not the source itself. + /// ## Change the sorting of import identifiers to logical sorting + /// This is the default behavior incase you do not override. This only applies to the named import/exports and not the source itself. /// /// ```json,options /// { @@ -634,6 +642,9 @@ declare_source_rule! { /// } /// } /// ``` + /// ```js,use_options,expect_diagnostic + /// import { var1, var2, var21, var11, var12, var22 } from 'my-package' + /// ``` /// pub OrganizeImports { version: "1.0.0", diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs index f0b597c2e94c..5b406e8bf631 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_attributes.rs @@ -44,6 +44,8 @@ declare_source_rule! { /// ### `sortOrder` /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. /// + /// Following will apply the natural sort order. + /// /// ```json,options /// { /// "options": { @@ -51,6 +53,22 @@ declare_source_rule! { /// } /// } /// ``` + /// ```jsx,use_options,expect_diagnostic + /// ; + /// ``` + /// + /// Following will apply the lexicographic sort order. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "lexicographic" + /// } + /// } + /// ``` + /// ```jsx,use_options,expect_diagnostic + /// ; + /// ``` /// pub UseSortedAttributes { version: "2.0.0", diff --git a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs index 68b7c0dc3a2c..7a75e3c4e2d5 100644 --- a/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_js_analyze/src/assist/source/use_sorted_keys.rs @@ -79,6 +79,8 @@ declare_source_rule! { /// ### `sortOrder` /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. /// + /// Following will apply the natural sort order. + /// /// ```json,options /// { /// "options": { @@ -86,6 +88,34 @@ declare_source_rule! { /// } /// } /// ``` + /// ```js,use_options,expect_diagnostic + /// const obj = { + /// val13: 1, + /// val1: 1, + /// val2: 1, + /// val21: 1, + /// val11: 1, + /// }; + /// ``` + /// + /// Following will apply the lexicographic sort order. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "lexicographic" + /// } + /// } + /// ``` + /// ```js,use_options,expect_diagnostic + /// const obj = { + /// val13: 1, + /// val1: 1, + /// val2: 1, + /// val21: 1, + /// val11: 1, + /// }; + /// ``` /// pub UseSortedKeys { version: "2.0.0", diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index 495a0b09b242..797ad43ae34b 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -33,6 +33,8 @@ declare_source_rule! { /// ### `sortOrder` /// This options supports `natural` and `lexicographic` values. Where as `natural` is the default. /// + /// Following will apply the natural sort order. + /// /// ```json,options /// { /// "options": { @@ -40,6 +42,34 @@ declare_source_rule! { /// } /// } /// ``` + /// ```json,use_options,expect_diagnostic + /// { + /// "val13": 1, + /// "val1": 1, + /// "val2": 1, + /// "val21": 1, + /// "val11": 1, + /// }; + /// ``` + /// + /// Following will apply the lexicographic sort order. + /// + /// ```json,options + /// { + /// "options": { + /// "sortOrder": "lexicographic" + /// } + /// } + /// ``` + /// ```json,use_options,expect_diagnostic + /// { + /// "val13": 1, + /// "val1": 1, + /// "val2": 1, + /// "val21": 1, + /// "val11": 1, + /// }; + /// ``` /// pub UseSortedKeys { version: "1.9.0", From 0ba0eba45ba06f02ac835f341d21d6fa24f45b4a Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 9 Jul 2025 13:27:30 +0200 Subject: [PATCH 26/27] Update auto-generated code --- packages/@biomejs/backend-jsonrpc/src/workspace.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 051b8df9522d..90c8fa5164e5 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -3447,9 +3447,14 @@ export type RuleFixConfiguration_for_UseStrictModeOptions = | RuleWithFixOptions_for_UseStrictModeOptions; export interface OrganizeImportsOptions { groups?: ImportGroups; + identifierOrder?: SortOrder; +} +export interface UseSortedAttributesOptions { + sortOrder?: SortOrder; +} +export interface UseSortedKeysOptions { + sortOrder?: SortOrder; } -export interface UseSortedAttributesOptions {} -export interface UseSortedKeysOptions {} export interface UseSortedPropertiesOptions {} export type RulePlainConfiguration = "off" | "on" | "info" | "warn" | "error"; export interface RuleWithFixOptions_for_NoAccessKeyOptions { @@ -7387,6 +7392,7 @@ export interface RuleWithFixOptions_for_UseStrictModeOptions { options: UseStrictModeOptions; } export type ImportGroups = ImportGroup[]; +export type SortOrder = "natural" | "lexicographic"; /** * Used to identify the kind of code action emitted by a rule */ From e2e4556f39b2ace104c1a8453830e06217678d65 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 21 Jul 2025 10:06:51 +0200 Subject: [PATCH 27/27] Update the lint rules --- .../biome_json_analyze/src/assist/source/use_sorted_keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs index 797ad43ae34b..6a34d0385d03 100644 --- a/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs +++ b/crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs @@ -49,7 +49,7 @@ declare_source_rule! { /// "val2": 1, /// "val21": 1, /// "val11": 1, - /// }; + /// } /// ``` /// /// Following will apply the lexicographic sort order. @@ -68,7 +68,7 @@ declare_source_rule! { /// "val2": 1, /// "val21": 1, /// "val11": 1, - /// }; + /// } /// ``` /// pub UseSortedKeys {