Skip to content

Commit 3b34922

Browse files
authored
Add block-no-redundant-nested-style-rules (#8684)
1 parent 6b50582 commit 3b34922

File tree

9 files changed

+300
-0
lines changed

9 files changed

+300
-0
lines changed

.changeset/lovely-mammals-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"stylelint": minor
3+
---
4+
5+
Added: `block-no-redundant-nested-style-rules` rule

docs/user-guide/rules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ Disallow redundancy with these `no-redundant` rules.
399399
<!-- prettier-ignore-start -->
400400
| | | |
401401
| :-- | :-: | :-: |
402+
| [`block-no-redundant-nested-style-rules`](../../lib/rules/block-no-redundant-nested-style-rules/README.md)<br/>Disallow redundant nested style rules within blocks. | | |
402403
| [`declaration-block-no-redundant-longhand-properties`](../../lib/rules/declaration-block-no-redundant-longhand-properties/README.md)<br/>Disallow redundant longhand properties within declaration-block. || 🔧 |
403404
| [`shorthand-property-no-redundant-values`](../../lib/rules/shorthand-property-no-redundant-values/README.md)<br/>Disallow redundant values within shorthand properties. || 🔧 |
404405
<!-- prettier-ignore-end -->
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# block-no-redundant-nested-style-rules
2+
3+
Disallow redundant nested style rules within blocks.
4+
5+
<!-- prettier-ignore -->
6+
```css
7+
a { & { color: red; } }
8+
/** ↑
9+
* This nested style rule */
10+
```
11+
12+
## Options
13+
14+
### `true`
15+
16+
```json
17+
{
18+
"block-no-redundant-nested-style-rules": true
19+
}
20+
```
21+
22+
The following patterns are considered problems:
23+
24+
<!-- prettier-ignore -->
25+
```css
26+
a { & { color: red; } }
27+
```
28+
29+
<!-- prettier-ignore -->
30+
```css
31+
a { @media all { & { color: red; } } }
32+
```
33+
34+
The following patterns are _not_ considered problems:
35+
36+
<!-- prettier-ignore -->
37+
```css
38+
a { color: red; }
39+
```
40+
41+
<!-- prettier-ignore -->
42+
```css
43+
a { @media all { color: red; } }
44+
```
45+
46+
<!-- prettier-ignore -->
47+
```css
48+
a { &.foo { color: red; } }
49+
```
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import rule from '../index.mjs';
2+
3+
const { messages, ruleName } = rule;
4+
5+
testRule({
6+
ruleName,
7+
config: [true],
8+
9+
accept: [
10+
{
11+
code: '',
12+
},
13+
{
14+
code: 'a {}',
15+
},
16+
{
17+
code: '& {}',
18+
},
19+
{
20+
code: 'a { &:hover {} }',
21+
},
22+
{
23+
code: 'a { &.foo {} }',
24+
},
25+
{
26+
code: 'a { &#foo {} }',
27+
},
28+
{
29+
code: 'a { &[foo] {} }',
30+
},
31+
{
32+
code: 'a { & .foo {} }',
33+
},
34+
{
35+
code: 'a { & > .foo {} }',
36+
},
37+
{
38+
code: 'a { &::before { content: ""; } }',
39+
},
40+
{
41+
code: 'a & {}',
42+
},
43+
{
44+
code: '@media all { a {} }',
45+
},
46+
{
47+
code: 'a { @media all { color: red; } }',
48+
},
49+
{
50+
code: 'a { /* comment */ &.foo {} }',
51+
description: 'with comment',
52+
},
53+
{
54+
code: 'a { & /* comment */ .foo {} }',
55+
description: 'nesting selector with comment',
56+
},
57+
],
58+
59+
reject: [
60+
{
61+
code: 'a { & {} }',
62+
message: messages.rejected,
63+
line: 1,
64+
column: 5,
65+
endLine: 1,
66+
endColumn: 9,
67+
},
68+
{
69+
code: 'a { @media all { & {} } }',
70+
message: messages.rejected,
71+
line: 1,
72+
column: 18,
73+
endLine: 1,
74+
endColumn: 22,
75+
},
76+
{
77+
code: '@scope (a) { & {} }',
78+
message: messages.rejected,
79+
line: 1,
80+
column: 14,
81+
endLine: 1,
82+
endColumn: 18,
83+
},
84+
{
85+
code: 'a { @media all {} & {} }',
86+
message: messages.rejected,
87+
line: 1,
88+
column: 19,
89+
endLine: 1,
90+
endColumn: 23,
91+
},
92+
{
93+
code: 'a { & {} & {} }',
94+
warnings: [
95+
{
96+
message: messages.rejected,
97+
line: 1,
98+
column: 5,
99+
endLine: 1,
100+
endColumn: 9,
101+
},
102+
{
103+
message: messages.rejected,
104+
line: 1,
105+
column: 10,
106+
endLine: 1,
107+
endColumn: 14,
108+
},
109+
],
110+
},
111+
{
112+
code: 'a { & /* foo */ {} }',
113+
message: messages.rejected,
114+
line: 1,
115+
column: 5,
116+
endLine: 1,
117+
endColumn: 19,
118+
description: 'with comment',
119+
},
120+
{
121+
code: 'a { /* foo */ & {} }',
122+
message: messages.rejected,
123+
line: 1,
124+
column: 15,
125+
endLine: 1,
126+
endColumn: 19,
127+
},
128+
],
129+
});

lib/rules/block-no-redundant-nested-style-rules/index.cjs

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { isRoot } from '../../utils/typeGuards.mjs';
2+
import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs';
3+
import report from '../../utils/report.mjs';
4+
import ruleMessages from '../../utils/ruleMessages.mjs';
5+
import validateOptions from '../../utils/validateOptions.mjs';
6+
7+
const ruleName = 'block-no-redundant-nested-style-rules';
8+
9+
const messages = ruleMessages(ruleName, {
10+
rejected: 'Unexpected redundant nested style rule',
11+
});
12+
13+
const meta = {
14+
url: 'https://stylelint.io/user-guide/rules/block-no-redundant-nested-style-rules',
15+
};
16+
17+
/** @type {import('stylelint').CoreRules[ruleName]} */
18+
const rule = (primary) => {
19+
return (root, result) => {
20+
const validOptions = validateOptions(result, ruleName, {
21+
actual: primary,
22+
possible: [true],
23+
});
24+
25+
if (!validOptions) return;
26+
27+
root.walkRules((ruleNode) => {
28+
if (!isStandardSyntaxRule(ruleNode)) return;
29+
30+
const { parent, selector } = ruleNode;
31+
32+
if (selector !== '&') return;
33+
34+
if (!parent) return;
35+
36+
if (isRoot(parent)) return;
37+
38+
report({
39+
message: messages.rejected,
40+
messageArgs: [],
41+
node: ruleNode,
42+
result,
43+
ruleName,
44+
});
45+
});
46+
};
47+
};
48+
49+
rule.ruleName = ruleName;
50+
rule.messages = messages;
51+
rule.meta = meta;
52+
export default rule;

lib/rules/index.cjs

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/rules/index.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const rules = {
3939
get 'block-no-empty'() {
4040
return import('./block-no-empty/index.mjs').then((m) => m.default);
4141
},
42+
get 'block-no-redundant-nested-style-rules'() {
43+
return import('./block-no-redundant-nested-style-rules/index.mjs').then((m) => m.default);
44+
},
4245
get 'color-function-alias-notation'() {
4346
return import('./color-function-alias-notation/index.mjs').then((m) => m.default);
4447
},

types/stylelint/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ declare namespace stylelint {
498498
ExpectedMessage<[atRule: string, property: string] | [atRule: string, descriptor: string]>
499499
>;
500500
'block-no-empty': CoreRule<true, { ignore: OneOrMany<'comments'> }>;
501+
'block-no-redundant-nested-style-rules': CoreRule<true>;
501502
'color-function-alias-notation': CoreRule<'with-alpha' | 'without-alpha', {}, AutofixMessage>;
502503
'color-function-notation': CoreRule<
503504
'modern' | 'legacy',

0 commit comments

Comments
 (0)