Skip to content

feat(lint/js): add noExcessiveSelectorClasses#9866

Merged
dyc3 merged 1 commit intomainfrom
dyc3/noExcessiveSelectorClasses
Apr 11, 2026
Merged

feat(lint/js): add noExcessiveSelectorClasses#9866
dyc3 merged 1 commit intomainfrom
dyc3/noExcessiveSelectorClasses

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented Apr 8, 2026

Summary

This PR adds noExcessiveSelectorClasses which is a port of https://stylelint.io/user-guide/rules/selector-max-class/

I chose make this rule have no effect when it's enabled and not configured. Should we emit a diagnostic in that case?

generated by gpt 5.4

Test Plan

snapshots

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 8, 2026

🦋 Changeset detected

Latest commit: a136179

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

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

@github-actions github-actions Bot added A-Project Area: project A-Linter Area: linter L-CSS Language: CSS and super languages A-Diagnostic Area: diagnostocis labels Apr 8, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 45c10199-0f5f-47a7-a00a-2567ccf045a9

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7014b and a136179.

⛔ Files ignored due to path filters (15)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs 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 (21)
  • .changeset/rare-schools-raise.md
  • crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.scss
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.scss
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_excessive_selector_classes.rs
✅ Files skipped from review due to trivial changes (8)
  • .changeset/rare-schools-raise.md
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.options.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css
  • crates/biome_rule_options/src/no_excessive_selector_classes.rs
  • crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs

Walkthrough

This PR introduces a new CSS linter rule noExcessiveSelectorClasses for Biome's nursery. The rule counts class selectors within CSS selectors and reports violations when they exceed a configured maxClasses threshold. The implementation includes the rule logic, configurable options, and comprehensive test fixtures covering standard CSS, nested selectors, pseudo-classes, and SCSS with interpolation handling.

Possibly related PRs

Suggested reviewers

  • ematipico
  • denbezrukov
  • Netail
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title claims the rule is for 'lint/js' but the changeset and all code additions are exclusively for CSS linting, not JavaScript. Change the title to 'feat(lint/css): add noExcessiveSelectorClasses' to accurately reflect that this is a CSS linting rule, not a JavaScript one.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the purpose (a port of stylelint's selector-max-class rule), mentions testing via snapshots, and discloses AI assistance as required.

✏️ 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/noExcessiveSelectorClasses

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

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/rare-schools-raise.md:
- Around line 1-4: The changeset front matter is malformed due to an extra '---'
delimiter; open the .changeset/rare-schools-raise.md file and remove the
redundant second '---' so the YAML front matter is a single start and end
delimiter, then ensure the remaining front matter correctly lists the package
mapping ("@biomejs/biome": patch) and no other stray delimiters or blank
front-matter sections remain.

In `@crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs`:
- Around line 138-139: The parameter name _ctx in fn diagnostic(_ctx:
&RuleContext<Self>, state: &Self::State) should be renamed to ctx because it is
used; update the function signature to fn diagnostic(ctx: &RuleContext<Self>,
state: &Self::State) and replace all usages of _ctx within the function body
(e.g., the call to _ctx.options().max_classes()) with ctx to reflect the actual
usage and remove the misleading underscore prefix.
- Line 97: The RuleSource declaration currently uses
RuleSource::Stylelint("selector-max-class").same(); update it to use .inspired()
instead of .same() to reflect the documented semantic ("Inspired by" not "Same
as") and the fact nested selectors are checked instead of being resolved; locate
the occurrence of RuleSource::Stylelint("selector-max-class").same() in the
no_excessive_selector_classes rule and replace .same() with .inspired() (no
other behavior changes required).
🪄 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: af0d4dd1-949b-446e-8582-9674e9da06d9

📥 Commits

Reviewing files that changed from the base of the PR and between 9342fe1 and fd2fef1.

⛔ Files ignored due to path filters (10)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs 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 (14)
  • .changeset/rare-schools-raise.md
  • crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.options.json
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_excessive_selector_classes.rs

Comment thread .changeset/rare-schools-raise.md
Comment thread crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 29 untouched benchmarks
⏩ 227 skipped benchmarks1


Comparing dyc3/noExcessiveSelectorClasses (a136179) with main (7ae83f2)

Open in CodSpeed

Footnotes

  1. 227 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions github-actions Bot added the L-Grit Language: GritQL label Apr 8, 2026
@dyc3 dyc3 marked this pull request as draft April 10, 2026 20:45
@dyc3 dyc3 force-pushed the dyc3/noExcessiveSelectorClasses branch from ea192ed to 6a7014b Compare April 10, 2026 21:22
@github-actions github-actions Bot removed the L-Grit Language: GritQL label Apr 10, 2026
@dyc3 dyc3 marked this pull request as ready for review April 10, 2026 23:05
@dyc3 dyc3 requested review from a team April 10, 2026 23:05
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs`:
- Around line 59-61: In the doc comment in no_excessive_selector_classes.rs (the
configured example under the maxClasses: 1 options block) add the code block
flag so the fenced snippet is ```css,use_options instead of just ```css; this
ensures the example is validated with the rule options applied—update the doc
comment near the .foo {} example in the no_excessive_selector_classes
documentation to include use_options.

In
`@crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css`:
- Around line 1-5: The current test only contains selectors like "div {}", ".foo
{}", "#id[attr]:hover::before {}", and ":nth-child(2n) {}", which also pass when
maxClasses is set, so update the fixture by adding a selector that would violate
a max-classes rule (to assert unconfigured==no-op), for example add a rule such
as ".a .b .c {}" or a concatenated class selector like ".foo.bar.baz {}" to the
test file so the snapshot proves the rule is a no-op when unconfigured.
🪄 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: f9df073d-5014-443d-9ae5-c3d41c6b246f

📥 Commits

Reviewing files that changed from the base of the PR and between ea192ed and 6a7014b.

⛔ Files ignored due to path filters (15)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css.snap is excluded by !**/*.snap and included by **
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.scss.snap is excluded by !**/*.snap and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs 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 (22)
  • .changeset/rare-schools-raise.md
  • crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.scss
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.scss
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_excessive_selector_classes.rs
✅ Files skipped from review due to trivial changes (10)
  • .changeset/rare-schools-raise.md
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.css
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.pseudo.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.max2.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.nested.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/valid.zero.options.json
  • crates/biome_css_analyze/tests/specs/nursery/noExcessiveSelectorClasses/invalid.options.json

Comment thread crates/biome_css_analyze/src/lint/nursery/no_excessive_selector_classes.rs Outdated
Copy link
Copy Markdown
Member

@chansuke chansuke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@dyc3 dyc3 force-pushed the dyc3/noExcessiveSelectorClasses branch from 6a7014b to a136179 Compare April 11, 2026 03:01
@dyc3 dyc3 merged commit 40bd180 into main Apr 11, 2026
21 checks passed
@dyc3 dyc3 deleted the dyc3/noExcessiveSelectorClasses branch April 11, 2026 03:17
@github-actions github-actions Bot mentioned this pull request Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-CSS Language: CSS and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants