Skip to content

Add Tailwind prefix sorting#136

Merged
praveenperera merged 2 commits into
masterfrom
tw-prefix-support
Jun 10, 2026
Merged

Add Tailwind prefix sorting#136
praveenperera merged 2 commits into
masterfrom
tw-prefix-support

Conversation

@praveenperera

@praveenperera praveenperera commented Jun 3, 2026

Copy link
Copy Markdown
Member

Summary

Add explicit Tailwind prefix support for sorting class lists.

  • add a --tailwind-prefix CLI option
  • normalize Tailwind v3 and v4 prefixed classes before sort-key generation
  • preserve original prefixed class strings in output
  • reuse prefix-aware sorter caches and support custom-sorter fallback paths
  • add regression coverage for v3, v4, negative, variant, and custom sorter cases

Closes: #135

Summary by CodeRabbit

  • New Features

    • Added --tailwind-prefix CLI option for specifying Tailwind prefix variants.
  • Breaking Changes

    • RustyWind now requires explicit tailwind_prefix field in struct literals.
    • PatternSorter is no longer a unit struct; use new() or default() constructors.
    • Prefix-aware sorting now stores normalized class instead of original input.

@praveenperera praveenperera changed the title [codex] Add Tailwind prefix sorting Add Tailwind prefix sorting Jun 3, 2026
@praveenperera praveenperera marked this pull request as ready for review June 10, 2026 14:42
@praveenperera

Copy link
Copy Markdown
Member Author

@coderabbitai review pr

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details
gh pr view 136 --repo avencera/rustywind

echo "---FILES CHANGED---"
gh pr diff 136 --repo avencera/rustywind

Length of output: 25976



@praveenperera Here is a detailed review of PR #136.


PR Review: Add Tailwind prefix sorting

The implementation is well-structured overall — good use of Cow<'_, str> to avoid unnecessary allocations, solid unit tests covering v3/v4/negative/arbitrary-variant cases, and the HybridSorter cache-key fix is a genuine bug fix bundled in. A few items worth addressing:


🔴 Breaking change: SortKey.class now stores the normalized class

rustywind-core/src/pattern_sorter.rs

// Before
let class_compact = compact_str::CompactString::new(class);        // original
// After
let class_compact = compact_str::CompactString::new(sort_class.as_ref()); // normalized

The class field in SortKey is public and its doc comment previously said "Original class string (for alphabetical tiebreaker)". It now holds the normalized (prefix-stripped) class instead. Any downstream code that reads sort_key.class expecting the original input will silently get a different value. The integration test in test_tailwind_prefix.rs even asserts the new behaviour explicitly:

assert_eq!(key.class.as_str(), "hover:-mr-4");  // was "hover:-tw-mr-4"

Consider calling this out as a semver-minor/major change in the CHANGELOG/commit message, and maybe rename the field to normalized_class to make the contract explicit.


🟡 TOCTOU gap in prefixed_pattern_sorter

rustywind-core/src/app.rs

fn prefixed_pattern_sorter(tailwind_prefix: &str) -> Arc<HybridSorter> {
    if let Some(sorter) = PREFIXED_PATTERN_SORTERS.read()...get(tailwind_prefix) {
        return Arc::clone(sorter);
    }
    // ← another thread can insert here
    let mut sorters = PREFIXED_PATTERN_SORTERS.write()...;
    Arc::clone(sorters.entry(...).or_insert_with(|| Arc::new(...)))
}

Two threads that both miss the read guard will each acquire the write lock and call or_insert_with. The entry API prevents a double-insert so it's not a correctness bug, but the second thread still constructs a HybridSorter that immediately gets discarded. With the typical number of prefixes (usually just one) this is negligible in practice, but the pattern is a bit surprising. An idiomatic alternative:

fn prefixed_pattern_sorter(tailwind_prefix: &str) -> Arc<HybridSorter> {
    // Fast-path: already exists
    {
        let sorters = PREFIXED_PATTERN_SORTERS.read()
            .expect("cache should not be poisoned");
        if let Some(s) = sorters.get(tailwind_prefix) {
            return Arc::clone(s);
        }
    }
    // Slow-path: insert under write lock, re-check to avoid duplicate construction
    let mut sorters = PREFIXED_PATTERN_SORTERS.write()
        .expect("cache should not be poisoned");
    Arc::clone(sorters
        .entry(tailwind_prefix.to_string())
        .or_insert_with(|| Arc::new(HybridSorter::new_with_tailwind_prefix(Some(tailwind_prefix)))))
}

The current code already does this — the only missing piece is the re-check after acquiring the write lock, which entry().or_insert_with() provides. So this is fine as-is; just flagging it for awareness.


🟡 v4_original_remainder: the first element of .zip() is always discarded

rustywind-core/src/app.rssort_variant_classes

let v4_original_remainder = self.tailwind_prefix
    .as_deref()
    .and_then(normalize_tailwind_prefix_value)
    .and_then(|prefix| {
        candidate.original
            .strip_prefix(prefix)
            .and_then(|rest| rest.strip_prefix(':'))
            .zip(normalized_remainder)                     // ← first elem discarded
            .map(|(_, normalized_remainder)| format!("{prefix}:{normalized_remainder}"))
    });

The .zip(normalized_remainder) here uses the left side purely as a guard ("is this a v4-prefixed class?") and then discards it. This is valid Rust but reads confusingly. Suggest:

.and_then(|prefix| {
    let is_v4 = candidate.original
        .strip_prefix(prefix)
        .and_then(|r| r.strip_prefix(':'))
        .is_some();
    if is_v4 {
        normalized_remainder.map(|nr| format!("{prefix}:{nr}"))
    } else {
        None
    }
});

🟡 Missing test: mixed prefixed + unprefixed classes

There is no test covering a class string that contains both prefixed and unprefixed classes, e.g.:

// "tw-flex text-xl tw-p-4" — what does sort produce?

This edge case can arise when users incrementally migrate to a prefix or mix utility sources, and the current lookup chain (sorter.get(original).or_else(|| sorter.get(normalized))) will fall through differently for each.


🟡 PatternSorter no longer a unit struct — check for Default impls

rustywind-core/src/pattern_sorter.rs

PatternSorter was previously pub struct PatternSorter; (a unit struct, implicitly Default). It is now:

pub struct PatternSorter {
    tailwind_prefix: Option<compact_str::CompactString>,
}

If any code outside this crate (or in tests) called PatternSorter::default(), it will now fail to compile unless a Default impl is added. Given that PatternSorter::new() is pub and tailwind_prefix is private, a #[derive(Default)] cannot be auto-generated. Suggest adding:

impl Default for PatternSorter {
    fn default() -> Self { Self::new() }
}

✅ Good catches bundled in this PR

  • hybrid_sorter.rs cache-key fix (sort_key.classclass_compact) is a real bug fix: previously, tw-text-xl was being cached under the key text-xl, causing cache misses on the next lookup of tw-text-xl.
  • Cow<'_, str> usage in normalize_tailwind_prefix avoids allocations for the common no-op case.
  • Bracket-depth tracking in utility_start correctly handles arbitrary variants like [&:hover]:tw-bg-red-500.
  • The or_insert_with in the write lock path is the right way to avoid a double-insert race.

Minor nit: CLI help text

/// Tailwind prefix to ignore while sorting classes, e.g. tw for tw: or tw- classes.

"Ignore" is a bit misleading — the prefix isn't ignored, it's stripped for sort-key lookup but preserved in the output. Something like:

/// Tailwind prefix used when sorting classes (prefix is stripped for lookup but kept in output).
/// Supports both v3 style (tw-flex) and v4 style (tw:flex). Example: --tailwind-prefix tw

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a8a02542-02da-4abd-8887-0d51616f053a

📥 Commits

Reviewing files that changed from the base of the PR and between 3c50e91 and 549e205.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • CHANGELOG.md
  • npm/packages/darwin-arm64/package.json
  • npm/packages/darwin-x64/package.json
  • npm/packages/linux-arm-gnueabihf/package.json
  • npm/packages/linux-arm64-gnu/package.json
  • npm/packages/linux-arm64-musl/package.json
  • npm/packages/linux-x64-musl/package.json
  • npm/packages/rustywind/package.json
  • npm/packages/win32-ia32-msvc/package.json
  • npm/packages/win32-x64-msvc/package.json
  • rustywind-cli/Cargo.toml
  • rustywind-cli/src/main.rs
  • rustywind-cli/src/options.rs
  • rustywind-core/CHANGELOG.md
  • rustywind-core/Cargo.toml
  • rustywind-core/src/app.rs
  • rustywind-core/src/hybrid_sorter.rs
  • rustywind-core/src/lib.rs
  • rustywind-core/src/pattern_sorter.rs
  • rustywind-core/src/tailwind_prefix.rs
  • rustywind-core/tests/test_tailwind_prefix.rs
  • rustywind-vite/Cargo.toml

📝 Walkthrough

Walkthrough

This PR adds support for Tailwind v3 and v4 prefixed class names (e.g., tw-text-lg, tw:text-lg) throughout the rustywind sorting pipeline. The implementation includes a new prefix normalization module, updates to core sorters, CLI integration, and comprehensive tests, with all versions bumped to RC.

Changes

Tailwind prefix support feature

Layer / File(s) Summary
Prefix normalization foundation
rustywind-core/src/lib.rs, rustywind-core/src/tailwind_prefix.rs
New tailwind_prefix module with normalize_tailwind_prefix and helpers that strip configured prefixes from class strings while preserving ! modifiers, supporting both Tailwind v3 (embedded in utility) and v4 (variant-like) placements, and correctly handling colons inside arbitrary variant brackets.
PatternSorter with prefix support
rustywind-core/src/pattern_sorter.rs
PatternSorter is no longer a unit struct; it now stores an optional tailwind_prefix and normalizes input classes before computing sort keys, enabling consistent sorting of prefixed utilities.
HybridSorter prefix constructors
rustywind-core/src/hybrid_sorter.rs
Added new_with_tailwind_prefix and with_cache_size_and_tailwind_prefix constructors that wire prefix into PatternSorter, and adjusted LRU cache keying to use precomputed compact class strings.
RustyWind core integration
rustywind-core/src/app.rs
RustyWind struct extended with pub tailwind_prefix: Option<String> field; constructors refactored to accept and store prefix. Sorting logic conditionally routes to prefixed pattern sorters when configured. New SortCandidate representation carries both original and normalized class for prefix-aware variant lookup and remainder computation.
CLI option and wiring
rustywind-cli/src/main.rs, rustywind-cli/src/options.rs
Added --tailwind-prefix CLI argument wired through Options::new_from_cli to set RustyWind.tailwind_prefix during initialization.
Prefix integration tests
rustywind-core/tests/test_tailwind_prefix.rs
Comprehensive test suite validating v3/v4 prefix normalization, mixed prefixed/unprefixed sorting consistency, negative class detection in HybridSorter, RustyWind output preservation of original prefixes, and custom sorter fallback behavior.
Version bumps and documentation
CHANGELOG.md, rustywind-core/CHANGELOG.md, Cargo.toml, npm/packages/*/package.json
All crates and npm packages updated from alpha to RC release (0.25.0-alpha.10.25.0-rc.1 CLI; 0.4.0-alpha.10.4.0-rc.1 core). CHANGELOGs document new prefix-aware sorting and breaking API changes.

Sequence Diagram

sequenceDiagram
  participant Client as User/CLI
  participant RW as RustyWind
  participant PS as PatternSorter
  participant HS as HybridSorter
  participant Norm as normalize_<br/>tailwind_prefix
  
  Client->>RW: sort_classes_vec(classes,<br/>prefix="tw")
  RW->>RW: Check Sorter type
  alt PatternSorter with prefix
    RW->>RW: Get prefixed_pattern_sorter("tw")
    RW->>HS: new_with_tailwind_prefix(Some("tw"))
  else HashMap-based Sorter
    RW->>RW: Build SortCandidate vec<br/>(original, normalized)
  end
  RW->>PS: get_sort_key("tw:text-lg")
  PS->>Norm: normalize_tailwind_prefix("tw:text-lg", "tw")
  Norm-->>PS: "text-lg"
  PS->>PS: parse & extract properties<br/>from "text-lg"
  PS-->>RW: SortKey(class="text-lg",<br/>is_negative=false, ...)
  RW->>RW: sort candidates by key
  RW-->>Client: sorted classes<br/>(original:"tw:text-lg")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


🐰 A prefix-aware sorter hops with grace,
Normalizing v3, v4 at full pace,
Tailwind utilities now sort just right,
Whether tw- or tw:, shining bright!
Release candidate ready to fly,
Rustywind's prefix dreams reach the sky. 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Tailwind prefix sorting' accurately summarizes the main change: adding support for Tailwind prefix handling in the sorting functionality.
Linked Issues check ✅ Passed All requirements from issue #135 are met: v3 and v4 prefix syntaxes are supported, normalization is implemented, a --tailwind-prefix CLI option is added, original classes are preserved in output, and comprehensive tests cover both syntaxes and edge cases.
Out of Scope Changes check ✅ Passed All changes are directly related to adding Tailwind prefix support; version bumps and documentation updates are appropriate for a release candidate.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tw-prefix-support

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

@praveenperera praveenperera merged commit 7596137 into master Jun 10, 2026
31 checks passed
@praveenperera praveenperera deleted the tw-prefix-support branch June 10, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support custom tailwind prefix

1 participant