Skip to content

feat(lint): add useTailwindShorthandClasses#10312

Open
dyc3 wants to merge 2 commits into
mainfrom
dyc3/useTailwindShorthandClasses-v2
Open

feat(lint): add useTailwindShorthandClasses#10312
dyc3 wants to merge 2 commits into
mainfrom
dyc3/useTailwindShorthandClasses-v2

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented May 8, 2026

Summary

This implements useTailwindShorthandClasses which is a port of https://github.com/schoero/eslint-plugin-better-tailwindcss/blob/main/docs/rules/enforce-shorthand-classes.md

This 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_number for 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

@github-actions github-actions Bot added A-Project Area: project A-Linter Area: linter A-Parser Area: parser L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis L-HTML Language: HTML and super languages L-Tailwind Language: Tailwind CSS labels May 8, 2026
@dyc3 dyc3 force-pushed the dyc3/useTailwindShorthandClasses-v2 branch from e285176 to ce04eca Compare May 8, 2026 17:57
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 8, 2026

⚠️ No Changeset found

Latest commit: 3e933fe

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

@dyc3 dyc3 force-pushed the dyc3/useTailwindShorthandClasses-v2 branch 2 times, most recently from eaa06b4 to 179c5ed Compare May 8, 2026 18:31
@github-actions github-actions Bot added the A-CLI Area: CLI label May 8, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 8, 2026

Merging this PR will degrade performance by 12.4%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 3 regressed benchmarks
✅ 246 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

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)

Open in CodSpeed

@dyc3 dyc3 force-pushed the dyc3/useTailwindShorthandClasses-v2 branch 2 times, most recently from 519f0f7 to 1ce2565 Compare May 8, 2026 21:44
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

Walkthrough

Adds 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

  • ematipico
  • Netail
  • chansuke
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the main feature being added: a new lint rule called useTailwindShorthandClasses for detecting and fixing non-shorthand Tailwind class usage.
Description check ✅ Passed The description clearly explains the motivation (porting from eslint-plugin-better-tailwindcss), implementation details, testing approach, and known limitations, all relevant to the changeset.
Linked Issues check ✅ Passed The PR successfully implements the primary objective from #8503: porting enforce-shorthand-classes as useTailwindShorthandClasses using the Tailwind parser, with comprehensive unit tests, snapshot tests, and support for HTML-like languages.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the useTailwindShorthandClasses rule: adding rule logic, test fixtures, configuration options, and supporting Tailwind utilities—no unrelated changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/useTailwindShorthandClasses-v2

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

@dyc3 dyc3 requested review from a team May 8, 2026 21:48
@dyc3 dyc3 force-pushed the dyc3/useTailwindShorthandClasses-v2 branch 2 times, most recently from 36b844d to f068bfd Compare May 8, 2026 22:52
@dyc3 dyc3 marked this pull request as ready for review May 8, 2026 22:56
Copy link
Copy Markdown
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 (5)
crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs (2)

36-48: 💤 Low value

has_function and match_function are 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 value

Inconsistent deserialisation pattern between attributes and functions.

functions is deserialised directly into result.functions, whilst attributes accumulates into a separate Vec before assignment. Unless there's a specific reason for this difference (e.g., multiple attributes keys 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 value

Minor: root is 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 value

Consider whether this trait indirection is needed.

The TailwindClassStringOptions trait directly delegates to the same method on UseTailwindShorthandClassesOptions. If this abstraction is solely for future extensibility or testing, it's fine to keep. Otherwise, you could simplify by using UseTailwindShorthandClassesOptions directly.

🤖 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 win

Add one non-configured function control in this fixture

Nice coverage for cn and tw. Please add one call like cx("w-4 h-4") and assert no diagnostic, so the functions option 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

📥 Commits

Reviewing files that changed from the base of the PR and between e4f8d83 and f068bfd.

⛔ Files ignored due to path filters (29)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__invalid_cases.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__valid_cases.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (38)
  • Cargo.toml
  • crates/biome_html_analyze/Cargo.toml
  • crates/biome_html_analyze/src/lib.rs
  • crates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs
  • crates/biome_html_analyze/src/tailwind.rs
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.options.json
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
  • crates/biome_js_analyze/Cargo.toml
  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_js_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs
  • crates/biome_js_analyze/src/shared/any_class_string_like.rs
  • crates/biome_js_analyze/src/tailwind.rs
  • crates/biome_js_analyze/tests/spec_tests.rs
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.options.json
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs
  • crates/biome_tailwind_logic/Cargo.toml
  • crates/biome_tailwind_logic/src/lib.rs
  • crates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rs
  • crates/biome_tailwind_syntax/src/lib.rs
  • crates/biome_tailwind_syntax/src/util.rs

Comment thread crates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rs Outdated
Comment thread crates/biome_tailwind_syntax/src/util.rs
@dyc3 dyc3 force-pushed the dyc3/useTailwindShorthandClasses-v2 branch from f52fec2 to 3e933fe Compare May 9, 2026 18:48
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html (1)

1-1: ⚡ Quick win

Add a matching valid fixture for the data-classes option 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

📥 Commits

Reviewing files that changed from the base of the PR and between f52fec2 and 3e933fe.

⛔ Files ignored due to path filters (31)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useSortedClasses/codeOptionsUnsorted.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__invalid_cases.snap is excluded by !**/*.snap and included by **
  • crates/biome_tailwind_logic/src/snapshots/biome_tailwind_logic__use_tailwind_shorthand_classes__tests__valid_cases.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (40)
  • Cargo.toml
  • crates/biome_html_analyze/Cargo.toml
  • crates/biome_html_analyze/src/lib.rs
  • crates/biome_html_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs
  • crates/biome_html_analyze/src/tailwind.rs
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/custom-attribute.options.json
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.html
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
  • crates/biome_html_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
  • crates/biome_js_analyze/Cargo.toml
  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_js_analyze/src/lint/nursery/use_tailwind_shorthand_classes.rs
  • crates/biome_js_analyze/src/shared/any_class_string_like.rs
  • crates/biome_js_analyze/src/tailwind.rs
  • crates/biome_js_analyze/tests/spec_tests.rs
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid-tailwind.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.astro
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/invalid.vue
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.astro
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/valid.vue
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useTailwindShorthandClasses/with-functions.options.json
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_tailwind_shorthand_classes.rs
  • crates/biome_tailwind_logic/Cargo.toml
  • crates/biome_tailwind_logic/src/lib.rs
  • crates/biome_tailwind_logic/src/use_tailwind_shorthand_classes.rs
  • crates/biome_tailwind_syntax/src/lib.rs
  • crates/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

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

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Parser Area: parser A-Project Area: project L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages L-Tailwind Language: Tailwind CSS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant