-
-
Notifications
You must be signed in to change notification settings - Fork 792
feat(linter): add 2 Tailwind CSS utility class lint rules #8623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add new nursery lint rules for Tailwind CSS utility classes: - `noDuplicateTailwindClasses`: Detects and removes duplicate CSS utility classes - `noUnnecessaryTailwindWhitespace`: Removes extra whitespace in class strings - `noEmptyTailwindArbitraryValue`: Flags empty arbitrary values like `w-[]` - `noInvalidTailwindVariantCombination`: Detects conflicting variants - `useTailwindLogicalProperties`: Suggests logical properties (`ms-*` instead of `ml-*`) - `useTailwindColorOpacityModifier`: Suggests opacity modifiers (`text-red-500/50`) All rules reuse existing infrastructure from `useSortedClasses` and require no changes to analyzer configuration or core infrastructure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Added changeset for the 6 new Tailwind CSS lint rules - Fixed clippy::manual_strip warning in useTailwindLogicalProperties 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove 4 new rules that weren't ported from eslint-plugin-better-tailwindcss: - noEmptyTailwindArbitraryValue - noInvalidTailwindVariantCombination - useTailwindColorOpacityModifier - useTailwindLogicalProperties Keep only the 2 rules that are direct ports: - noDuplicateTailwindClasses (from no-duplicate-classes) - noUnnecessaryTailwindWhitespace (from no-unnecessary-whitespace) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
🦋 Changeset detectedLatest commit: 8fd7703 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughThis PR introduces a new lint rule Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (1 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (1)
66-72: Consider addingissue_numberfor tracking.Per guidelines, nursery rules benefit from an
issue_numberfield linking to the tracking issue. The PR objectives mention #6502 as the request for duplicate class detection.🔎 Suggested addition
pub NoDuplicateTailwindClasses { version: "next", name: "noDuplicateTailwindClasses", language: "jsx", recommended: false, fix_kind: FixKind::Safe, + issue_number: Some("6502"), }crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs (2)
53-59: Consider addingissue_numberfor tracking.Similar to the duplicate classes rule, adding an
issue_numberhelps track the feature request. The PR objectives reference #1274 as the Tailwind CSS support tracking issue.🔎 Suggested addition
pub NoUnnecessaryTailwindWhitespace { version: "next", name: "noUnnecessaryTailwindWhitespace", language: "jsx", recommended: false, fix_kind: FixKind::Safe, + issue_number: Some("1274"), }
79-86: Detection doesn't explicitly cover tabs or mixed whitespace.Line 82's
contains(" ")only detects double spaces. Tabs or mixed whitespace (e.g.,"flex\tp-4"or"flex \t p-4") will still be normalised bysplit_whitespace(), but the detection check and diagnostic won't explicitly mention them. The rule works correctly due to the guard at line 92, but the diagnostic message might not be fully accurate for tab-heavy inputs.If you'd like more precise detection:
🔎 Optional enhancement
- let has_multiple_spaces = value_str.contains(" "); + // Check for multiple consecutive whitespace characters (spaces, tabs, newlines) + let has_multiple_whitespace = value_str + .chars() + .zip(value_str.chars().skip(1)) + .any(|(a, b)| a.is_whitespace() && b.is_whitespace());And update the diagnostic message at line 111-112 accordingly.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/valid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsx.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (8)
.changeset/tailwind-utility-class-rules.mdcrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use thedbg!()macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests
Files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
🧠 Learnings (22)
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Place new rules inside the `nursery` group during development
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs.changeset/tailwind-utility-class-rules.md
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Assist rules should detect refactoring opportunities and emit code action signals
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Check if a variable is global using the semantic model to avoid false positives
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Add `issue_number` field to `declare_lint_rule!` macro for work-in-progress rules
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use helper functions like `map`, `filter`, and `and_then` to avoid deep indentation
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Safe` in `declare_lint_rule!` for safe code actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `version` field to `next` in `declare_lint_rule!` macro
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/biome_rule_options/lib/**/*.rs : Use `rename_all = "camelCase"` in serde derive macro for rule options
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `domains` field in `declare_lint_rule!` to tag rules that belong to specific concepts like testing or frameworks
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `sources` field with `RuleSource` to cite ESLint or other rules that inspired the implementation
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/tests/specs/**/*valid* : Create test files prefixed with `valid` for code that should not trigger the rule
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx
📚 Learning: 2025-12-04T13:29:49.287Z
Learnt from: dyc3
Repo: biomejs/biome PR: 8291
File: crates/biome_html_formatter/tests/specs/prettier/vue/html-vue/elastic-header.html:10-10
Timestamp: 2025-12-04T13:29:49.287Z
Learning: Files under `crates/biome_html_formatter/tests/specs/prettier` are test fixtures synced from Prettier and should not receive detailed code quality reviews (e.g., HTTP vs HTTPS, formatting suggestions, etc.). These files are test data meant to validate formatter behavior and should be preserved as-is.
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/tests/specs/**/*invalid* : Create test files prefixed with `invalid` for code that should trigger the rule
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/tests/specs/**/*.jsonc : Use `.jsonc` files to contain arrays of code snippet strings for snapshot tests
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsxcrates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsxcrates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `declare_lint_rule!` macro to declare analyzer rule types and implement the RuleMeta trait
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-21T21:15:03.796Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: Changesets should describe user-facing changes only; internal refactoring without behavior changes does not require a changeset
Applied to files:
.changeset/tailwind-utility-class-rules.md
📚 Learning: 2025-12-21T21:15:03.796Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For new nursery rules, send PRs to the maintenance branch `main`
Applied to files:
.changeset/tailwind-utility-class-rules.md
🔇 Additional comments (9)
crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/valid.jsx (1)
1-16: LGTM!Good coverage of valid cases: non-duplicates, variant-differentiated classes, similar-but-different utilities, empty strings, and single-class scenarios. Based on learnings, the
validprefix correctly indicates these should not trigger diagnostics.crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx (1)
1-18: LGTM!Comprehensive invalid test cases covering leading/trailing whitespace, multiple consecutive spaces, combined scenarios, and multiline strings with newlines and tabs. Good coverage of the
classNameattribute variant too.crates/biome_js_analyze/src/lint/nursery/use_sorted_classes.rs (1)
1-1: Visibility change looks appropriate.Making
any_class_string_likepublic enables the new Tailwind rules to reuseAnyClassStringLike. Since all consumers are within the same crate (biome_js_analyze), this is a reasonable approach. If external crate access becomes a concern later, consider usingpub(crate)instead.crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/valid.jsx (1)
1-12: LGTM!Straightforward valid cases with properly formatted whitespace. Good inclusion of the empty class and single class edge cases.
crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsx (1)
1-16: LGTM!Solid invalid test coverage: exact duplicates, non-adjacent duplicates, variant-prefixed duplicates, multiple duplicates per string, and arbitrary value duplicates. Well structured!
.changeset/tailwind-utility-class-rules.md (1)
1-9: LGTM!Changeset correctly documents the two new nursery rules with appropriate links. Patch version bump is suitable for new nursery rules.
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (2)
106-122: Whitespace normalisation occurs as a side effect.Using
split_whitespace()andjoin(" ")means this rule will also normalise whitespace when removing duplicates. This is generally fine, but users may see whitespace fixes from this rule even whennoUnnecessaryTailwindWhitespaceis disabled. Consider documenting this in the rustdoc if intentional.
155-207: LGTM!The action implementation correctly handles all
AnyClassStringLikevariants and preserves quote styles. Good consistency with the existinguseSortedClassesrule.crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs (1)
131-183: LGTM!Action implementation correctly handles all
AnyClassStringLikevariants with proper quote style preservation. Consistent with other Tailwind rules in this PR.
- Add issue_number to noDuplicateTailwindClasses (biomejs#6502) - Add issue_number to noUnnecessaryTailwindWhitespace (biomejs#1274) - Improve whitespace detection to handle tabs and mixed whitespace 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this 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 (3)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (2)
144-148: Minor: Consider simpler pluralisation.The
<Emphasis>wrapper around the conditional "es" suffix is a bit unusual. Standard approach would be to conditionally include the whole word. Not blocking though — it works.
173-180: Inconsistent quote preservation forJsLiteralMemberName.Unlike
JsStringLiteralExpression(line 162-165) andJsxString(line 182-185), this branch doesn't attempt to detect the original quote style from the token. It falls back directly toctx.preferred_quote().Consider preserving the original quote style for consistency:
🔎 Suggested fix
AnyClassStringLike::JsLiteralMemberName(string_literal) => { - let replacement = js_literal_member_name(if ctx.preferred_quote().is_double() { + let is_double_quote = string_literal + .value_token() + .map(|token| token.text_trimmed().starts_with('"')) + .unwrap_or(ctx.preferred_quote().is_double()); + let replacement = js_literal_member_name(if is_double_quote { js_string_literal(deduplicated) } else { js_string_literal_single_quotes(deduplicated) }); mutation.replace_node(string_literal.clone(), replacement); }crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs (1)
158-165: Same quote preservation inconsistency forJsLiteralMemberName.As noted in
no_duplicate_tailwind_classes.rs, this branch doesn't detect the original quote style. Consider applying the same fix here for consistency.Also worth noting: the
action()implementations in both rules are nearly identical. A shared helper function could reduce duplication in future, but not blocking for this PR.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryTailwindWhitespace/invalid.jsx.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (2)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rscrates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use thedbg!()macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests
Files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Add `issue_number` field to `declare_lint_rule!` macro for work-in-progress rules
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Place new rules inside the `nursery` group during development
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Commit rule work with message format `feat(biome_<language>_analyze): <ruleName>`
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: For new nursery rules, send PRs to the maintenance branch `main`
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Add `issue_number` field to `declare_lint_rule!` macro for work-in-progress rules
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Safe` in `declare_lint_rule!` for safe code actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `declare_lint_rule!` macro to declare analyzer rule types and implement the RuleMeta trait
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Unsafe` in `declare_lint_rule!` for unsafe code actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Place new rules inside the `nursery` group during development
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Assist rules should detect refactoring opportunities and emit code action signals
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `version` field to `next` in `declare_lint_rule!` macro
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `deprecated` field to `declare_lint_rule!` macro when deprecating a rule
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `domains` field in `declare_lint_rule!` to tag rules that belong to specific concepts like testing or frameworks
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
🔇 Additional comments (3)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (2)
13-83: LGTM!Rule declaration follows Biome conventions nicely. Documentation is clear,
issue_numberis present for nursery rules, andfix_kindcorrectly indicates a safe fix. Based on learnings, this is spot on.
107-117: LGTM!Duplicate detection logic is correct. The linear search at line 110 is fine given typical duplicate counts are small.
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs (1)
12-61: LGTM!Rule declaration is well-structured with clear documentation and proper metadata. Based on learnings,
issue_numberis correctly included for nursery rules.
| // Check for unnecessary whitespace | ||
| let has_leading_whitespace = value_str.starts_with(char::is_whitespace); | ||
| let has_trailing_whitespace = value_str.ends_with(char::is_whitespace); | ||
| // Check for multiple consecutive whitespace characters (spaces, tabs, newlines) | ||
| let has_multiple_whitespace = value_str | ||
| .as_bytes() | ||
| .windows(2) | ||
| .any(|w| w[0].is_ascii_whitespace() && w[1].is_ascii_whitespace()); | ||
|
|
||
| if !has_leading_whitespace && !has_trailing_whitespace && !has_multiple_whitespace { | ||
| return None; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor inconsistency in whitespace detection methods.
Lines 81-82 use char::is_whitespace (Unicode-aware), but lines 84-87 use is_ascii_whitespace() (ASCII only). This could cause edge cases with Unicode whitespace characters like non-breaking spaces between classes — they'd be normalised correctly but the diagnostic message might not mention "multiple consecutive whitespace".
For Tailwind classes this is unlikely to matter in practice, but for consistency you could use char::is_whitespace throughout:
🔎 Suggested fix
- let has_multiple_whitespace = value_str
- .as_bytes()
- .windows(2)
- .any(|w| w[0].is_ascii_whitespace() && w[1].is_ascii_whitespace());
+ let chars: Vec<char> = value_str.chars().collect();
+ let has_multiple_whitespace = chars
+ .windows(2)
+ .any(|w| w[0].is_whitespace() && w[1].is_whitespace());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check for unnecessary whitespace | |
| let has_leading_whitespace = value_str.starts_with(char::is_whitespace); | |
| let has_trailing_whitespace = value_str.ends_with(char::is_whitespace); | |
| // Check for multiple consecutive whitespace characters (spaces, tabs, newlines) | |
| let has_multiple_whitespace = value_str | |
| .as_bytes() | |
| .windows(2) | |
| .any(|w| w[0].is_ascii_whitespace() && w[1].is_ascii_whitespace()); | |
| if !has_leading_whitespace && !has_trailing_whitespace && !has_multiple_whitespace { | |
| return None; | |
| } | |
| // Check for unnecessary whitespace | |
| let has_leading_whitespace = value_str.starts_with(char::is_whitespace); | |
| let has_trailing_whitespace = value_str.ends_with(char::is_whitespace); | |
| // Check for multiple consecutive whitespace characters (spaces, tabs, newlines) | |
| let chars: Vec<char> = value_str.chars().collect(); | |
| let has_multiple_whitespace = chars | |
| .windows(2) | |
| .any(|w| w[0].is_whitespace() && w[1].is_whitespace()); | |
| if !has_leading_whitespace && !has_trailing_whitespace && !has_multiple_whitespace { | |
| return None; | |
| } |
🤖 Prompt for AI Agents
In
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
around lines 80-91, replace the mixed use of char::is_whitespace (Unicode-aware)
and is_ascii_whitespace (ASCII-only) when detecting consecutive whitespace:
iterate over characters (e.g., via chars() and pairing adjacent chars or
collecting into a char slice and using windows) and use char::is_whitespace for
both leading/trailing and consecutive-whitespace checks so Unicode whitespace
(e.g., NBSP) is handled consistently; update the has_multiple_whitespace logic
to operate on chars instead of raw bytes and keep the rest of the conditional
unchanged.
| // Check for multiple consecutive whitespace characters | ||
| let has_multiple_whitespace = value_str | ||
| .as_bytes() | ||
| .windows(2) | ||
| .any(|w| w[0].is_ascii_whitespace() && w[1].is_ascii_whitespace()); | ||
| if has_multiple_whitespace { | ||
| issues.push("multiple consecutive whitespace characters"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same whitespace check inconsistency.
Lines 117-120 repeat the ASCII-only check. If you update run(), update this to match.
🤖 Prompt for AI Agents
In
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_tailwind_whitespace.rs
around lines 116 to 123, the consecutive-whitespace check still uses an
ASCII-only byte-based windows check; change it to a Unicode-aware char-based
check to match the updated run() behavior — e.g., iterate over chars (zip
current and next char or use windows over char indices) and use
char::is_whitespace() for both chars, then push the same issue when any pair of
consecutive whitespace chars is found.
I really don't think we need a rule for this. We already |
| language: "jsx", | ||
| recommended: false, | ||
| fix_kind: FixKind::Safe, | ||
| issue_number: Some("6502"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think an issue_number is relevant here, but we should add a sources array with better-tailwinda
| language: "jsx", | ||
| recommended: false, | ||
| fix_kind: FixKind::Safe, | ||
| issue_number: Some("1274"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
- Remove `noUnnecessaryTailwindWhitespace` rule (redundant with `useSortedClasses`) - Remove `issue_number` from `noDuplicateTailwindClasses` - Add `EslintBetterTailwindcss` to `RuleSource` enum - Add `sources` attribution to `noDuplicateTailwindClasses` - Update changeset to reflect single rule 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (1)
175-182: Consider preserving the original quote style for consistency.The
JsLiteralMemberNamecase usesctx.preferred_quote()directly without checking the original node's quote style, unlikeJsStringLiteralExpression(Line 164-167) andJsxString(Line 184-187) which both inspect the original token first. This inconsistency might unnecessarily change quote styles during the fix.🔎 Suggested refactor to preserve original quote style
AnyClassStringLike::JsLiteralMemberName(string_literal) => { - let replacement = js_literal_member_name(if ctx.preferred_quote().is_double() { + let is_double_quote = string_literal + .value() + .ok() + .and_then(|v| v.text().chars().next()) + .map(|c| c == '"') + .unwrap_or(ctx.preferred_quote().is_double()); + let replacement = js_literal_member_name(if is_double_quote { js_string_literal(deduplicated) } else { js_string_literal_single_quotes(deduplicated) }); mutation.replace_node(string_literal.clone(), replacement); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/noDuplicateTailwindClasses/invalid.jsx.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (3)
.changeset/tailwind-utility-class-rules.mdcrates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- .changeset/tailwind-utility-class-rules.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use thedbg!()macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests
Files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Add `issue_number` field to `declare_lint_rule!` macro for work-in-progress rules
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Commit rule work with message format `feat(biome_<language>_analyze): <ruleName>`
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `sources` field with `RuleSource` to cite ESLint or other rules that inspired the implementation
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use language-specific rule names if the rule is meant for a specific language only
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Implement `action` function in Rule trait to provide code actions
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use generic rule names if the rule could potentially be implemented for multiple languages
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Assist rules should detect refactoring opportunities and emit code action signals
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `version` field to `next` in `declare_lint_rule!` macro
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `language` field in `declare_lint_rule!` macro to the language the rule primarily applies to
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `declare_lint_rule!` macro to declare analyzer rule types and implement the RuleMeta trait
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should check syntax according to language specification and emit error diagnostics
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `language` to `jsx`, `ts`, or `tsx` for rules that only apply to specific JavaScript dialects
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `domains` field in `declare_lint_rule!` to tag rules that belong to specific concepts like testing or frameworks
Applied to files:
crates/biome_analyze/src/rule.rscrates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Parse rule functions must be prefixed with `parse_` and use the name defined in the grammar file, e.g., `parse_for_statement` or `parse_expression`
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-21T21:15:03.796Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: Applies to **/*.rs : Use inline rustdoc documentation for rules, assists, and their options
Applied to files:
crates/biome_analyze/src/rule.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Add `issue_number` field to `declare_lint_rule!` macro for work-in-progress rules
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/nursery/**/*.rs : Place new rules inside the `nursery` group during development
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Safe` in `declare_lint_rule!` for safe code actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs
🔇 Additional comments (11)
crates/biome_analyze/src/rule.rs (5)
109-110: LGTM! Enum variant follows established pattern.The new
EslintBetterTailwindcssvariant is properly documented and structured consistently with other ESLint plugin variants in the codebase.
190-190: LGTM! Display implementation is consistent.The display name follows the
eslint-plugin-{name}convention used by other ESLint plugins.
276-276: LGTM! Pattern match correctly extracts rule name.The implementation follows the established pattern for extracting rule names from source variants.
320-322: LGTM! Namespacing follows plugin conventions.The
better-tailwindcss/{rule_name}format is consistent with how other ESLint plugins are namespaced in the codebase.
366-366: The URL format is correct.The upstream repository confirms this documentation structure exists and is accessible. Looks good to merge.
crates/biome_js_analyze/src/lint/nursery/no_duplicate_tailwind_classes.rs (6)
1-13: LGTM!The imports are well-organised and all appear to be used appropriately for the rule implementation.
15-76: LGTM!The rule declaration correctly follows Biome conventions, and the
sourcesfield properly attributes the upstream ESLint rule as requested by the maintainer.
78-85: LGTM!The state structure appropriately uses boxed types for owned data.
93-131: LGTM!The duplicate detection logic correctly preserves first-occurrence order and efficiently tracks duplicates using
FxHashSet.
133-156: LGTM!The diagnostic message is clear, properly pluralised, and provides helpful guidance.
201-210: LGTM!The action creation follows the standard pattern with appropriate metadata and a clear message.
|
Thanks @ematipico and @Netail for the review feedback! I've addressed both points:
The PR now contains just the single noDuplicateTailwindClasses rule ported from https://github.com/schoero/eslint-plugin-better-tailwindcss. |
Summary
This PR ports
2 lint rules1 lint rule from eslint-plugin-better-tailwindcss to Biome's nursery group.noDuplicateTailwindClassesnoUnnecessaryTailwindWhitespaceno-unnecessary-whitespace— Removed: redundant withuseSortedClassesThe rule reuses the existing
UseSortedClassesOptionsfromuseSortedClasses, so it works with the sameattributesandfunctionsconfiguration.Related Issues
no-custom-classnamefromeslint-plugin-tailwind#6502 - Request for Tailwind duplicate class detectionTest Plan
cargo test -p biome_js_analyze -- no_duplicate_tailwindFuture Work
If maintainers are open to it, I'd like to continue porting rules from eslint-plugin-better-tailwindcss. Here's the proposed roadmap:
Next Priority: High-Value Ports
noConflictingTailwindClassesp-2 p-4where only the last one appliesnoDeprecatedTailwindClassesAdditional Ports
useConsistentTailwindImportantPositionnoRestrictedTailwindClassesuseTailwindShorthandClassesI'm happy to split these into separate PRs for easier review.
Context
I'm a maintainer of promptfoo and have been using Biome to enforce our Tailwind rules. Happy to help expand Tailwind support in Biome!
AI Assistance Disclosure
This PR was developed with assistance from Claude Code.