Skip to content

feat(analyze/json): useSortedPackageJson#9134

Open
ematipico wants to merge 7 commits intonextfrom
feat/use-sorted-pkg-json
Open

feat(analyze/json): useSortedPackageJson#9134
ematipico wants to merge 7 commits intonextfrom
feat/use-sorted-pkg-json

Conversation

@ematipico
Copy link
Member

Summary

Superseeds #8659 with all the applied feedback from us.

I changed the API signature of some functions. Some function were returning Option<Result<>>, which is an awkward, and useless API. Now it results Option.

With the help of a coding agent:

  • I updated the callsites of the changed APIs
  • Fixed the testing harness. It was using serde which is.... useless to us. We have our own parser and formatter.
  • Fixed all the bugs uncovered by the testing harness

Test Plan

All tests should pass

Docs

@changeset-bot
Copy link

changeset-bot bot commented Feb 18, 2026

🦋 Changeset detected

Latest commit: d421fd8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
@biomejs/biome Minor
@biomejs/cli-win32-x64 Minor
@biomejs/cli-win32-arm64 Minor
@biomejs/cli-darwin-x64 Minor
@biomejs/cli-darwin-arm64 Minor
@biomejs/cli-linux-x64 Minor
@biomejs/cli-linux-arm64 Minor
@biomejs/cli-linux-x64-musl Minor
@biomejs/cli-linux-arm64-musl Minor
@biomejs/wasm-web Minor
@biomejs/wasm-bundler Minor
@biomejs/wasm-nodejs Minor
@biomejs/backend-jsonrpc Patch
@biomejs/js-api Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Project Area: project A-Linter Area: linter A-Parser Area: parser A-Tooling Area: internal tools L-JSON Language: JSON and super languages A-Diagnostic Area: diagnostocis labels Feb 18, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 18, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing feat/use-sorted-pkg-json (d421fd8) with next (4f85adb)

Open in CodSpeed

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

ematipico and others added 3 commits February 18, 2026 11:20
Co-authored-by: Arthur Fiorette <me@arthur.place>
Co-authored-by: Arthur Fiorette <me@arthur.place>
Co-authored-by: Arthur Fiorette <me@arthur.place>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Walkthrough

Adds an organizePackageJson assist action (exposed as UseSortedPackageJson) and wires it as an assist-action. Introduces RuleSource::SortPackageJson and a data-driven package.json field schema plus multiple sorter modules (constants, helpers, dependencies, dependencies_meta, scripts, eslint_config, prettier_config, exports) under a unified try_transform_field entry point. Implements JSON object/array sorting/transform utilities, test fixtures verifying compatibility with sort-package-json, a UseSortedPackageJsonOptions type, small JSON syntax API adjustments, and several simplifications to inner_string_text/value_token unwrap handling across analyzers and migration tooling.

Possibly related PRs

Suggested reviewers

  • dyc3
  • Netail
  • arendjr
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: introducing a new assist action useSortedPackageJson for JSON analysis in Biome.
Description check ✅ Passed The description is related to the changeset, explaining the refactoring of API signatures and improvements to the testing harness, though lacking specific implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/use-sorted-pkg-json

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
crates/biome_json_analyze/src/lint/nursery/use_required_scripts.rs (1)

106-115: Consider avoiding intermediate Vec<String> allocation.

Per repository guidance on avoiding string allocations for comparisons, you could skip collecting into existing_scripts and compare directly using TokenText:

♻️ Suggested refactor
-        let existing_scripts: Vec<String> = scripts_object
-            .json_member_list()
-            .iter()
-            .flatten()
-            .filter_map(|member| {
-                let name = member.name().ok()?;
-                let text = name.inner_string_text()?;
-                Some(text.text().to_string())
-            })
-            .collect();
-
-        let missing_scripts: Vec<String> = options
+        let members = scripts_object.json_member_list();
+        let missing_scripts: Vec<String> = options
             .required_scripts
             .iter()
