@tenphi/eslint-plugin-tasty 1.7.3 → 1.8.1
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/README.md +9 -1
- package/dist/configs.js +1 -0
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +95 -26
- package/dist/constants.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/state-key-parser.js +24 -1
- package/dist/parsers/state-key-parser.js.map +1 -1
- package/dist/parsers/value-parser.js +6 -1
- package/dist/parsers/value-parser.js.map +1 -1
- package/dist/pseudo-elements.js +85 -0
- package/dist/pseudo-elements.js.map +1 -0
- package/dist/rules/no-ignored-pseudo-element-state.js +155 -0
- package/dist/rules/no-ignored-pseudo-element-state.js.map +1 -0
- package/dist/rules/no-state-in-selector.js +30 -23
- package/dist/rules/no-state-in-selector.js.map +1 -1
- package/dist/rules/valid-value.js +2 -2
- package/dist/rules/valid-value.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName, getStringValue, unwrapExpression } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
import { getStateAtoms, parseStateKey } from "../parsers/state-key-parser.js";
|
|
5
|
+
import { pseudoElementItems, pseudoElementMismatch } from "../pseudo-elements.js";
|
|
6
|
+
import selectorParser from "postcss-selector-parser";
|
|
7
|
+
|
|
8
|
+
//#region src/rules/no-ignored-pseudo-element-state.ts
|
|
9
|
+
/**
|
|
10
|
+
* What a state atom tests that a pseudo-element has none of, or `null`.
|
|
11
|
+
* `@root()`, `@parent()`, and at-rule conditions describe other things, and
|
|
12
|
+
* `@alias` definitions are not known here.
|
|
13
|
+
*/
|
|
14
|
+
function atomMismatch(atom) {
|
|
15
|
+
if (atom.startsWith("@")) return null;
|
|
16
|
+
if (/^[:.[]/.test(atom)) return pseudoElementMismatch(atom);
|
|
17
|
+
return "modifiers";
|
|
18
|
+
}
|
|
19
|
+
/** The first item of `selectors` that ends in a pseudo-element. */
|
|
20
|
+
function firstPseudoElementItem(selectors) {
|
|
21
|
+
for (const selector of selectors ?? []) {
|
|
22
|
+
const [item] = pseudoElementItems(selector) ?? [];
|
|
23
|
+
if (item) return item;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** The items of a selector list, or `null` when it cannot be parsed. */
|
|
28
|
+
function selectorItems(selector) {
|
|
29
|
+
try {
|
|
30
|
+
return selectorParser().astSync(selector).nodes.map((item) => item.toString().trim());
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
var no_ignored_pseudo_element_state_default = createRule({
|
|
36
|
+
name: "no-ignored-pseudo-element-state",
|
|
37
|
+
meta: {
|
|
38
|
+
type: "problem",
|
|
39
|
+
docs: { description: "Report states that Tasty ignores because they test a pseudo-element for attributes, modifiers, classes, or tree position" },
|
|
40
|
+
messages: { ignoredState: "Tasty ignores state '{{state}}' on '{{selector}}': a pseudo-element has no {{mismatch}}. To condition on the element the pseudo-element belongs to, write the condition in the selector before the pseudo-element." },
|
|
41
|
+
schema: []
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: [],
|
|
44
|
+
create(context) {
|
|
45
|
+
const ctx = new TastyContext(context);
|
|
46
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
47
|
+
/** The key and object of the property whose value is `node`. */
|
|
48
|
+
function ownerEntry(node) {
|
|
49
|
+
let wrapped = node;
|
|
50
|
+
while (wrapped.parent && unwrapExpression(wrapped.parent) === node) wrapped = wrapped.parent;
|
|
51
|
+
const prop = wrapped.parent;
|
|
52
|
+
if (prop?.type !== "Property" || prop.computed) return null;
|
|
53
|
+
if (prop.parent.type !== "ObjectExpression") return null;
|
|
54
|
+
const key = getKeyName(prop.key);
|
|
55
|
+
return key === null ? null : {
|
|
56
|
+
key,
|
|
57
|
+
object: prop.parent
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** The literal selector of a selector-mode call, such as useGlobalStyles. */
|
|
61
|
+
function baseSelector(node) {
|
|
62
|
+
if (!ctx.getStyleContext(node)?.isSelectorMode) return null;
|
|
63
|
+
const call = node.parent;
|
|
64
|
+
if (call?.type !== "CallExpression" || call.arguments[1] !== node) return null;
|
|
65
|
+
return getStringValue(unwrapExpression(call.arguments[0]));
|
|
66
|
+
}
|
|
67
|
+
function checkStateMap(map, own, root) {
|
|
68
|
+
for (const prop of map.properties) {
|
|
69
|
+
if (prop.type !== "Property") continue;
|
|
70
|
+
const state = prop.computed ? getStringValue(unwrapExpression(prop.key)) : getKeyName(prop.key);
|
|
71
|
+
if (state === null || state.trim() === "" || state.trim() === "_") continue;
|
|
72
|
+
if (parseStateKey(state).errors.length > 0) continue;
|
|
73
|
+
for (const atom of getStateAtoms(state)) {
|
|
74
|
+
const selector = atom.own ? own ?? root : root;
|
|
75
|
+
if (selector === null) continue;
|
|
76
|
+
const mismatch = atomMismatch(atom.value);
|
|
77
|
+
if (mismatch === null) continue;
|
|
78
|
+
context.report({
|
|
79
|
+
node: prop.key,
|
|
80
|
+
messageId: "ignoredState",
|
|
81
|
+
data: {
|
|
82
|
+
state,
|
|
83
|
+
selector,
|
|
84
|
+
mismatch
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `suffixes` are the selectors this object's styles apply to, relative
|
|
93
|
+
* to the root (`&`); `root` is the base selector item of a selector-mode
|
|
94
|
+
* call that ends in a pseudo-element, if there is one.
|
|
95
|
+
*/
|
|
96
|
+
function checkStyleObject(node, suffixes, root) {
|
|
97
|
+
visited.add(node);
|
|
98
|
+
const own = firstPseudoElementItem(suffixes);
|
|
99
|
+
for (const prop of node.properties) {
|
|
100
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
101
|
+
const key = getKeyName(prop.key);
|
|
102
|
+
const value = unwrapExpression(prop.value);
|
|
103
|
+
if (key === null || value.type !== "ObjectExpression") continue;
|
|
104
|
+
if (ctx.isSubElementKey(key)) checkStyleObject(value, subElementSuffixes(value, key, suffixes), root);
|
|
105
|
+
else if (ctx.isNestedSelectorKey(key)) checkStyleObject(value, extend(suffixes, key, true), root);
|
|
106
|
+
else if (!ctx.isSpecialKey(key)) checkStateMap(value, own, root);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function subElementSuffixes(node, name, parents) {
|
|
110
|
+
for (const prop of node.properties) {
|
|
111
|
+
if (prop.type !== "Property") continue;
|
|
112
|
+
if ((prop.computed ? getStringValue(unwrapExpression(prop.key)) : getKeyName(prop.key)) !== "$") continue;
|
|
113
|
+
const selector = getStringValue(unwrapExpression(prop.value));
|
|
114
|
+
return selector === null ? null : extend(parents, selector, false);
|
|
115
|
+
}
|
|
116
|
+
return [name];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Selectors relative to `parents`: `&` attaches to the owner, so
|
|
120
|
+
* `&::before` under `a` is `a::before`. Other items of a nested `&` key
|
|
121
|
+
* are descendants of the owner; other `$` items stand on their own.
|
|
122
|
+
*/
|
|
123
|
+
function extend(parents, selector, nested) {
|
|
124
|
+
const items = selectorItems(selector);
|
|
125
|
+
if (items === null) return null;
|
|
126
|
+
const result = [];
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (!item.startsWith("&") && !nested) {
|
|
129
|
+
result.push(item);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (parents === null) return null;
|
|
133
|
+
for (const parent of parents) result.push(item.startsWith("&") ? parent + item.slice(1) : `${parent} ${item}`);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function handleStyleObject(node) {
|
|
138
|
+
if (visited.has(node) || !ctx.isStyleObject(node)) return;
|
|
139
|
+
const owner = ownerEntry(node);
|
|
140
|
+
if (owner && (ctx.isSubElementKey(owner.key) || ctx.isNestedSelectorKey(owner.key)) && ctx.isStyleObject(owner.object)) return;
|
|
141
|
+
const base = baseSelector(node);
|
|
142
|
+
checkStyleObject(node, ["&"], base === null ? null : firstPseudoElementItem([base]));
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
ImportDeclaration(node) {
|
|
146
|
+
ctx.trackImport(node);
|
|
147
|
+
},
|
|
148
|
+
...styleObjectListeners(handleStyleObject)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
export { no_ignored_pseudo_element_state_default as default };
|
|
155
|
+
//# sourceMappingURL=no-ignored-pseudo-element-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-ignored-pseudo-element-state.js","names":[],"sources":["../../src/rules/no-ignored-pseudo-element-state.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getStateAtoms, parseStateKey } from '../parsers/state-key-parser.js';\nimport {\n pseudoElementItems,\n pseudoElementMismatch,\n} from '../pseudo-elements.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\n/**\n * What a state atom tests that a pseudo-element has none of, or `null`.\n * `@root()`, `@parent()`, and at-rule conditions describe other things, and\n * `@alias` definitions are not known here.\n */\nfunction atomMismatch(atom: string): string | null {\n if (atom.startsWith('@')) return null;\n if (/^[:.[]/.test(atom)) return pseudoElementMismatch(atom);\n // Boolean and value modifiers (`hovered`, `theme=dark`) are attributes.\n return 'modifiers';\n}\n\n/** The first item of `selectors` that ends in a pseudo-element. */\nfunction firstPseudoElementItem(selectors: string[] | null): string | null {\n for (const selector of selectors ?? []) {\n const [item] = pseudoElementItems(selector) ?? [];\n if (item) return item;\n }\n return null;\n}\n\n/** The items of a selector list, or `null` when it cannot be parsed. */\nfunction selectorItems(selector: string): string[] | null {\n try {\n return selectorParser()\n .astSync(selector)\n .nodes.map((item) => item.toString().trim());\n } catch {\n return null;\n }\n}\n\nexport default createRule<[], 'ignoredState'>({\n name: 'no-ignored-pseudo-element-state',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Report states that Tasty ignores because they test a pseudo-element for attributes, modifiers, classes, or tree position',\n },\n messages: {\n ignoredState:\n \"Tasty ignores state '{{state}}' on '{{selector}}': a pseudo-element has no {{mismatch}}. To condition on the element the pseudo-element belongs to, write the condition in the selector before the pseudo-element.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n\n /** The key and object of the property whose value is `node`. */\n function ownerEntry(\n node: TSESTree.ObjectExpression,\n ): { key: string; object: TSESTree.ObjectExpression } | null {\n let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const prop = wrapped.parent;\n if (prop?.type !== 'Property' || prop.computed) return null;\n if (prop.parent.type !== 'ObjectExpression') return null;\n const key = getKeyName(prop.key);\n return key === null ? null : { key, object: prop.parent };\n }\n\n /** The literal selector of a selector-mode call, such as useGlobalStyles. */\n function baseSelector(node: TSESTree.ObjectExpression): string | null {\n if (!ctx.getStyleContext(node)?.isSelectorMode) return null;\n const call = node.parent;\n if (call?.type !== 'CallExpression' || call.arguments[1] !== node) {\n return null;\n }\n return getStringValue(unwrapExpression(call.arguments[0]));\n }\n\n function checkStateMap(\n map: TSESTree.ObjectExpression,\n own: string | null,\n root: string | null,\n ) {\n for (const prop of map.properties) {\n if (prop.type !== 'Property') continue;\n const state = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (state === null || state.trim() === '' || state.trim() === '_') {\n continue;\n }\n // Malformed keys are valid-state-key's to report.\n if (parseStateKey(state).errors.length > 0) continue;\n\n for (const atom of getStateAtoms(state)) {\n // Root states describe the root; @own() states the selected element.\n const selector = atom.own ? (own ?? root) : root;\n if (selector === null) continue;\n const mismatch = atomMismatch(atom.value);\n if (mismatch === null) continue;\n context.report({\n node: prop.key,\n messageId: 'ignoredState',\n data: { state, selector, mismatch },\n });\n break;\n }\n }\n }\n\n /**\n * `suffixes` are the selectors this object's styles apply to, relative\n * to the root (`&`); `root` is the base selector item of a selector-mode\n * call that ends in a pseudo-element, if there is one.\n */\n function checkStyleObject(\n node: TSESTree.ObjectExpression,\n suffixes: string[] | null,\n root: string | null,\n ) {\n visited.add(node);\n const own = firstPseudoElementItem(suffixes);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n const value = unwrapExpression(prop.value);\n if (key === null || value.type !== 'ObjectExpression') continue;\n\n if (ctx.isSubElementKey(key)) {\n checkStyleObject(\n value,\n subElementSuffixes(value, key, suffixes),\n root,\n );\n } else if (ctx.isNestedSelectorKey(key)) {\n checkStyleObject(value, extend(suffixes, key, true), root);\n } else if (!ctx.isSpecialKey(key)) {\n checkStateMap(value, own, root);\n }\n }\n }\n\n function subElementSuffixes(\n node: TSESTree.ObjectExpression,\n name: string,\n parents: string[] | null,\n ): string[] | null {\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key !== '$') continue;\n const selector = getStringValue(unwrapExpression(prop.value));\n return selector === null ? null : extend(parents, selector, false);\n }\n // Without `$`, the sub-element is its own [data-element] element.\n return [name];\n }\n\n /**\n * Selectors relative to `parents`: `&` attaches to the owner, so\n * `&::before` under `a` is `a::before`. Other items of a nested `&` key\n * are descendants of the owner; other `$` items stand on their own.\n */\n function extend(\n parents: string[] | null,\n selector: string,\n nested: boolean,\n ): string[] | null {\n const items = selectorItems(selector);\n if (items === null) return null;\n const result: string[] = [];\n for (const item of items) {\n if (!item.startsWith('&') && !nested) {\n result.push(item);\n continue;\n }\n if (parents === null) return null;\n for (const parent of parents) {\n result.push(\n item.startsWith('&') ? parent + item.slice(1) : `${parent} ${item}`,\n );\n }\n }\n return result;\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (visited.has(node) || !ctx.isStyleObject(node)) return;\n // Sub-elements and nested selectors are checked from their owner,\n // which knows the selector they extend.\n const owner = ownerEntry(node);\n if (\n owner &&\n (ctx.isSubElementKey(owner.key) ||\n ctx.isNestedSelectorKey(owner.key)) &&\n ctx.isStyleObject(owner.object)\n ) {\n return;\n }\n\n const base = baseSelector(node);\n const root = base === null ? null : firstPseudoElementItem([base]);\n checkStyleObject(node, ['&'], root);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":"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"}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { getKeyName, getStringValue, unwrapExpression } from "../utils.js";
|
|
3
3
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
import { isPseudoElement, pseudoElementMismatch, pseudoName, splitAtPseudoElement } from "../pseudo-elements.js";
|
|
4
5
|
import selectorParser from "postcss-selector-parser";
|
|
5
6
|
|
|
6
7
|
//#region src/rules/no-state-in-selector.ts
|
|
7
8
|
const STRUCTURAL_PSEUDOS = new Set([":is", ":where"]);
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
function isCondition(node) {
|
|
10
|
+
if (node.type === "attribute") return !(node.attribute === "data-element" && !node.namespace && node.operator === "=" && node.value);
|
|
11
|
+
if (node.type !== "pseudo") return false;
|
|
12
|
+
return !isPseudoElement(node) && !STRUCTURAL_PSEUDOS.has(pseudoName(node));
|
|
13
|
+
}
|
|
14
|
+
function findCondition(nodes) {
|
|
15
|
+
for (const node of nodes) {
|
|
16
|
+
if (isCondition(node)) return node.toString().trim();
|
|
17
|
+
if (selectorParser.isContainer(node)) {
|
|
18
|
+
const condition = findCondition(node.nodes);
|
|
19
|
+
if (condition !== null) return condition;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/** Whether @own() can express `node` on a pseudo-element. */
|
|
25
|
+
function isPseudoElementState(node) {
|
|
26
|
+
return node.type === "pseudo" && !pseudoElementMismatch(node.toString());
|
|
27
|
+
}
|
|
28
|
+
function firstItemCondition(item) {
|
|
29
|
+
const split = splitAtPseudoElement(item);
|
|
30
|
+
if (split === null) return findCondition(item.nodes);
|
|
31
|
+
return findCondition(item.nodes.slice(0, split.compound)) ?? findCondition(item.nodes.slice(split.last + 1).filter(isPseudoElementState));
|
|
32
|
+
}
|
|
14
33
|
function firstStateCondition(selector) {
|
|
15
34
|
let root;
|
|
16
35
|
try {
|
|
@@ -18,23 +37,11 @@ function firstStateCondition(selector) {
|
|
|
18
37
|
} catch {
|
|
19
38
|
return null;
|
|
20
39
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let name = node.value.toLowerCase();
|
|
27
|
-
if (name.includes("\\")) {
|
|
28
|
-
const colons = name.startsWith("::") ? "::" : ":";
|
|
29
|
-
const identifier = selectorParser().astSync(name.slice(colons.length)).first?.first;
|
|
30
|
-
if (identifier?.type === "tag") name = colons + identifier.value.toLowerCase();
|
|
31
|
-
}
|
|
32
|
-
if (name.startsWith("::") || LEGACY_PSEUDO_ELEMENTS.has(name) || STRUCTURAL_PSEUDOS.has(name)) return;
|
|
33
|
-
} else return;
|
|
34
|
-
condition = node.toString().trim();
|
|
35
|
-
return false;
|
|
36
|
-
});
|
|
37
|
-
return condition;
|
|
40
|
+
for (const item of root.nodes) {
|
|
41
|
+
const condition = firstItemCondition(item);
|
|
42
|
+
if (condition !== null) return condition;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
38
45
|
}
|
|
39
46
|
var no_state_in_selector_default = createRule({
|
|
40
47
|
name: "no-state-in-selector",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-state-in-selector.js","names":[],"sources":["../../src/rules/no-state-in-selector.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\nconst STRUCTURAL_PSEUDOS = new Set([':is', ':where']);\
|
|
1
|
+
{"version":3,"file":"no-state-in-selector.js","names":[],"sources":["../../src/rules/no-state-in-selector.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport {\n isPseudoElement,\n pseudoElementMismatch,\n pseudoName,\n splitAtPseudoElement,\n} from '../pseudo-elements.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\nconst STRUCTURAL_PSEUDOS = new Set([':is', ':where']);\n\nfunction isCondition(node: selectorParser.Node): boolean {\n if (node.type === 'attribute') {\n // An exact data-element identity is structural, like Tasty's uppercase\n // element names. Other attributes express conditions on that element.\n return !(\n node.attribute === 'data-element' &&\n !node.namespace &&\n node.operator === '=' &&\n node.value\n );\n }\n if (node.type !== 'pseudo') return false;\n return !isPseudoElement(node) && !STRUCTURAL_PSEUDOS.has(pseudoName(node));\n}\n\nfunction findCondition(nodes: selectorParser.Node[]): string | null {\n for (const node of nodes) {\n if (isCondition(node)) return node.toString().trim();\n // Continue into structural wrappers: :where(img:hover) still contains\n // a condition, although the wrapper itself is structural.\n if (selectorParser.isContainer(node)) {\n const condition = findCondition(node.nodes);\n if (condition !== null) return condition;\n }\n }\n return null;\n}\n\n/** Whether @own() can express `node` on a pseudo-element. */\nfunction isPseudoElementState(node: selectorParser.Node): boolean {\n return node.type === 'pseudo' && !pseudoElementMismatch(node.toString());\n}\n\nfunction firstItemCondition(item: selectorParser.Selector): string | null {\n const split = splitAtPseudoElement(item);\n if (split === null) return findCondition(item.nodes);\n\n // The item ends in a pseudo-element, which Tasty treats as the subject:\n // @own() states go after the pseudo-element, and the states it cannot have\n // are dropped. Conditions on the originating element before it, and inside\n // it (::slotted(img:hover), ::after:hover::marker), must stay in `$`.\n return (\n findCondition(item.nodes.slice(0, split.compound)) ??\n findCondition(item.nodes.slice(split.last + 1).filter(isPseudoElementState))\n );\n}\n\nfunction firstStateCondition(selector: string): string | null {\n let root: selectorParser.Root;\n try {\n root = selectorParser().astSync(selector);\n } catch {\n // This recommendation is not a CSS syntax validator. Incomplete selectors\n // must not crash lint or produce a misleading state-map recommendation.\n return null;\n }\n\n // Each item of a selector list is judged on its own.\n for (const item of root.nodes) {\n const condition = firstItemCondition(item);\n if (condition !== null) return condition;\n }\n return null;\n}\n\nexport default createRule<[], 'stateInSelector'>({\n name: 'no-state-in-selector',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Keep sub-element selectors structural and express conditions in property state maps',\n },\n messages: {\n stateInSelector:\n \"Move state condition '{{condition}}' out of the sub-element '$' selector and into property state maps. Use @own(...) for the sub-element's own state. If the condition must stay in this selector, explicitly disable tasty/no-state-in-selector on this line with a reason.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (visited.has(node) || !ctx.isStyleObject(node)) return;\n visited.add(node);\n const isSubElement = ctx.isInsideSubElement(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n // Some listeners match only the shared/JSX root. Follow its owned\n // sub-elements explicitly, without entering state maps or metadata.\n if (!prop.computed && key && ctx.isSubElementKey(key)) {\n const child = unwrapExpression(prop.value);\n if (child.type === 'ObjectExpression') handleStyleObject(child);\n }\n if (!isSubElement || key !== '$') continue;\n\n const selector = getStringValue(unwrapExpression(prop.value));\n if (selector === null) continue;\n const condition = firstStateCondition(selector);\n if (condition === null) continue;\n\n context.report({\n node: prop.value,\n messageId: 'stateInSelector',\n data: { condition },\n });\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;AAYA,MAAM,qBAAqB,IAAI,IAAI,CAAC,OAAO,SAAS,CAAC;AAErD,SAAS,YAAY,MAAoC;AACvD,KAAI,KAAK,SAAS,YAGhB,QAAO,EACL,KAAK,cAAc,kBACnB,CAAC,KAAK,aACN,KAAK,aAAa,OAClB,KAAK;AAGT,KAAI,KAAK,SAAS,SAAU,QAAO;AACnC,QAAO,CAAC,gBAAgB,KAAK,IAAI,CAAC,mBAAmB,IAAI,WAAW,KAAK,CAAC;;AAG5E,SAAS,cAAc,OAA6C;AAClE,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,YAAY,KAAK,CAAE,QAAO,KAAK,UAAU,CAAC,MAAM;AAGpD,MAAI,eAAe,YAAY,KAAK,EAAE;GACpC,MAAM,YAAY,cAAc,KAAK,MAAM;AAC3C,OAAI,cAAc,KAAM,QAAO;;;AAGnC,QAAO;;;AAIT,SAAS,qBAAqB,MAAoC;AAChE,QAAO,KAAK,SAAS,YAAY,CAAC,sBAAsB,KAAK,UAAU,CAAC;;AAG1E,SAAS,mBAAmB,MAA8C;CACxE,MAAM,QAAQ,qBAAqB,KAAK;AACxC,KAAI,UAAU,KAAM,QAAO,cAAc,KAAK,MAAM;AAMpD,QACE,cAAc,KAAK,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAClD,cAAc,KAAK,MAAM,MAAM,MAAM,OAAO,EAAE,CAAC,OAAO,qBAAqB,CAAC;;AAIhF,SAAS,oBAAoB,UAAiC;CAC5D,IAAI;AACJ,KAAI;AACF,SAAO,gBAAgB,CAAC,QAAQ,SAAS;SACnC;AAGN,SAAO;;AAIT,MAAK,MAAM,QAAQ,KAAK,OAAO;EAC7B,MAAM,YAAY,mBAAmB,KAAK;AAC1C,MAAI,cAAc,KAAM,QAAO;;AAEjC,QAAO;;AAGT,mCAAe,WAAkC;CAC/C,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,gRACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EACrC,MAAM,0BAAU,IAAI,SAAoC;EAExD,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,QAAQ,IAAI,KAAK,IAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AACnD,WAAQ,IAAI,KAAK;GACjB,MAAM,eAAe,IAAI,mBAAmB,KAAK;AAEjD,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,WAAY;IAC9B,MAAM,MAAM,KAAK,WACb,eAAe,iBAAiB,KAAK,IAAI,CAAC,GAC1C,WAAW,KAAK,IAAI;AAGxB,QAAI,CAAC,KAAK,YAAY,OAAO,IAAI,gBAAgB,IAAI,EAAE;KACrD,MAAM,QAAQ,iBAAiB,KAAK,MAAM;AAC1C,SAAI,MAAM,SAAS,mBAAoB,mBAAkB,MAAM;;AAEjE,QAAI,CAAC,gBAAgB,QAAQ,IAAK;IAElC,MAAM,WAAW,eAAe,iBAAiB,KAAK,MAAM,CAAC;AAC7D,QAAI,aAAa,KAAM;IACvB,MAAM,YAAY,oBAAoB,SAAS;AAC/C,QAAI,cAAc,KAAM;AAExB,YAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,WAAW;KACpB,CAAC;;;AAIN,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -88,8 +88,8 @@ var valid_value_default = createRule({
|
|
|
88
88
|
});
|
|
89
89
|
continue;
|
|
90
90
|
}
|
|
91
|
-
if (!expectation.acceptsColor && (token.type === "color-token" || token.type === "color-ref")) {
|
|
92
|
-
const colorName = token.type === "color-token" ? `#${token.name}` : `##${token.name}
|
|
91
|
+
if (!expectation.acceptsColor && (token.type === "color-token" || token.type === "color-ref" || token.type === "color-keyword")) {
|
|
92
|
+
const colorName = token.type === "color-token" ? `#${token.name}` : token.type === "color-ref" ? `##${token.name}` : token.raw;
|
|
93
93
|
context.report({
|
|
94
94
|
node,
|
|
95
95
|
messageId: "unexpectedColor",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken:\n \"Unknown token '{{token}}' in '{{property}}' value. Check for a typo.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.functions === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.functions)) {\n opts.knownFuncs = new Set(ctx.config.functions);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token'
|
|
1
|
+
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken:\n \"Unknown token '{{token}}' in '{{property}}' value. Check for a typo.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.functions === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.functions)) {\n opts.knownFuncs = new Set(ctx.config.functions);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens and keywords in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token' ||\n token.type === 'color-ref' ||\n token.type === 'color-keyword')\n ) {\n const colorName =\n token.type === 'color-token'\n ? `#${token.name}`\n : token.type === 'color-ref'\n ? `##${token.name}`\n : token.raw;\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: colorName },\n });\n continue;\n }\n\n // Check color functions in non-color properties\n if (\n !expectation.acceptsColor &&\n token.type === 'css-function' &&\n isColorFunction(token.name)\n ) {\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: `${token.name}()` },\n });\n continue;\n }\n\n // Skip tokens already reported via parser errors\n if (\n (token.type === 'unknown' || token.type === 'css-function') &&\n 'raw' in token &&\n token.raw &&\n erroredRaws.has(token.raw)\n ) {\n continue;\n }\n\n // Check unknown tokens against property expectations\n if (token.type === 'unknown') {\n const raw = token.raw;\n\n if (expectation.acceptsMods === true) {\n // Passthrough: accept any unknown token\n } else if (expectation.acceptsMods === false) {\n context.report({\n node,\n messageId: 'unexpectedMod',\n data: { property, mod: raw },\n });\n } else if (Array.isArray(expectation.acceptsMods)) {\n if (!expectation.acceptsMods.includes(raw)) {\n context.report({\n node,\n messageId: 'invalidMod',\n data: {\n property,\n mod: raw,\n accepted: expectation.acceptsMods.join(', '),\n },\n });\n }\n }\n }\n }\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n if (!/^[#$]/.test(property)) checkValue(value, property, node);\n });\n },\n});\n\nconst COLOR_FUNC_NAMES = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'color-mix',\n 'color-contrast',\n]);\n\nfunction isColorFunction(name: string): boolean {\n return COLOR_FUNC_NAMES.has(name);\n}\n"],"mappings":"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"}
|