Deprecated: Function get_magic_quotes_gpc() is deprecated in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 99

Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 619

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1169

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176

Warning: Cannot modify header information - headers already sent by (output started at /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php:99) in /hermes/walnacweb04/walnacweb04ab/b2791/pow.jasaeld/htdocs/De1337/nothing/index.php on line 1176
8000 feat(useSortedClasses): add support for custom classes by devxoul · Pull Request #7752 · biomejs/biome · GitHub
Nothing Special   »   [go: up one dir, main page]

Skip to content

Conversation

devxoul
Copy link
@devxoul devxoul commented Oct 14, 2025

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:

  • Use Tailwind CSS plugins that add custom utilities
  • Define their own utility classes in their projects
  • Have custom component classes (like button variants, card styles, etc.)

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:

  1. 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.)

  2. 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:

{
  "classes": [
    "tailwind.utilities",  // reserved for future preset
    "custom-",
    "foo-",
    "other-framework.options"  // reserved for other frameworks
  ]
}

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:

  • Easier to understand and use
  • Doesn't preclude future enhancements
  • Provides immediate value without introducing complex abstractions

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>
Copy link
changeset-bot bot commented Oct 14, 2025

⚠️ No Changeset found

Latest commit: 71f97df

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Oct 14, 2025
Copy link
Contributor
coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds 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

A-Diagnostic

Suggested reviewers

  • ematipico
  • dyc3

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely and accurately summarises the key change by indicating that the useSortedClasses lint rule now supports custom classes, clearly reflecting the main feature introduced in the changeset.
Description Check ✅ Passed The description clearly outlines the addition of custom component and utility class support, provides context and motivation, and matches the changes made in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

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: 2

🧹 Nitpick comments (6)
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (1)

203-217: Avoid rebuilding custom SortConfig per node

This 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 ergonomics

When 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 semantics

Deserialisation 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 iterator

Current 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-breaks

The “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

📥 Commits

Reviewing files that changed from the base of the PR and between aef57d5 and a3d5186.

⛔ 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 semantics

Config 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 names

The switch enables owned layer names cleanly across both paths. Looks good.


213-228: Static/owned wrappers read well

The thin wrappers keep call sites tidy. No issues spotted.


246-278: Tests updated for String layer names: LGTM

The 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

Comment on lines +1 to +14
// 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" />;
Copy link
Contributor

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.

devxoul and others added 5 commits October 14, 2025 18:54
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
@devxoul devxoul force-pushed the tailwind-custom-classes branch from ea70d7b to 71f97df Compare October 14, 2025 10:01
@devxoul
Copy link
Author
devxoul commented Oct 14, 2025

@coderabbitai Fixed everything you mentioned!

Copy link
Contributor
coderabbitai bot commented Oct 14, 2025

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!

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: 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 node

This 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 UtilityLayerOwned

Handy for testing and diagnostics.

-pub struct UtilityLayerOwned {
+#[derive(Debug, Clone)]
+pub struct UtilityLayerOwned {

40-49: Scope new fields narrower than pub

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 polish

Implementation 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 with sort_config.layer_index(utility_info.layer.as_str())?
  • Remove #[allow(dead_code)] from layer_index() in sort_config.rs now that it’s used
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d5186 and 71f97df.

⛔ 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: LGTM

Config shape and patterns look good; mirrors valid options file.

crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/validCustomClasses.options.json (1)

3-17: LGTM

Matches 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 classes

Nice update clarifying variants vs custom classes support.


87-109: Docs for classes option read well

Examples 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, consider Cow<'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.

Comment on lines +1 to +11
// 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" />;
Copy link
Contributor

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.

Suggested change
// 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.

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

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0