-            .filter(|script| !existing_scripts.iter().any(|s| s == *script))
+            .filter(|script| {
+                !members.iter().flatten().any(|member| {
+                    member
+                        .name()
+                        .ok()
+                        .and_then(|n| n.inner_string_text())
+                        .is_some_and(|text| text.text() == script.as_str())
+                })
+            })
             .cloned()
             .collect();

Based on learnings: "Avoid string allocations by using &str or TokenText for comparisons instead of calling to_string()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_json_analyze/src/lint/nursery/use_required_scripts.rs` around
lines 106 - 115, The code currently builds an intermediate Vec<String> called
existing_scripts by calling to_string() on each member name; instead avoid the
allocation by operating on TokenText/&str directly — e.g., use
scripts_object.json_member_list().iter().flatten().filter_map(|member|
member.name().ok()?.inner_string_text().map(|t| t.text())).then either collect
into a HashSet<&TokenText> or use the iterator with any/contains against the
TokenText/&str you need to compare; update places referencing existing_scripts
to use the new iterator/collection so no to_string() allocations occur (look for
symbols scripts_object, json_member_list, existing_scripts, and
name()/inner_string_text()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_analyze/src/rule.rs`:
- Line 460: The URL mapped for Self::SortPackageJson is wrong (it's not an npm
CLI command); update the mapping in rule.rs so SortPackageJson points to the
third‑party package page (for example the npm package URL
"https://www.npmjs.com/package/sort-package-json" or the project repo
"https://github.com/keithamus/sort-package-json") by replacing the existing
string for Self::SortPackageJson with the correct package URL.

In `@crates/biome_json_analyze/src/assist/source/use_sorted_package_json.rs`:
- Around line 370-405: The current organize_members function uses a
HashMap<TokenText, JsonMember> (member_map) which collapses duplicate keys and
loses members; instead collect a Vec of (TokenText, JsonMember) from the
incoming JsonMemberList (preserving duplicates and original occurrence order),
then build a grouping (e.g., HashMap<TokenText, Vec<JsonMember>> or better an
ordered map like Vec<(TokenText, Vec<JsonMember>)>) so each key maps to a queue
of all members; iterate sorted_names from get_sorted_field_order and for each
name drain/iterate its Vec of JsonMember values, applying
apply_field_transformer to each member and pushing them onto elements (adding
separators between entries), and return make::json_member_list(elements,
separators); do not replace member_map with a single-value map that drops
duplicates and ensure JsonMemberList ordering for duplicate keys is preserved.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/dependencies_meta.rs`:
- Around line 37-81: The transform function currently bails out when
sort_object_by_comparator returns None so nested objects never get deep‑sorted;
update transform to always call deep_sort_nested_objects on the object (use the
sorted object when sort_object_by_comparator returns Some or the original object
when it returns None) and only return Some(AnyJsonValue::from(...)) if either
the top-level sort or the deep sort produced changes. Also change
deep_sort_nested_objects to return None when it detects no changes (instead of
Some(object.clone())), using super::helpers::sort_alphabetically_deep to detect
nested changes; reference functions: transform, sort_object_by_comparator,
deep_sort_nested_objects, get_package_name, and
super::helpers::sort_alphabetically_deep.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/prettier_config.rs`:
- Around line 5-68: Summary: transform currently sorts top-level keys but leaves
the "overrides" array and each override's "options" object unsorted; update it
to alphabetise both. Fix: in transform (function transform, variables
overrides_member, keys_with_members, make::json_member_list) when
overrides_member is Some, parse its JsonMember value as a JSON array, iterate
each array element that is an object and rebuild each override object by sorting
its members alphabetically and, if it has an "options" member, parse that
options value as an object and sort its members alphabetically too; then
reassemble a new overrides JsonMember with a json array of sorted override
objects and use that sorted overrides member when building new_members so
overrides remains last; add a unit test that supplies a prettier config with
unsorted overrides and options and asserts alphabetical ordering.

In `@crates/biome_json_analyze/tests/compat_sortpkg_tests.rs`:
- Around line 210-277: The test function test_compat_with_sortpkg currently
prints a success message with eprintln!("All {total} sort-package-json
compatibility tests passed"); replace that debug output by using dbg!(...) or
remove it per guideline: change the eprintln! call to a dbg!(format!("All {}
sort-package-json compatibility tests passed", total)) (or simply drop the
success print) so the test uses the dbg! macro for debugging output instead of
eprintln!.

---

Nitpick comments:
In `@crates/biome_json_analyze/src/lint/nursery/use_required_scripts.rs`:
- Around line 106-115: The code currently builds an intermediate Vec<String>
called existing_scripts by calling to_string() on each member name; instead
avoid the allocation by operating on TokenText/&str directly — e.g., use
scripts_object.json_member_list().iter().flatten().filter_map(|member|
member.name().ok()?.inner_string_text().map(|t| t.text())).then either collect
into a HashSet<&TokenText> or use the iterator with any/contains against the
TokenText/&str you need to compare; update places referencing existing_scripts
to use the new iterator/collection so no to_string() allocations occur (look for
symbols scripts_object, json_member_list, existing_scripts, and
name()/inner_string_text()).

@ematipico ematipico force-pushed the feat/use-sorted-pkg-json branch from a507e67 to ad46c7b Compare February 18, 2026 11:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/helpers.rs (1)

237-264: Consider using rustc_hash::FxHashSet for consistency.

The crate already depends on rustc-hash per the library context. Using FxHashSet instead of std::collections::HashSet would be more consistent and potentially faster for string keys.

Suggested change
+use rustc_hash::FxHashSet;
+
 /// Remove duplicate string values from an array
 pub fn uniq_array(array: &AnyJsonValue) -> Option<AnyJsonValue> {
     let array_value = array.as_json_array_value()?;
     let elements = array_value.elements();

-    let mut seen: std::collections::HashSet<TokenText> = std::collections::HashSet::new();
+    let mut seen: FxHashSet<TokenText> = FxHashSet::default();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/helpers.rs`
around lines 237 - 264, The function uniq_array uses std::collections::HashSet
for seen; replace it with rustc_hash::FxHashSet to match the crate's dependency
and improve performance: import rustc_hash::FxHashSet (or prefix it as
rustc_hash::FxHashSet) and change the variable declaration from
std::collections::HashSet<TokenText> to FxHashSet<TokenText>, and construct it
with FxHashSet::default() or FxHashSet::with_capacity(...) instead of
HashSet::new(); keep all other logic in uniq_array (and references to TokenText)
unchanged.
crates/biome_json_analyze/tests/compat_sortpkg/data.json (2)

40-42: Duplicate test names may cause confusion.

Lines 22 and 41 both use "Should sort \dependenciesMeta` as object."Similarly, multipleexports` tests share identical names (lines 227, 246, 265, etc.). Consider adding distinguishing suffixes (e.g., "with scoped packages", "with versioned keys") to aid debugging when tests fail.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_json_analyze/tests/compat_sortpkg/data.json` around lines 40 -
42, The JSON test entries in data.json have duplicate "testName" values (e.g.,
multiple "Should sort `dependenciesMeta` as object." and repeated "exports" test
names), which makes failing tests ambiguous; update each offending test object
by making its "testName" unique—append a short distinguishing suffix such as
"—with scoped packages", "—with versioned keys", or "—case X" to the "testName"
field for the entries that test different scenarios so each test can be
identified unambiguously (locate and edit the "testName" properties in the JSON
objects that start with "Should sort `dependenciesMeta` as object." and the
repeated "exports" tests).

183-184: Clarify the "input": null pattern.

Several test cases use "input": null (lines 184, 391, 415, 687, 711). If this means "output serves as both input and expected result", consider adding a comment at the top of the file or in a README explaining this convention for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_json_analyze/tests/compat_sortpkg/data.json` around lines 183 -
184, Several test entries in data.json use the pattern "input": null to indicate
that the test's output value should be used as the input (i.e., expected result
doubles as input); add a short clarifying note at the top of this JSON test data
file (or in an adjacent README) that documents this convention and lists the
meaning of "input": null, referencing the "testName" fields (for example entries
like "Should sort `eslintConfig.override[]` same as `eslintConfig`") and the
"input"/"output" keys so future maintainers understand that null input means
"use output as input."
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_analyze/src/rule.rs`:
- Line 460: The URL for Self::SortPackageJson is incorrect (points to a
non-existent npm CLI command); update the string returned for the
SortPackageJson variant to the official package page (for example
"https://www.npmjs.com/package/sort-package-json" or the GitHub repo
"https://github.com/keithamus/sort-package-json") in the match/impl handling the
rule URLs so the variant Self::SortPackageJson returns the correct third-party
package URL.

In `@crates/biome_json_analyze/src/assist/source/use_sorted_package_json.rs`:
- Around line 370-405: The current organize_members collects members into a
HashMap<TokenText, JsonMember> which drops duplicate keys; change this to
collect into a HashMap<TokenText, Vec<JsonMember>> (or similar per-key queue) so
all JsonMember occurrences are preserved in insertion order, then when iterating
sorted_names fetch the Vec for that key and push each member (applying
apply_field_transformer to each) into elements in the original occurrence order,
adding separators between members as before and finally build the JsonMemberList
via make::json_member_list; keep using extract_field_names and
get_sorted_field_order for the ordering but replace the single-value member_map
with a per-key list to avoid collapsing duplicates.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/dependencies_meta.rs`:
- Around line 37-81: transform currently short‑circuits when
sort_object_by_comparator returns None and deep_sort_nested_objects always
returns Some even if unchanged; change transform to always obtain a
JsonObjectValue to pass into deep_sort_nested_objects (use the sorted_top_level
if Some or the original object.clone() if None) so nested objects are inspected
even when top-level ordering didn’t change, and modify deep_sort_nested_objects
to return None when no nested changes were made (only return
Some(updated_object) when any member was replaced with a deep-sorted value);
reference functions: transform, sort_object_by_comparator,
deep_sort_nested_objects, and super::helpers::sort_alphabetically_deep.

In `@crates/biome_json_analyze/tests/compat_sortpkg_tests.rs`:
- Around line 210-277: In test_compat_with_sortpkg replace the debug eprintln!
call that prints the success message with either a dbg! invocation or remove it:
locate the eprintln!("All {total} sort-package-json compatibility tests passed")
in the test_compat_with_sortpkg function and change it to dbg!(format!("All {}
sort-package-json compatibility tests passed", total)) (or simply delete the
line) so debug output uses dbg! or is dropped per guidelines.

---

Nitpick comments:
In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/helpers.rs`:
- Around line 237-264: The function uniq_array uses std::collections::HashSet
for seen; replace it with rustc_hash::FxHashSet to match the crate's dependency
and improve performance: import rustc_hash::FxHashSet (or prefix it as
rustc_hash::FxHashSet) and change the variable declaration from
std::collections::HashSet<TokenText> to FxHashSet<TokenText>, and construct it
with FxHashSet::default() or FxHashSet::with_capacity(...) instead of
HashSet::new(); keep all other logic in uniq_array (and references to TokenText)
unchanged.

In `@crates/biome_json_analyze/tests/compat_sortpkg/data.json`:
- Around line 40-42: The JSON test entries in data.json have duplicate
"testName" values (e.g., multiple "Should sort `dependenciesMeta` as object."
and repeated "exports" test names), which makes failing tests ambiguous; update
each offending test object by making its "testName" unique—append a short
distinguishing suffix such as "—with scoped packages", "—with versioned keys",
or "—case X" to the "testName" field for the entries that test different
scenarios so each test can be identified unambiguously (locate and edit the
"testName" properties in the JSON objects that start with "Should sort
`dependenciesMeta` as object." and the repeated "exports" tests).
- Around line 183-184: Several test entries in data.json use the pattern
"input": null to indicate that the test's output value should be used as the
input (i.e., expected result doubles as input); add a short clarifying note at
the top of this JSON test data file (or in an adjacent README) that documents
this convention and lists the meaning of "input": null, referencing the
"testName" fields (for example entries like "Should sort
`eslintConfig.override[]` same as `eslintConfig`") and the "input"/"output" keys
so future maintainers understand that null input means "use output as input."

@ematipico ematipico requested review from a team February 18, 2026 12:05
@arthurfiorette
Copy link

Sorry for not having time to fix all the past reviews :/ Work is demanding a lot for the last weeks... Thanks for picking this up, great work!!


fn run(ctx: &RuleContext<Self>) -> Option<Self::State> {
// Skip package.json files - they have specialized sorting via organizePackageJson
let path = ctx.file_path();
Copy link

@arthurfiorette arthurfiorette Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one thing I was not sure when I did it... Maybe we should only disable this when useSortedPackageJson is enabled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually a breaking change, so I will revert this part. We can enhance this diagnostic, and tell the user to use the new action if the file is package.json

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree as long as useSortedPackageJson wins when both are enabled

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_analyze/src/rule.rs`:
- Around line 294-297: The variant_index mapping currently assigns the same
index 38 to Self::HtmlEslint and Self::EslintE18e, causing duplicate indices;
update the mapping so each enum variant has a unique sequential index by
changing Self::EslintE18e to return 39 (and increment subsequent indices
accordingly—e.g., change Self::EslintBetterTailwindcss to 40 and
Self::SortPackageJson to 41) in the function/match that implements variant_index
to restore deterministic ordering.

In `@crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs`:
- Around line 214-216: The comment incorrectly states we "Skip package.json
files - they have specialized sorting via organizePackageJson" while the code
only retrieves the path (ctx.file_path()) and proceeds with the
useSortedPackageJson action; either implement an actual skip or update the
comment to match behavior — I recommend updating the comment to something like
"Handle package.json with useSortedPackageJson note" or, if you want to skip,
add a guard that checks the filename (e.g., if ctx.file_path().file_name() ==
Some("package.json")) and branch to organizePackageJson or return early;
reference use_sorted_keys.rs, ctx.file_path(), organizePackageJson and
useSortedPackageJson when making the change.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/helpers.rs`:
- Around line 274-320: The function uniq_and_sort_array currently moves all
non-string elements to the end (non_string_elements) which changes their
relative positions in mixed-type arrays; instead detect mixed arrays and bail
out to avoid reordering: in uniq_and_sort_array, after collecting string_values
and non_string_elements, if both collections are non-empty (i.e., the array
contains at least one string and at least one non-string), return None (do not
modify) so positions are preserved; keep this check before deduping/sorting and
before calling rebuild_array.

Comment on lines 294 to +297
Self::HtmlEslint(_) => 38,
Self::EslintE18e(_) => 38,
Self::EslintBetterTailwindcss(_) => 39,
Self::SortPackageJson => 40,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate variant_index for HtmlEslint and EslintE18e.

Both HtmlEslint (line 294) and EslintE18e (line 295) return index 38. This could cause non-deterministic ordering when comparing rule sources. One of these should be renumbered (e.g., EslintE18e => 39, then bump subsequent indices).

Proposed fix
             Self::HtmlEslint(_) => 38,
-            Self::EslintE18e(_) => 38,
-            Self::EslintBetterTailwindcss(_) => 39,
-            Self::SortPackageJson => 40,
+            Self::EslintE18e(_) => 39,
+            Self::EslintBetterTailwindcss(_) => 40,
+            Self::SortPackageJson => 41,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Self::HtmlEslint(_) => 38,
Self::EslintE18e(_) => 38,
Self::EslintBetterTailwindcss(_) => 39,
Self::SortPackageJson => 40,
Self::HtmlEslint(_) => 38,
Self::EslintE18e(_) => 39,
Self::EslintBetterTailwindcss(_) => 40,
Self::SortPackageJson => 41,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_analyze/src/rule.rs` around lines 294 - 297, The variant_index
mapping currently assigns the same index 38 to Self::HtmlEslint and
Self::EslintE18e, causing duplicate indices; update the mapping so each enum
variant has a unique sequential index by changing Self::EslintE18e to return 39
(and increment subsequent indices accordingly—e.g., change
Self::EslintBetterTailwindcss to 40 and Self::SortPackageJson to 41) in the
function/match that implements variant_index to restore deterministic ordering.

Comment on lines +214 to +216
// Skip package.json files - they have specialized sorting via organizePackageJson
let path = ctx.file_path();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Comment says “skip”, but we don’t

Line 214 says we skip package.json and names “organizePackageJson”, but the code only adds a note and the action is useSortedPackageJson. Either implement the skip or update the comment to match the behaviour/name.

Suggested tweak
-        // Skip package.json files - they have specialized sorting via organizePackageJson
+        // package.json files have specialised sorting via useSortedPackageJson; add a note.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Skip package.json files - they have specialized sorting via organizePackageJson
let path = ctx.file_path();
// package.json files have specialised sorting via useSortedPackageJson; add a note.
let path = ctx.file_path();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_json_analyze/src/assist/source/use_sorted_keys.rs` around lines
214 - 216, The comment incorrectly states we "Skip package.json files - they
have specialized sorting via organizePackageJson" while the code only retrieves
the path (ctx.file_path()) and proceeds with the useSortedPackageJson action;
either implement an actual skip or update the comment to match behavior — I
recommend updating the comment to something like "Handle package.json with
useSortedPackageJson note" or, if you want to skip, add a guard that checks the
filename (e.g., if ctx.file_path().file_name() == Some("package.json")) and
branch to organizePackageJson or return early; reference use_sorted_keys.rs,
ctx.file_path(), organizePackageJson and useSortedPackageJson when making the
change.

Comment on lines +274 to +320
/// Remove duplicates and sort array alphabetically
pub fn uniq_and_sort_array(array: &AnyJsonValue) -> Option<AnyJsonValue> {
let array_value = array.as_json_array_value()?;
let elements = array_value.elements();

let mut string_values: Vec<(TokenText, AnyJsonValue)> = Vec::new();
let mut non_string_elements = Vec::new();

for element in (&elements).into_iter().flatten() {
if let Some(string_val) = element.as_json_string_value()
&& let Ok(text) = string_val.inner_string_text()
{
string_values.push((text, element.clone()));
} else {
non_string_elements.push(element.clone());
}
}

// Remove duplicates and sort
let mut seen: std::collections::HashSet<TokenText> = std::collections::HashSet::new();
let mut unique_sorted: Vec<(TokenText, AnyJsonValue)> = string_values
.into_iter()
.filter(|(s, _)| seen.insert(s.clone()))
.collect();
unique_sorted.sort_by(|a, b| a.0.cmp(&b.0));

let original_count = elements.iter().count();
let new_count = unique_sorted.len() + non_string_elements.len();

// Check if order changed
let current_strings: Vec<TokenText> = elements
.iter()
.filter_map(|e| e.ok()?.as_json_string_value()?.inner_string_text().ok())
.collect();
let sorted_strings: Vec<&TokenText> = unique_sorted.iter().map(|(s, _)| s).collect();

if original_count == new_count && current_strings.iter().eq(sorted_strings.iter().copied()) {
return None;
}

let mut final_elements = Vec::new();
for (_, elem) in unique_sorted {
final_elements.push(elem);
}
final_elements.extend(non_string_elements);

rebuild_array(array_value, final_elements)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid reordering non‑string array entries.
When a mixed array is present, non‑string items are pushed to the end whenever sorting/dedup happens, which changes their relative order. If mixed arrays are possible, consider bailing out or preserving positions.

🛠️ Suggested conservative guard
     for element in (&elements).into_iter().flatten() {
         if let Some(string_val) = element.as_json_string_value()
             && let Ok(text) = string_val.inner_string_text()
         {
             string_values.push((text, element.clone()));
         } else {
             non_string_elements.push(element.clone());
         }
     }
+
+    if !non_string_elements.is_empty() {
+        return None;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Remove duplicates and sort array alphabetically
pub fn uniq_and_sort_array(array: &AnyJsonValue) -> Option<AnyJsonValue> {
let array_value = array.as_json_array_value()?;
let elements = array_value.elements();
let mut string_values: Vec<(TokenText, AnyJsonValue)> = Vec::new();
let mut non_string_elements = Vec::new();
for element in (&elements).into_iter().flatten() {
if let Some(string_val) = element.as_json_string_value()
&& let Ok(text) = string_val.inner_string_text()
{
string_values.push((text, element.clone()));
} else {
non_string_elements.push(element.clone());
}
}
// Remove duplicates and sort
let mut seen: std::collections::HashSet<TokenText> = std::collections::HashSet::new();
let mut unique_sorted: Vec<(TokenText, AnyJsonValue)> = string_values
.into_iter()
.filter(|(s, _)| seen.insert(s.clone()))
.collect();
unique_sorted.sort_by(|a, b| a.0.cmp(&b.0));
let original_count = elements.iter().count();
let new_count = unique_sorted.len() + non_string_elements.len();
// Check if order changed
let current_strings: Vec<TokenText> = elements
.iter()
.filter_map(|e| e.ok()?.as_json_string_value()?.inner_string_text().ok())
.collect();
let sorted_strings: Vec<&TokenText> = unique_sorted.iter().map(|(s, _)| s).collect();
if original_count == new_count && current_strings.iter().eq(sorted_strings.iter().copied()) {
return None;
}
let mut final_elements = Vec::new();
for (_, elem) in unique_sorted {
final_elements.push(elem);
}
final_elements.extend(non_string_elements);
rebuild_array(array_value, final_elements)
/// Remove duplicates and sort array alphabetically
pub fn uniq_and_sort_array(array: &AnyJsonValue) -> Option<AnyJsonValue> {
let array_value = array.as_json_array_value()?;
let elements = array_value.elements();
let mut string_values: Vec<(TokenText, AnyJsonValue)> = Vec::new();
let mut non_string_elements = Vec::new();
for element in (&elements).into_iter().flatten() {
if let Some(string_val) = element.as_json_string_value()
&& let Ok(text) = string_val.inner_string_text()
{
string_values.push((text, element.clone()));
} else {
non_string_elements.push(element.clone());
}
}
if !non_string_elements.is_empty() {
return None;
}
// Remove duplicates and sort
let mut seen: std::collections::HashSet<TokenText> = std::collections::HashSet::new();
let mut unique_sorted: Vec<(TokenText, AnyJsonValue)> = string_values
.into_iter()
.filter(|(s, _)| seen.insert(s.clone()))
.collect();
unique_sorted.sort_by(|a, b| a.0.cmp(&b.0));
let original_count = elements.iter().count();
let new_count = unique_sorted.len() + non_string_elements.len();
// Check if order changed
let current_strings: Vec<TokenText> = elements
.iter()
.filter_map(|e| e.ok()?.as_json_string_value()?.inner_string_text().ok())
.collect();
let sorted_strings: Vec<&TokenText> = unique_sorted.iter().map(|(s, _)| s).collect();
if original_count == new_count && current_strings.iter().eq(sorted_strings.iter().copied()) {
return None;
}
let mut final_elements = Vec::new();
for (_, elem) in unique_sorted {
final_elements.push(elem);
}
final_elements.extend(non_string_elements);
rebuild_array(array_value, final_elements)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_json_analyze/src/assist/source/use_sorted_package_json/sorters/helpers.rs`
around lines 274 - 320, The function uniq_and_sort_array currently moves all
non-string elements to the end (non_string_elements) which changes their
relative positions in mixed-type arrays; instead detect mixed arrays and bail
out to avoid reordering: in uniq_and_sort_array, after collecting string_values
and non_string_elements, if both collections are non-empty (i.e., the array
contains at least one string and at least one non-string), return None (do not
modify) so positions are preserved; keep this check before deduping/sorting and
before calling rebuild_array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Parser Area: parser A-Project Area: project A-Tooling Area: internal tools L-JSON Language: JSON and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments