New Rule: `no-duplicate-selectors`
- Dominant language
- JavaScript
- Stars
- 308
- Forks
- 44
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 18
Description
### Rule details
Disallow selectors that duplicate an earlier rule's selector, and repeated selectors within a single selector list.
### What type of rule is this?
Warns about a potential problem
### Example code
```css
.card {
color: black;
}
/* could be far apart - a copy-paste or merge-conflict artifact */
.card {
color: gray; /* silently wins; editing the first block appears to do nothing */
}
.button,
.button {
padding: 8px;
}
```
### Prior Art
- [no-duplicate-selectors](https://stylelint.io/user-guide/rules/no-duplicate-selectors/), enabled in stylelint's error-catching preset: https://github.com/stylelint/stylelint-config-recommended/blob/02f3006b4bb99c33b9002dc94477946f84c225d6/index.js#L35
### Participation
- [x] I am willing to submit a pull request to implement this rule.
### AI acknowledgment
- [ ] I did not use AI to generate this issue report.
- [x] (If the above is not checked) I have reviewed the AI-generated content before submitting.
### Additional comments
- The failure mode this catches is the same one as `no-duplicate-imports`: the duplicate is valid CSS handled deterministically by the browser, but it usually enters the file unintentionally (copy-paste, merge conflict) and far away from the original, so it survives review. The result is confusing - declarations in the later block silently win ties, so edits to the earlier block appear to have no effect.
- Only selectors in the same context would be compared: an identical selector under a different `@media`/`@supports`/`@layer` condition or a different nesting parent is not a duplicate. Comparison would be done on normalized selector text (whitespace-insensitive).
- Optional for first implementation: detecting equivalent *resolved* selectors written differently (`.a .b` vs `.a { & .b }`).
Contributor guide
Assessment
This issue has not been assessed yet.