feat(lint): add useTailwindShorthandClasses#10312
Conversation
e285176 to
ce04eca
Compare
|
eaa06b4 to
179c5ed
Compare
Merging this PR will degrade performance by 12.4%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | html_analyzer[real/wikipedia-JavaScript.html] |
188 ms | 214.6 ms | -12.4% |
| ❌ | html_analyzer[real/wikipedia-fr-Guerre_de_Canudos.html] |
448.6 ms | 502.7 ms | -10.76% |
| ❌ | html_analyzer[real/wikipedia-Unix.html] |
164.9 ms | 187.4 ms | -11.97% |
Comparing dyc3/useTailwindShorthandClasses-v2 (3e933fe) with main (e4f8d83)
519f0f7 to
1ce2565
Compare
WalkthroughAdds a cross-language useTailwindShorthandClasses lint rule (HTML and JSX) that parses Tailwind class strings, detects compressible sequences, and provides unsafe auto-fixes. Introduces a new biome_tailwind_logic crate implementing analysis and fixes (TW_COMPRESSABLES, analyze_tailwind_shorthand, auto_fix*), shared ClassStringLikeOptions-based extraction utilities, HTML/JS mutation helpers preserving quote style, a tailwind_syntax utility (is_node_equal), workspace manifest updates, and many test fixtures across Astro/JSX/HTML/Svelte/Vue. Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
36b844d to
f068bfd
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs (2)
36-48: 💤 Low value
has_functionandmatch_functionare identical implementations.Both methods perform the exact same logic. If this duplication is intentional for the trait interface, consider delegating one to the other to reduce maintenance burden.
pub fn match_function(&self, name: &str) -> bool { - self.functions.as_deref().map_or_else( - || DEFAULT_FUNCTIONS.contains(&name), - |functions| functions.iter().any(|matcher| matcher.as_ref() == name), - ) + self.has_function(name) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs` around lines 36 - 48, has_function and match_function contain identical logic; to avoid duplication, make one delegate to the other (e.g., implement match_function by returning self.has_function(name) or implement has_function by returning self.match_function(name)) while preserving the existing behavior and signatures; update the body of the delegating method (match_function or has_function) to call the other and return its bool result so only one implementation holds the iterator/map_or_else logic (functions, DEFAULT_FUNCTIONS, has_function, match_function).
96-105: 💤 Low valueInconsistent deserialisation pattern between
attributesandfunctions.
functionsis deserialised directly intoresult.functions, whilstattributesaccumulates into a separateVecbefore assignment. Unless there's a specific reason for this difference (e.g., multipleattributeskeys being merged), consider using the same direct assignment pattern for consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs` around lines 96 - 105, The deserialization is inconsistent: "attributes" accumulates into a local attributes Vec before extending, while "functions" is assigned directly to result.functions; change "attributes" to the same direct assignment pattern as "functions" by calling Deserializable::deserialize(ctx, &value, &key_text) and assigning the result to result.attributes (or the appropriate field) instead of extending a separate Vec, ensuring both use the same return type and null/None handling as used for result.functions; update any intermediate variable names (attributes) to avoid dead code and keep the Deserializable::deserialize call signature (ctx, &value, &key_text) consistent.crates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs (1)
100-107: 💤 Low valueMinor:
rootis cloned for each violation.If there are multiple shorthand violations in a single class list,
root.clone()is called for each. This is likely fine given typical class counts, but worth noting if performance becomes a concern with very large class lists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs` around lines 100 - 107, The code clones `root` for each violation when building TailwindShorthandState; to avoid repeated clones, clone `root` once and reuse it in the mapping. Locate the call to analyze_tailwind_shorthand(&root.candidates()) and the subsequent .map(|violation| TailwindShorthandState { root: root.clone(), violation, }) and change it to capture a single cloned_root (e.g., let cloned_root = root.clone()) outside the iterator and use cloned_root (or a reference/moved value) inside the map so each TailwindShorthandState reuses the same cloned root instead of cloning per-violation.crates/biome_html_analyze/src/tailwind.rs (1)
6-14: 💤 Low valueConsider whether this trait indirection is needed.
The
TailwindClassStringOptionstrait directly delegates to the same method onUseTailwindShorthandClassesOptions. If this abstraction is solely for future extensibility or testing, it's fine to keep. Otherwise, you could simplify by usingUseTailwindShorthandClassesOptionsdirectly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_html_analyze/src/tailwind.rs` around lines 6 - 14, The TailwindClassStringOptions trait simply forwards has_attribute to UseTailwindShorthandClassesOptions and can be removed to simplify the code; delete the pub(crate) trait TailwindClassStringOptions and its impl for UseTailwindShorthandClassesOptions, then update any function signatures, trait bounds or generic constraints that reference TailwindClassStringOptions to accept &UseTailwindShorthandClassesOptions (or UseTailwindShorthandClassesOptions where appropriate) and call UseTailwindShorthandClassesOptions::has_attribute directly (adjusting callers of methods that took the trait to pass the concrete type).crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx (1)
1-3: ⚡ Quick winAdd one non-configured function control in this fixture
Nice coverage for
cnandtw. Please add one call likecx("w-4 h-4")and assert no diagnostic, so thefunctionsoption boundary is explicitly locked down.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx` around lines 1 - 3, Add a call to a non-configured function named cx to the fixture so the functions boundary is explicit: insert cx("w-4 h-4") alongside the existing cn("px-2 py-2") and tw.div`...` lines (referencing the cn, tw, and cx symbols) and update the test expectation to assert that no diagnostic is emitted for that cx invocation. Ensure the new call is present in with-functions.jsx and that the test asserts no diagnostic for it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rs`:
- Line 251: The Vec allocation uses replacement_bases.len() but the code loops
over required_bases and pushes for each required base; change the allocation for
flagged_candidates from Vec::with_capacity(replacement_bases.len()) to
Vec::with_capacity(required_bases.len()) to avoid under-allocation and
unnecessary reallocations (referencing flagged_candidates, replacement_bases and
required_bases in the surrounding code).
In `@crates/biome_tailwind_syntax/src/util.rs`:
- Around line 5-7: Add a runnable doctest to the doc comment for is_node_equal
that constructs small TailwindSyntaxNode instances (using the crate's public
constructors or parsing helpers) and asserts expected results: one example where
two nodes with identical descendant kinds/tokens return true and one where a
differing token/kind returns false; make sure the doctest code block uses
standard Rust doc-test syntax (/// ```rust ... ```), imports or qualifies
TailwindSyntaxNode and calls is_node_equal so the test runs during cargo test.
---
Nitpick comments:
In
`@crates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs`:
- Around line 100-107: The code clones `root` for each violation when building
TailwindShorthandState; to avoid repeated clones, clone `root` once and reuse it
in the mapping. Locate the call to
analyze_tailwind_shorthand(&root.candidates()) and the subsequent
.map(|violation| TailwindShorthandState { root: root.clone(), violation, }) and
change it to capture a single cloned_root (e.g., let cloned_root = root.clone())
outside the iterator and use cloned_root (or a reference/moved value) inside the
map so each TailwindShorthandState reuses the same cloned root instead of
cloning per-violation.
In `@crates/biome_html_analyze/src/tailwind.rs`:
- Around line 6-14: The TailwindClassStringOptions trait simply forwards
has_attribute to UseTailwindShorthandClassesOptions and can be removed to
simplify the code; delete the pub(crate) trait TailwindClassStringOptions and
its impl for UseTailwindShorthandClassesOptions, then update any function
signatures, trait bounds or generic constraints that reference
TailwindClassStringOptions to accept &UseTailwindShorthandClassesOptions (or
UseTailwindShorthandClassesOptions where appropriate) and call
UseTailwindShorthandClassesOptions::has_attribute directly (adjusting callers of
methods that took the trait to pass the concrete type).
In
`@crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx`:
- Around line 1-3: Add a call to a non-configured function named cx to the
fixture so the functions boundary is explicit: insert cx("w-4 h-4") alongside
the existing cn("px-2 py-2") and tw.div`...` lines (referencing the cn, tw, and
cx symbols) and update the test expectation to assert that no diagnostic is
emitted for that cx invocation. Ensure the new call is present in
with-functions.jsx and that the test asserts no diagnostic for it.
In `@crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs`:
- Around line 36-48: has_function and match_function contain identical logic; to
avoid duplication, make one delegate to the other (e.g., implement
match_function by returning self.has_function(name) or implement has_function by
returning self.match_function(name)) while preserving the existing behavior and
signatures; update the body of the delegating method (match_function or
has_function) to call the other and return its bool result so only one
implementation holds the iterator/map_or_else logic (functions,
DEFAULT_FUNCTIONS, has_function, match_function).
- Around line 96-105: The deserialization is inconsistent: "attributes"
accumulates into a local attributes Vec before extending, while "functions" is
assigned directly to result.functions; change "attributes" to the same direct
assignment pattern as "functions" by calling Deserializable::deserialize(ctx,
&value, &key_text) and assigning the result to result.attributes (or the
appropriate field) instead of extending a separate Vec, ensuring both use the
same return type and null/None handling as used for result.functions; update any
intermediate variable names (attributes) to avoid dead code and keep the
Deserializable::deserialize call signature (ctx, &value, &key_text) consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 81bfc3dc-f095-47c3-a6ad-aa59582d4bc1
⛔ Files ignored due to path filters (29)
Cargo.lockis excluded by!**/*.lockand included by**crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_configuration/src/generated/domain_selector.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_configuration/src/generated/linter_options_check.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx.snapis excluded by!**/*.snapand included by**crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__invalid_cases.snapis excluded by!**/*.snapand included by**crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__valid_cases.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (38)
Cargo.tomlcrates/biome_html_analyze/Cargo.tomlcrates/biome_html_analyze/src/lib.rscrates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rscrates/biome_html_analyze/src/tailwind.rscrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.options.jsoncrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astrocrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.sveltecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vuecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astrocrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.sveltecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vuecrates/biome_js_analyze/Cargo.tomlcrates/biome_js_analyze/src/lib.rscrates/biome_js_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rscrates/biome_js_analyze/src/shared/any_class_string_like.rscrates/biome_js_analyze/src/tailwind.rscrates/biome_js_analyze/tests/spec_tests.rscrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astrocrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.sveltecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vuecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astrocrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.sveltecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vuecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.options.jsoncrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/use_tailwind_shorthand_classes.rscrates/biome_tailwind_logic/Cargo.tomlcrates/biome_tailwind_logic/src/lib.rscrates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rscrates/biome_tailwind_syntax/src/lib.rscrates/biome_tailwind_syntax/src/util.rs
f52fec2 to
3e933fe
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html (1)
1-1: ⚡ Quick winAdd a matching valid fixture for the
data-classesoption path.Line 1 covers the failing branch for custom attributes; please add a sibling valid case (for example
data-classes="size-4") so this option is exercised both ways.As per coding guidelines "
crates/*/tests/**/*: ... lint rules ... with valid/invalid cases".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html` at line 1, Add a sibling valid test fixture that exercises the custom attribute path for data-classes so the option is tested in the passing case; create a file alongside the failing spec that contains a simple element using the custom attribute with a valid shorthand value (e.g., <div data-classes="size-4"></div>) so the test suite has both valid and invalid cases for the data-classes option.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html`:
- Line 1: Add a sibling valid test fixture that exercises the custom attribute
path for data-classes so the option is tested in the passing case; create a file
alongside the failing spec that contains a simple element using the custom
attribute with a valid shorthand value (e.g., <div data-classes="size-4"></div>)
so the test suite has both valid and invalid cases for the data-classes option.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d4bbda4a-347d-4b5a-a35f-4a95b7cb2119
⛔ Files ignored due to path filters (31)
Cargo.lockis excluded by!**/*.lockand included by**crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_configuration/src/generated/domain_selector.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_configuration/src/generated/linter_options_check.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx.snapis excluded by!**/*.snapand included by**crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__invalid_cases.snapis excluded by!**/*.snapand included by**crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__valid_cases.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (40)
Cargo.tomlcrates/biome_html_analyze/Cargo.tomlcrates/biome_html_analyze/src/lib.rscrates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rscrates/biome_html_analyze/src/tailwind.rscrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.options.jsoncrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astrocrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.sveltecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vuecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astrocrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.htmlcrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.sveltecrates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vuecrates/biome_js_analyze/Cargo.tomlcrates/biome_js_analyze/src/lib.rscrates/biome_js_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rscrates/biome_js_analyze/src/shared/any_class_string_like.rscrates/biome_js_analyze/src/tailwind.rscrates/biome_js_analyze/tests/spec_tests.rscrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astrocrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.sveltecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vuecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astrocrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.sveltecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vuecrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsxcrates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.options.jsoncrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/use_tailwind_shorthand_classes.rscrates/biome_tailwind_logic/Cargo.tomlcrates/biome_tailwind_logic/src/lib.rscrates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rscrates/biome_tailwind_syntax/src/lib.rscrates/biome_tailwind_syntax/src/util.rs
✅ Files skipped from review due to trivial changes (19)
- crates/biome_tailwind_logic/src/lib.rs
- crates/biome_js_analyze/src/lib.rs
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.jsx
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.options.json
- crates/biome_tailwind_syntax/src/util.rs
- crates/biome_html_analyze/src/lib.rs
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
- crates/biome_js_analyze/tests/spec_tests.rs
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.options.json
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
- crates/biome_tailwind_logic/Cargo.toml
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
- crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
🚧 Files skipped from review as they are similar to previous changes (15)
- Cargo.toml
- crates/biome_rule_options/src/lib.rs
- crates/biome_tailwind_syntax/src/lib.rs
- crates/biome_js_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs
- crates/biome_js_analyze/Cargo.toml
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
- crates/biome_html_analyze/Cargo.toml
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx
- crates/biome_js_analyze/src/tailwind.rs
- crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs
- crates/biome_js_analyze/src/shared/any_class_string_like.rs
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
- crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx
- crates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rs
- crates/biome_html_analyze/src/tailwind.rs
Summary
This implements
useTailwindShorthandClasseswhich is a port of https://github.com/schoero/eslint-plugin-better-tailwindcss/blob/main/docs/rules/enforce-shorthand-classes.mdThis uses the same business logic as #8503. I heavily guided gpt 5.4/5.5 for doing all the re-plumbing. It's significantly smarter than the upstream rule, thanks to our parser.
The hardest part about this PR is going to be making sure the plumbing is right, and making sure all the testing is in place to make it work for HTML-ish languages.
There are some limitations with how this is currently implemented that I chose not to address in this PR because this PR is already rather large. I've documented them in the rule docs. I will open a tracking issue and set the
issue_numberfor those problems when this is approved.Regarding performance regressions: I improved it as much as I think is reasonable right now. But, the rule does need to query every single HtmlAttribute to function, and our benchmark fixtures have a lot of those. The grit regression makes no sense to me.
supercedes and closes #8503 and the other PRs in that stack
Test Plan
snapshots
Docs