@tenphi/eslint-plugin-tasty 0.7.0 → 0.8.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 +9 -3
- package/dist/configs.js +9 -4
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +132 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/rules/known-property.js +1 -1
- package/dist/rules/known-property.js.map +1 -1
- package/dist/rules/no-runtime-styles-mutation.js +8 -5
- package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
- package/dist/rules/no-unknown-state-alias.js +1 -1
- package/dist/rules/no-unknown-state-alias.js.map +1 -1
- package/dist/rules/prefer-auto-calc.js +59 -0
- package/dist/rules/prefer-auto-calc.js.map +1 -0
- package/dist/rules/prefer-custom-property-syntax.js +76 -0
- package/dist/rules/prefer-custom-property-syntax.js.map +1 -0
- package/dist/rules/prefer-directional-shorthand.js +83 -0
- package/dist/rules/prefer-directional-shorthand.js.map +1 -0
- package/dist/rules/prefer-hide.js +52 -0
- package/dist/rules/prefer-hide.js.map +1 -0
- package/dist/rules/valid-color-token.js +1 -1
- package/dist/rules/valid-color-token.js.map +1 -1
- package/dist/rules/valid-custom-property.js +2 -2
- package/dist/rules/valid-custom-property.js.map +1 -1
- package/dist/rules/valid-custom-unit.js +1 -1
- package/dist/rules/valid-custom-unit.js.map +1 -1
- package/dist/rules/valid-default-state-order.js +75 -0
- package/dist/rules/valid-default-state-order.js.map +1 -0
- package/dist/rules/valid-directional-modifier.js +1 -1
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-preset.js +6 -3
- package/dist/rules/valid-preset.js.map +1 -1
- package/dist/rules/valid-recipe.js +1 -1
- package/dist/rules/valid-recipe.js.map +1 -1
- package/dist/rules/valid-transition.js +17 -2
- package/dist/rules/valid-transition.js.map +1 -1
- package/dist/rules/valid-value.js +1 -1
- package/dist/rules/valid-value.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
|
|
5
|
+
//#region src/rules/prefer-custom-property-syntax.ts
|
|
6
|
+
const VAR_REGEX = /var\(\s*--([a-zA-Z0-9_-]+)\s*(?:,\s*([^)]+))?\s*\)/g;
|
|
7
|
+
function suggestVarSyntax(name, fallback) {
|
|
8
|
+
if (name.endsWith("-color")) {
|
|
9
|
+
const tastyToken = `#${name.slice(0, -6)}`;
|
|
10
|
+
if (fallback) return `(${tastyToken}, ${fallback.trim()})`;
|
|
11
|
+
return tastyToken;
|
|
12
|
+
}
|
|
13
|
+
const propToken = `$${name}`;
|
|
14
|
+
if (fallback) return `(${propToken}, ${fallback.trim()})`;
|
|
15
|
+
return propToken;
|
|
16
|
+
}
|
|
17
|
+
var prefer_custom_property_syntax_default = createRule({
|
|
18
|
+
name: "prefer-custom-property-syntax",
|
|
19
|
+
meta: {
|
|
20
|
+
type: "suggestion",
|
|
21
|
+
docs: { description: "Suggest Tasty $prop and (#color, fallback) syntax instead of var(--prop)" },
|
|
22
|
+
messages: { preferCustomPropertySyntax: "Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'." },
|
|
23
|
+
schema: []
|
|
24
|
+
},
|
|
25
|
+
defaultOptions: [],
|
|
26
|
+
create(context) {
|
|
27
|
+
const ctx = new TastyContext(context);
|
|
28
|
+
function checkValue(value, node) {
|
|
29
|
+
VAR_REGEX.lastIndex = 0;
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = VAR_REGEX.exec(value)) !== null) {
|
|
32
|
+
const raw = match[0];
|
|
33
|
+
const name = match[1];
|
|
34
|
+
const fallback = match[2];
|
|
35
|
+
const suggestion = suggestVarSyntax(name, fallback);
|
|
36
|
+
context.report({
|
|
37
|
+
node,
|
|
38
|
+
messageId: "preferCustomPropertySyntax",
|
|
39
|
+
data: {
|
|
40
|
+
raw,
|
|
41
|
+
suggestion
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function handleStyleObject(node) {
|
|
47
|
+
if (!ctx.isStyleObject(node)) return;
|
|
48
|
+
for (const prop of node.properties) {
|
|
49
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
50
|
+
const key = getKeyName(prop.key);
|
|
51
|
+
if (key === null) continue;
|
|
52
|
+
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
53
|
+
const str = getStringValue(prop.value);
|
|
54
|
+
if (str) {
|
|
55
|
+
checkValue(str, prop.value);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
59
|
+
if (stateProp.type !== "Property") continue;
|
|
60
|
+
const stateStr = getStringValue(stateProp.value);
|
|
61
|
+
if (stateStr) checkValue(stateStr, stateProp.value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ImportDeclaration(node) {
|
|
67
|
+
ctx.trackImport(node);
|
|
68
|
+
},
|
|
69
|
+
...styleObjectListeners(handleStyleObject)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { prefer_custom_property_syntax_default as default };
|
|
76
|
+
//# sourceMappingURL=prefer-custom-property-syntax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-custom-property-syntax.js","names":[],"sources":["../../src/rules/prefer-custom-property-syntax.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'preferCustomPropertySyntax';\n\nconst VAR_REGEX = /var\\(\\s*--([a-zA-Z0-9_-]+)\\s*(?:,\\s*([^)]+))?\\s*\\)/g;\n\nfunction suggestVarSyntax(name: string, fallback?: string): string {\n if (name.endsWith('-color')) {\n const tokenName = name.slice(0, -'-color'.length);\n const tastyToken = `#${tokenName}`;\n if (fallback) {\n return `(${tastyToken}, ${fallback.trim()})`;\n }\n return tastyToken;\n }\n\n const propToken = `$${name}`;\n if (fallback) {\n return `(${propToken}, ${fallback.trim()})`;\n }\n return propToken;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-custom-property-syntax',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Suggest Tasty $prop and (#color, fallback) syntax instead of var(--prop)',\n },\n messages: {\n preferCustomPropertySyntax:\n \"Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(value: string, node: TSESTree.Node): void {\n VAR_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = VAR_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const fallback = match[2];\n const suggestion = suggestVarSyntax(name, fallback);\n\n context.report({\n node,\n messageId: 'preferCustomPropertySyntax',\n data: { raw, suggestion },\n });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,MAAM,YAAY;AAElB,SAAS,iBAAiB,MAAc,UAA2B;AACjE,KAAI,KAAK,SAAS,SAAS,EAAE;EAE3B,MAAM,aAAa,IADD,KAAK,MAAM,GAAG,GAAiB;AAEjD,MAAI,SACF,QAAO,IAAI,WAAW,IAAI,SAAS,MAAM,CAAC;AAE5C,SAAO;;CAGT,MAAM,YAAY,IAAI;AACtB,KAAI,SACF,QAAO,IAAI,UAAU,IAAI,SAAS,MAAM,CAAC;AAE3C,QAAO;;AAGT,4CAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4EACH;EACD,UAAU,EACR,4BACE,iEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WAAW,OAAe,MAA2B;AAC5D,aAAU,YAAY;GACtB,IAAI;AAEJ,WAAQ,QAAQ,UAAU,KAAK,MAAM,MAAM,MAAM;IAC/C,MAAM,MAAM,MAAM;IAClB,MAAM,OAAO,MAAM;IACnB,MAAM,WAAW,MAAM;IACvB,MAAM,aAAa,iBAAiB,MAAM,SAAS;AAEnD,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAK;MAAY;KAC1B,CAAC;;;EAIN,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAClB,QAAI,aAAa,KAAK,IAAI,CAAE;IAE5B,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,gBAAW,KAAK,KAAK,MAAM;AAC3B;;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,YAAW,UAAU,UAAU,MAAM;;;;AAO/C,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { DIRECTIONAL_MODIFIERS } from "../constants.js";
|
|
3
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
4
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
5
|
+
|
|
6
|
+
//#region src/rules/prefer-directional-shorthand.ts
|
|
7
|
+
const SIDES = [
|
|
8
|
+
"top",
|
|
9
|
+
"right",
|
|
10
|
+
"bottom",
|
|
11
|
+
"left"
|
|
12
|
+
];
|
|
13
|
+
function isZeroLike(token) {
|
|
14
|
+
const trimmed = token.trim();
|
|
15
|
+
if (trimmed === "0") return true;
|
|
16
|
+
return /^0+(?:\.0+)?(?:px|em|rem|%|x|r|cr|bw|ow|lh|sf)?$/i.test(trimmed);
|
|
17
|
+
}
|
|
18
|
+
function suggestDirectional(value) {
|
|
19
|
+
const tokens = value.trim().split(/\s+/);
|
|
20
|
+
if (tokens.length !== 4) return null;
|
|
21
|
+
const nonZero = tokens.map((token, index) => ({
|
|
22
|
+
token,
|
|
23
|
+
side: SIDES[index]
|
|
24
|
+
})).filter(({ token }) => !isZeroLike(token));
|
|
25
|
+
if (nonZero.length !== 1) return null;
|
|
26
|
+
const { token, side } = nonZero[0];
|
|
27
|
+
return `${token} ${side}`;
|
|
28
|
+
}
|
|
29
|
+
var prefer_directional_shorthand_default = createRule({
|
|
30
|
+
name: "prefer-directional-shorthand",
|
|
31
|
+
meta: {
|
|
32
|
+
type: "suggestion",
|
|
33
|
+
docs: { description: "Suggest Tasty directional shorthand instead of 4-value CSS box syntax with zero placeholders" },
|
|
34
|
+
messages: { preferDirectionalShorthand: "'{{property}}' value '{{raw}}' only sets one side. Use the directional shorthand '{{suggestion}}' (e.g. '1x bottom') instead." },
|
|
35
|
+
schema: []
|
|
36
|
+
},
|
|
37
|
+
defaultOptions: [],
|
|
38
|
+
create(context) {
|
|
39
|
+
const ctx = new TastyContext(context);
|
|
40
|
+
function checkValue(property, value, node) {
|
|
41
|
+
const suggestion = suggestDirectional(value);
|
|
42
|
+
if (!suggestion) return;
|
|
43
|
+
context.report({
|
|
44
|
+
node,
|
|
45
|
+
messageId: "preferDirectionalShorthand",
|
|
46
|
+
data: {
|
|
47
|
+
property,
|
|
48
|
+
raw: value,
|
|
49
|
+
suggestion
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function handleStyleObject(node) {
|
|
54
|
+
if (!ctx.isStyleObject(node)) return;
|
|
55
|
+
for (const prop of node.properties) {
|
|
56
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
57
|
+
const key = getKeyName(prop.key);
|
|
58
|
+
if (key === null) continue;
|
|
59
|
+
if (!(key in DIRECTIONAL_MODIFIERS)) continue;
|
|
60
|
+
const str = getStringValue(prop.value);
|
|
61
|
+
if (str) {
|
|
62
|
+
checkValue(key, str, prop.value);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
66
|
+
if (stateProp.type !== "Property") continue;
|
|
67
|
+
const stateStr = getStringValue(stateProp.value);
|
|
68
|
+
if (stateStr) checkValue(key, stateStr, stateProp.value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
ImportDeclaration(node) {
|
|
74
|
+
ctx.trackImport(node);
|
|
75
|
+
},
|
|
76
|
+
...styleObjectListeners(handleStyleObject)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
export { prefer_directional_shorthand_default as default };
|
|
83
|
+
//# sourceMappingURL=prefer-directional-shorthand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-directional-shorthand.js","names":[],"sources":["../../src/rules/prefer-directional-shorthand.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { DIRECTIONAL_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'preferDirectionalShorthand';\n\nconst SIDES = ['top', 'right', 'bottom', 'left'] as const;\n\nfunction isZeroLike(token: string): boolean {\n const trimmed = token.trim();\n if (trimmed === '0') return true;\n return /^0+(?:\\.0+)?(?:px|em|rem|%|x|r|cr|bw|ow|lh|sf)?$/i.test(trimmed);\n}\n\nfunction suggestDirectional(value: string): string | null {\n const tokens = value.trim().split(/\\s+/);\n if (tokens.length !== 4) return null;\n\n const nonZero = tokens\n .map((token, index) => ({ token, side: SIDES[index] }))\n .filter(({ token }) => !isZeroLike(token));\n\n if (nonZero.length !== 1) return null;\n\n const { token, side } = nonZero[0];\n return `${token} ${side}`;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-directional-shorthand',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Suggest Tasty directional shorthand instead of 4-value CSS box syntax with zero placeholders',\n },\n messages: {\n preferDirectionalShorthand:\n \"'{{property}}' value '{{raw}}' only sets one side. Use the directional shorthand '{{suggestion}}' (e.g. '1x bottom') instead.\",\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 const suggestion = suggestDirectional(value);\n if (!suggestion) return;\n\n context.report({\n node,\n messageId: 'preferDirectionalShorthand',\n data: { property, raw: value, suggestion },\n });\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (!(key in DIRECTIONAL_MODIFIERS)) continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(key, str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkValue(key, stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,MAAM,QAAQ;CAAC;CAAO;CAAS;CAAU;CAAO;AAEhD,SAAS,WAAW,OAAwB;CAC1C,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,YAAY,IAAK,QAAO;AAC5B,QAAO,oDAAoD,KAAK,QAAQ;;AAG1E,SAAS,mBAAmB,OAA8B;CACxD,MAAM,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;AACxC,KAAI,OAAO,WAAW,EAAG,QAAO;CAEhC,MAAM,UAAU,OACb,KAAK,OAAO,WAAW;EAAE;EAAO,MAAM,MAAM;EAAQ,EAAE,CACtD,QAAQ,EAAE,YAAY,CAAC,WAAW,MAAM,CAAC;AAE5C,KAAI,QAAQ,WAAW,EAAG,QAAO;CAEjC,MAAM,EAAE,OAAO,SAAS,QAAQ;AAChC,QAAO,GAAG,MAAM,GAAG;;AAGrB,2CAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,gGACH;EACD,UAAU,EACR,4BACE,iIACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;GACN,MAAM,aAAa,mBAAmB,MAAM;AAC5C,OAAI,CAAC,WAAY;AAEjB,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KAAE;KAAU,KAAK;KAAO;KAAY;IAC3C,CAAC;;EAGJ,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAClB,QAAI,EAAE,OAAO,uBAAwB;IAErC,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,gBAAW,KAAK,KAAK,KAAK,MAAM;AAChC;;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,YAAW,KAAK,UAAU,UAAU,MAAM;;;;AAOpD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
|
|
5
|
+
//#region src/rules/prefer-hide.ts
|
|
6
|
+
var prefer_hide_default = createRule({
|
|
7
|
+
name: "prefer-hide",
|
|
8
|
+
meta: {
|
|
9
|
+
type: "suggestion",
|
|
10
|
+
docs: { description: "Suggest hide: true instead of display: 'none' for Tasty's display/hide priority handling" },
|
|
11
|
+
messages: { preferHide: "Use 'hide: true' instead of \"display: 'none'\" so it composes with Tasty's display/hide priority handling." },
|
|
12
|
+
schema: []
|
|
13
|
+
},
|
|
14
|
+
defaultOptions: [],
|
|
15
|
+
create(context) {
|
|
16
|
+
const ctx = new TastyContext(context);
|
|
17
|
+
function checkDisplayValue(value, node) {
|
|
18
|
+
if (value.trim().toLowerCase() !== "none") return;
|
|
19
|
+
context.report({
|
|
20
|
+
node,
|
|
21
|
+
messageId: "preferHide"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function handleStyleObject(node) {
|
|
25
|
+
if (!ctx.isStyleObject(node)) return;
|
|
26
|
+
for (const prop of node.properties) {
|
|
27
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
28
|
+
if (getKeyName(prop.key) !== "display") continue;
|
|
29
|
+
const str = getStringValue(prop.value);
|
|
30
|
+
if (str) {
|
|
31
|
+
checkDisplayValue(str, prop.value);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
35
|
+
if (stateProp.type !== "Property") continue;
|
|
36
|
+
const stateStr = getStringValue(stateProp.value);
|
|
37
|
+
if (stateStr) checkDisplayValue(stateStr, stateProp.value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
ImportDeclaration(node) {
|
|
43
|
+
ctx.trackImport(node);
|
|
44
|
+
},
|
|
45
|
+
...styleObjectListeners(handleStyleObject)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { prefer_hide_default as default };
|
|
52
|
+
//# sourceMappingURL=prefer-hide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-hide.js","names":[],"sources":["../../src/rules/prefer-hide.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'preferHide';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-hide',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Suggest hide: true instead of display: 'none' for Tasty's display/hide priority handling\",\n },\n messages: {\n preferHide:\n \"Use 'hide: true' instead of \\\"display: 'none'\\\" so it composes with Tasty's display/hide priority handling.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkDisplayValue(value: string, node: TSESTree.Node): void {\n if (value.trim().toLowerCase() !== 'none') return;\n\n context.report({\n node,\n messageId: 'preferHide',\n });\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'display') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkDisplayValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkDisplayValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,0BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4FACH;EACD,UAAU,EACR,YACE,+GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,OAAe,MAA2B;AACnE,OAAI,MAAM,MAAM,CAAC,aAAa,KAAK,OAAQ;AAE3C,WAAQ,OAAO;IACb;IACA,WAAW;IACZ,CAAC;;EAGJ,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,UAAW;IAEvB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,uBAAkB,KAAK,KAAK,MAAM;AAClC;;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,mBAAkB,UAAU,UAAU,MAAM;;;;AAOtD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -10,7 +10,7 @@ var valid_color_token_default = createRule({
|
|
|
10
10
|
docs: { description: "Validate color token syntax and existence" },
|
|
11
11
|
messages: {
|
|
12
12
|
invalidSyntax: "Invalid color token '{{token}}': {{reason}}.",
|
|
13
|
-
unknownToken: "Unknown color token '{{token}}'."
|
|
13
|
+
unknownToken: "Unknown color token '{{token}}'. Declare it as a '#name' key, or add it to 'tokens' in your tasty config."
|
|
14
14
|
},
|
|
15
15
|
schema: []
|
|
16
16
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-color-token.js","names":[],"sources":["../../src/rules/valid-color-token.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport {\n getKeyName,\n getStringValue,\n validateColorTokenSyntax,\n isRawHexColor,\n} from '../utils.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownToken';\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-color-token',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate color token syntax and existence',\n },\n messages: {\n invalidSyntax: \"Invalid color token '{{token}}': {{reason}}.\",\n unknownToken
|
|
1
|
+
{"version":3,"file":"valid-color-token.js","names":[],"sources":["../../src/rules/valid-color-token.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport {\n getKeyName,\n getStringValue,\n validateColorTokenSyntax,\n isRawHexColor,\n} from '../utils.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownToken';\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-color-token',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate color token syntax and existence',\n },\n messages: {\n invalidSyntax: \"Invalid color token '{{token}}': {{reason}}.\",\n unknownToken:\n \"Unknown color token '{{token}}'. Declare it as a '#name' key, or add it to 'tokens' in your tasty config.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const fileColorTokens = new Set<string>();\n const pendingChecks: PendingExistenceCheck[] = [];\n\n function collectLocalTokens(node: TSESTree.ObjectExpression): void {\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key && key.startsWith('#') && !key.startsWith('##')) {\n fileColorTokens.add(key);\n }\n }\n }\n\n function checkColorTokensInValue(value: string, node: TSESTree.Node): void {\n const tokenRegex = /##?[a-zA-Z][a-zA-Z0-9-]*(?:\\.\\$?[a-zA-Z0-9-]+)?/g;\n let match;\n\n while ((match = tokenRegex.exec(value)) !== null) {\n const token = match[0];\n\n if (isRawHexColor(token)) continue;\n\n const syntaxError = validateColorTokenSyntax(token);\n if (syntaxError) {\n context.report({\n node,\n messageId: 'invalidSyntax',\n data: { token, reason: syntaxError },\n });\n continue;\n }\n\n if (ctx.config.tokens === false) continue;\n\n const baseName = token.startsWith('##')\n ? '#' + token.slice(2).split('.')[0]\n : '#' + token.slice(1).split('.')[0];\n\n if (baseName === '#current') continue;\n\n pendingChecks.push({ token, baseName, node });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n collectLocalTokens(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n\n if (prop.value.type === 'Literal') {\n const str = getStringValue(prop.value);\n if (str && str.includes('#')) {\n checkColorTokensInValue(str, prop.value);\n }\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const str = getStringValue(stateProp.value);\n if (str && str.includes('#')) {\n checkColorTokensInValue(str, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n\n 'Program:exit'() {\n if (\n !Array.isArray(ctx.config.tokens) ||\n ctx.config.tokens.length === 0\n ) {\n return;\n }\n\n for (const { token, baseName, node } of pendingChecks) {\n if (fileColorTokens.has(baseName)) continue;\n if (ctx.config.tokens.includes(baseName)) continue;\n\n context.report({\n node,\n messageId: 'unknownToken',\n data: { token },\n });\n }\n },\n };\n },\n});\n"],"mappings":";;;;;AAkBA,gCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,6CACd;EACD,UAAU;GACR,eAAe;GACf,cACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EACrC,MAAM,kCAAkB,IAAI,KAAa;EACzC,MAAM,gBAAyC,EAAE;EAEjD,SAAS,mBAAmB,MAAuC;AACjE,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,OAAO,IAAI,WAAW,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,CACrD,iBAAgB,IAAI,IAAI;;;EAK9B,SAAS,wBAAwB,OAAe,MAA2B;GACzE,MAAM,aAAa;GACnB,IAAI;AAEJ,WAAQ,QAAQ,WAAW,KAAK,MAAM,MAAM,MAAM;IAChD,MAAM,QAAQ,MAAM;AAEpB,QAAI,cAAc,MAAM,CAAE;IAE1B,MAAM,cAAc,yBAAyB,MAAM;AACnD,QAAI,aAAa;AACf,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE;OAAO,QAAQ;OAAa;MACrC,CAAC;AACF;;AAGF,QAAI,IAAI,OAAO,WAAW,MAAO;IAEjC,MAAM,WAAW,MAAM,WAAW,KAAK,GACnC,MAAM,MAAM,MAAM,EAAE,CAAC,MAAM,IAAI,CAAC,KAChC,MAAM,MAAM,MAAM,EAAE,CAAC,MAAM,IAAI,CAAC;AAEpC,QAAI,aAAa,WAAY;AAE7B,kBAAc,KAAK;KAAE;KAAO;KAAU;KAAM,CAAC;;;EAIjD,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,sBAAmB,KAAK;AAExB,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,WAAY;AAE9B,QAAI,KAAK,MAAM,SAAS,WAAW;KACjC,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,SAAI,OAAO,IAAI,SAAS,IAAI,CAC1B,yBAAwB,KAAK,KAAK,MAAM;;AAI5C,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,MAAM,eAAe,UAAU,MAAM;AAC3C,SAAI,OAAO,IAAI,SAAS,IAAI,CAC1B,yBAAwB,KAAK,UAAU,MAAM;;;;AAOvD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAE1C,iBAAiB;AACf,QACE,CAAC,MAAM,QAAQ,IAAI,OAAO,OAAO,IACjC,IAAI,OAAO,OAAO,WAAW,EAE7B;AAGF,SAAK,MAAM,EAAE,OAAO,UAAU,UAAU,eAAe;AACrD,SAAI,gBAAgB,IAAI,SAAS,CAAE;AACnC,SAAI,IAAI,OAAO,OAAO,SAAS,SAAS,CAAE;AAE1C,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,OAAO;MAChB,CAAC;;;GAGP;;CAEJ,CAAC"}
|
|
@@ -10,8 +10,8 @@ var valid_custom_property_default = createRule({
|
|
|
10
10
|
type: "suggestion",
|
|
11
11
|
docs: { description: "Validate $name custom property references" },
|
|
12
12
|
messages: {
|
|
13
|
-
invalidSyntax: "Invalid custom property
|
|
14
|
-
unknownProperty: "Unknown custom property '{{token}}'."
|
|
13
|
+
invalidSyntax: "Invalid custom property '{{token}}'. Use '$name' with letters, digits, or hyphens (e.g. $accent-color).",
|
|
14
|
+
unknownProperty: "Unknown custom property '{{token}}'. Declare it as a '$name' key in this styles object, or add it to your tasty config."
|
|
15
15
|
},
|
|
16
16
|
schema: []
|
|
17
17
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-custom-property.js","names":[],"sources":["../../src/rules/valid-custom-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownProperty';\n\nconst CUSTOM_PROP_REGEX = /\\$\\$?[a-zA-Z][a-zA-Z0-9-]*/g;\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-property',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Validate $name custom property references',\n },\n messages: {\n invalidSyntax
|
|
1
|
+
{"version":3,"file":"valid-custom-property.js","names":[],"sources":["../../src/rules/valid-custom-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownProperty';\n\nconst CUSTOM_PROP_REGEX = /\\$\\$?[a-zA-Z][a-zA-Z0-9-]*/g;\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-property',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Validate $name custom property references',\n },\n messages: {\n invalidSyntax:\n \"Invalid custom property '{{token}}'. Use '$name' with letters, digits, or hyphens (e.g. $accent-color).\",\n unknownProperty:\n \"Unknown custom property '{{token}}'. Declare it as a '$name' key in this styles object, or add it to your tasty config.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const fileCustomProperties = new Set<string>();\n const pendingChecks: PendingExistenceCheck[] = [];\n\n function collectLocalProperties(node: TSESTree.ObjectExpression): void {\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key && key.startsWith('$') && !key.startsWith('$$')) {\n fileCustomProperties.add(key);\n }\n }\n }\n\n function checkValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.tokens === false) return;\n\n let match;\n CUSTOM_PROP_REGEX.lastIndex = 0;\n\n while ((match = CUSTOM_PROP_REGEX.exec(value)) !== null) {\n const token = match[0];\n const baseName = token.startsWith('$$') ? '$' + token.slice(2) : token;\n\n pendingChecks.push({ token, baseName, node });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n collectLocalProperties(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n\n const str = getStringValue(prop.value);\n if (str && str.includes('$')) {\n checkValue(str, prop.value);\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr && stateStr.includes('$')) {\n checkValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n\n 'Program:exit'() {\n if (\n !Array.isArray(ctx.config.tokens) ||\n ctx.config.tokens.length === 0\n ) {\n return;\n }\n\n for (const { token, baseName, node } of pendingChecks) {\n if (fileCustomProperties.has(baseName)) continue;\n if (ctx.config.tokens.includes(baseName)) continue;\n\n context.report({\n node,\n messageId: 'unknownProperty',\n data: { token },\n });\n }\n },\n };\n },\n});\n"],"mappings":";;;;;AAOA,MAAM,oBAAoB;AAQ1B,oCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,6CACd;EACD,UAAU;GACR,eACE;GACF,iBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EACrC,MAAM,uCAAuB,IAAI,KAAa;EAC9C,MAAM,gBAAyC,EAAE;EAEjD,SAAS,uBAAuB,MAAuC;AACrE,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,OAAO,IAAI,WAAW,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,CACrD,sBAAqB,IAAI,IAAI;;;EAKnC,SAAS,WAAW,OAAe,MAA2B;AAC5D,OAAI,IAAI,OAAO,WAAW,MAAO;GAEjC,IAAI;AACJ,qBAAkB,YAAY;AAE9B,WAAQ,QAAQ,kBAAkB,KAAK,MAAM,MAAM,MAAM;IACvD,MAAM,QAAQ,MAAM;IACpB,MAAM,WAAW,MAAM,WAAW,KAAK,GAAG,MAAM,MAAM,MAAM,EAAE,GAAG;AAEjE,kBAAc,KAAK;KAAE;KAAO;KAAU;KAAM,CAAC;;;EAIjD,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,0BAAuB,KAAK;AAE5B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,WAAY;IAE9B,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,OAAO,IAAI,SAAS,IAAI,CAC1B,YAAW,KAAK,KAAK,MAAM;AAG7B,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,YAAY,SAAS,SAAS,IAAI,CACpC,YAAW,UAAU,UAAU,MAAM;;;;AAO/C,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAE1C,iBAAiB;AACf,QACE,CAAC,MAAM,QAAQ,IAAI,OAAO,OAAO,IACjC,IAAI,OAAO,OAAO,WAAW,EAE7B;AAGF,SAAK,MAAM,EAAE,OAAO,UAAU,UAAU,eAAe;AACrD,SAAI,qBAAqB,IAAI,SAAS,CAAE;AACxC,SAAI,IAAI,OAAO,OAAO,SAAS,SAAS,CAAE;AAE1C,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,OAAO;MAChB,CAAC;;;GAGP;;CAEJ,CAAC"}
|
|
@@ -8,7 +8,7 @@ var valid_custom_unit_default = createRule({
|
|
|
8
8
|
meta: {
|
|
9
9
|
type: "problem",
|
|
10
10
|
docs: { description: "Validate that custom units in style values are recognized" },
|
|
11
|
-
messages: { unknownUnit: "Unknown unit '{{unit}}' in value '{{
|
|
11
|
+
messages: { unknownUnit: "Unknown unit '{{unit}}' in '{{value}}'. Use a built-in unit (x, r, cr, bw, ow, lh, sf) or a CSS unit, or add '{{unit}}' to 'units' in your tasty config." },
|
|
12
12
|
schema: []
|
|
13
13
|
},
|
|
14
14
|
defaultOptions: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-custom-unit.js","names":[],"sources":["../../src/rules/valid-custom-unit.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getStringValue, extractCustomUnit, isValidUnit } from '../utils.js';\n\ntype MessageIds = 'unknownUnit';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-unit',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate that custom units in style values are recognized',\n },\n messages: {\n unknownUnit
|
|
1
|
+
{"version":3,"file":"valid-custom-unit.js","names":[],"sources":["../../src/rules/valid-custom-unit.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getStringValue, extractCustomUnit, isValidUnit } from '../utils.js';\n\ntype MessageIds = 'unknownUnit';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-unit',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate that custom units in style values are recognized',\n },\n messages: {\n unknownUnit:\n \"Unknown unit '{{unit}}' in '{{value}}'. Use a built-in unit (x, r, cr, bw, ow, lh, sf) or a CSS unit, or add '{{unit}}' to 'units' in your tasty config.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkUnitsInValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.units === false) return;\n\n // Split by spaces and check each token\n const tokens = value.split(/\\s+/);\n for (const token of tokens) {\n // Skip function calls, colors, modifiers, special values\n if (\n token.startsWith('#') ||\n token.startsWith('$') ||\n token.startsWith('@') ||\n token.includes('(') ||\n token.includes(')') ||\n token === 'true' ||\n token === 'false' ||\n token === 'none' ||\n token === 'auto' ||\n token === 'inherit' ||\n token === 'initial' ||\n token === 'unset' ||\n token === 'revert'\n ) {\n continue;\n }\n\n const unit = extractCustomUnit(token);\n if (unit && !isValidUnit(unit, ctx.config)) {\n context.report({\n node,\n messageId: 'unknownUnit',\n data: { unit, value: token },\n });\n }\n }\n }\n\n function checkNode(node: TSESTree.Node): void {\n const str = getStringValue(node);\n if (str) {\n checkUnitsInValue(str, node);\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n\n checkNode(prop.value);\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type === 'Property') {\n checkNode(stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,gCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,6DACd;EACD,UAAU,EACR,aACE,4JACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,OAAe,MAA2B;AACnE,OAAI,IAAI,OAAO,UAAU,MAAO;GAGhC,MAAM,SAAS,MAAM,MAAM,MAAM;AACjC,QAAK,MAAM,SAAS,QAAQ;AAE1B,QACE,MAAM,WAAW,IAAI,IACrB,MAAM,WAAW,IAAI,IACrB,MAAM,WAAW,IAAI,IACrB,MAAM,SAAS,IAAI,IACnB,MAAM,SAAS,IAAI,IACnB,UAAU,UACV,UAAU,WACV,UAAU,UACV,UAAU,UACV,UAAU,aACV,UAAU,aACV,UAAU,WACV,UAAU,SAEV;IAGF,MAAM,OAAO,kBAAkB,MAAM;AACrC,QAAI,QAAQ,CAAC,YAAY,MAAM,IAAI,OAAO,CACxC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAM,OAAO;MAAO;KAC7B,CAAC;;;EAKR,SAAS,UAAU,MAA2B;GAC5C,MAAM,MAAM,eAAe,KAAK;AAChC,OAAI,IACF,mBAAkB,KAAK,KAAK;;EAIhC,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,WAAY;AAE9B,cAAU,KAAK,MAAM;AAErB,QAAI,KAAK,MAAM,SAAS,oBACtB;UAAK,MAAM,aAAa,KAAK,MAAM,WACjC,KAAI,UAAU,SAAS,WACrB,WAAU,UAAU,MAAM;;;;AAOpC,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
|
|
5
|
+
//#region src/rules/valid-default-state-order.ts
|
|
6
|
+
function getStateKeyName(key) {
|
|
7
|
+
if (key.type === "Identifier") return key.name;
|
|
8
|
+
if (key.type === "Literal" && typeof key.value === "string") return key.value;
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
var valid_default_state_order_default = createRule({
|
|
12
|
+
name: "valid-default-state-order",
|
|
13
|
+
meta: {
|
|
14
|
+
type: "suggestion",
|
|
15
|
+
docs: { description: "Warn when the default ('') state is misplaced or redundant relative to the '_' fallback floor" },
|
|
16
|
+
messages: {
|
|
17
|
+
misplacedDefaultState: "Default state ('') in '{{property}}' must be the first key in the state map. Move it before other states — otherwise it overrides them at runtime.",
|
|
18
|
+
redundantDefaultState: "Default state ('') in '{{property}}' is redundant when '_' is the only other state. Remove '' — '_' already provides the always-on fallback value."
|
|
19
|
+
},
|
|
20
|
+
schema: []
|
|
21
|
+
},
|
|
22
|
+
defaultOptions: [],
|
|
23
|
+
create(context) {
|
|
24
|
+
const ctx = new TastyContext(context);
|
|
25
|
+
function checkStateMap(property, stateMap) {
|
|
26
|
+
const stateKeys = [];
|
|
27
|
+
for (const prop of stateMap.properties) {
|
|
28
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
29
|
+
const stateKey = getStateKeyName(prop.key);
|
|
30
|
+
if (stateKey === null) continue;
|
|
31
|
+
stateKeys.push({
|
|
32
|
+
key: stateKey,
|
|
33
|
+
node: prop
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const defaultIndex = stateKeys.findIndex((entry) => entry.key === "");
|
|
37
|
+
const floorIndex = stateKeys.findIndex((entry) => entry.key === "_");
|
|
38
|
+
if (defaultIndex !== -1 && floorIndex !== -1 && stateKeys.length === 2) {
|
|
39
|
+
context.report({
|
|
40
|
+
node: stateKeys[defaultIndex].node.key,
|
|
41
|
+
messageId: "redundantDefaultState",
|
|
42
|
+
data: { property }
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (defaultIndex > 0) context.report({
|
|
47
|
+
node: stateKeys[defaultIndex].node.key,
|
|
48
|
+
messageId: "misplacedDefaultState",
|
|
49
|
+
data: { property }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function handleStyleObject(node) {
|
|
53
|
+
if (!ctx.isStyleObject(node)) return;
|
|
54
|
+
for (const prop of node.properties) {
|
|
55
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
56
|
+
const key = getKeyName(prop.key);
|
|
57
|
+
if (key === null) continue;
|
|
58
|
+
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
59
|
+
if (prop.value.type !== "ObjectExpression") continue;
|
|
60
|
+
if (!ctx.isStateMap(prop.value, prop)) continue;
|
|
61
|
+
checkStateMap(key, prop.value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
ImportDeclaration(node) {
|
|
66
|
+
ctx.trackImport(node);
|
|
67
|
+
},
|
|
68
|
+
...styleObjectListeners(handleStyleObject)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { valid_default_state_order_default as default };
|
|
75
|
+
//# sourceMappingURL=valid-default-state-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valid-default-state-order.js","names":[],"sources":["../../src/rules/valid-default-state-order.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\n\ntype MessageIds = 'misplacedDefaultState' | 'redundantDefaultState';\n\nfunction getStateKeyName(key: TSESTree.Node): string | null {\n if (key.type === 'Identifier') return key.name;\n if (key.type === 'Literal' && typeof key.value === 'string') return key.value;\n return null;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-default-state-order',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when the default ('') state is misplaced or redundant relative to the '_' fallback floor\",\n },\n messages: {\n misplacedDefaultState:\n \"Default state ('') in '{{property}}' must be the first key in the state map. Move it before other states — otherwise it overrides them at runtime.\",\n redundantDefaultState:\n \"Default state ('') in '{{property}}' is redundant when '_' is the only other state. Remove '' — '_' already provides the always-on fallback value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkStateMap(\n property: string,\n stateMap: TSESTree.ObjectExpression,\n ): void {\n const stateKeys: {\n key: string;\n node: TSESTree.Property;\n }[] = [];\n\n for (const prop of stateMap.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const stateKey = getStateKeyName(prop.key);\n if (stateKey === null) continue;\n stateKeys.push({ key: stateKey, node: prop });\n }\n\n const defaultIndex = stateKeys.findIndex((entry) => entry.key === '');\n const floorIndex = stateKeys.findIndex((entry) => entry.key === '_');\n\n if (defaultIndex !== -1 && floorIndex !== -1 && stateKeys.length === 2) {\n context.report({\n node: stateKeys[defaultIndex].node.key,\n messageId: 'redundantDefaultState',\n data: { property },\n });\n return;\n }\n\n if (defaultIndex > 0) {\n context.report({\n node: stateKeys[defaultIndex].node.key,\n messageId: 'misplacedDefaultState',\n data: { property },\n });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n if (!ctx.isStateMap(prop.value, prop)) continue;\n\n checkStateMap(key, prop.value);\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,SAAS,gBAAgB,KAAmC;AAC1D,KAAI,IAAI,SAAS,aAAc,QAAO,IAAI;AAC1C,KAAI,IAAI,SAAS,aAAa,OAAO,IAAI,UAAU,SAAU,QAAO,IAAI;AACxE,QAAO;;AAGT,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iGACH;EACD,UAAU;GACR,uBACE;GACF,uBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,cACP,UACA,UACM;GACN,MAAM,YAGA,EAAE;AAER,QAAK,MAAM,QAAQ,SAAS,YAAY;AACtC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,WAAW,gBAAgB,KAAK,IAAI;AAC1C,QAAI,aAAa,KAAM;AACvB,cAAU,KAAK;KAAE,KAAK;KAAU,MAAM;KAAM,CAAC;;GAG/C,MAAM,eAAe,UAAU,WAAW,UAAU,MAAM,QAAQ,GAAG;GACrE,MAAM,aAAa,UAAU,WAAW,UAAU,MAAM,QAAQ,IAAI;AAEpE,OAAI,iBAAiB,MAAM,eAAe,MAAM,UAAU,WAAW,GAAG;AACtE,YAAQ,OAAO;KACb,MAAM,UAAU,cAAc,KAAK;KACnC,WAAW;KACX,MAAM,EAAE,UAAU;KACnB,CAAC;AACF;;AAGF,OAAI,eAAe,EACjB,SAAQ,OAAO;IACb,MAAM,UAAU,cAAc,KAAK;IACnC,WAAW;IACX,MAAM,EAAE,UAAU;IACnB,CAAC;;EAIN,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAClB,QAAI,aAAa,KAAK,IAAI,CAAE;AAE5B,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAC5C,QAAI,CAAC,IAAI,WAAW,KAAK,OAAO,KAAK,CAAE;AAEvC,kBAAc,KAAK,KAAK,MAAM;;;AAIlC,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -19,7 +19,7 @@ var valid_directional_modifier_default = createRule({
|
|
|
19
19
|
meta: {
|
|
20
20
|
type: "problem",
|
|
21
21
|
docs: { description: "Validate that directional modifiers are used only on properties that support them" },
|
|
22
|
-
messages: { invalidDirectionalModifier: "Property '{{property}}' does not support directional
|
|
22
|
+
messages: { invalidDirectionalModifier: "Property '{{property}}' does not support directional modifiers. They work on: border, radius, padding, margin, fade, inset." },
|
|
23
23
|
schema: []
|
|
24
24
|
},
|
|
25
25
|
defaultOptions: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-directional-modifier.js","names":[],"sources":["../../src/rules/valid-directional-modifier.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { DIRECTIONAL_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'invalidDirectionalModifier';\n\nconst ALL_DIRECTIONS = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n]);\n\nexport default createRule<[], MessageIds>({\n name: 'valid-directional-modifier',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate that directional modifiers are used only on properties that support them',\n },\n messages: {\n invalidDirectionalModifier:\n \"Property '{{property}}' does not support directional
|
|
1
|
+
{"version":3,"file":"valid-directional-modifier.js","names":[],"sources":["../../src/rules/valid-directional-modifier.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { DIRECTIONAL_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'invalidDirectionalModifier';\n\nconst ALL_DIRECTIONS = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n]);\n\nexport default createRule<[], MessageIds>({\n name: 'valid-directional-modifier',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate that directional modifiers are used only on properties that support them',\n },\n messages: {\n invalidDirectionalModifier:\n \"Property '{{property}}' does not support directional modifiers. They work on: border, radius, padding, margin, fade, inset.\",\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 const tokens = value.trim().split(/\\s+/);\n\n for (const token of tokens) {\n if (!ALL_DIRECTIONS.has(token)) continue;\n\n const allowedMods = DIRECTIONAL_MODIFIERS[property];\n if (!allowedMods || !allowedMods.has(token)) {\n context.report({\n node,\n messageId: 'invalidDirectionalModifier',\n data: { property, modifier: token },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n if (!(key in DIRECTIONAL_MODIFIERS)) continue;\n\n // Direct value\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(key, str, prop.value);\n continue;\n }\n\n // State map\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkValue(key, stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,yCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,qFACH;EACD,UAAU,EACR,4BACE,+HACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;GACN,MAAM,SAAS,MAAM,MAAM,CAAC,MAAM,MAAM;AAExC,QAAK,MAAM,SAAS,QAAQ;AAC1B,QAAI,CAAC,eAAe,IAAI,MAAM,CAAE;IAEhC,MAAM,cAAc,sBAAsB;AAC1C,QAAI,CAAC,eAAe,CAAC,YAAY,IAAI,MAAM,CACzC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAU,UAAU;MAAO;KACpC,CAAC;;;EAKR,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAElB,QAAI,EAAE,OAAO,uBAAwB;IAGrC,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,gBAAW,KAAK,KAAK,KAAK,MAAM;AAChC;;AAIF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,YAAW,KAAK,UAAU,UAAU,MAAM;;;;AAOpD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -10,8 +10,8 @@ var valid_preset_default = createRule({
|
|
|
10
10
|
type: "problem",
|
|
11
11
|
docs: { description: "Validate preset property values against config" },
|
|
12
12
|
messages: {
|
|
13
|
-
unknownPreset: "Unknown preset '{{name}}'.",
|
|
14
|
-
unknownModifier: "Unknown preset modifier '{{modifier}}'."
|
|
13
|
+
unknownPreset: "Unknown preset '{{name}}'. Add it to 'presets' in your tasty config, or use a configured preset.",
|
|
14
|
+
unknownModifier: "Unknown preset modifier '{{modifier}}'. Valid modifiers: {{valid}}."
|
|
15
15
|
},
|
|
16
16
|
schema: []
|
|
17
17
|
},
|
|
@@ -45,7 +45,10 @@ var valid_preset_default = createRule({
|
|
|
45
45
|
for (const mod of modSegment.split(/\s+/).filter(Boolean)) if (!PRESET_MODIFIERS.has(mod)) context.report({
|
|
46
46
|
node,
|
|
47
47
|
messageId: "unknownModifier",
|
|
48
|
-
data: {
|
|
48
|
+
data: {
|
|
49
|
+
modifier: mod,
|
|
50
|
+
valid: [...PRESET_MODIFIERS].join(", ")
|
|
51
|
+
}
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-preset.js","names":[],"sources":["../../src/rules/valid-preset.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { PRESET_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'unknownPreset' | 'unknownModifier';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-preset',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate preset property values against config',\n },\n messages: {\n unknownPreset
|
|
1
|
+
{"version":3,"file":"valid-preset.js","names":[],"sources":["../../src/rules/valid-preset.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { PRESET_MODIFIERS } from '../constants.js';\n\ntype MessageIds = 'unknownPreset' | 'unknownModifier';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-preset',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate preset property values against config',\n },\n messages: {\n unknownPreset:\n \"Unknown preset '{{name}}'. Add it to 'presets' in your tasty config, or use a configured preset.\",\n unknownModifier:\n \"Unknown preset modifier '{{modifier}}'. Valid modifiers: {{valid}}.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const CSS_GLOBAL_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n ]);\n\n function checkPresetValue(value: string, node: TSESTree.Node): void {\n const segments = value.trim().split(/\\s*\\/\\s*/);\n if (segments.length === 0) return;\n\n const nameSegment = segments[0].trim();\n const modSegment = segments[1]?.trim();\n\n if (!nameSegment) return;\n if (CSS_GLOBAL_KEYWORDS.has(nameSegment)) return;\n\n const nameTokens = nameSegment.split(/\\s+/).filter(Boolean);\n // Mod-only shorthand: every token in the name segment is a recognized\n // modifier (e.g. preset=\"bold\", preset=\"bold italic\").\n const isModOnlyShorthand =\n nameTokens.length > 0 &&\n nameTokens.every((t) => PRESET_MODIFIERS.has(t));\n\n if (!isModOnlyShorthand) {\n const presetName = nameTokens[0];\n if (\n presetName &&\n ctx.config.presets.length > 0 &&\n !ctx.config.presets.includes(presetName)\n ) {\n context.report({\n node,\n messageId: 'unknownPreset',\n data: { name: presetName },\n });\n }\n }\n\n if (modSegment) {\n for (const mod of modSegment.split(/\\s+/).filter(Boolean)) {\n if (!PRESET_MODIFIERS.has(mod)) {\n context.report({\n node,\n messageId: 'unknownModifier',\n data: {\n modifier: mod,\n valid: [...PRESET_MODIFIERS].join(', '),\n },\n });\n }\n }\n }\n }\n\n function checkObject(node: TSESTree.ObjectExpression): void {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'preset') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkPresetValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'Literal' && prop.value.value === true) {\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkPresetValue(stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n checkObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU;GACR,eACE;GACF,iBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,sBAAsB,IAAI,IAAI;GAClC;GACA;GACA;GACA;GACA;GACD,CAAC;EAEF,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,WAAW,MAAM,MAAM,CAAC,MAAM,WAAW;AAC/C,OAAI,SAAS,WAAW,EAAG;GAE3B,MAAM,cAAc,SAAS,GAAG,MAAM;GACtC,MAAM,aAAa,SAAS,IAAI,MAAM;AAEtC,OAAI,CAAC,YAAa;AAClB,OAAI,oBAAoB,IAAI,YAAY,CAAE;GAE1C,MAAM,aAAa,YAAY,MAAM,MAAM,CAAC,OAAO,QAAQ;AAO3D,OAAI,EAHF,WAAW,SAAS,KACpB,WAAW,OAAO,MAAM,iBAAiB,IAAI,EAAE,CAAC,GAEzB;IACvB,MAAM,aAAa,WAAW;AAC9B,QACE,cACA,IAAI,OAAO,QAAQ,SAAS,KAC5B,CAAC,IAAI,OAAO,QAAQ,SAAS,WAAW,CAExC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,MAAM,YAAY;KAC3B,CAAC;;AAIN,OAAI,YACF;SAAK,MAAM,OAAO,WAAW,MAAM,MAAM,CAAC,OAAO,QAAQ,CACvD,KAAI,CAAC,iBAAiB,IAAI,IAAI,CAC5B,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ,UAAU;MACV,OAAO,CAAC,GAAG,iBAAiB,CAAC,KAAK,KAAK;MACxC;KACF,CAAC;;;EAMV,SAAS,YAAY,MAAuC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,sBAAiB,KAAK,KAAK,MAAM;AACjC;;AAGF,QAAI,KAAK,MAAM,SAAS,aAAa,KAAK,MAAM,UAAU,KACxD;AAGF,QAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,kBAAiB,UAAU,UAAU,MAAM;;;;EAOrD,SAAS,kBAAkB,MAAiC;AAC1D,eAAY,KAAK;;AAGnB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -8,7 +8,7 @@ var valid_recipe_default = createRule({
|
|
|
8
8
|
meta: {
|
|
9
9
|
type: "problem",
|
|
10
10
|
docs: { description: "Validate recipe property values against config" },
|
|
11
|
-
messages: { unknownRecipe: "Unknown recipe '{{name}}'." },
|
|
11
|
+
messages: { unknownRecipe: "Unknown recipe '{{name}}'. Add it to 'recipes' in your tasty config, or use a configured recipe." },
|
|
12
12
|
schema: []
|
|
13
13
|
},
|
|
14
14
|
defaultOptions: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-recipe.js","names":[],"sources":["../../src/rules/valid-recipe.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'unknownRecipe';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-recipe',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate recipe property values against config',\n },\n messages: {\n unknownRecipe
|
|
1
|
+
{"version":3,"file":"valid-recipe.js","names":[],"sources":["../../src/rules/valid-recipe.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds = 'unknownRecipe';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-recipe',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate recipe property values against config',\n },\n messages: {\n unknownRecipe:\n \"Unknown recipe '{{name}}'. Add it to 'recipes' in your tasty config, or use a configured recipe.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRecipeValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.recipes.length === 0) return;\n\n // Split by / for pre/post merge separation\n const sections = value.split('/');\n for (const section of sections) {\n const names = section.trim().split(/\\s+/);\n for (const name of names) {\n if (name.length === 0 || name === 'none') continue;\n if (!ctx.config.recipes.includes(name)) {\n context.report({\n node,\n messageId: 'unknownRecipe',\n data: { name },\n });\n }\n }\n }\n }\n\n function checkObject(node: TSESTree.ObjectExpression): void {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key !== 'recipe') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkRecipeValue(str, prop.value);\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n checkObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU,EACR,eACE,oGACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,iBAAiB,OAAe,MAA2B;AAClE,OAAI,IAAI,OAAO,QAAQ,WAAW,EAAG;GAGrC,MAAM,WAAW,MAAM,MAAM,IAAI;AACjC,QAAK,MAAM,WAAW,UAAU;IAC9B,MAAM,QAAQ,QAAQ,MAAM,CAAC,MAAM,MAAM;AACzC,SAAK,MAAM,QAAQ,OAAO;AACxB,SAAI,KAAK,WAAW,KAAK,SAAS,OAAQ;AAC1C,SAAI,CAAC,IAAI,OAAO,QAAQ,SAAS,KAAK,CACpC,SAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,MAAM;MACf,CAAC;;;;EAMV,SAAS,YAAY,MAAuC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,IACF,kBAAiB,KAAK,KAAK,MAAM;;;EAKvC,SAAS,kBAAkB,MAAiC;AAC1D,eAAY,KAAK;;AAGnB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
-
import { KNOWN_CSS_PROPERTIES, SEMANTIC_TRANSITIONS } from "../constants.js";
|
|
2
|
+
import { KNOWN_CSS_PROPERTIES, SEMANTIC_TRANSITIONS, TRANSITION_SEMANTIC_MAPPING } from "../constants.js";
|
|
3
3
|
import { getKeyName, getStringValue } from "../utils.js";
|
|
4
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
5
5
|
|
|
@@ -9,7 +9,10 @@ var valid_transition_default = createRule({
|
|
|
9
9
|
meta: {
|
|
10
10
|
type: "suggestion",
|
|
11
11
|
docs: { description: "Validate transition property values use valid semantic transition names" },
|
|
12
|
-
messages: {
|
|
12
|
+
messages: {
|
|
13
|
+
unknownTransition: "Unknown transition name '{{name}}'. Use a semantic name ({{known}}) or a CSS property name.",
|
|
14
|
+
preferSemanticTransition: "Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead."
|
|
15
|
+
},
|
|
13
16
|
schema: []
|
|
14
17
|
},
|
|
15
18
|
defaultOptions: [],
|
|
@@ -23,6 +26,18 @@ var valid_transition_default = createRule({
|
|
|
23
26
|
const name = parts[0];
|
|
24
27
|
if (name.startsWith("$$")) continue;
|
|
25
28
|
if (name.startsWith("##")) continue;
|
|
29
|
+
const semantic = TRANSITION_SEMANTIC_MAPPING[name];
|
|
30
|
+
if (semantic) {
|
|
31
|
+
context.report({
|
|
32
|
+
node,
|
|
33
|
+
messageId: "preferSemanticTransition",
|
|
34
|
+
data: {
|
|
35
|
+
native: name,
|
|
36
|
+
semantic
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
26
41
|
if (!SEMANTIC_TRANSITIONS.has(name) && !KNOWN_CSS_PROPERTIES.has(name) && name !== "all" && name !== "none") context.report({
|
|
27
42
|
node,
|
|
28
43
|
messageId: "unknownTransition",
|