@tenphi/eslint-plugin-tasty 1.5.1 → 1.6.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/README.md +90 -4
- package/dist/configs.js +3 -1
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/context.js +44 -28
- package/dist/context.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/rules/consistent-token-usage.js +88 -87
- package/dist/rules/consistent-token-usage.js.map +1 -1
- package/dist/rules/no-classname-prop.js +27 -0
- package/dist/rules/no-classname-prop.js.map +1 -0
- package/dist/rules/no-important.js +23 -28
- package/dist/rules/no-important.js.map +1 -1
- package/dist/rules/no-raw-color-values.js +40 -59
- package/dist/rules/no-raw-color-values.js.map +1 -1
- package/dist/rules/no-raw-motion-duration.js +6 -24
- package/dist/rules/no-raw-motion-duration.js.map +1 -1
- package/dist/rules/no-raw-transition-duration.js +7 -49
- package/dist/rules/no-raw-transition-duration.js.map +1 -1
- package/dist/rules/no-runtime-styles-mutation.js +2 -2
- package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
- package/dist/rules/no-state-in-selector.js +1 -1
- package/dist/rules/no-state-in-selector.js.map +1 -1
- package/dist/rules/no-style-prop.js +27 -0
- package/dist/rules/no-style-prop.js.map +1 -0
- package/dist/rules/no-style-spread.js +2 -2
- package/dist/rules/no-style-spread.js.map +1 -1
- package/dist/rules/no-styles-prop.js +7 -5
- package/dist/rules/no-styles-prop.js.map +1 -1
- package/dist/rules/prefer-auto-calc.js +5 -27
- package/dist/rules/prefer-auto-calc.js.map +1 -1
- package/dist/rules/prefer-custom-property-syntax.js +12 -31
- package/dist/rules/prefer-custom-property-syntax.js.map +1 -1
- package/dist/rules/prefer-shorthand-property.js +32 -10
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- package/dist/rules/tasty-component-props.js +33 -0
- package/dist/rules/tasty-component-props.js.map +1 -0
- package/dist/rules/valid-boolean-property.js +6 -26
- package/dist/rules/valid-boolean-property.js.map +1 -1
- package/dist/rules/valid-color-token.js +11 -26
- package/dist/rules/valid-color-token.js.map +1 -1
- package/dist/rules/valid-custom-property.js +11 -25
- package/dist/rules/valid-custom-property.js.map +1 -1
- package/dist/rules/valid-custom-unit.js +10 -27
- package/dist/rules/valid-custom-unit.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +5 -27
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-preset.js +5 -29
- package/dist/rules/valid-preset.js.map +1 -1
- package/dist/rules/valid-radius-shape.js +5 -25
- package/dist/rules/valid-radius-shape.js.map +1 -1
- package/dist/rules/valid-recipe.js +5 -20
- package/dist/rules/valid-recipe.js.map +1 -1
- package/dist/rules/valid-transition.js +5 -25
- package/dist/rules/valid-transition.js.map +1 -1
- package/dist/rules/valid-value.js +5 -31
- package/dist/rules/valid-value.js.map +1 -1
- package/dist/style-values.js +71 -0
- package/dist/style-values.js.map +1 -0
- package/dist/utils.js +1 -10
- package/dist/utils.js.map +1 -1
- package/dist/value-words.js +76 -0
- package/dist/value-words.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
-
import { LOGICAL_BORDER_STYLES } from "../constants.js";
|
|
3
|
-
import {
|
|
4
|
-
import { TastyContext
|
|
5
|
-
import {
|
|
2
|
+
import { LOGICAL_BORDER_STYLES, LOGICAL_STYLES } from "../constants.js";
|
|
3
|
+
import { getStringValue } from "../utils.js";
|
|
4
|
+
import { TastyContext } from "../context.js";
|
|
5
|
+
import { styleValueListeners } from "../style-values.js";
|
|
6
|
+
import { scanValueWords } from "../value-words.js";
|
|
7
|
+
import { replaceInStringValue } from "../fix-utils.js";
|
|
6
8
|
|
|
7
9
|
//#region src/rules/consistent-token-usage.ts
|
|
8
10
|
/**
|
|
@@ -15,6 +17,49 @@ import { replaceInStringValue, replaceStringValue } from "../fix-utils.js";
|
|
|
15
17
|
* happened to use.
|
|
16
18
|
*/
|
|
17
19
|
const BORDER_WIDTH_PROPERTIES = new Set(["border", ...LOGICAL_BORDER_STYLES]);
|
|
20
|
+
/** Enhanced handlers that convert numeric inputs to pixels. Unitless CSS stays out. */
|
|
21
|
+
const NUMERIC_PIXEL_PROPERTIES = new Set([
|
|
22
|
+
...LOGICAL_STYLES,
|
|
23
|
+
"gap",
|
|
24
|
+
"radius",
|
|
25
|
+
"outlineOffset",
|
|
26
|
+
"width",
|
|
27
|
+
"minWidth",
|
|
28
|
+
"maxWidth",
|
|
29
|
+
"height",
|
|
30
|
+
"minHeight",
|
|
31
|
+
"maxHeight",
|
|
32
|
+
"padding",
|
|
33
|
+
"paddingTop",
|
|
34
|
+
"paddingRight",
|
|
35
|
+
"paddingBottom",
|
|
36
|
+
"paddingLeft",
|
|
37
|
+
"margin",
|
|
38
|
+
"marginTop",
|
|
39
|
+
"marginRight",
|
|
40
|
+
"marginBottom",
|
|
41
|
+
"marginLeft",
|
|
42
|
+
"inset",
|
|
43
|
+
"top",
|
|
44
|
+
"right",
|
|
45
|
+
"bottom",
|
|
46
|
+
"left",
|
|
47
|
+
"border",
|
|
48
|
+
"borderTop",
|
|
49
|
+
"borderRight",
|
|
50
|
+
"borderBottom",
|
|
51
|
+
"borderLeft",
|
|
52
|
+
"scrollPadding",
|
|
53
|
+
"scrollPaddingTop",
|
|
54
|
+
"scrollPaddingRight",
|
|
55
|
+
"scrollPaddingBottom",
|
|
56
|
+
"scrollPaddingLeft",
|
|
57
|
+
"scrollMargin",
|
|
58
|
+
"scrollMarginTop",
|
|
59
|
+
"scrollMarginRight",
|
|
60
|
+
"scrollMarginBottom",
|
|
61
|
+
"scrollMarginLeft"
|
|
62
|
+
]);
|
|
18
63
|
const PX_TO_UNIT = {
|
|
19
64
|
"8px": "1x",
|
|
20
65
|
"16px": "2x",
|
|
@@ -32,7 +77,8 @@ var consistent_token_usage_default = createRule({
|
|
|
32
77
|
hasSuggestions: true,
|
|
33
78
|
docs: { description: "Suggest using design tokens and custom units instead of raw CSS values" },
|
|
34
79
|
messages: {
|
|
35
|
-
|
|
80
|
+
rawPixelValue: "Use a design token or custom unit instead of raw pixel value '{{raw}}'. For an intentional pixel value, explicitly disable tasty/consistent-token-usage on this line with a reason.",
|
|
81
|
+
preferToken: "Consider using '{{suggestion}}' instead of '{{raw}}'. For an intentional pixel value, explicitly disable tasty/consistent-token-usage on this line with a reason.",
|
|
36
82
|
replaceWithToken: "Replace '{{raw}}' with '{{suggestion}}'"
|
|
37
83
|
},
|
|
38
84
|
schema: []
|
|
@@ -41,104 +87,59 @@ var consistent_token_usage_default = createRule({
|
|
|
41
87
|
create(context) {
|
|
42
88
|
const ctx = new TastyContext(context);
|
|
43
89
|
function checkValue(property, value, node) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
suggestion
|
|
59
|
-
},
|
|
60
|
-
fix(fixer) {
|
|
61
|
-
return replaceStringValue(fixer, node, suggestion);
|
|
62
|
-
}
|
|
63
|
-
}]
|
|
90
|
+
if (/^[#$]/.test(property)) return;
|
|
91
|
+
const pixels = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const word of scanValueWords(value)) {
|
|
93
|
+
if (!/^[+-]?(?:\d*\.\d+|\d+\.?\d*)(?:e[+-]?\d+)?px$/i.test(word.value)) continue;
|
|
94
|
+
const amount = Number(word.value.slice(0, -2));
|
|
95
|
+
if (amount === 0) continue;
|
|
96
|
+
const raw = word.value;
|
|
97
|
+
const canonical = `${amount}px`;
|
|
98
|
+
const suggestion = property === "radius" && amount === 6 ? "1r" : BORDER_WIDTH_PROPERTIES.has(property) && amount === 1 ? "1bw" : PX_TO_UNIT[canonical];
|
|
99
|
+
const edits = pixels.get(raw) ?? [];
|
|
100
|
+
edits.push({
|
|
101
|
+
start: word.offset,
|
|
102
|
+
end: word.offset + raw.length,
|
|
103
|
+
replacement: suggestion ?? ""
|
|
64
104
|
});
|
|
65
|
-
|
|
105
|
+
pixels.set(raw, edits);
|
|
66
106
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
data: {
|
|
78
|
-
raw: "6px",
|
|
79
|
-
suggestion: "1r"
|
|
80
|
-
},
|
|
81
|
-
fix(fixer) {
|
|
82
|
-
return replaceStringValue(fixer, node, "1r");
|
|
83
|
-
}
|
|
84
|
-
}]
|
|
85
|
-
});
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (BORDER_WIDTH_PROPERTIES.has(property) && trimmed.includes("1px")) {
|
|
89
|
-
const edits = [];
|
|
90
|
-
const re = /\b1px\b/g;
|
|
91
|
-
let match;
|
|
92
|
-
while ((match = re.exec(value)) !== null) edits.push({
|
|
93
|
-
start: match.index,
|
|
94
|
-
end: match.index + match[0].length,
|
|
95
|
-
replacement: "1bw"
|
|
96
|
-
});
|
|
97
|
-
if (edits.length === 0) return;
|
|
107
|
+
for (const [raw, edits] of pixels) {
|
|
108
|
+
const suggestion = edits[0].replacement;
|
|
109
|
+
if (!suggestion) {
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
messageId: "rawPixelValue",
|
|
113
|
+
data: { raw }
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
98
117
|
context.report({
|
|
99
118
|
node,
|
|
100
119
|
messageId: "preferToken",
|
|
101
120
|
data: {
|
|
102
|
-
suggestion
|
|
103
|
-
raw
|
|
121
|
+
suggestion,
|
|
122
|
+
raw
|
|
104
123
|
},
|
|
105
124
|
suggest: [{
|
|
106
125
|
messageId: "replaceWithToken",
|
|
107
126
|
data: {
|
|
108
|
-
raw
|
|
109
|
-
suggestion
|
|
127
|
+
raw,
|
|
128
|
+
suggestion
|
|
110
129
|
},
|
|
111
|
-
fix(fixer) {
|
|
112
|
-
return replaceInStringValue(fixer, node, edits, context.sourceCode);
|
|
113
|
-
}
|
|
130
|
+
fix: (fixer) => getStringValue(node) === null ? fixer.replaceText(node, `'${suggestion}'`) : replaceInStringValue(fixer, node, edits, context.sourceCode)
|
|
114
131
|
}]
|
|
115
132
|
});
|
|
116
133
|
}
|
|
117
134
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
const str = getStringValue(prop.value);
|
|
125
|
-
if (str) {
|
|
126
|
-
checkValue(key, str, prop.value);
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (prop.value.type === "ObjectExpression" && ctx.isStateMap(prop.value, prop)) for (const stateProp of prop.value.properties) {
|
|
130
|
-
if (stateProp.type !== "Property") continue;
|
|
131
|
-
const stateStr = getStringValue(stateProp.value);
|
|
132
|
-
if (stateStr) checkValue(key, stateStr, stateProp.value);
|
|
133
|
-
}
|
|
135
|
+
return styleValueListeners(ctx, (property, node) => {
|
|
136
|
+
const value = getStringValue(node);
|
|
137
|
+
if (value !== null) checkValue(property, value, node);
|
|
138
|
+
else if (NUMERIC_PIXEL_PROPERTIES.has(property)) {
|
|
139
|
+
if (node.type === "Literal" && typeof node.value === "number") checkValue(property, `${node.value}px`, node);
|
|
140
|
+
else if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+") && node.argument.type === "Literal" && typeof node.argument.value === "number") checkValue(property, `${node.operator === "-" ? -node.argument.value : node.argument.value}px`, node);
|
|
134
141
|
}
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
ImportDeclaration(node) {
|
|
138
|
-
ctx.trackImport(node);
|
|
139
|
-
},
|
|
140
|
-
...styleObjectListeners(handleStyleObject)
|
|
141
|
-
};
|
|
142
|
+
});
|
|
142
143
|
}
|
|
143
144
|
});
|
|
144
145
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consistent-token-usage.js","names":[],"sources":["../../src/rules/consistent-token-usage.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext
|
|
1
|
+
{"version":3,"file":"consistent-token-usage.js","names":[],"sources":["../../src/rules/consistent-token-usage.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { styleValueListeners } from '../style-values.js';\nimport { LOGICAL_BORDER_STYLES, LOGICAL_STYLES } from '../constants.js';\nimport { getStringValue } from '../utils.js';\nimport { replaceInStringValue } from '../fix-utils.js';\nimport { scanValueWords } from '../value-words.js';\n\ntype MessageIds = 'preferToken' | 'rawPixelValue' | 'replaceWithToken';\n\n/**\n * Properties whose value carries a border width, so a `1px` in one is the\n * `1bw` token spelled out.\n *\n * The logical axes belong here for the same reason `border` does: they take the\n * same width/style/colour value, parsed by the same `parseBorderValue`. Leaving\n * them out would make the advice depend on which axis vocabulary the author\n * happened to use.\n */\nconst BORDER_WIDTH_PROPERTIES = new Set<string>([\n 'border',\n ...LOGICAL_BORDER_STYLES,\n]);\n\n/** Enhanced handlers that convert numeric inputs to pixels. Unitless CSS stays out. */\nconst NUMERIC_PIXEL_PROPERTIES = new Set<string>([\n ...LOGICAL_STYLES,\n 'gap',\n 'radius',\n 'outlineOffset',\n 'width',\n 'minWidth',\n 'maxWidth',\n 'height',\n 'minHeight',\n 'maxHeight',\n 'padding',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'inset',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'border',\n 'borderTop',\n 'borderRight',\n 'borderBottom',\n 'borderLeft',\n 'scrollPadding',\n 'scrollPaddingTop',\n 'scrollPaddingRight',\n 'scrollPaddingBottom',\n 'scrollPaddingLeft',\n 'scrollMargin',\n 'scrollMarginTop',\n 'scrollMarginRight',\n 'scrollMarginBottom',\n 'scrollMarginLeft',\n]);\n\nconst PX_TO_UNIT: Record<string, string> = {\n '8px': '1x',\n '16px': '2x',\n '24px': '3x',\n '32px': '4x',\n '40px': '5x',\n '48px': '6x',\n '56px': '7x',\n '64px': '8x',\n};\n\nexport default createRule<[], MessageIds>({\n name: 'consistent-token-usage',\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'Suggest using design tokens and custom units instead of raw CSS values',\n },\n messages: {\n rawPixelValue:\n \"Use a design token or custom unit instead of raw pixel value '{{raw}}'. For an intentional pixel value, explicitly disable tasty/consistent-token-usage on this line with a reason.\",\n preferToken:\n \"Consider using '{{suggestion}}' instead of '{{raw}}'. For an intentional pixel value, explicitly disable tasty/consistent-token-usage on this line with a reason.\",\n replaceWithToken: \"Replace '{{raw}}' with '{{suggestion}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(\n property: string,\n value: string,\n node: TSESTree.Node,\n ): void {\n // Definitions establish the scale itself; quoted text and URLs are opaque.\n if (/^[#$]/.test(property)) return;\n const pixels = new Map<\n string,\n { start: number; end: number; replacement: string }[]\n >();\n for (const word of scanValueWords(value)) {\n if (!/^[+-]?(?:\\d*\\.\\d+|\\d+\\.?\\d*)(?:e[+-]?\\d+)?px$/i.test(word.value))\n continue;\n const amount = Number(word.value.slice(0, -2));\n if (amount === 0) continue;\n const raw = word.value;\n const canonical = `${amount}px`;\n const suggestion =\n property === 'radius' && amount === 6\n ? '1r'\n : BORDER_WIDTH_PROPERTIES.has(property) && amount === 1\n ? '1bw'\n : PX_TO_UNIT[canonical];\n const edits = pixels.get(raw) ?? [];\n edits.push({\n start: word.offset,\n end: word.offset + raw.length,\n replacement: suggestion ?? '',\n });\n pixels.set(raw, edits);\n }\n for (const [raw, edits] of pixels) {\n const suggestion = edits[0].replacement;\n if (!suggestion) {\n context.report({ node, messageId: 'rawPixelValue', data: { raw } });\n continue;\n }\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion, raw },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw, suggestion },\n fix: (fixer) =>\n getStringValue(node) === null\n ? fixer.replaceText(node, `'${suggestion}'`)\n : replaceInStringValue(\n fixer,\n node,\n edits,\n context.sourceCode,\n ),\n },\n ],\n });\n }\n }\n\n return styleValueListeners(ctx, (property, node) => {\n const value = getStringValue(node);\n if (value !== null) {\n checkValue(property, value, node);\n } else if (NUMERIC_PIXEL_PROPERTIES.has(property)) {\n if (node.type === 'Literal' && typeof node.value === 'number') {\n checkValue(property, `${node.value}px`, node);\n } else if (\n node.type === 'UnaryExpression' &&\n (node.operator === '-' || node.operator === '+') &&\n node.argument.type === 'Literal' &&\n typeof node.argument.value === 'number'\n ) {\n const amount =\n node.operator === '-' ? -node.argument.value : node.argument.value;\n checkValue(property, `${amount}px`, node);\n }\n }\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,MAAM,0BAA0B,IAAI,IAAY,CAC9C,UACA,GAAG,sBACJ,CAAC;;AAGF,MAAM,2BAA2B,IAAI,IAAY;CAC/C,GAAG;CACH;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,aAAqC;CACzC,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACT;AAED,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,0EACH;EACD,UAAU;GACR,eACE;GACF,aACE;GACF,kBAAkB;GACnB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;AAEN,OAAI,QAAQ,KAAK,SAAS,CAAE;GAC5B,MAAM,yBAAS,IAAI,KAGhB;AACH,QAAK,MAAM,QAAQ,eAAe,MAAM,EAAE;AACxC,QAAI,CAAC,iDAAiD,KAAK,KAAK,MAAM,CACpE;IACF,MAAM,SAAS,OAAO,KAAK,MAAM,MAAM,GAAG,GAAG,CAAC;AAC9C,QAAI,WAAW,EAAG;IAClB,MAAM,MAAM,KAAK;IACjB,MAAM,YAAY,GAAG,OAAO;IAC5B,MAAM,aACJ,aAAa,YAAY,WAAW,IAChC,OACA,wBAAwB,IAAI,SAAS,IAAI,WAAW,IAClD,QACA,WAAW;IACnB,MAAM,QAAQ,OAAO,IAAI,IAAI,IAAI,EAAE;AACnC,UAAM,KAAK;KACT,OAAO,KAAK;KACZ,KAAK,KAAK,SAAS,IAAI;KACvB,aAAa,cAAc;KAC5B,CAAC;AACF,WAAO,IAAI,KAAK,MAAM;;AAExB,QAAK,MAAM,CAAC,KAAK,UAAU,QAAQ;IACjC,MAAM,aAAa,MAAM,GAAG;AAC5B,QAAI,CAAC,YAAY;AACf,aAAQ,OAAO;MAAE;MAAM,WAAW;MAAiB,MAAM,EAAE,KAAK;MAAE,CAAC;AACnE;;AAEF,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAY;MAAK;KACzB,SAAS,CACP;MACE,WAAW;MACX,MAAM;OAAE;OAAK;OAAY;MACzB,MAAM,UACJ,eAAe,KAAK,KAAK,OACrB,MAAM,YAAY,MAAM,IAAI,WAAW,GAAG,GAC1C,qBACE,OACA,MACA,OACA,QAAQ,WACT;MACR,CACF;KACF,CAAC;;;AAIN,SAAO,oBAAoB,MAAM,UAAU,SAAS;GAClD,MAAM,QAAQ,eAAe,KAAK;AAClC,OAAI,UAAU,KACZ,YAAW,UAAU,OAAO,KAAK;YACxB,yBAAyB,IAAI,SAAS,EAC/C;QAAI,KAAK,SAAS,aAAa,OAAO,KAAK,UAAU,SACnD,YAAW,UAAU,GAAG,KAAK,MAAM,KAAK,KAAK;aAE7C,KAAK,SAAS,sBACb,KAAK,aAAa,OAAO,KAAK,aAAa,QAC5C,KAAK,SAAS,SAAS,aACvB,OAAO,KAAK,SAAS,UAAU,SAI/B,YAAW,UAAU,GADnB,KAAK,aAAa,MAAM,CAAC,KAAK,SAAS,QAAQ,KAAK,SAAS,MAChC,KAAK,KAAK;;IAG7C;;CAEL,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { TastyContext } from "../context.js";
|
|
3
|
+
import { tastyComponentPropListeners } from "./tasty-component-props.js";
|
|
4
|
+
|
|
5
|
+
//#region src/rules/no-classname-prop.ts
|
|
6
|
+
var no_classname_prop_default = createRule({
|
|
7
|
+
name: "no-classname-prop",
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: { description: "Prefer Tasty styling and data-element identities over className" },
|
|
11
|
+
messages: { noClassNameProp: "Avoid the 'className' prop on Tasty components. Keep styling in Tasty; for sub-element styling, use data-element=\"Name\" and define the matching capitalized sub-element in the parent's styles. If a third-party library requires classes, explicitly disable tasty/no-classname-prop for this usage with an ESLint comment and a reason." },
|
|
12
|
+
schema: []
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create(context) {
|
|
16
|
+
return tastyComponentPropListeners(new TastyContext(context), (name, node) => {
|
|
17
|
+
if (name === "className") context.report({
|
|
18
|
+
node,
|
|
19
|
+
messageId: "noClassNameProp"
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { no_classname_prop_default as default };
|
|
27
|
+
//# sourceMappingURL=no-classname-prop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-classname-prop.js","names":[],"sources":["../../src/rules/no-classname-prop.ts"],"sourcesContent":["import { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { tastyComponentPropListeners } from './tasty-component-props.js';\n\nexport default createRule<[], 'noClassNameProp'>({\n name: 'no-classname-prop',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Prefer Tasty styling and data-element identities over className',\n },\n messages: {\n noClassNameProp:\n \"Avoid the 'className' prop on Tasty components. Keep styling in Tasty; for sub-element styling, use data-element=\\\"Name\\\" and define the matching capitalized sub-element in the parent's styles. If a third-party library requires classes, explicitly disable tasty/no-classname-prop for this usage with an ESLint comment and a reason.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n return tastyComponentPropListeners(ctx, (name, node) => {\n if (name === 'className')\n context.report({ node, messageId: 'noClassNameProp' });\n });\n },\n});\n"],"mappings":";;;;;AAIA,gCAAe,WAAkC;CAC/C,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,mEACH;EACD,UAAU,EACR,iBACE,+UACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;AAEd,SAAO,4BADK,IAAI,aAAa,QAAQ,GACI,MAAM,SAAS;AACtD,OAAI,SAAS,YACX,SAAQ,OAAO;IAAE;IAAM,WAAW;IAAmB,CAAC;IACxD;;CAEL,CAAC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { getStringValue } from "../utils.js";
|
|
3
|
-
import { TastyContext
|
|
4
|
-
import {
|
|
3
|
+
import { TastyContext } from "../context.js";
|
|
4
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
5
|
+
import { maskValueLiterals } from "../value-words.js";
|
|
6
|
+
import { replaceInStringValue } from "../fix-utils.js";
|
|
5
7
|
|
|
6
8
|
//#region src/rules/no-important.ts
|
|
7
9
|
var no_important_default = createRule({
|
|
@@ -18,33 +20,26 @@ var no_important_default = createRule({
|
|
|
18
20
|
const ctx = new TastyContext(context);
|
|
19
21
|
function checkNode(node) {
|
|
20
22
|
const str = getStringValue(node);
|
|
21
|
-
if (str
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
23
|
+
if (!str) return;
|
|
24
|
+
const edits = [...maskValueLiterals(str).matchAll(/!important\b(?!-)/gi)].map((match) => {
|
|
25
|
+
let start = match.index;
|
|
26
|
+
while (start > 0 && /\s/.test(str[start - 1])) start--;
|
|
27
|
+
return {
|
|
28
|
+
start,
|
|
29
|
+
end: match.index + match[0].length,
|
|
30
|
+
replacement: ""
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
if (!edits.length) return;
|
|
34
|
+
context.report({
|
|
35
|
+
node,
|
|
36
|
+
messageId: "noImportant",
|
|
37
|
+
fix: (fixer) => replaceInStringValue(fixer, node, edits, context.sourceCode)
|
|
38
|
+
});
|
|
31
39
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (prop.type !== "Property") continue;
|
|
36
|
-
checkNode(prop.value);
|
|
37
|
-
if (prop.value.type === "ObjectExpression" && ctx.isStateMap(prop.value, prop)) {
|
|
38
|
-
for (const stateProp of prop.value.properties) if (stateProp.type === "Property") checkNode(stateProp.value);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
ImportDeclaration(node) {
|
|
44
|
-
ctx.trackImport(node);
|
|
45
|
-
},
|
|
46
|
-
...styleObjectListeners(handleStyleObject)
|
|
47
|
-
};
|
|
40
|
+
return stringStyleValueListeners(ctx, (_value, node) => {
|
|
41
|
+
checkNode(node);
|
|
42
|
+
});
|
|
48
43
|
}
|
|
49
44
|
});
|
|
50
45
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-important.js","names":[],"sources":["../../src/rules/no-important.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext
|
|
1
|
+
{"version":3,"file":"no-important.js","names":[],"sources":["../../src/rules/no-important.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 { getStringValue } from '../utils.js';\nimport { replaceInStringValue } from '../fix-utils.js';\nimport { maskValueLiterals } from '../value-words.js';\n\ntype MessageIds = 'noImportant';\n\nexport default createRule<[], MessageIds>({\n name: 'no-important',\n meta: {\n type: 'problem',\n fixable: 'code',\n docs: {\n description: 'Disallow !important in tasty style values',\n },\n messages: {\n noImportant:\n 'Do not use !important in tasty styles. The tasty system manages specificity via doubled selectors and state ordering.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkNode(node: TSESTree.Node): void {\n const str = getStringValue(node);\n if (!str) return;\n const masked = maskValueLiterals(str);\n const edits = [...masked.matchAll(/!important\\b(?!-)/gi)].map((match) => {\n let start = match.index;\n while (start > 0 && /\\s/.test(str[start - 1])) start--;\n return { start, end: match.index + match[0].length, replacement: '' };\n });\n if (!edits.length) return;\n context.report({\n node,\n messageId: 'noImportant',\n fix: (fixer) =>\n replaceInStringValue(fixer, node, edits, context.sourceCode),\n });\n }\n\n return stringStyleValueListeners(ctx, (_value, node) => {\n checkNode(node);\n });\n },\n});\n"],"mappings":";;;;;;;;AAUA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aAAa,6CACd;EACD,UAAU,EACR,aACE,yHACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,UAAU,MAA2B;GAC5C,MAAM,MAAM,eAAe,KAAK;AAChC,OAAI,CAAC,IAAK;GAEV,MAAM,QAAQ,CAAC,GADA,kBAAkB,IAAI,CACZ,SAAS,sBAAsB,CAAC,CAAC,KAAK,UAAU;IACvE,IAAI,QAAQ,MAAM;AAClB,WAAO,QAAQ,KAAK,KAAK,KAAK,IAAI,QAAQ,GAAG,CAAE;AAC/C,WAAO;KAAE;KAAO,KAAK,MAAM,QAAQ,MAAM,GAAG;KAAQ,aAAa;KAAI;KACrE;AACF,OAAI,CAAC,MAAM,OAAQ;AACnB,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,UACJ,qBAAqB,OAAO,MAAM,OAAO,QAAQ,WAAW;IAC/D,CAAC;;AAGJ,SAAO,0BAA0B,MAAM,QAAQ,SAAS;AACtD,aAAU,KAAK;IACf;;CAEL,CAAC"}
|
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { COLOR_BEARING_PROPERTIES, NAMED_CSS_COLORS } from "../constants.js";
|
|
3
|
-
import {
|
|
4
|
-
import { TastyContext
|
|
3
|
+
import { getStringValue, isRawHexColor } from "../utils.js";
|
|
4
|
+
import { TastyContext } from "../context.js";
|
|
5
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
6
|
+
import { scanValueWords } from "../value-words.js";
|
|
5
7
|
|
|
6
8
|
//#region src/rules/no-raw-color-values.ts
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
const COLOR_FUNCTIONS = new Set([
|
|
10
|
+
"rgb",
|
|
11
|
+
"rgba",
|
|
12
|
+
"hsl",
|
|
13
|
+
"hsla",
|
|
14
|
+
"hwb",
|
|
15
|
+
"lab",
|
|
16
|
+
"lch",
|
|
17
|
+
"oklab",
|
|
18
|
+
"oklch",
|
|
19
|
+
"okhsl",
|
|
20
|
+
"okhsv",
|
|
21
|
+
"okhst",
|
|
22
|
+
"color",
|
|
23
|
+
"device-cmyk",
|
|
24
|
+
"light-dark"
|
|
25
|
+
]);
|
|
10
26
|
var no_raw_color_values_default = createRule({
|
|
11
27
|
name: "no-raw-color-values",
|
|
12
28
|
meta: {
|
|
13
29
|
type: "suggestion",
|
|
14
30
|
docs: { description: "Suggest using color tokens instead of raw hex/rgb/okhsl/named colors" },
|
|
15
31
|
messages: {
|
|
16
|
-
rawHexColor: "Use a color token instead of raw hex color '{{value}}'.",
|
|
17
|
-
rawColorFunction: "Use a color token instead of raw {{func}}() color.",
|
|
18
|
-
rawNamedColor: "Use a color token instead of raw named color '{{name}}'."
|
|
32
|
+
rawHexColor: "Use a color token instead of raw hex color '{{value}}'. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason.",
|
|
33
|
+
rawColorFunction: "Use a color token instead of raw {{func}}() color. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason.",
|
|
34
|
+
rawNamedColor: "Use a color token instead of raw named color '{{name}}'. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason."
|
|
19
35
|
},
|
|
20
36
|
schema: []
|
|
21
37
|
},
|
|
@@ -38,61 +54,26 @@ var no_raw_color_values_default = createRule({
|
|
|
38
54
|
return false;
|
|
39
55
|
}
|
|
40
56
|
function checkValue(value, node, propertyKey) {
|
|
41
|
-
if (isInTokenDefinition(node)) return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
4,
|
|
49
|
-
6,
|
|
50
|
-
8
|
|
51
|
-
].includes(hex.length)) context.report({
|
|
52
|
-
node,
|
|
53
|
-
messageId: "rawHexColor",
|
|
54
|
-
data: { value: match[0] }
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
COLOR_FUNC_REGEX.lastIndex = 0;
|
|
58
|
-
while ((match = COLOR_FUNC_REGEX.exec(value)) !== null) context.report({
|
|
57
|
+
if (isInTokenDefinition(node) || propertyKey && /^[#$]/.test(propertyKey)) return;
|
|
58
|
+
for (const word of scanValueWords(value)) if (isRawHexColor(word.value)) context.report({
|
|
59
|
+
node,
|
|
60
|
+
messageId: "rawHexColor",
|
|
61
|
+
data: { value: word.value }
|
|
62
|
+
});
|
|
63
|
+
else if (word.isFunction && COLOR_FUNCTIONS.has(word.value.toLowerCase())) context.report({
|
|
59
64
|
node,
|
|
60
65
|
messageId: "rawColorFunction",
|
|
61
|
-
data: { func:
|
|
66
|
+
data: { func: word.value }
|
|
67
|
+
});
|
|
68
|
+
else if (!word.isFunction && propertyKey && COLOR_BEARING_PROPERTIES.has(propertyKey) && NAMED_CSS_COLORS.has(word.value.toLowerCase())) context.report({
|
|
69
|
+
node,
|
|
70
|
+
messageId: "rawNamedColor",
|
|
71
|
+
data: { name: word.value }
|
|
62
72
|
});
|
|
63
|
-
if (propertyKey && COLOR_BEARING_PROPERTIES.has(propertyKey)) {
|
|
64
|
-
NAMED_COLOR_REGEX.lastIndex = 0;
|
|
65
|
-
while ((match = NAMED_COLOR_REGEX.exec(value)) !== null) {
|
|
66
|
-
const prev = match.index > 0 ? value[match.index - 1] : "";
|
|
67
|
-
if (prev === "#" || prev === "$") continue;
|
|
68
|
-
context.report({
|
|
69
|
-
node,
|
|
70
|
-
messageId: "rawNamedColor",
|
|
71
|
-
data: { name: match[1] }
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function handleStyleObject(node) {
|
|
77
|
-
if (!ctx.isStyleObject(node)) return;
|
|
78
|
-
for (const prop of node.properties) {
|
|
79
|
-
if (prop.type !== "Property") continue;
|
|
80
|
-
const key = getKeyName(prop.key);
|
|
81
|
-
const str = getStringValue(prop.value);
|
|
82
|
-
if (str) checkValue(str, prop.value, key);
|
|
83
|
-
if (prop.value.type === "ObjectExpression" && ctx.isStateMap(prop.value, prop)) for (const stateProp of prop.value.properties) {
|
|
84
|
-
if (stateProp.type !== "Property") continue;
|
|
85
|
-
const stateStr = getStringValue(stateProp.value);
|
|
86
|
-
if (stateStr) checkValue(stateStr, stateProp.value, key);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
73
|
}
|
|
90
|
-
return {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
...styleObjectListeners(handleStyleObject)
|
|
95
|
-
};
|
|
74
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
75
|
+
checkValue(value, node, property);
|
|
76
|
+
});
|
|
96
77
|
}
|
|
97
78
|
});
|
|
98
79
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-raw-color-values.js","names":[],"sources":["../../src/rules/no-raw-color-values.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext
|
|
1
|
+
{"version":3,"file":"no-raw-color-values.js","names":[],"sources":["../../src/rules/no-raw-color-values.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 { getStringValue, isRawHexColor } from '../utils.js';\nimport { scanValueWords } from '../value-words.js';\nimport { COLOR_BEARING_PROPERTIES, NAMED_CSS_COLORS } from '../constants.js';\n\ntype MessageIds = 'rawHexColor' | 'rawColorFunction' | 'rawNamedColor';\n\nconst COLOR_FUNCTIONS = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'okhsl',\n 'okhsv',\n 'okhst',\n 'color',\n 'device-cmyk',\n 'light-dark',\n]);\n\nexport default createRule<[], MessageIds>({\n name: 'no-raw-color-values',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Suggest using color tokens instead of raw hex/rgb/okhsl/named colors',\n },\n messages: {\n rawHexColor:\n \"Use a color token instead of raw hex color '{{value}}'. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason.\",\n rawColorFunction:\n 'Use a color token instead of raw {{func}}() color. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason.',\n rawNamedColor:\n \"Use a color token instead of raw named color '{{name}}'. For an intentional one-off color, explicitly disable tasty/no-raw-color-values on this line with a reason.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function isInTokenDefinition(node: TSESTree.Node): boolean {\n // Check if this is inside a :root or token-defining context\n let current: TSESTree.Node | undefined = node;\n while (current) {\n if (current.type === 'CallExpression') {\n const imp = ctx.isTastyCall(current);\n if (imp && imp.importedName === 'tastyStatic') {\n const firstArg = current.arguments[0];\n const selectorStr = getStringValue(firstArg);\n if (selectorStr === ':root') return true;\n }\n break;\n }\n current = current.parent;\n }\n return false;\n }\n\n function checkValue(\n value: string,\n node: TSESTree.Node,\n propertyKey?: string | null,\n ): void {\n if (\n isInTokenDefinition(node) ||\n (propertyKey && /^[#$]/.test(propertyKey))\n )\n return;\n for (const word of scanValueWords(value)) {\n if (isRawHexColor(word.value)) {\n context.report({\n node,\n messageId: 'rawHexColor',\n data: { value: word.value },\n });\n } else if (\n word.isFunction &&\n COLOR_FUNCTIONS.has(word.value.toLowerCase())\n ) {\n context.report({\n node,\n messageId: 'rawColorFunction',\n data: { func: word.value },\n });\n } else if (\n !word.isFunction &&\n propertyKey &&\n COLOR_BEARING_PROPERTIES.has(propertyKey) &&\n NAMED_CSS_COLORS.has(word.value.toLowerCase())\n ) {\n context.report({\n node,\n messageId: 'rawNamedColor',\n data: { name: word.value },\n });\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n checkValue(value, node, property);\n });\n },\n});\n"],"mappings":";;;;;;;;AAUA,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,kCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,wEACH;EACD,UAAU;GACR,aACE;GACF,kBACE;GACF,eACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,oBAAoB,MAA8B;GAEzD,IAAI,UAAqC;AACzC,UAAO,SAAS;AACd,QAAI,QAAQ,SAAS,kBAAkB;KACrC,MAAM,MAAM,IAAI,YAAY,QAAQ;AACpC,SAAI,OAAO,IAAI,iBAAiB,eAAe;MAC7C,MAAM,WAAW,QAAQ,UAAU;AAEnC,UADoB,eAAe,SAAS,KACxB,QAAS,QAAO;;AAEtC;;AAEF,cAAU,QAAQ;;AAEpB,UAAO;;EAGT,SAAS,WACP,OACA,MACA,aACM;AACN,OACE,oBAAoB,KAAK,IACxB,eAAe,QAAQ,KAAK,YAAY,CAEzC;AACF,QAAK,MAAM,QAAQ,eAAe,MAAM,CACtC,KAAI,cAAc,KAAK,MAAM,CAC3B,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,OAAO,KAAK,OAAO;IAC5B,CAAC;YAEF,KAAK,cACL,gBAAgB,IAAI,KAAK,MAAM,aAAa,CAAC,CAE7C,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,MAAM,KAAK,OAAO;IAC3B,CAAC;YAEF,CAAC,KAAK,cACN,eACA,yBAAyB,IAAI,YAAY,IACzC,iBAAiB,IAAI,KAAK,MAAM,aAAa,CAAC,CAE9C,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,MAAM,KAAK,OAAO;IAC3B,CAAC;;AAKR,SAAO,0BAA0B,MAAM,OAAO,MAAM,aAAa;AAC/D,cAAW,OAAO,MAAM,SAAS;IACjC;;CAEL,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { TastyContext } from "../context.js";
|
|
3
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
4
4
|
import { scanTokens } from "../parsers/utils.js";
|
|
5
5
|
import { replaceInStringValue } from "../fix-utils.js";
|
|
6
6
|
import { getDurationTokens, hasRawTimeInExpression, isNonzeroTime, isTimeExpression, isTimeLiteral, isZeroTime } from "./motion-duration-utils.js";
|
|
@@ -18,7 +18,7 @@ var no_raw_motion_duration_default = createRule({
|
|
|
18
18
|
hasSuggestions: true,
|
|
19
19
|
docs: { description: "Suggest motion tokens for animation and explicit CSS duration properties" },
|
|
20
20
|
messages: {
|
|
21
|
-
rawMotionDuration: "{{property}} duration '{{duration}}' is hardcoded. Use a duration token.",
|
|
21
|
+
rawMotionDuration: "{{property}} duration '{{duration}}' is hardcoded. Use a duration token. For intentional custom timing, explicitly disable tasty/no-raw-motion-duration on this line with a reason.",
|
|
22
22
|
useDurationToken: "Replace '{{duration}}' with '{{token}}'"
|
|
23
23
|
},
|
|
24
24
|
schema: []
|
|
@@ -76,27 +76,9 @@ var no_raw_motion_duration_default = createRule({
|
|
|
76
76
|
}
|
|
77
77
|
checkGroup(group, property, node);
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
if (prop.type !== "Property" || prop.computed) continue;
|
|
83
|
-
const key = getKeyName(prop.key);
|
|
84
|
-
if (!key || !PROPERTIES.has(key)) continue;
|
|
85
|
-
const value = getStringValue(prop.value);
|
|
86
|
-
if (value) checkValue(value, key, prop.value);
|
|
87
|
-
else if (prop.value.type === "ObjectExpression" && ctx.isStateMap(prop.value, prop)) for (const stateProp of prop.value.properties) {
|
|
88
|
-
if (stateProp.type !== "Property") continue;
|
|
89
|
-
const stateValue = getStringValue(stateProp.value);
|
|
90
|
-
if (stateValue) checkValue(stateValue, key, stateProp.value);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
ImportDeclaration(node) {
|
|
96
|
-
ctx.trackImport(node);
|
|
97
|
-
},
|
|
98
|
-
...styleObjectListeners(handleStyleObject)
|
|
99
|
-
};
|
|
79
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
80
|
+
if (PROPERTIES.has(property)) checkValue(value, property, node);
|
|
81
|
+
});
|
|
100
82
|
}
|
|
101
83
|
});
|
|
102
84
|
|