@tenphi/eslint-plugin-tasty 0.7.0 → 0.9.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 +10 -5
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +322 -3
- package/dist/constants.js.map +1 -1
- package/dist/fix-utils.js +92 -0
- package/dist/fix-utils.js.map +1 -0
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/rules/consistent-token-usage.js +59 -12
- package/dist/rules/consistent-token-usage.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-important.js +12 -4
- package/dist/rules/no-important.js.map +1 -1
- package/dist/rules/no-nested-selector.js +2 -2
- package/dist/rules/no-nested-selector.js.map +1 -1
- package/dist/rules/no-own-at-root.js +9 -1
- package/dist/rules/no-own-at-root.js.map +1 -1
- package/dist/rules/no-raw-color-values.js +23 -7
- package/dist/rules/no-raw-color-values.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 +81 -0
- package/dist/rules/prefer-auto-calc.js.map +1 -0
- package/dist/rules/prefer-custom-property-syntax.js +151 -0
- package/dist/rules/prefer-custom-property-syntax.js.map +1 -0
- package/dist/rules/prefer-directional-shorthand.js +88 -0
- package/dist/rules/prefer-directional-shorthand.js.map +1 -0
- package/dist/rules/prefer-hide.js +58 -0
- package/dist/rules/prefer-hide.js.map +1 -0
- package/dist/rules/prefer-shorthand-property.js +5 -0
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- 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 +106 -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-radius-shape.js +18 -2
- package/dist/rules/valid-radius-shape.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 +52 -5
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-runtime-styles-mutation.js","names":[],"sources":["../../src/rules/no-runtime-styles-mutation.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { isStaticValue } from '../utils.js';\n\ntype MessageIds = 'dynamicStyleValue';\n\nexport default createRule<[], MessageIds>({\n name: 'no-runtime-styles-mutation',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Warn when style objects contain runtime-computed values',\n },\n messages: {\n dynamicStyleValue:\n
|
|
1
|
+
{"version":3,"file":"no-runtime-styles-mutation.js","names":[],"sources":["../../src/rules/no-runtime-styles-mutation.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { isStaticValue } from '../utils.js';\n\ntype MessageIds = 'dynamicStyleValue';\n\nexport default createRule<[], MessageIds>({\n name: 'no-runtime-styles-mutation',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Warn when style objects contain runtime-computed values',\n },\n messages: {\n dynamicStyleValue:\n \"Style value for '{{property}}' should be static. Use the 'mods' prop, tokens, or CSS custom properties for dynamic behavior.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkProperties(\n node: TSESTree.ObjectExpression,\n parentProperty: string | null = null,\n ): void {\n for (const prop of node.properties) {\n if (prop.type === 'SpreadElement') {\n context.report({\n node: prop,\n messageId: 'dynamicStyleValue',\n data: { property: parentProperty ?? '(spread)' },\n });\n continue;\n }\n\n if (prop.type !== 'Property') continue;\n\n const key =\n !prop.computed && prop.key.type === 'Identifier'\n ? prop.key.name\n : parentProperty;\n\n // Skip sub-elements (they contain nested style objects)\n if (\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n /^[A-Z]/.test(prop.key.name)\n ) {\n if (prop.value.type === 'ObjectExpression') {\n checkProperties(prop.value, key);\n }\n continue;\n }\n\n // Skip @keyframes and @properties\n if (\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n prop.key.name.startsWith('@')\n ) {\n continue;\n }\n\n if (!isStaticValue(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'dynamicStyleValue',\n data: { property: key ?? '(unknown)' },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n checkProperties(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,yCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,2DACd;EACD,UAAU,EACR,mBACE,gIACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,gBACP,MACA,iBAAgC,MAC1B;AACN,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,iBAAiB;AACjC,aAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM,EAAE,UAAU,kBAAkB,YAAY;MACjD,CAAC;AACF;;AAGF,QAAI,KAAK,SAAS,WAAY;IAE9B,MAAM,MACJ,CAAC,KAAK,YAAY,KAAK,IAAI,SAAS,eAChC,KAAK,IAAI,OACT;AAGN,QACE,CAAC,KAAK,YACN,KAAK,IAAI,SAAS,gBAClB,SAAS,KAAK,KAAK,IAAI,KAAK,EAC5B;AACA,SAAI,KAAK,MAAM,SAAS,mBACtB,iBAAgB,KAAK,OAAO,IAAI;AAElC;;AAIF,QACE,CAAC,KAAK,YACN,KAAK,IAAI,SAAS,gBAClB,KAAK,IAAI,KAAK,WAAW,IAAI,CAE7B;AAGF,QAAI,CAAC,cAAc,KAAK,MAAM,CAC5B,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,UAAU,OAAO,aAAa;KACvC,CAAC;;;EAKR,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,mBAAgB,KAAK;;AAGvB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -8,7 +8,7 @@ var no_unknown_state_alias_default = createRule({
|
|
|
8
8
|
meta: {
|
|
9
9
|
type: "suggestion",
|
|
10
10
|
docs: { description: "Warn when a @name state alias is used that isn't in the config" },
|
|
11
|
-
messages: { unknownAlias: "Unknown state alias '{{alias}}'." },
|
|
11
|
+
messages: { unknownAlias: "Unknown state alias '{{alias}}'. Add it to 'states' in your tasty config, or define it locally as a '@name' key." },
|
|
12
12
|
schema: []
|
|
13
13
|
},
|
|
14
14
|
defaultOptions: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-unknown-state-alias.js","names":[],"sources":["../../src/rules/no-unknown-state-alias.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 isKnownStateAlias,\n collectLocalStateAliases,\n findRootStyleObject,\n} from '../utils.js';\n\ntype MessageIds = 'unknownAlias';\n\nexport default createRule<[], MessageIds>({\n name: 'no-unknown-state-alias',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a @name state alias is used that isn't in the config\",\n },\n messages: {\n unknownAlias
|
|
1
|
+
{"version":3,"file":"no-unknown-state-alias.js","names":[],"sources":["../../src/rules/no-unknown-state-alias.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 isKnownStateAlias,\n collectLocalStateAliases,\n findRootStyleObject,\n} from '../utils.js';\n\ntype MessageIds = 'unknownAlias';\n\nexport default createRule<[], MessageIds>({\n name: 'no-unknown-state-alias',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a @name state alias is used that isn't in the config\",\n },\n messages: {\n unknownAlias:\n \"Unknown state alias '{{alias}}'. Add it to 'states' in your tasty config, or define it locally as a '@name' key.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkStateKeys(\n obj: TSESTree.ObjectExpression,\n localAliases: string[],\n ): void {\n for (const prop of obj.properties) {\n if (prop.type !== 'Property') continue;\n\n const key = !prop.computed\n ? getKeyName(prop.key)\n : getStringValue(prop.key);\n if (key === null || !key.startsWith('@')) continue;\n\n if (\n !isKnownStateAlias(key, ctx.config) &&\n !localAliases.includes(key)\n ) {\n context.report({\n node: prop.key,\n messageId: 'unknownAlias',\n data: { alias: key },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n const rootObj = findRootStyleObject(node);\n const localAliases = collectLocalStateAliases(rootObj);\n\n // Skip if no states configured and no local aliases\n if (ctx.config.states.length === 0 && localAliases.length === 0) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n // Skip sub-elements and special keys\n const key = getKeyName(prop.key);\n if (key === null || /^[A-Z@&]/.test(key)) continue;\n\n // Check state map objects for unknown aliases\n if (prop.value.type === 'ObjectExpression') {\n checkStateKeys(prop.value, localAliases);\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAaA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,kEACH;EACD,UAAU,EACR,cACE,oHACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,eACP,KACA,cACM;AACN,QAAK,MAAM,QAAQ,IAAI,YAAY;AACjC,QAAI,KAAK,SAAS,WAAY;IAE9B,MAAM,MAAM,CAAC,KAAK,WACd,WAAW,KAAK,IAAI,GACpB,eAAe,KAAK,IAAI;AAC5B,QAAI,QAAQ,QAAQ,CAAC,IAAI,WAAW,IAAI,CAAE;AAE1C,QACE,CAAC,kBAAkB,KAAK,IAAI,OAAO,IACnC,CAAC,aAAa,SAAS,IAAI,CAE3B,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,OAAO,KAAK;KACrB,CAAC;;;EAKR,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;GAG9B,MAAM,eAAe,yBADL,oBAAoB,KAAK,CACa;AAGtD,OAAI,IAAI,OAAO,OAAO,WAAW,KAAK,aAAa,WAAW,EAAG;AAEjE,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAG/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,QAAQ,WAAW,KAAK,IAAI,CAAE;AAG1C,QAAI,KAAK,MAAM,SAAS,mBACtB,gBAAe,KAAK,OAAO,aAAa;;;AAK9C,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
import { parseValue } from "../parsers/value-parser.js";
|
|
5
|
+
import { replaceInStringValue } from "../fix-utils.js";
|
|
6
|
+
|
|
7
|
+
//#region src/rules/prefer-auto-calc.ts
|
|
8
|
+
var prefer_auto_calc_default = createRule({
|
|
9
|
+
name: "prefer-auto-calc",
|
|
10
|
+
meta: {
|
|
11
|
+
type: "suggestion",
|
|
12
|
+
fixable: "code",
|
|
13
|
+
docs: { description: "Suggest Tasty auto-calc parentheses instead of explicit calc()" },
|
|
14
|
+
messages: { preferAutoCalc: "Replace 'calc({{inner}})' with Tasty auto-calc '({{inner}})' (calc() is added automatically)." },
|
|
15
|
+
schema: []
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
const ctx = new TastyContext(context);
|
|
20
|
+
function checkValue(value, node) {
|
|
21
|
+
const result = parseValue(value, { skipUnitValidation: true });
|
|
22
|
+
let cursor = 0;
|
|
23
|
+
for (const group of result.groups) for (const part of group.parts) for (const token of part.tokens) {
|
|
24
|
+
if (token.type !== "css-function" || token.name !== "calc") continue;
|
|
25
|
+
const start = value.indexOf(token.raw, cursor);
|
|
26
|
+
if (start === -1) {
|
|
27
|
+
context.report({
|
|
28
|
+
node,
|
|
29
|
+
messageId: "preferAutoCalc",
|
|
30
|
+
data: { inner: token.args }
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
cursor = start + token.raw.length;
|
|
35
|
+
const end = cursor;
|
|
36
|
+
const replacement = `(${token.args})`;
|
|
37
|
+
context.report({
|
|
38
|
+
node,
|
|
39
|
+
messageId: "preferAutoCalc",
|
|
40
|
+
data: { inner: token.args },
|
|
41
|
+
fix(fixer) {
|
|
42
|
+
return replaceInStringValue(fixer, node, [{
|
|
43
|
+
start,
|
|
44
|
+
end,
|
|
45
|
+
replacement
|
|
46
|
+
}], context.sourceCode);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function handleStyleObject(node) {
|
|
52
|
+
if (!ctx.isStyleObject(node)) return;
|
|
53
|
+
for (const prop of node.properties) {
|
|
54
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
55
|
+
const key = getKeyName(prop.key);
|
|
56
|
+
if (key === null) continue;
|
|
57
|
+
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
58
|
+
const str = getStringValue(prop.value);
|
|
59
|
+
if (str) {
|
|
60
|
+
checkValue(str, prop.value);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
64
|
+
if (stateProp.type !== "Property") continue;
|
|
65
|
+
const stateStr = getStringValue(stateProp.value);
|
|
66
|
+
if (stateStr) checkValue(stateStr, stateProp.value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ImportDeclaration(node) {
|
|
72
|
+
ctx.trackImport(node);
|
|
73
|
+
},
|
|
74
|
+
...styleObjectListeners(handleStyleObject)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
export { prefer_auto_calc_default as default };
|
|
81
|
+
//# sourceMappingURL=prefer-auto-calc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prefer-auto-calc.js","names":[],"sources":["../../src/rules/prefer-auto-calc.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 { parseValue } from '../parsers/value-parser.js';\nimport { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'preferAutoCalc';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-auto-calc',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest Tasty auto-calc parentheses instead of explicit calc()',\n },\n messages: {\n preferAutoCalc:\n \"Replace 'calc({{inner}})' with Tasty auto-calc '({{inner}})' (calc() is added automatically).\",\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 const result = parseValue(value, { skipUnitValidation: true });\n\n let cursor = 0;\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n if (token.type !== 'css-function' || token.name !== 'calc') {\n continue;\n }\n\n const start = value.indexOf(token.raw, cursor);\n if (start === -1) {\n context.report({\n node,\n messageId: 'preferAutoCalc',\n data: { inner: token.args },\n });\n continue;\n }\n cursor = start + token.raw.length;\n const end = cursor;\n const replacement = `(${token.args})`;\n\n context.report({\n node,\n messageId: 'preferAutoCalc',\n data: { inner: token.args },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement }],\n context.sourceCode,\n );\n },\n });\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 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":";;;;;;;AASA,+BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,kEACH;EACD,UAAU,EACR,gBACE,iGACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WAAW,OAAe,MAA2B;GAC5D,MAAM,SAAS,WAAW,OAAO,EAAE,oBAAoB,MAAM,CAAC;GAE9D,IAAI,SAAS;AACb,QAAK,MAAM,SAAS,OAAO,OACzB,MAAK,MAAM,QAAQ,MAAM,MACvB,MAAK,MAAM,SAAS,KAAK,QAAQ;AAC/B,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,OAClD;IAGF,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,OAAO;AAC9C,QAAI,UAAU,IAAI;AAChB,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,OAAO,MAAM,MAAM;MAC5B,CAAC;AACF;;AAEF,aAAS,QAAQ,MAAM,IAAI;IAC3B,MAAM,MAAM;IACZ,MAAM,cAAc,IAAI,MAAM,KAAK;AAEnC,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,OAAO,MAAM,MAAM;KAC3B,IAAI,OAAO;AACT,aAAO,qBACL,OACA,MACA,CAAC;OAAE;OAAO;OAAK;OAAa,CAAC,EAC7B,QAAQ,WACT;;KAEJ,CAAC;;;EAMV,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,151 @@
|
|
|
1
|
+
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
|
+
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
|
+
import { replaceInStringValue } from "../fix-utils.js";
|
|
5
|
+
|
|
6
|
+
//#region src/rules/prefer-custom-property-syntax.ts
|
|
7
|
+
const VAR_REGEX = /var\(\s*--([a-zA-Z0-9_-]+)\s*(?:,\s*([^)]+))?\s*\)/g;
|
|
8
|
+
const COLOR_PROP_REGEX = /(?<![$#])\$([a-zA-Z][a-zA-Z0-9_-]*)-color\b(\.[0-9]+|\.\$[a-zA-Z][a-zA-Z0-9_-]*)?/g;
|
|
9
|
+
const KEYWORD_REGEX = /\b(transparent|currentColor)\b/gi;
|
|
10
|
+
function normalizeFallback(fallback) {
|
|
11
|
+
const trimmed = fallback.trim();
|
|
12
|
+
const lower = trimmed.toLowerCase();
|
|
13
|
+
if (lower === "transparent") return "#clear";
|
|
14
|
+
if (lower === "currentcolor") return "#current";
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
function suggestVarSyntax(name, fallback) {
|
|
18
|
+
if (name.endsWith("-color")) {
|
|
19
|
+
const tastyToken = `#${name.slice(0, -6)}`;
|
|
20
|
+
if (fallback) return `(${tastyToken}, ${normalizeFallback(fallback)})`;
|
|
21
|
+
return tastyToken;
|
|
22
|
+
}
|
|
23
|
+
const propToken = `$${name}`;
|
|
24
|
+
if (fallback) return `(${propToken}, ${normalizeFallback(fallback)})`;
|
|
25
|
+
return propToken;
|
|
26
|
+
}
|
|
27
|
+
function isInsideSpans(start, end, spans) {
|
|
28
|
+
for (const span of spans) if (start >= span.start && end <= span.end) return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
var prefer_custom_property_syntax_default = createRule({
|
|
32
|
+
name: "prefer-custom-property-syntax",
|
|
33
|
+
meta: {
|
|
34
|
+
type: "suggestion",
|
|
35
|
+
fixable: "code",
|
|
36
|
+
docs: { description: "Prefer Tasty token syntax ($prop, #color, #clear, #current) over raw CSS (var(), $x-color, transparent, currentColor)" },
|
|
37
|
+
messages: {
|
|
38
|
+
preferCustomPropertySyntax: "Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'.",
|
|
39
|
+
preferColorToken: "Use color token '{{suggestion}}' instead of custom property reference '{{raw}}'.",
|
|
40
|
+
preferClearToken: "Use the '#clear' token instead of the 'transparent' keyword.",
|
|
41
|
+
preferCurrentToken: "Use the '#current' token instead of the 'currentColor' keyword."
|
|
42
|
+
},
|
|
43
|
+
schema: []
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create(context) {
|
|
47
|
+
const ctx = new TastyContext(context);
|
|
48
|
+
function checkValue(value, node) {
|
|
49
|
+
const varSpans = [];
|
|
50
|
+
VAR_REGEX.lastIndex = 0;
|
|
51
|
+
let match;
|
|
52
|
+
while ((match = VAR_REGEX.exec(value)) !== null) {
|
|
53
|
+
const raw = match[0];
|
|
54
|
+
const name = match[1];
|
|
55
|
+
const fallback = match[2];
|
|
56
|
+
const suggestion = suggestVarSyntax(name, fallback);
|
|
57
|
+
const start = match.index;
|
|
58
|
+
const end = start + raw.length;
|
|
59
|
+
if (match.index !== void 0) varSpans.push({
|
|
60
|
+
start: match.index,
|
|
61
|
+
end: match.index + raw.length
|
|
62
|
+
});
|
|
63
|
+
context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: "preferCustomPropertySyntax",
|
|
66
|
+
data: {
|
|
67
|
+
raw,
|
|
68
|
+
suggestion
|
|
69
|
+
},
|
|
70
|
+
fix(fixer) {
|
|
71
|
+
return replaceInStringValue(fixer, node, [{
|
|
72
|
+
start,
|
|
73
|
+
end,
|
|
74
|
+
replacement: suggestion
|
|
75
|
+
}], context.sourceCode);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
COLOR_PROP_REGEX.lastIndex = 0;
|
|
80
|
+
while ((match = COLOR_PROP_REGEX.exec(value)) !== null) {
|
|
81
|
+
const raw = match[0];
|
|
82
|
+
const suggestion = `#${match[1]}${match[2] ?? ""}`;
|
|
83
|
+
const start = match.index;
|
|
84
|
+
const end = start + raw.length;
|
|
85
|
+
context.report({
|
|
86
|
+
node,
|
|
87
|
+
messageId: "preferColorToken",
|
|
88
|
+
data: {
|
|
89
|
+
raw,
|
|
90
|
+
suggestion
|
|
91
|
+
},
|
|
92
|
+
fix(fixer) {
|
|
93
|
+
return replaceInStringValue(fixer, node, [{
|
|
94
|
+
start,
|
|
95
|
+
end,
|
|
96
|
+
replacement: suggestion
|
|
97
|
+
}], context.sourceCode);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
KEYWORD_REGEX.lastIndex = 0;
|
|
102
|
+
while ((match = KEYWORD_REGEX.exec(value)) !== null) {
|
|
103
|
+
const start = match.index;
|
|
104
|
+
const end = start + match[0].length;
|
|
105
|
+
if (isInsideSpans(start, end, varSpans)) continue;
|
|
106
|
+
const lower = match[0].toLowerCase();
|
|
107
|
+
const replacement = lower === "transparent" ? "#clear" : "#current";
|
|
108
|
+
context.report({
|
|
109
|
+
node,
|
|
110
|
+
messageId: lower === "transparent" ? "preferClearToken" : "preferCurrentToken",
|
|
111
|
+
fix(fixer) {
|
|
112
|
+
return replaceInStringValue(fixer, node, [{
|
|
113
|
+
start,
|
|
114
|
+
end,
|
|
115
|
+
replacement
|
|
116
|
+
}], context.sourceCode);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function handleStyleObject(node) {
|
|
122
|
+
if (!ctx.isStyleObject(node)) return;
|
|
123
|
+
for (const prop of node.properties) {
|
|
124
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
125
|
+
const key = getKeyName(prop.key);
|
|
126
|
+
if (key === null) continue;
|
|
127
|
+
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
128
|
+
const str = getStringValue(prop.value);
|
|
129
|
+
if (str) {
|
|
130
|
+
checkValue(str, prop.value);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
134
|
+
if (stateProp.type !== "Property") continue;
|
|
135
|
+
const stateStr = getStringValue(stateProp.value);
|
|
136
|
+
if (stateStr) checkValue(stateStr, stateProp.value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
ImportDeclaration(node) {
|
|
142
|
+
ctx.trackImport(node);
|
|
143
|
+
},
|
|
144
|
+
...styleObjectListeners(handleStyleObject)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
export { prefer_custom_property_syntax_default as default };
|
|
151
|
+
//# 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';\nimport { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds =\n | 'preferCustomPropertySyntax'\n | 'preferColorToken'\n | 'preferClearToken'\n | 'preferCurrentToken';\n\nconst VAR_REGEX = /var\\(\\s*--([a-zA-Z0-9_-]+)\\s*(?:,\\s*([^)]+))?\\s*\\)/g;\n// `$x-color` → `#x`. Negative lookbehind avoids `$$func` / `##transition`.\n// `\\b` after `-color` avoids matching `$x-colorful`. Optional opacity suffix.\nconst COLOR_PROP_REGEX =\n /(?<![$#])\\$([a-zA-Z][a-zA-Z0-9_-]*)-color\\b(\\.[0-9]+|\\.\\$[a-zA-Z][a-zA-Z0-9_-]*)?/g;\nconst KEYWORD_REGEX = /\\b(transparent|currentColor)\\b/gi;\n\nfunction normalizeFallback(fallback: string): string {\n const trimmed = fallback.trim();\n const lower = trimmed.toLowerCase();\n if (lower === 'transparent') return '#clear';\n if (lower === 'currentcolor') return '#current';\n return trimmed;\n}\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}, ${normalizeFallback(fallback)})`;\n }\n return tastyToken;\n }\n\n const propToken = `$${name}`;\n if (fallback) {\n return `(${propToken}, ${normalizeFallback(fallback)})`;\n }\n return propToken;\n}\n\ninterface Span {\n start: number;\n end: number;\n}\n\nfunction isInsideSpans(start: number, end: number, spans: Span[]): boolean {\n for (const span of spans) {\n if (start >= span.start && end <= span.end) return true;\n }\n return false;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-custom-property-syntax',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Prefer Tasty token syntax ($prop, #color, #clear, #current) over raw CSS (var(), $x-color, transparent, currentColor)',\n },\n messages: {\n preferCustomPropertySyntax:\n \"Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'.\",\n preferColorToken:\n \"Use color token '{{suggestion}}' instead of custom property reference '{{raw}}'.\",\n preferClearToken:\n \"Use the '#clear' token instead of the 'transparent' keyword.\",\n preferCurrentToken:\n \"Use the '#current' token instead of the 'currentColor' keyword.\",\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 // Pass 1: var(--x) / var(--x, fallback) → $x / (#x, fallback).\n // Records spans so standalone keywords inside var() aren't double-reported.\n const varSpans: Span[] = [];\n VAR_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\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 const start = match.index;\n const end = start + raw.length;\n\n if (match.index !== undefined) {\n varSpans.push({\n start: match.index,\n end: match.index + raw.length,\n });\n }\n\n context.report({\n node,\n messageId: 'preferCustomPropertySyntax',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 2: $x-color → #x.\n COLOR_PROP_REGEX.lastIndex = 0;\n while ((match = COLOR_PROP_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const opacity = match[2] ?? '';\n const suggestion = `#${name}${opacity}`;\n const start = match.index;\n const end = start + raw.length;\n\n context.report({\n node,\n messageId: 'preferColorToken',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 3: standalone transparent / currentColor (skip inside var()).\n KEYWORD_REGEX.lastIndex = 0;\n while ((match = KEYWORD_REGEX.exec(value)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (isInsideSpans(start, end, varSpans)) continue;\n\n const lower = match[0].toLowerCase();\n const replacement = lower === 'transparent' ? '#clear' : '#current';\n context.report({\n node,\n messageId:\n lower === 'transparent' ? 'preferClearToken' : 'preferCurrentToken',\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement }],\n context.sourceCode,\n );\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 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":";;;;;;AAYA,MAAM,YAAY;AAGlB,MAAM,mBACJ;AACF,MAAM,gBAAgB;AAEtB,SAAS,kBAAkB,UAA0B;CACnD,MAAM,UAAU,SAAS,MAAM;CAC/B,MAAM,QAAQ,QAAQ,aAAa;AACnC,KAAI,UAAU,cAAe,QAAO;AACpC,KAAI,UAAU,eAAgB,QAAO;AACrC,QAAO;;AAGT,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,kBAAkB,SAAS,CAAC;AAExD,SAAO;;CAGT,MAAM,YAAY,IAAI;AACtB,KAAI,SACF,QAAO,IAAI,UAAU,IAAI,kBAAkB,SAAS,CAAC;AAEvD,QAAO;;AAQT,SAAS,cAAc,OAAe,KAAa,OAAwB;AACzE,MAAK,MAAM,QAAQ,MACjB,KAAI,SAAS,KAAK,SAAS,OAAO,KAAK,IAAK,QAAO;AAErD,QAAO;;AAGT,4CAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,yHACH;EACD,UAAU;GACR,4BACE;GACF,kBACE;GACF,kBACE;GACF,oBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WAAW,OAAe,MAA2B;GAG5D,MAAM,WAAmB,EAAE;AAC3B,aAAU,YAAY;GACtB,IAAI;AACJ,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;IACnD,MAAM,QAAQ,MAAM;IACpB,MAAM,MAAM,QAAQ,IAAI;AAExB,QAAI,MAAM,UAAU,OAClB,UAAS,KAAK;KACZ,OAAO,MAAM;KACb,KAAK,MAAM,QAAQ,IAAI;KACxB,CAAC;AAGJ,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAK;MAAY;KACzB,IAAI,OAAO;AACT,aAAO,qBACL,OACA,MACA,CAAC;OAAE;OAAO;OAAK,aAAa;OAAY,CAAC,EACzC,QAAQ,WACT;;KAEJ,CAAC;;AAIJ,oBAAiB,YAAY;AAC7B,WAAQ,QAAQ,iBAAiB,KAAK,MAAM,MAAM,MAAM;IACtD,MAAM,MAAM,MAAM;IAGlB,MAAM,aAAa,IAFN,MAAM,KACH,MAAM,MAAM;IAE5B,MAAM,QAAQ,MAAM;IACpB,MAAM,MAAM,QAAQ,IAAI;AAExB,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAK;MAAY;KACzB,IAAI,OAAO;AACT,aAAO,qBACL,OACA,MACA,CAAC;OAAE;OAAO;OAAK,aAAa;OAAY,CAAC,EACzC,QAAQ,WACT;;KAEJ,CAAC;;AAIJ,iBAAc,YAAY;AAC1B,WAAQ,QAAQ,cAAc,KAAK,MAAM,MAAM,MAAM;IACnD,MAAM,QAAQ,MAAM;IACpB,MAAM,MAAM,QAAQ,MAAM,GAAG;AAC7B,QAAI,cAAc,OAAO,KAAK,SAAS,CAAE;IAEzC,MAAM,QAAQ,MAAM,GAAG,aAAa;IACpC,MAAM,cAAc,UAAU,gBAAgB,WAAW;AACzD,YAAQ,OAAO;KACb;KACA,WACE,UAAU,gBAAgB,qBAAqB;KACjD,IAAI,OAAO;AACT,aAAO,qBACL,OACA,MACA,CAAC;OAAE;OAAO;OAAK;OAAa,CAAC,EAC7B,QAAQ,WACT;;KAEJ,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,88 @@
|
|
|
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
|
+
import { replaceStringValue } from "../fix-utils.js";
|
|
6
|
+
|
|
7
|
+
//#region src/rules/prefer-directional-shorthand.ts
|
|
8
|
+
const SIDES = [
|
|
9
|
+
"top",
|
|
10
|
+
"right",
|
|
11
|
+
"bottom",
|
|
12
|
+
"left"
|
|
13
|
+
];
|
|
14
|
+
function isZeroLike(token) {
|
|
15
|
+
const trimmed = token.trim();
|
|
16
|
+
if (trimmed === "0") return true;
|
|
17
|
+
return /^0+(?:\.0+)?(?:px|em|rem|%|x|r|cr|bw|ow|lh|sf)?$/i.test(trimmed);
|
|
18
|
+
}
|
|
19
|
+
function suggestDirectional(value) {
|
|
20
|
+
const tokens = value.trim().split(/\s+/);
|
|
21
|
+
if (tokens.length !== 4) return null;
|
|
22
|
+
const nonZero = tokens.map((token, index) => ({
|
|
23
|
+
token,
|
|
24
|
+
side: SIDES[index]
|
|
25
|
+
})).filter(({ token }) => !isZeroLike(token));
|
|
26
|
+
if (nonZero.length !== 1) return null;
|
|
27
|
+
const { token, side } = nonZero[0];
|
|
28
|
+
return `${token} ${side}`;
|
|
29
|
+
}
|
|
30
|
+
var prefer_directional_shorthand_default = createRule({
|
|
31
|
+
name: "prefer-directional-shorthand",
|
|
32
|
+
meta: {
|
|
33
|
+
type: "suggestion",
|
|
34
|
+
fixable: "code",
|
|
35
|
+
docs: { description: "Suggest Tasty directional shorthand instead of 4-value CSS box syntax with zero placeholders" },
|
|
36
|
+
messages: { preferDirectionalShorthand: "'{{property}}' value '{{raw}}' only sets one side. Use the directional shorthand '{{suggestion}}' (e.g. '1x bottom') instead." },
|
|
37
|
+
schema: []
|
|
38
|
+
},
|
|
39
|
+
defaultOptions: [],
|
|
40
|
+
create(context) {
|
|
41
|
+
const ctx = new TastyContext(context);
|
|
42
|
+
function checkValue(property, value, node) {
|
|
43
|
+
const suggestion = suggestDirectional(value);
|
|
44
|
+
if (!suggestion) return;
|
|
45
|
+
context.report({
|
|
46
|
+
node,
|
|
47
|
+
messageId: "preferDirectionalShorthand",
|
|
48
|
+
data: {
|
|
49
|
+
property,
|
|
50
|
+
raw: value,
|
|
51
|
+
suggestion
|
|
52
|
+
},
|
|
53
|
+
fix(fixer) {
|
|
54
|
+
return replaceStringValue(fixer, node, suggestion);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function handleStyleObject(node) {
|
|
59
|
+
if (!ctx.isStyleObject(node)) return;
|
|
60
|
+
for (const prop of node.properties) {
|
|
61
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
62
|
+
const key = getKeyName(prop.key);
|
|
63
|
+
if (key === null) continue;
|
|
64
|
+
if (!(key in DIRECTIONAL_MODIFIERS)) continue;
|
|
65
|
+
const str = getStringValue(prop.value);
|
|
66
|
+
if (str) {
|
|
67
|
+
checkValue(key, str, prop.value);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
71
|
+
if (stateProp.type !== "Property") continue;
|
|
72
|
+
const stateStr = getStringValue(stateProp.value);
|
|
73
|
+
if (stateStr) checkValue(key, stateStr, stateProp.value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
ImportDeclaration(node) {
|
|
79
|
+
ctx.trackImport(node);
|
|
80
|
+
},
|
|
81
|
+
...styleObjectListeners(handleStyleObject)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { prefer_directional_shorthand_default as default };
|
|
88
|
+
//# 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';\nimport { replaceStringValue } from '../fix-utils.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 fixable: 'code',\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 fix(fixer) {\n return replaceStringValue(fixer, node, 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 (!(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":";;;;;;;AASA,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,SAAS;EACT,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;IAC1C,IAAI,OAAO;AACT,YAAO,mBAAmB,OAAO,MAAM,WAAW;;IAErD,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,58 @@
|
|
|
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
|
+
function isNone(value) {
|
|
7
|
+
return value.trim().toLowerCase() === "none";
|
|
8
|
+
}
|
|
9
|
+
var prefer_hide_default = createRule({
|
|
10
|
+
name: "prefer-hide",
|
|
11
|
+
meta: {
|
|
12
|
+
type: "suggestion",
|
|
13
|
+
fixable: "code",
|
|
14
|
+
docs: { description: "Suggest hide: true instead of display: 'none' for Tasty's display/hide priority handling" },
|
|
15
|
+
messages: { preferHide: "Use 'hide: true' instead of \"display: 'none'\" so it composes with Tasty's display/hide priority handling." },
|
|
16
|
+
schema: []
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
const ctx = new TastyContext(context);
|
|
21
|
+
function handleStyleObject(node) {
|
|
22
|
+
if (!ctx.isStyleObject(node)) return;
|
|
23
|
+
for (const prop of node.properties) {
|
|
24
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
25
|
+
if (getKeyName(prop.key) !== "display") continue;
|
|
26
|
+
const str = getStringValue(prop.value);
|
|
27
|
+
if (str) {
|
|
28
|
+
if (isNone(str)) context.report({
|
|
29
|
+
node: prop.value,
|
|
30
|
+
messageId: "preferHide",
|
|
31
|
+
fix(fixer) {
|
|
32
|
+
return [fixer.replaceText(prop.key, "hide"), fixer.replaceText(prop.value, "true")];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
38
|
+
if (stateProp.type !== "Property") continue;
|
|
39
|
+
const stateStr = getStringValue(stateProp.value);
|
|
40
|
+
if (stateStr && isNone(stateStr)) context.report({
|
|
41
|
+
node: stateProp.value,
|
|
42
|
+
messageId: "preferHide"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ImportDeclaration(node) {
|
|
49
|
+
ctx.trackImport(node);
|
|
50
|
+
},
|
|
51
|
+
...styleObjectListeners(handleStyleObject)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { prefer_hide_default as default };
|
|
58
|
+
//# 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\nfunction isNone(value: string): boolean {\n return value.trim().toLowerCase() === 'none';\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-hide',\n meta: {\n type: 'suggestion',\n fixable: 'code',\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 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 if (isNone(str)) {\n context.report({\n node: prop.value,\n messageId: 'preferHide',\n fix(fixer) {\n return [\n fixer.replaceText(prop.key, 'hide'),\n fixer.replaceText(prop.value, 'true'),\n ];\n },\n });\n }\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 && isNone(stateStr)) {\n context.report({\n node: stateProp.value,\n messageId: 'preferHide',\n });\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,SAAS,OAAO,OAAwB;AACtC,QAAO,MAAM,MAAM,CAAC,aAAa,KAAK;;AAGxC,0BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,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,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,SAAI,OAAO,IAAI,CACb,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACX,IAAI,OAAO;AACT,cAAO,CACL,MAAM,YAAY,KAAK,KAAK,OAAO,EACnC,MAAM,YAAY,KAAK,OAAO,OAAO,CACtC;;MAEJ,CAAC;AAEJ;;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,YAAY,OAAO,SAAS,CAC9B,SAAQ,OAAO;MACb,MAAM,UAAU;MAChB,WAAW;MACZ,CAAC;;;;AAOZ,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -8,6 +8,7 @@ var prefer_shorthand_property_default = createRule({
|
|
|
8
8
|
name: "prefer-shorthand-property",
|
|
9
9
|
meta: {
|
|
10
10
|
type: "suggestion",
|
|
11
|
+
fixable: "code",
|
|
11
12
|
docs: { description: "Suggest tasty shorthand when a native CSS property with a tasty alternative is used" },
|
|
12
13
|
messages: { preferShorthand: "Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'." },
|
|
13
14
|
schema: []
|
|
@@ -28,6 +29,10 @@ var prefer_shorthand_property_default = createRule({
|
|
|
28
29
|
data: {
|
|
29
30
|
native: key,
|
|
30
31
|
alternative: mapping.hint
|
|
32
|
+
},
|
|
33
|
+
fix(fixer) {
|
|
34
|
+
if (!mapping.safeFix) return null;
|
|
35
|
+
return fixer.replaceText(prop.key, mapping.property);
|
|
31
36
|
}
|
|
32
37
|
});
|
|
33
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.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';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\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 const mapping = SHORTHAND_MAPPING[key];\n if (mapping) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative: mapping.hint },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,qEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,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;IAElB,MAAM,UAAU,kBAAkB;AAClC,QAAI,QACF,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM;MAAE,QAAQ;MAAK,aAAa,QAAQ;MAAM;
|
|
1
|
+
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.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';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\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 const mapping = SHORTHAND_MAPPING[key];\n if (mapping) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative: mapping.hint },\n fix(fixer) {\n // Only auto-fix the carry-over subset where the value passes\n // through unchanged (e.g. backgroundColor → fill, borderRadius →\n // radius). Directional / min/max / border-* renames change\n // semantics and stay report-only.\n if (!mapping.safeFix) return null;\n return fixer.replaceText(prop.key, mapping.property);\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,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,qEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,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;IAElB,MAAM,UAAU,kBAAkB;AAClC,QAAI,QACF,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM;MAAE,QAAQ;MAAK,aAAa,QAAQ;MAAM;KAChD,IAAI,OAAO;AAKT,UAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,aAAO,MAAM,YAAY,KAAK,KAAK,QAAQ,SAAS;;KAEvD,CAAC;;;AAKR,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: [],
|