@tenphi/eslint-plugin-tasty 0.6.4 → 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 +11 -5
- 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/context.js.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/parsers/state-key-parser.js +26 -1
- package/dist/parsers/state-key-parser.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-own-at-root.js +1 -1
- package/dist/rules/no-own-at-root.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/require-default-state.js +4 -3
- package/dist/rules/require-default-state.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 +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-styles-structure.js +1 -1
- package/dist/rules/valid-styles-structure.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
|
@@ -8,7 +8,7 @@ var require_default_state_default = createRule({
|
|
|
8
8
|
meta: {
|
|
9
9
|
type: "suggestion",
|
|
10
10
|
docs: { description: "Warn when a state mapping object doesn't have a default ('') key" },
|
|
11
|
-
messages: { missingDefaultState: "State mapping for '{{property}}' has no default ('') value." },
|
|
11
|
+
messages: { missingDefaultState: "State mapping for '{{property}}' has no default ('') or fallback floor ('_') value." },
|
|
12
12
|
schema: []
|
|
13
13
|
},
|
|
14
14
|
defaultOptions: [],
|
|
@@ -25,8 +25,9 @@ var require_default_state_default = createRule({
|
|
|
25
25
|
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
26
26
|
if (prop.value.type !== "ObjectExpression") continue;
|
|
27
27
|
if (!prop.value.properties.some((p) => {
|
|
28
|
-
if (p.type !== "Property") return false;
|
|
29
|
-
|
|
28
|
+
if (p.type !== "Property" || p.computed) return false;
|
|
29
|
+
const stateKey = getKeyName(p.key);
|
|
30
|
+
return stateKey === "" || stateKey === "_";
|
|
30
31
|
})) context.report({
|
|
31
32
|
node: prop.value,
|
|
32
33
|
messageId: "missingDefaultState",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-default-state.js","names":[],"sources":["../../src/rules/require-default-state.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 = 'missingDefaultState';\n\nexport default createRule<[], MessageIds>({\n name: 'require-default-state',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a state mapping object doesn't have a default ('') key\",\n },\n messages: {\n missingDefaultState:\n \"State mapping for '{{property}}' has no default ('') value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n const styleCtx = ctx.getStyleContext(node);\n if (!styleCtx) return;\n\n // Skip if extending (omitting '' is intentional)\n if (styleCtx.isExtending) 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 // Skip sub-elements and special keys\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n //
|
|
1
|
+
{"version":3,"file":"require-default-state.js","names":[],"sources":["../../src/rules/require-default-state.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 = 'missingDefaultState';\n\nexport default createRule<[], MessageIds>({\n name: 'require-default-state',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a state mapping object doesn't have a default ('') key\",\n },\n messages: {\n missingDefaultState:\n \"State mapping for '{{property}}' has no default ('') or fallback floor ('_') value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n const styleCtx = ctx.getStyleContext(node);\n if (!styleCtx) return;\n\n // Skip if extending (omitting '' is intentional)\n if (styleCtx.isExtending) 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 // Skip sub-elements and special keys\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n // A '' default or a '_' fallback floor both provide a value in the\n // normal state, so either one satisfies the requirement.\n const hasDefault = prop.value.properties.some((p) => {\n if (p.type !== 'Property' || p.computed) return false;\n const stateKey = getKeyName(p.key);\n return stateKey === '' || stateKey === '_';\n });\n\n if (!hasDefault) {\n context.report({\n node: prop.value,\n messageId: 'missingDefaultState',\n data: { property: key },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,oCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU,EACR,qBACE,uFACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;GAC1D,MAAM,WAAW,IAAI,gBAAgB,KAAK;AAC1C,OAAI,CAAC,SAAU;AAGf,OAAI,SAAS,YAAa;AAE1B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAGlB,QAAI,aAAa,KAAK,IAAI,CAAE;AAE5B,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAU5C,QAAI,CANe,KAAK,MAAM,WAAW,MAAM,MAAM;AACnD,SAAI,EAAE,SAAS,cAAc,EAAE,SAAU,QAAO;KAChD,MAAM,WAAW,WAAW,EAAE,IAAI;AAClC,YAAO,aAAa,MAAM,aAAa;MACvC,CAGA,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,UAAU,KAAK;KACxB,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: [],
|
|
@@ -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"}
|
|
@@ -27,7 +27,7 @@ var valid_styles_structure_default = createRule({
|
|
|
27
27
|
/^\[/
|
|
28
28
|
];
|
|
29
29
|
function looksLikeStateKey(key) {
|
|
30
|
-
if (key === "") return true;
|
|
30
|
+
if (key === "" || key === "_") return true;
|
|
31
31
|
return STATE_KEY_PATTERNS.some((p) => p.test(key));
|
|
32
32
|
}
|
|
33
33
|
function handleStyleObject(node) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-styles-structure.js","names":[],"sources":["../../src/rules/valid-styles-structure.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 =\n | 'stateKeyAtTopLevel'\n | 'invalidKeyframesStructure'\n | 'invalidPropertiesStructure'\n | 'invalidFontFaceStructure'\n | 'invalidCounterStyleStructure'\n | 'recipeNotString';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-styles-structure',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate overall structure of styles object passed to tasty APIs',\n },\n messages: {\n stateKeyAtTopLevel:\n \"State key '{{key}}' at top level is not valid. State maps belong inside property values, not at the root of the styles object.\",\n invalidKeyframesStructure:\n '@keyframes value must be an object of { name: { step: styles } }.',\n invalidPropertiesStructure:\n '@properties value must be an object of { name: { syntax, inherits, initialValue } }.',\n invalidFontFaceStructure:\n '@fontFace value must be an object of { familyName: descriptors | descriptors[] }.',\n invalidCounterStyleStructure:\n '@counterStyle value must be an object of { name: descriptors }.',\n recipeNotString: \"'recipe' value must be a string.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const STATE_KEY_PATTERNS = [\n /^:/, // pseudo-class\n /^\\./, // class selector\n /^\\[/, // attribute selector\n ];\n\n function looksLikeStateKey(key: string): boolean {\n if (key === '') return true;\n return STATE_KEY_PATTERNS.some((p) => p.test(key));\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 // Check for state keys at top level (common mistake)\n if (looksLikeStateKey(key)) {\n context.report({\n node: prop.key,\n messageId: 'stateKeyAtTopLevel',\n data: { key },\n });\n continue;\n }\n\n // Validate @keyframes structure\n if (key === '@keyframes') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidKeyframesStructure',\n });\n }\n continue;\n }\n\n // Validate @properties structure\n if (key === '@properties') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidPropertiesStructure',\n });\n }\n continue;\n }\n\n // Validate @fontFace structure\n if (key === '@fontFace') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidFontFaceStructure',\n });\n }\n continue;\n }\n\n // Validate @counterStyle structure\n if (key === '@counterStyle') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidCounterStyleStructure',\n });\n }\n continue;\n }\n\n // Validate recipe is a string\n if (key === 'recipe') {\n const str = getStringValue(prop.value);\n if (str === null && prop.value.type !== 'Literal') {\n // Allow string literals, template literals without expressions\n if (\n prop.value.type !== 'TemplateLiteral' ||\n prop.value.expressions.length > 0\n ) {\n context.report({\n node: prop.value,\n messageId: 'recipeNotString',\n });\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAaA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU;GACR,oBACE;GACF,2BACE;GACF,4BACE;GACF,0BACE;GACF,8BACE;GACF,iBAAiB;GAClB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,qBAAqB;GACzB;GACA;GACA;GACD;EAED,SAAS,kBAAkB,KAAsB;
|
|
1
|
+
{"version":3,"file":"valid-styles-structure.js","names":[],"sources":["../../src/rules/valid-styles-structure.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 =\n | 'stateKeyAtTopLevel'\n | 'invalidKeyframesStructure'\n | 'invalidPropertiesStructure'\n | 'invalidFontFaceStructure'\n | 'invalidCounterStyleStructure'\n | 'recipeNotString';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-styles-structure',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate overall structure of styles object passed to tasty APIs',\n },\n messages: {\n stateKeyAtTopLevel:\n \"State key '{{key}}' at top level is not valid. State maps belong inside property values, not at the root of the styles object.\",\n invalidKeyframesStructure:\n '@keyframes value must be an object of { name: { step: styles } }.',\n invalidPropertiesStructure:\n '@properties value must be an object of { name: { syntax, inherits, initialValue } }.',\n invalidFontFaceStructure:\n '@fontFace value must be an object of { familyName: descriptors | descriptors[] }.',\n invalidCounterStyleStructure:\n '@counterStyle value must be an object of { name: descriptors }.',\n recipeNotString: \"'recipe' value must be a string.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const STATE_KEY_PATTERNS = [\n /^:/, // pseudo-class\n /^\\./, // class selector\n /^\\[/, // attribute selector\n ];\n\n function looksLikeStateKey(key: string): boolean {\n // '' default and '_' fallback floor are state-map keys, never top-level.\n if (key === '' || key === '_') return true;\n return STATE_KEY_PATTERNS.some((p) => p.test(key));\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 // Check for state keys at top level (common mistake)\n if (looksLikeStateKey(key)) {\n context.report({\n node: prop.key,\n messageId: 'stateKeyAtTopLevel',\n data: { key },\n });\n continue;\n }\n\n // Validate @keyframes structure\n if (key === '@keyframes') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidKeyframesStructure',\n });\n }\n continue;\n }\n\n // Validate @properties structure\n if (key === '@properties') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidPropertiesStructure',\n });\n }\n continue;\n }\n\n // Validate @fontFace structure\n if (key === '@fontFace') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidFontFaceStructure',\n });\n }\n continue;\n }\n\n // Validate @counterStyle structure\n if (key === '@counterStyle') {\n if (prop.value.type !== 'ObjectExpression') {\n context.report({\n node: prop.value,\n messageId: 'invalidCounterStyleStructure',\n });\n }\n continue;\n }\n\n // Validate recipe is a string\n if (key === 'recipe') {\n const str = getStringValue(prop.value);\n if (str === null && prop.value.type !== 'Literal') {\n // Allow string literals, template literals without expressions\n if (\n prop.value.type !== 'TemplateLiteral' ||\n prop.value.expressions.length > 0\n ) {\n context.report({\n node: prop.value,\n messageId: 'recipeNotString',\n });\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAaA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU;GACR,oBACE;GACF,2BACE;GACF,4BACE;GACF,0BACE;GACF,8BACE;GACF,iBAAiB;GAClB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,qBAAqB;GACzB;GACA;GACA;GACD;EAED,SAAS,kBAAkB,KAAsB;AAE/C,OAAI,QAAQ,MAAM,QAAQ,IAAK,QAAO;AACtC,UAAO,mBAAmB,MAAM,MAAM,EAAE,KAAK,IAAI,CAAC;;EAGpD,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;AAGlB,QAAI,kBAAkB,IAAI,EAAE;AAC1B,aAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACX,MAAM,EAAE,KAAK;MACd,CAAC;AACF;;AAIF,QAAI,QAAQ,cAAc;AACxB,SAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,eAAe;AACzB,SAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,aAAa;AACvB,SAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,iBAAiB;AAC3B,SAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,UAEV;SADY,eAAe,KAAK,MAAM,KAC1B,QAAQ,KAAK,MAAM,SAAS,WAEtC;UACE,KAAK,MAAM,SAAS,qBACpB,KAAK,MAAM,YAAY,SAAS,EAEhC,SAAQ,OAAO;OACb,MAAM,KAAK;OACX,WAAW;OACZ,CAAC;;;;;AAOZ,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",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.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 {
|
|
1
|
+
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.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 {\n SEMANTIC_TRANSITIONS,\n KNOWN_CSS_PROPERTIES,\n TRANSITION_SEMANTIC_MAPPING,\n} from '../constants.js';\n\ntype MessageIds = 'unknownTransition' | 'preferSemanticTransition';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-transition',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Validate transition property values use valid semantic transition names',\n },\n messages: {\n unknownTransition:\n \"Unknown transition name '{{name}}'. Use a semantic name ({{known}}) or a CSS property name.\",\n preferSemanticTransition:\n \"Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkTransitionValue(value: string, node: TSESTree.Node): void {\n const groups = value.split(',');\n\n for (const group of groups) {\n const parts = group.trim().split(/\\s+/);\n if (parts.length === 0) continue;\n\n const name = parts[0];\n\n // $$ prefix is always valid (custom property reference: $$name -> --name)\n if (name.startsWith('$$')) continue;\n\n // ## prefix is always valid (color property reference: ##name -> --name-color)\n if (name.startsWith('##')) continue;\n\n const semantic = TRANSITION_SEMANTIC_MAPPING[name];\n if (semantic) {\n context.report({\n node,\n messageId: 'preferSemanticTransition',\n data: { native: name, semantic },\n });\n continue;\n }\n\n if (\n !SEMANTIC_TRANSITIONS.has(name) &&\n !KNOWN_CSS_PROPERTIES.has(name) &&\n name !== 'all' &&\n name !== 'none'\n ) {\n context.report({\n node,\n messageId: 'unknownTransition',\n data: {\n name,\n known: [...SEMANTIC_TRANSITIONS].join(', '),\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 !== 'transition') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkTransitionValue(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 checkTransitionValue(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,+BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,2EACH;EACD,UAAU;GACR,mBACE;GACF,0BACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,qBAAqB,OAAe,MAA2B;GACtE,MAAM,SAAS,MAAM,MAAM,IAAI;AAE/B,QAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM;AACvC,QAAI,MAAM,WAAW,EAAG;IAExB,MAAM,OAAO,MAAM;AAGnB,QAAI,KAAK,WAAW,KAAK,CAAE;AAG3B,QAAI,KAAK,WAAW,KAAK,CAAE;IAE3B,MAAM,WAAW,4BAA4B;AAC7C,QAAI,UAAU;AACZ,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE,QAAQ;OAAM;OAAU;MACjC,CAAC;AACF;;AAGF,QACE,CAAC,qBAAqB,IAAI,KAAK,IAC/B,CAAC,qBAAqB,IAAI,KAAK,IAC/B,SAAS,SACT,SAAS,OAET,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ;MACA,OAAO,CAAC,GAAG,qBAAqB,CAAC,KAAK,KAAK;MAC5C;KACF,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;AAG/C,QADY,WAAW,KAAK,IAAI,KACpB,aAAc;IAE1B,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,0BAAqB,KAAK,KAAK,MAAM;AACrC;;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,sBAAqB,UAAU,UAAU,MAAM;;;;AAOzD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -40,7 +40,7 @@ var valid_value_default = createRule({
|
|
|
40
40
|
unexpectedMod: "Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.",
|
|
41
41
|
unexpectedColor: "Property '{{property}}' does not accept color tokens, but found '{{color}}'.",
|
|
42
42
|
invalidMod: "Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.",
|
|
43
|
-
unknownToken: "Unknown token '{{token}}' in '{{property}}' value.",
|
|
43
|
+
unknownToken: "Unknown token '{{token}}' in '{{property}}' value. Check for a typo.",
|
|
44
44
|
parseError: "{{message}}"
|
|
45
45
|
},
|
|
46
46
|
schema: []
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@properties',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken: \"Unknown token '{{token}}' in '{{property}}' value.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.funcs === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.funcs)) {\n opts.knownFuncs = new Set(ctx.config.funcs);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token' || token.type === 'color-ref')\n ) {\n const colorName =\n token.type === 'color-token'\n ? `#${token.name}`\n : `##${token.name}`;\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: colorName },\n });\n continue;\n }\n\n // Check color functions in non-color properties\n if (\n !expectation.acceptsColor &&\n token.type === 'css-function' &&\n isColorFunction(token.name)\n ) {\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: `${token.name}()` },\n });\n continue;\n }\n\n // Skip tokens already reported via parser errors\n if (\n (token.type === 'unknown' || token.type === 'css-function') &&\n 'raw' in token &&\n token.raw &&\n erroredRaws.has(token.raw)\n ) {\n continue;\n }\n\n // Check unknown tokens against property expectations\n if (token.type === 'unknown') {\n const raw = token.raw;\n\n if (expectation.acceptsMods === true) {\n // Passthrough: accept any unknown token\n } else if (expectation.acceptsMods === false) {\n context.report({\n node,\n messageId: 'unexpectedMod',\n data: { property, mod: raw },\n });\n } else if (Array.isArray(expectation.acceptsMods)) {\n if (!expectation.acceptsMods.includes(raw)) {\n context.report({\n node,\n messageId: 'invalidMod',\n data: {\n property,\n mod: raw,\n accepted: expectation.acceptsMods.join(', '),\n },\n });\n }\n }\n }\n }\n }\n }\n }\n\n function processProperty(prop: TSESTree.Property): void {\n const key = !prop.computed ? getKeyName(prop.key) : null;\n\n if (key && (/^[A-Z]/.test(key) || key.startsWith('@'))) return;\n if (key && (key.startsWith('$') || key.startsWith('#'))) return;\n if (key && key.startsWith('&')) return;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(str, key, prop.value);\n return;\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(stateStr, key, stateProp.value);\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') continue;\n processProperty(prop);\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n\nconst COLOR_FUNC_NAMES = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'color-mix',\n 'color-contrast',\n]);\n\nfunction isColorFunction(name: string): boolean {\n return COLOR_FUNC_NAMES.has(name);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@properties',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken:\n \"Unknown token '{{token}}' in '{{property}}' value. Check for a typo.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.funcs === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.funcs)) {\n opts.knownFuncs = new Set(ctx.config.funcs);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token' || token.type === 'color-ref')\n ) {\n const colorName =\n token.type === 'color-token'\n ? `#${token.name}`\n : `##${token.name}`;\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: colorName },\n });\n continue;\n }\n\n // Check color functions in non-color properties\n if (\n !expectation.acceptsColor &&\n token.type === 'css-function' &&\n isColorFunction(token.name)\n ) {\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: `${token.name}()` },\n });\n continue;\n }\n\n // Skip tokens already reported via parser errors\n if (\n (token.type === 'unknown' || token.type === 'css-function') &&\n 'raw' in token &&\n token.raw &&\n erroredRaws.has(token.raw)\n ) {\n continue;\n }\n\n // Check unknown tokens against property expectations\n if (token.type === 'unknown') {\n const raw = token.raw;\n\n if (expectation.acceptsMods === true) {\n // Passthrough: accept any unknown token\n } else if (expectation.acceptsMods === false) {\n context.report({\n node,\n messageId: 'unexpectedMod',\n data: { property, mod: raw },\n });\n } else if (Array.isArray(expectation.acceptsMods)) {\n if (!expectation.acceptsMods.includes(raw)) {\n context.report({\n node,\n messageId: 'invalidMod',\n data: {\n property,\n mod: raw,\n accepted: expectation.acceptsMods.join(', '),\n },\n });\n }\n }\n }\n }\n }\n }\n }\n\n function processProperty(prop: TSESTree.Property): void {\n const key = !prop.computed ? getKeyName(prop.key) : null;\n\n if (key && (/^[A-Z]/.test(key) || key.startsWith('@'))) return;\n if (key && (key.startsWith('$') || key.startsWith('#'))) return;\n if (key && key.startsWith('&')) return;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(str, key, prop.value);\n return;\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(stateStr, key, stateProp.value);\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') continue;\n processProperty(prop);\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n\nconst COLOR_FUNC_NAMES = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'color-mix',\n 'color-contrast',\n]);\n\nfunction isColorFunction(name: string): boolean {\n return COLOR_FUNC_NAMES.has(name);\n}\n"],"mappings":"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"}
|