@mrhenry/stylelint-mrhenry-attribute-selector-no-unknown 2.0.1 → 2.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 +38 -3
- package/package.json +4 -3
- package/validate-options.mjs +25 -0
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import stylelint from 'stylelint';
|
|
2
2
|
import selectorParser from 'postcss-selector-parser';
|
|
3
3
|
import { data } from './data.mjs';
|
|
4
|
+
import { isRecordOfStringArrays, isString } from './validate-options.mjs';
|
|
4
5
|
|
|
5
6
|
const ruleName = "@mrhenry/stylelint-mrhenry-attribute-selector-no-unknown";
|
|
6
7
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -27,7 +28,34 @@ const ruleFunction = (primaryOption, secondaryOption, context) => {
|
|
|
27
28
|
|
|
28
29
|
/* c8 ignore next */
|
|
29
30
|
if (!validPrimary) return;
|
|
30
|
-
|
|
31
|
+
|
|
32
|
+
const validSecondary = stylelint.utils.validateOptions(postcssResult, ruleName, {
|
|
33
|
+
actual: secondaryOption,
|
|
34
|
+
possible: {
|
|
35
|
+
globalAttributes: [isString],
|
|
36
|
+
attributesByTagName: isRecordOfStringArrays,
|
|
37
|
+
},
|
|
38
|
+
optional: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/* c8 ignore next */
|
|
42
|
+
if (!validSecondary) return;
|
|
43
|
+
|
|
44
|
+
const userData = {
|
|
45
|
+
/** @type {Set<string>} */
|
|
46
|
+
globalAttributes: new Set(secondaryOption?.globalAttributes ?? []),
|
|
47
|
+
/** @type {Map<string, string[]>} */
|
|
48
|
+
attributesByTagName: new Map(Object.entries(secondaryOption?.attributesByTagName ?? {})),
|
|
49
|
+
/** @type {Set<string>} */
|
|
50
|
+
allAttributes: new Set(secondaryOption?.globalAttributes ?? []),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const attributes of userData.attributesByTagName.values()) {
|
|
54
|
+
for (const attribute of attributes) {
|
|
55
|
+
userData.allAttributes.add(attribute);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
postcssRoot.walkRules((rule) => {
|
|
32
60
|
if (!rule.selector.includes('[')) {
|
|
33
61
|
return;
|
|
@@ -46,8 +74,12 @@ const ruleFunction = (primaryOption, secondaryOption, context) => {
|
|
|
46
74
|
|
|
47
75
|
// Global attributes are always allowed.
|
|
48
76
|
if (data.globalAttributes.has(attribute)) return;
|
|
77
|
+
if (userData.globalAttributes.has(attribute)) return;
|
|
49
78
|
|
|
50
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
!data.allAttributes.has(attribute) &&
|
|
81
|
+
!userData.allAttributes.has(attribute)
|
|
82
|
+
) {
|
|
51
83
|
// An unknown attribute that doesn't start with "data-" is always invalid.
|
|
52
84
|
stylelint.utils.report({
|
|
53
85
|
message: messages.expected,
|
|
@@ -64,13 +96,16 @@ const ruleFunction = (primaryOption, secondaryOption, context) => {
|
|
|
64
96
|
|
|
65
97
|
const tagName = findTagNameInCompound(node);
|
|
66
98
|
if (!tagName) {
|
|
67
|
-
// Without a tagname we
|
|
99
|
+
// Without a tagname we assume that any attribute is allowed.
|
|
68
100
|
return;
|
|
69
101
|
}
|
|
70
102
|
|
|
71
103
|
const attributesByTagName = data.attributesByTagName.get(tagName);
|
|
72
104
|
if (attributesByTagName && attributesByTagName.includes(attribute)) return;
|
|
73
105
|
|
|
106
|
+
const userAttributesByTagName = userData.attributesByTagName.get(tagName);
|
|
107
|
+
if (userAttributesByTagName && userAttributesByTagName.includes(attribute)) return;
|
|
108
|
+
|
|
74
109
|
// If the tag name is a standard element
|
|
75
110
|
// we assume that only attributes that are valid for that tag are allowed.
|
|
76
111
|
stylelint.utils.report({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrhenry/stylelint-mrhenry-attribute-selector-no-unknown",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Warn when unknown attribute selectors are used",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"index.mjs",
|
|
20
20
|
"data.mjs",
|
|
21
|
+
"validate-options.mjs",
|
|
21
22
|
"LICENSE",
|
|
22
23
|
"README.md"
|
|
23
24
|
],
|
|
@@ -26,12 +27,12 @@
|
|
|
26
27
|
"stylelint-plugin"
|
|
27
28
|
],
|
|
28
29
|
"dependencies": {
|
|
29
|
-
"postcss-selector-parser": "^6.0.
|
|
30
|
+
"postcss-selector-parser": "^6.0.15"
|
|
30
31
|
},
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"stylelint": "^16.0.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"stylelint": "^16.0
|
|
36
|
+
"stylelint": "^16.1.0"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function isString(value) {
|
|
2
|
+
return typeof value === 'string' || value instanceof String;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function isRecordOfStringArrays(value) {
|
|
6
|
+
if (!value) return false;
|
|
7
|
+
if (typeof value !== 'object') return false;
|
|
8
|
+
if (Array.isArray(value)) return false;
|
|
9
|
+
|
|
10
|
+
for (const key in value) {
|
|
11
|
+
if (Object.hasOwnProperty.call(value, key)) {
|
|
12
|
+
/* c8 ignore next */
|
|
13
|
+
if (!isString(key)) return false;
|
|
14
|
+
|
|
15
|
+
if (!value[key]) return false;
|
|
16
|
+
if (!Array.isArray(value[key])) return false;
|
|
17
|
+
|
|
18
|
+
for (const attr of value[key]) {
|
|
19
|
+
if (!isString(attr)) return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|