-
-
Notifications
You must be signed in to change notification settings - Fork 724
feat(useSortedClasses): add support for custom classes #7752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add CustomClasses struct to define custom component and utility classes that can be sorted alongside Tailwind's default classes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
WalkthroughAdds support for custom class ordering for the nursery/useSortedClasses lint via a new options.classes key (components and utilities patterns). Introduces an owned utility-layer representation and SortConfig::with_custom_classes to merge presets with custom classes, plus SortConfig.layer_index to resolve owned/static layers. Generalises utility lookup to handle both static and owned layers and changes UtilityInfo.layer to a String. The lint now selects a custom SortConfig when classes are provided; tests and options files for valid/invalid custom-class scenarios were added. Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (6)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (1)
203-217
: Avoid rebuilding custom SortConfig per nodeThis constructs a custom SortConfig for every matched node. Consider caching once per analysis run (e.g. derive it once when options.classes is present and reuse) to reduce allocations on large files.
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (1)
20-24
: Provide unified accessors to avoid map misuse; minor ergonomicsWhen utilities_owned is Some, layer_index_map is unused. To prevent accidental use, expose helpers that abstract the owned/static split.
Apply this diff to add a safe accessor:
impl SortConfig { @@ pub fn new(preset: &ConfigPreset) -> Self { @@ Self { utilities: preset.utilities, variants: preset.variants, layer_index_map, utilities_owned: None, layer_index_map_owned: None, } } + + /// Gets the layer index, regardless of owned/static backing. + pub fn layer_index(&self, name: &str) -> Option<usize> { + if let Some(map) = &self.layer_index_map_owned { + map.get(name).copied() + } else { + self.layer_index_map.get(name).copied() + } + } }Optional niceties:
- Derive Debug/Clone on UtilityLayerOwned for easier debugging/reuse.
- Reserve capacity when building classes to reduce reallocs:
let mut classes = Vec::with_capacity(layer.classes.len() + custom_len_estimate);
Also applies to: 45-49, 67-70, 72-130
crates/biome_rule_options/src/use_sorted_classes.rs (1)
8-68
: Options wiring LGTM; consider documenting pattern semanticsDeserialisation and validation look solid (unknown keys handled, empties normalised). Consider adding a short rustdoc note on pattern semantics (prefix vs
$
exact) to keep this in sync with the rule docs.Also applies to: 80-83, 119-119, 162-162
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs (3)
110-149
: Nice abstraction with UtilityLayerLike; consider adding an iteratorCurrent index-based access forces an Option check that’s never None for these impls. An optional iterator method (e.g., classes_iter) would simplify loops and avoid bounds checks.
153-210
: Longest-prefix match across layers: clarify tie-breaksThe “longest prefix” logic is solid. When lengths tie, the first encountered layer/class wins; that effectively encodes layer order as the tie-breaker. Consider:
- Documenting this explicitly in a comment.
- Adding a test that asserts equal-length tie resolves by layer order (across layers), to lock in behaviour.
562-591
: Guard against config mismatch (owned utilities vs owned index map)You select utilities and layer-index map independently. If only one of utilities_owned or layer_index_map_owned is Some, lookups can fail unexpectedly. Add a debug assertion and use a single boolean to drive both selections.
Apply minimal guard:
- // Use utilities_owned if available, otherwise use static utilities + // Use utilities_owned if available, 10000 otherwise use static utilities. + // Both owned fields must be set/unset together. + debug_assert_eq!( + sort_config.utilities_owned.is_some(), + sort_config.layer_index_map_owned.is_some(), + "inconsistent SortConfig: utilities_owned and layer_index_map_owned must be set together" + ); + let using_owned = sort_config.utilities_owned.is_some(); - let utility_info = if let Some(utilities_owned) = &sort_config.utilities_owned { - get_utility_info_owned(utilities_owned, &utility_data.utility) - } else { - get_utility_info(sort_config.utilities, &utility_data.utility) - }; + let utility_info = if using_owned { + get_utility_info_owned( + // Safe due to the debug_assert above + sort_config.utilities_owned.as_ref().unwrap(), + &utility_data.utility, + ) + } else { + get_utility_info(sort_config.utilities, &utility_data.utility) + }; @@ - // Use owned layer index map if available - let layer_index = if let Some(layer_index_map_owned) = &sort_config.layer_index_map_owned { - *layer_index_map_owned.get(&utility_info.layer)? - } else { - *sort_config - .layer_index_map - .get(utility_info.layer.as_str())? - }; + // Use the matching layer index map consistently with the utilities set + let layer_index = if using_owned { + *sort_config + .layer_index_map_owned + .as_ref() + .unwrap() + .get(utility_info.layer.as_str())? + } else { + *sort_config + .layer_index_map + .get(utility_info.layer.as_str())? + };Also please verify that the owned map includes "arbitrary" with the expected index.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx.snap
is excluded by!**/*.snap
and included by**
📒 Files selected for processing (6)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
(3 hunks)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
(10 hunks)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
(3 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
(1 hunks)crates/biome_rule_options/src/use_sorted_classes.rs
(4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**
📄 CodeRabbit inference engine (CLAUDE.md)
Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_rule_options/src/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
**/tests/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place test files under a tests/ directory in each crate
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Before committing, format Rust and TOML files (e.g., via
just f
/just format
)
Files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_rule_options/src/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Document rules, assists, and options via inline rustdoc in Rust source
Files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_rule_options/src/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
🧠 Learnings (5)
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/specs/nursery/** : Place snapshot tests for new rules under tests/specs/nursery/<ruleName>/ with files prefixed by invalid* and valid*
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/*.rs : Define per-rule options in biome_rule_options/lib/<rule>.rs with a dedicated options struct/enum (camelCase serde names, deny_unknown_fields, default) and derive Serialize/Deserialize/Deserializable (and schemars JsonSchema when schema feature is on)
Applied to files:
crates/biome_rule_options/src/use_sorted_classes.rs
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/specs/**/*.jsonc : Use .jsonc files to hold arrays of code snippets for snapshot tests; these snippets run in script mode (no import/export)
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : Place all new JavaScript lint rules in the nursery group under biome_js_analyze/lib/src/lint/nursery/<rule_name>.rs
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
PR: biomejs/biome#7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
🧬 Code graph analysis (2)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (2)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (1)
with_custom_classes
(73-131)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort.rs (1)
sort_class_name
(131-210)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs (1)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (1)
build_variant_weight
(26-32)
🔇 Additional comments (4)
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json (1)
9-12
: Options look correct; confirm pattern semanticsConfig matches the new classes option. Please confirm/clarify in docs whether entries are treated as prefixes by default and
$
denotes exact match (e.g.card$
).crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs (3)
105-108
: Layer as String: sensible for owned namesThe switch enables owned layer names cleanly across both paths. Looks good.
213-228
: Static/owned wrappers read wellThe thin wrappers keep call sites tidy. No issues spotted.
246-278
: Tests updated for String layer names: LGTMThe expectations reflect String-based layers correctly and cover exact/partial/longest/arbitrary. Thanks for the coverage.
Also applies to: 300-305, 319-324, 338-343
// Valid (already sorted) | ||
<div class="container btn-primary px-2 text-red-500 custom-utility" />; | ||
|
||
// Invalid (needs sorting - custom classes should go after their layer's classes) | ||
<div class="custom-utility px-2 container text-red-500" />; | ||
|
||
// Invalid (utilities need sorting with custom class) | ||
<div class="text-red-500 custom-utility px-2" />; | ||
|
||
// Custom component class | ||
<div class="card container block" />; | ||
|
||
// Multiple custom classes | ||
<div class="text-lg custom-spacing px-4 custom-layout" />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Test discovery risk: mix of valid/invalid in a single file
Specs usually use files prefixed with valid*/invalid* (or explicit markers). Mixing both in one file may be ignored or flake.
Run to verify how tests are picked up:
If the harness expects valid*/invalid* files, please split:
- validCustomClasses.jsx: known-good examples
- invalidCustomClasses.jsx: cases that should emit diagnostics
Based on learnings
🏁 Script executed:
#!/bin/bash
# List useSortedClasses specs and grep for expect_diagnostic markers
fd . 'crates/biome_js_analyze/tests/specs/nursery/useSortedClasses' -t f
echo "---- grep expect_diagnostic in JSX specs ----"
rg -n 'expect_diagnostic' -g 'crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/**/*.jsx' -C1 || true
Length of output: 2449
Split customClasses.jsx into valid/invalid snapshot tests
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx mixes valid and invalid cases—rename and split into validCustomClasses.jsx and invalidCustomClasses.jsx (with matching .snap and .options.json) to align with the CONTRIBUTING guidelines.
🤖 Prompt for AI Agents
In
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
lines 1-14: this file mixes valid and invalid snapshot cases; split them into
two separate test files named validCustomClasses.jsx and
invalidCustomClasses.jsx, move the valid examples into validCustomClasses.jsx
and the invalid examples into invalidCustomClasses.jsx, and create matching
snapshot (.snap) and options.json files for each
(validCustomClasses.snap/validCustomClasses.options.json and
invalidCustomClasses.snap/invalidCustomClasses.options.json) following the
existing test conventions so each test only contains either valid or invalid
cases.
Add UtilityLayerOwned struct and with_custom_classes method to support dynamic custom class lists alongside static preset classes. This allows merging custom component and utility classes into the sort configuration at runtime. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add UtilityLayerLike trait to abstract over static and owned utility layer types, enabling generic utility matching logic that works with both UtilityLayer and UtilityLayerOwned. This allows the same matching algorithm to work with both static preset classes and dynamically added custom classes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Integrate the custom classes option into the useSortedClasses rule. When custom classes are provided in the options, create a dynamic SortConfig that merges them with the default Tailwind preset. Custom classes are appended to the end of their respective layer (components or utilities) and sorted after default classes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add test cases for custom component and utility classes to verify that they are properly recognized and sorted after default Tailwind classes in their respective layers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Document tie-breaking behavior for equal-length prefix matches and add test to verify layer order is used as tie-breaker
ea70d7b
to
71f97df
Compare
@coderabbitai Fixed everything you mentioned! |
Tip For best results, initiate chat on the files or code changes. @devxoul: Fantastic work! 🎉 Thanks for addressing all the feedback. The implementation looks solid with the custom classes support now in place. If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx (1)
1-14
: Split into valid/invalid specs**This file mixes valid and invalid snippets; please split into validCustomClasses.jsx and invalidCustomClasses.jsx (with matching .options.json/.snap) to align with test conventions and avoid flaky discovery.
Based on learnings
🧹 Nitpick comments (6)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (2)
176-179
: Rename section to “Custom classes ordering”You cover components and utilities; “utilities” in the heading might mislead. Suggest “Custom classes ordering”.
206-221
: Avoid rebuilding sort config on every nodeThis constructs a custom SortConfig for each string literal/template. Consider caching:
- Cache the preset once (LazyLock) and reuse it in with_custom_classes.
- If feasible, cache the computed SortConfig per-options for the rule run.
Minimal change to cache the preset:
@@ -use presets::get_config_preset; +use presets::{get_config_preset, ConfigPreset}; @@ static SORT_CONFIG: LazyLock<SortConfig> = LazyLock::new(|| SortConfig::new(&get_config_preset(&UseSortedClassesPreset::default()))); + +static CONFIG_PRESET: LazyLock<ConfigPreset> = + LazyLock::new(|| get_config_preset(&UseSortedClassesPreset::default())); @@ - let config = SortConfig::with_custom_classes( - &get_config_preset(&UseSortedClassesPreset::default()), + let config = SortConfig::with_custom_classes( + &CONFIG_PRESET, custom_classes.components.as_deref(), custom_classes.utilities.as_deref(), );Please sanity-check compile-time constraints around ConfigPreset lifetimes.
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (4)
20-24
: Derive Debug/Clone for UtilityLayerOwnedHandy for testing and diagnostics.
-pub struct UtilityLayerOwned { +#[derive(Debug, Clone)] +pub struct UtilityLayerOwned {
40-49
: Scope new fields narrower thanpub
Expose as
pub(crate)
(or keep private with accessors) to avoid leaking internal representation.- pub utilities_owned: Option<Vec<UtilityLayerOwned>>, + pub(crate) utilities_owned: Option<Vec<UtilityLayerOwned>>, - pub layer_index_map_owned: Option<HashMap<String, usize>>, + pub(crate) layer_index_map_owned: Option<HashMap<String, usize>>,
82-141
: with_custom_classes: looks correct; minor polishImplementation merges as intended; consider small tweaks:
- Inline “arbitrary” index via a const.
- Use
extend(custom.iter().map(ToString::to_string))
for brevity.
72-81
: Unify layer-index access and drop dead_code allowance
- In class_info.rs (line 635), replace the direct
.layer_index_map.get(…)
call withsort_config.layer_index(utility_info.layer.as_str())?
- Remove
#[allow(dead_code)]
fromlayer_index()
in sort_config.rs now that it’s used
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx.snap
is excluded by!**/*.snap
and included by**
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx.snap
is excluded by!**/*.snap
and included by**
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.jsx.snap
is excluded by!**/*.snap
and included by**
📒 Files selected for processing (9)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
(4 hunks)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
(10 hunks)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
(3 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.jsx
(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.options.json
🧰 Additional context used
📓 Path-based instructions (5)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**
📄 CodeRabbit inference engine (CLAUDE.md)
Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
**/tests/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place test files under a tests/ directory in each crate
Files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Before committing, format Rust and TOML files (e.g., via
just f
/just format
)
Files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Document rules, assists, and options via inline rustdoc in Rust source
Files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs
🧠 Learnings (5)
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/specs/nursery/** : Place snapshot tests for new rules under tests/specs/nursery/<ruleName>/ with files prefixed by invalid* and valid*
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : Place all new JavaScript lint rules in the nursery group under biome_js_analyze/lib/src/lint/nursery/<rule_name>.rs
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
PR: biomejs/biome#7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
📚 Learning: 2025-10-02T12:58:46.845Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_for
8000
matter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:58:46.845Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/specs/**/options.json : Place an options.json file alongside test cases to apply non-default Biome options to all tests in that folder
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
📚 Learning: 2025-10-02T12:57:33.228Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-02T12:57:33.228Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/specs/**/*.jsonc : Use .jsonc files to hold arrays of code snippets for snapshot tests; these snippets run in script mode (no import/export)
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/customClasses.jsx
🧬 Code graph analysis (2)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (3)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (1)
with_custom_classes
(83-141)crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort.rs (1)
sort_class_name
(131-210)crates/biome_analyze/src/options.rs (2)
value
(20-28)value
(27-27)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs (1)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs (2)
build_variant_weight
(26-32)layer_index
(74-80)
🔇 Additional comments (9)
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.options.json (1)
3-17
: LGTMConfig shape and patterns look good; mirrors valid options file.
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json (1)
3-17
: LGTMMatches the invalid suite’s config; good for parity across test sets.
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (2)
47-47
: Docs: good clarification on custom classesNice update clarifying variants vs custom classes support.
87-109
: Docs forclasses
option read wellExamples and matching semantics (prefix vs exact) are clear. Good addition.
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/class_info.rs (5)
105-105
: Pragmatic choice for custom layers.Changing to
String
enables owned layer names but allocates on every lookup. For a linter, this overhead is acceptable. If profiling later shows this as a hotspot, considerCow<'static, str>
to avoid allocations for static layers.
110-149
: Elegant abstraction for dual layer representations.The trait design cleanly unifies static and owned layers, enabling generic processing whilst maintaining type safety and performance. Well done.
153-213
: Refactored logic correctly handles both layer types.The generic implementation cleanly supports static and owned utilities. The tie-breaking comment (lines 195-197) is particularly helpful—layer order as tie-breaker for equal-length partial matches is a sensible design choice.
597-612
: Verify panic strategy for invariant violations.The debug assertion at lines 597-601 catches mismatched owned/static fields during development, but
unwrap()
at line 607 will panic in release builds if the invariant is somehow violated. Is this the intended behaviour—treating such violations as unrecoverable programmer errors?If so, consider
.expect()
with a clear message. If not, propagate the error gracefully.
349-375
: Excellent test coverage for tie-breaking logic.The test clearly validates that when partial matches have equal length across layers, the first layer wins. Well-documented and appropriately scoped.
// Invalid (needs sorting - custom classes should go after their layer's classes) | ||
<div class="custom-utility px-2 container text-red-500" />; | ||
|
||
// Invalid (utilities need sorting with custom class) | ||
<div class="text-red-500 custom-utility px-2" />; | ||
|
||
// Custom component class | ||
<div class="card container block" />; | ||
|
||
// Multiple custom classes | ||
<div class="text-lg custom-spacing px-4 custom-layout" />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
❓ Verification inconclusive
Mark failing cases with expect_diagnostic
Add explicit markers so the harness asserts diagnostics for each invalid snippet.
Apply:
-// Invalid (needs sorting - custom classes should go after their layer's classes)
+// expect_diagnostic: needs sorting - custom classes should go after their layer's classes
<div class="custom-utility px-2 container text-red-500" />;
-// Invalid (utilities need sorting with custom class)
+// expect_diagnostic: utilities need sorting with custom class
<div class="text-red-500 custom-utility px-2" />;
-// Custom component class
+// expect_diagnostic: custom component class ordering
<div class="card container block" />;
-// Multiple custom classes
+// expect_diagnostic: multiple custom classes ordering
<div class="text-lg custom-spacing px-4 custom-layout" />;
Quick check:
🏁 Script executed:
#!/bin/bash
# Verify markers exist across useSortedClasses specs
rg -n 'expect_diagnostic' crates/biome_js_analyze/tests/specs/nursery/useSortedClasses -g '!**/*.snap' -S -C1
Length of output: 0
Add expect_diagnostic markers to invalid test cases in invalidCustomClasses.jsx
Add explicit // expect_diagnostic
comments so the test harness flags each invalid snippet.
-// Invalid (needs sorting - custom classes should go after their layer's classes)
+// expect_diagnostic: needs sorting - custom classes should go after their layer's classes
<div class="custom-utility px-2 container text-red-500" />;
-// Invalid (utilities need sorting with custom class)
+// expect_diagnostic: utilities need sorting with custom class
<div class="text-red-500 custom-utility px-2" />;
-// Custom component class
+// expect_diagnostic: custom component class ordering
<div class="card container block" />;
-// Multiple custom classes
+// expect_diagnostic: multiple custom classes ordering
<div class="text-lg custom-spacing px-4 custom-layout" />;
Location: crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx:1-11
📝 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.
// Invalid (needs sorting - custom classes should go after their layer's classes) | |
<div class="custom-utility px-2 container text-red-500" />; | |
// Invalid (utilities need sorting with custom class) | |
<div class="text-red-500 custom-utility px-2" />; | |
// Custom component class | |
<div class="card container block" />; | |
// Multiple custom classes | |
<div class="text-lg custom-spacing px-4 custom-layout" />; | |
// expect_diagnostic: needs sorting - custom classes should go after their layer's classes | |
<div class="custom-utility px-2 container text-red-500" />; | |
// expect_diagnostic: utilities need sorting with custom class | |
<div class="text-red-500 custom-utility px-2" />; | |
// expect_diagnostic: custom component class ordering | |
<div class="card container block" />; | |
// expect_diagnostic: multiple custom classes ordering | |
<div class="text-lg custom-spacing px-4 custom-layout" />; |
🤖 Prompt for AI Agents
In
crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/invalidCustomClasses.jsx
around lines 1 to 11, the invalid test cases are missing explicit
expect_diagnostic comments; add a // expect_diagnostic comment to each invalid
snippet (each <div ... /> line) so the test harness recognizes and flags them as
failing cases, placing the comment on the same line or immediately after each
snippet.
Summary
This PR adds support for custom component and utility classes to the
useSortedClasses
rule.Motivation
Currently, the
useSortedClasses
rule only recognizes and sorts Tailwind CSS's default classes. However, users often:Without this feature, these custom classes remain unsorted and may appear in inconsistent positions, defeating the purpose of the rule.
Design Decisions
Long-term Vision vs. Pragmatic Solution
Ideally, Biome would parse and read the Tailwind configuration file (or CSS files) to automatically detect custom utilities. However, there are significant concerns with this approach:
Tight coupling: This would make Biome heavily coupled to Tailwind CSS specifics, which goes against Biome's goal of being a general-purpose utility class sorter that works with any framework (UnoCSS, custom utilities, etc.)
Implementation timeline: Implementing a robust configuration parser would take considerable time and effort before users can benefit from custom class support.
Given these constraints, this PR proposes a pragmatic interim solution that can be used immediately while we design and implement a better long-term approach.
Alternative Designs Considered
I've considered a more extensible approach that could support framework-specific presets:
However, this design introduces new concepts like tailwind.utilities as reserved identifiers, which felt premature at this stage. The current simpler approach of explicit components and utilities arrays is: