Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/compare-tailwind.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Compare Tailwind

on: [push, pull_request, workflow_dispatch]

permissions:
contents: read

jobs:
compare-tailwind:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
profile: minimal

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: tests/tailwind-compare/package-lock.json

- name: Install just
uses: extractions/setup-just@v2

- name: Compare RustyWind with Prettier Tailwind plugin
run: just compare-tailwind

- name: Upload comparison report
if: always()
uses: actions/upload-artifact@v4
with:
name: tailwind-compare-results
path: target/tailwind-compare/results/
if-no-files-found: ignore
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Added

- Add `--no-named-colors` to keep ambiguous project-defined names such as
`text-display` unknown instead of inferring them as colors; the option applies
to pattern sorting and preserves named-color inference as the default
- Add `--json` to the check, dry-run, bare, write, and stdin modes to emit one
machine-readable JSON document with changed files, changed class-group counts,
proposed or written content, and structured `{path, message}` errors for
Expand All @@ -13,9 +16,17 @@

### Changed

- Sort arbitrary named design-system colors such as `bg-muted`,
`text-foreground`, `border-input`, and `bg-card/90` as color utilities instead
of unknown classes; `border-{t,r,b,l,x,y,s,e}-<name>` now resolves to side
border color instead of border width, while shadow-family named values still
sort as unknown classes because they are ambiguous with custom `--shadow-*`
sizes
- Verify sorting parity against Tailwind CSS 4.3.3 and
prettier-plugin-tailwindcss 0.8.1 with zero ordering divergences across all
pinned comparison corpora and fuzz rounds; the comparison and fuzz harnesses
pinned comparison corpora and fuzz rounds; the comparison harness stylesheet
now defines a shadcn-style semantic palette so named design-system colors are
graded against Prettier; the comparison and fuzz harnesses
now pin that toolchain, and the fuzz harness resolves Tailwind v4 ordering
through a `tailwindStylesheet` entry point instead of the plugin's bundled
fallback
Expand All @@ -24,6 +35,12 @@

- Replace the panic on unreadable stdin with a normal error message

### Breaking changes

- `RustyWind` now includes an `infer_named_colors` option. Code constructing
`RustyWind` with a struct literal must set `infer_named_colors: true` to keep
the default behavior, or use `RustyWind::new` or `RustyWind::default`.

## [0.26.0] - 2026-07-21

### Added
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ Use with tailwind prettier plugin

- `rustywind --vite-css <url to the css generated by vite>`

By default, RustyWind treats project-defined names under color-capable prefixes as colors, so
classes such as `bg-muted` and `text-foreground` sort with Tailwind utilities. If a project uses an
ambiguous name for another purpose, such as `text-display` for a custom font size, keep those names
unknown with the pattern sorter:

- `rustywind --no-named-colors .`

Built-in palette colors, arbitrary colors, and CSS-variable colors remain recognized. For exact
project-specific ordering, use `--output-css-file` or `--vite-css` instead.

```shell
Usage: rustywind [OPTIONS] [PATH]...

Expand Down Expand Up @@ -151,6 +161,9 @@ Options:
--allow-duplicates
When set, RustyWind will not delete duplicated classes

--no-named-colors
Do not infer project-defined named values as colors

--config-file <CONFIG_FILE>
When set, RustyWind will use the config file to derive configurations. The config file current only supports json with one property sortOrder, e.g. { "sortOrder": ["class1", ...] }

Expand Down
27 changes: 27 additions & 0 deletions rustywind-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ pub struct Cli {
/// When set, RustyWind will not delete duplicated classes.
#[arg(long)]
allow_duplicates: bool,
/// Do not infer project-defined named values as colors
#[arg(
long,
conflicts_with_all = &["config_file", "output_css_file", "vite_css"]
)]
no_named_colors: bool,
/// When set, RustyWind will use the config file to derive configurations. The config file
/// current only supports json with one property sortOrder, e.g.
/// { "sortOrder": ["class1", ...] }.
Expand Down Expand Up @@ -429,4 +435,25 @@ mod tests {

assert_eq!(cli.tailwind_prefix.as_deref(), Some("tw"));
}

#[test]
fn parses_no_named_colors() {
let cli = Cli::try_parse_from(["rustywind", "--no-named-colors", "index.html"])
.expect("named-color opt-out should parse");

assert!(cli.no_named_colors);
}

#[test]
fn no_named_colors_rejects_custom_sorters() {
for sorter_args in [
&["--config-file", "sorter.json"][..],
&["--output-css-file", "tailwind.css"][..],
&["--vite-css", "http://localhost/main.css"][..],
] {
let mut args = vec!["rustywind", "--no-named-colors", "index.html"];
args.extend_from_slice(sorter_args);
assert!(Cli::try_parse_from(args).is_err());
}
}
}
1 change: 1 addition & 0 deletions rustywind-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ impl Options {
allow_duplicates: cli.allow_duplicates,
class_wrapping: get_class_wrapping_from_cli(&cli),
tailwind_prefix: cli.tailwind_prefix.clone(),
infer_named_colors: !cli.no_named_colors,
};

Ok(Options {
Expand Down
48 changes: 48 additions & 0 deletions rustywind-cli/tests/named_colors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::io::Write as _;
use std::process::{Command, Stdio};

fn sort_stdin(args: &[&str], input: &str) -> std::process::Output {
let mut child = Command::new(assert_cmd::cargo::cargo_bin!("rustywind"))
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("rustywind should start");
child
.stdin
.as_mut()
.expect("stdin should be piped")
.write_all(input.as_bytes())
.expect("stdin should be written");
child.wait_with_output().expect("rustywind should finish")
}

#[test]
fn no_named_colors_keeps_ambiguous_names_unknown() {
if std::env::var_os("CROSS_RUNNER").is_some() {
return;
}

let input = r#"<div class="text-display flex"></div>"#;
let default = sort_stdin(&["--stdin", "--stdin-filename", "input.html"], input);
let disabled = sort_stdin(
&[
"--stdin",
"--stdin-filename",
"input.html",
"--no-named-colors",
],
input,
);

assert!(default.status.success());
assert!(default.stderr.is_empty());
assert_eq!(
String::from_utf8(default.stdout).unwrap(),
r#"<div class="flex text-display"></div>"#
);
assert!(disabled.status.success());
assert!(disabled.stderr.is_empty());
assert_eq!(String::from_utf8(disabled.stdout).unwrap(), input);
}
14 changes: 14 additions & 0 deletions rustywind-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@

### Added

- Add configurable named-color inference through `RustyWind`, `PatternSorter`,
`HybridSorter`, and `UtilityMap`; inference remains enabled by default
- Add `SortReport` and `RustyWind::sort_document_with_report`, which return the
sorted contents together with the number of class groups (attribute values or
custom-regex captures) whose text changed; `sort_document` is now a thin
wrapper over the report

### Breaking changes

- `RustyWind` now includes an `infer_named_colors` option. Code constructing
`RustyWind` with a struct literal must set it explicitly or use an existing
constructor.

## [0.5.0] - 2026-07-21

### Added
Expand All @@ -24,6 +32,12 @@

### Changed

- Classify arbitrary named design-system values in color-capable utility-map
families, including opacity-stripped values such as `bg-card/90`, so they
receive color properties instead of remaining unknown;
`border-{t,r,b,l,x,y,s,e}-<name>` now maps to side border color instead of
border width, while shadow-family named values remain unclassified because
they are ambiguous with custom `--shadow-*` sizes
- Parse known markup languages with Winnow and extract typed class-attribute spans directly instead
of scanning every regular-expression match against every tag
- Parse template expressions, arbitrary-value brackets, comments, strings, and regular expressions
Expand Down
Loading
Loading