@mrhenry/stylelint-mrhenry-nesting 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/stylelint-mrhenry-nesting.js +201 -0
package/package.json
CHANGED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const stylelint = require("stylelint");
|
|
2
|
+
const selectorParser = require('postcss-selector-parser');
|
|
3
|
+
|
|
4
|
+
const ruleName = "plugin/stylelint-mrhenry-nesting";
|
|
5
|
+
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
6
|
+
rejectedAtRule: (name) => {
|
|
7
|
+
return `Nested at-rules with name "${name}" is not allowed.`;
|
|
8
|
+
},
|
|
9
|
+
rejectedMustStartWithAmpersand: () => {
|
|
10
|
+
return `Nested selectors must start with "&".`;
|
|
11
|
+
},
|
|
12
|
+
rejectedMustEndWithPseudo: () => {
|
|
13
|
+
return `Nested selectors must end with a pseudo selectors.`;
|
|
14
|
+
},
|
|
15
|
+
rejectedMustContainOnlyOneAmpersand: () => {
|
|
16
|
+
return `Nested selectors must only contain a single "&".`;
|
|
17
|
+
},
|
|
18
|
+
rejectedNestingDepth: () => {
|
|
19
|
+
return `Nested rules must be limited to 1 level deep.`;
|
|
20
|
+
},
|
|
21
|
+
rejectedNestingSelectorIncorrectShape: () => {
|
|
22
|
+
return `Nested selectors must be compound selectors, starting with "&" and followed by a single pseudo selector.`;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const meta = {
|
|
27
|
+
url: "https://github.com/mrhenry/stylelint-mrhenry/tree/main/packages/nesting"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ruleFunction = (primaryOption, secondaryOptionObject, context) => {
|
|
31
|
+
return (postcssRoot, postcssResult) => {
|
|
32
|
+
postcssRoot.walkAtRules((atrule) => {
|
|
33
|
+
let name = atrule.name.toLowerCase();
|
|
34
|
+
if (
|
|
35
|
+
name === 'media' ||
|
|
36
|
+
name === 'supports' ||
|
|
37
|
+
name === 'container' ||
|
|
38
|
+
name === 'scope'
|
|
39
|
+
) {
|
|
40
|
+
// always allowed
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let rulesDepth = 0;
|
|
45
|
+
let parent = atrule.parent;
|
|
46
|
+
while (parent) {
|
|
47
|
+
if (parent.type === 'rule') {
|
|
48
|
+
rulesDepth++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
parent = parent.parent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (rulesDepth === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stylelint.utils.report({
|
|
59
|
+
message: messages.rejectedAtRule(name),
|
|
60
|
+
node: atrule,
|
|
61
|
+
index: 0,
|
|
62
|
+
endIndex: atrule.name.length,
|
|
63
|
+
result: postcssResult,
|
|
64
|
+
ruleName,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
postcssRoot.walkRules((rule) => {
|
|
69
|
+
{
|
|
70
|
+
let rulesDepth = 1;
|
|
71
|
+
let parent = rule.parent;
|
|
72
|
+
while (parent) {
|
|
73
|
+
if (parent.type === 'rule') {
|
|
74
|
+
rulesDepth++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
parent = parent.parent;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (rulesDepth === 1) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (rulesDepth > 2) {
|
|
85
|
+
stylelint.utils.report({
|
|
86
|
+
message: messages.rejectedNestingDepth(),
|
|
87
|
+
node: rule,
|
|
88
|
+
index: 0,
|
|
89
|
+
endIndex: rule.selector.length,
|
|
90
|
+
result: postcssResult,
|
|
91
|
+
ruleName,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const selectorsAST = selectorParser().astSync(rule.selector);
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < selectorsAST.nodes.length; i++) {
|
|
99
|
+
const selectorAST = selectorsAST.nodes[i];
|
|
100
|
+
|
|
101
|
+
let nestingCounter = 0;
|
|
102
|
+
{
|
|
103
|
+
let nestingCounter = 0;
|
|
104
|
+
selectorAST.walkNesting(() => {
|
|
105
|
+
nestingCounter++;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (nestingCounter > 1) {
|
|
109
|
+
stylelint.utils.report({
|
|
110
|
+
message: messages.rejectedMustContainOnlyOneAmpersand(),
|
|
111
|
+
node: rule,
|
|
112
|
+
index: 0,
|
|
113
|
+
endIndex: rule.selector.length,
|
|
114
|
+
result: postcssResult,
|
|
115
|
+
ruleName,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (selectorAST.nodes?.[0]?.type !== 'nesting') {
|
|
123
|
+
if (context.fix && nestingCounter === 0) {
|
|
124
|
+
fixSelector(rule, selectorsAST, selectorAST);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
stylelint.utils.report({
|
|
129
|
+
message: messages.rejectedMustStartWithAmpersand(),
|
|
130
|
+
node: rule,
|
|
131
|
+
index: 0,
|
|
132
|
+
endIndex: rule.selector.length,
|
|
133
|
+
result: postcssResult,
|
|
134
|
+
ruleName,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (selectorAST.nodes?.length !== 2) {
|
|
141
|
+
if (context.fix) {
|
|
142
|
+
selectorAST.nodes?.[0]?.remove();
|
|
143
|
+
fixSelector(rule, selectorsAST, selectorAST);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
stylelint.utils.report({
|
|
148
|
+
message: messages.rejectedNestingSelectorIncorrectShape(),
|
|
149
|
+
node: rule,
|
|
150
|
+
index: 0,
|
|
151
|
+
endIndex: rule.selector.length,
|
|
152
|
+
result: postcssResult,
|
|
153
|
+
ruleName,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (selectorAST.nodes?.[1]?.type !== 'pseudo') {
|
|
160
|
+
if (context.fix) {
|
|
161
|
+
selectorAST.nodes?.[0]?.remove();
|
|
162
|
+
fixSelector(rule, selectorsAST, selectorAST);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
stylelint.utils.report({
|
|
167
|
+
message: messages.rejectedMustEndWithPseudo(),
|
|
168
|
+
node: rule,
|
|
169
|
+
index: 0,
|
|
170
|
+
endIndex: rule.selector.length,
|
|
171
|
+
result: postcssResult,
|
|
172
|
+
ruleName,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
function fixSelector(rule, selectorsAST, selectorAST) {
|
|
183
|
+
selectorAST.replaceWith(selectorParser.selector({
|
|
184
|
+
nodes: [
|
|
185
|
+
selectorParser.nesting(),
|
|
186
|
+
selectorParser.pseudo({
|
|
187
|
+
value: ':is',
|
|
188
|
+
nodes: [
|
|
189
|
+
selectorAST
|
|
190
|
+
]
|
|
191
|
+
}),
|
|
192
|
+
]
|
|
193
|
+
}))
|
|
194
|
+
rule.selector = selectorsAST.toString();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ruleFunction.ruleName = ruleName;
|
|
198
|
+
ruleFunction.messages = messages;
|
|
199
|
+
ruleFunction.meta = meta;
|
|
200
|
+
|
|
201
|
+
module.exports = stylelint.createPlugin(ruleName, ruleFunction);
|