@mrhenry/stylelint-mrhenry-nesting 3.0.0 → 3.1.0
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/index.mjs +68 -6
- package/package.json +4 -3
package/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import stylelint from 'stylelint';
|
|
2
2
|
import selectorParser from 'postcss-selector-parser';
|
|
3
|
+
import { compare, selectorSpecificity } from '@csstools/selector-specificity';
|
|
3
4
|
|
|
4
5
|
const ruleName = "@mrhenry/stylelint-mrhenry-nesting";
|
|
5
6
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -18,6 +19,9 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
|
18
19
|
rejectedNestingSelectorIncorrectShape: () => {
|
|
19
20
|
return `Nested selectors must be compound selectors, starting with "&" and followed by a single pseudo selector.`;
|
|
20
21
|
},
|
|
22
|
+
rejectedMixedSpecificity: () => {
|
|
23
|
+
return `Each selector of a list in a nested context take the specificity of the most specific list item. This can lead to unexpected results.`;
|
|
24
|
+
},
|
|
21
25
|
});
|
|
22
26
|
|
|
23
27
|
const meta = {
|
|
@@ -25,13 +29,29 @@ const meta = {
|
|
|
25
29
|
fixable: true,
|
|
26
30
|
};
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
/** @type {import('stylelint').Rule<true|null, Array<string | RegExp>>} */
|
|
33
|
+
const ruleFunction = (primaryOption, secondaryOption, context) => {
|
|
29
34
|
return (postcssRoot, postcssResult) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const validPrimary = stylelint.utils.validateOptions(postcssResult, ruleName, {
|
|
36
|
+
actual: primaryOption,
|
|
37
|
+
possible: [true]
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/* c8 ignore next */
|
|
41
|
+
if (!validPrimary) return;
|
|
42
|
+
|
|
43
|
+
const validSecondary = stylelint.utils.validateOptions(postcssResult, ruleName, {
|
|
44
|
+
actual: secondaryOption,
|
|
45
|
+
possible: {
|
|
46
|
+
ignoreAtRules: [isString, isRegExp],
|
|
47
|
+
},
|
|
48
|
+
optional: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/* c8 ignore next */
|
|
52
|
+
if (!validSecondary) return;
|
|
33
53
|
|
|
34
|
-
const ignoreAtRulesOptions =
|
|
54
|
+
const ignoreAtRulesOptions = secondaryOption?.ignoreAtRules ?? [];
|
|
35
55
|
|
|
36
56
|
postcssRoot.walkAtRules((atrule) => {
|
|
37
57
|
let name = atrule.name.toLowerCase();
|
|
@@ -39,7 +59,8 @@ const ruleFunction = (primaryOption, secondaryOptionObject, context) => {
|
|
|
39
59
|
name === 'media' ||
|
|
40
60
|
name === 'supports' ||
|
|
41
61
|
name === 'container' ||
|
|
42
|
-
name === 'scope'
|
|
62
|
+
name === 'scope' ||
|
|
63
|
+
name === 'starting-style'
|
|
43
64
|
) {
|
|
44
65
|
// always allowed
|
|
45
66
|
return;
|
|
@@ -84,6 +105,39 @@ const ruleFunction = (primaryOption, secondaryOptionObject, context) => {
|
|
|
84
105
|
});
|
|
85
106
|
});
|
|
86
107
|
|
|
108
|
+
postcssRoot.walkRules((rule) => {
|
|
109
|
+
const containsBlocks = rule.nodes && rule.nodes.some((node) => node.type === 'rule' || node.type === 'atrule');
|
|
110
|
+
if (!containsBlocks) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const selectorAST = selectorParser().astSync(rule.selector);
|
|
115
|
+
if (selectorAST.nodes?.length < 2) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const specificities = selectorAST.nodes.map((node) => {
|
|
120
|
+
return selectorSpecificity(node);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const specificitiesAreEqual = specificities.every((specificity) => {
|
|
124
|
+
return compare(specificity, specificities[0]) === 0;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (specificitiesAreEqual) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
stylelint.utils.report({
|
|
132
|
+
message: messages.rejectedMixedSpecificity(),
|
|
133
|
+
node: rule,
|
|
134
|
+
index: 0,
|
|
135
|
+
endIndex: rule.selector.length,
|
|
136
|
+
result: postcssResult,
|
|
137
|
+
ruleName,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
87
141
|
postcssRoot.walkRules((rule) => {
|
|
88
142
|
{
|
|
89
143
|
let rulesDepth = 1;
|
|
@@ -315,3 +369,11 @@ function getFirstCompoundOrSelf(x) {
|
|
|
315
369
|
|
|
316
370
|
return x.nodes[0];
|
|
317
371
|
}
|
|
372
|
+
|
|
373
|
+
function isRegExp(value) {
|
|
374
|
+
return value instanceof RegExp;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function isString(value) {
|
|
378
|
+
return typeof value === 'string' || value instanceof String;
|
|
379
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrhenry/stylelint-mrhenry-nesting",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Mr. Henry's preferred way of writing nested CSS",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"stylelint-plugin"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"
|
|
31
|
+
"@csstools/selector-specificity": "^3.0.1",
|
|
32
|
+
"postcss-selector-parser": "^6.0.15"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
35
|
"stylelint": "^16.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"stylelint": "^16.0
|
|
38
|
+
"stylelint": "^16.1.0"
|
|
38
39
|
}
|
|
39
40
|
}
|