@tenphi/eslint-plugin-tasty 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/config.js +100 -20
  2. package/dist/config.js.map +1 -1
  3. package/dist/configs.js +5 -4
  4. package/dist/configs.js.map +1 -1
  5. package/dist/constants.js +67 -2
  6. package/dist/constants.js.map +1 -1
  7. package/dist/context.js +40 -7
  8. package/dist/context.js.map +1 -1
  9. package/dist/index.js +3 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/parsers/state-key-parser.js +486 -0
  12. package/dist/parsers/state-key-parser.js.map +1 -0
  13. package/dist/parsers/utils.js +128 -0
  14. package/dist/parsers/utils.js.map +1 -0
  15. package/dist/parsers/value-parser.js +613 -0
  16. package/dist/parsers/value-parser.js.map +1 -0
  17. package/dist/rules/consistent-token-usage.js +20 -19
  18. package/dist/rules/consistent-token-usage.js.map +1 -1
  19. package/dist/rules/known-property.js +31 -30
  20. package/dist/rules/known-property.js.map +1 -1
  21. package/dist/rules/no-duplicate-state.js +12 -11
  22. package/dist/rules/no-duplicate-state.js.map +1 -1
  23. package/dist/rules/no-important.js +12 -11
  24. package/dist/rules/no-important.js.map +1 -1
  25. package/dist/rules/no-nested-selector.js +15 -14
  26. package/dist/rules/no-nested-selector.js.map +1 -1
  27. package/dist/rules/no-nested-state-map.js +19 -18
  28. package/dist/rules/no-nested-state-map.js.map +1 -1
  29. package/dist/rules/no-raw-color-values.js +15 -14
  30. package/dist/rules/no-raw-color-values.js.map +1 -1
  31. package/dist/rules/no-runtime-styles-mutation.js +6 -5
  32. package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
  33. package/dist/rules/no-unknown-state-alias.js +12 -11
  34. package/dist/rules/no-unknown-state-alias.js.map +1 -1
  35. package/dist/rules/prefer-shorthand-property.js +19 -18
  36. package/dist/rules/prefer-shorthand-property.js.map +1 -1
  37. package/dist/rules/require-default-state.js +22 -21
  38. package/dist/rules/require-default-state.js.map +1 -1
  39. package/dist/rules/static-no-dynamic-values.js +7 -6
  40. package/dist/rules/static-no-dynamic-values.js.map +1 -1
  41. package/dist/rules/static-valid-selector.js +1 -1
  42. package/dist/rules/valid-boolean-property.js +19 -18
  43. package/dist/rules/valid-boolean-property.js.map +1 -1
  44. package/dist/rules/valid-color-token.js +33 -21
  45. package/dist/rules/valid-color-token.js.map +1 -1
  46. package/dist/rules/valid-custom-property.js +31 -19
  47. package/dist/rules/valid-custom-property.js.map +1 -1
  48. package/dist/rules/valid-custom-unit.js +12 -16
  49. package/dist/rules/valid-custom-unit.js.map +1 -1
  50. package/dist/rules/valid-directional-modifier.js +21 -20
  51. package/dist/rules/valid-directional-modifier.js.map +1 -1
  52. package/dist/rules/valid-preset.js +5 -2
  53. package/dist/rules/valid-preset.js.map +1 -1
  54. package/dist/rules/valid-radius-shape.js +19 -18
  55. package/dist/rules/valid-radius-shape.js.map +1 -1
  56. package/dist/rules/valid-recipe.js +5 -2
  57. package/dist/rules/valid-recipe.js.map +1 -1
  58. package/dist/rules/valid-state-definition.js +70 -0
  59. package/dist/rules/valid-state-definition.js.map +1 -0
  60. package/dist/rules/valid-state-key.js +39 -102
  61. package/dist/rules/valid-state-key.js.map +1 -1
  62. package/dist/rules/valid-styles-structure.js +39 -38
  63. package/dist/rules/valid-styles-structure.js.map +1 -1
  64. package/dist/rules/valid-sub-element.js +21 -20
  65. package/dist/rules/valid-sub-element.js.map +1 -1
  66. package/dist/rules/valid-transition.js +19 -18
  67. package/dist/rules/valid-transition.js.map +1 -1
  68. package/dist/rules/valid-value.js +117 -64
  69. package/dist/rules/valid-value.js.map +1 -1
  70. package/package.json +1 -8
  71. package/dist/parser.js +0 -46
  72. package/dist/parser.js.map +0 -1
@@ -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 } 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 modifier '{{modifier}}'.\",\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 return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n 'CallExpression ObjectExpression'(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 },\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,iFACH;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;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kCAAkC,MAAiC;AACjE,QAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,SAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,SAAI,KAAK,SAAS,cAAc,KAAK,SAAU;KAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,SAAI,QAAQ,KAAM;AAElB,SAAI,EAAE,OAAO,uBAAwB;KAGrC,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,SAAI,KAAK;AACP,iBAAW,KAAK,KAAK,KAAK,MAAM;AAChC;;AAIF,SAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,UAAI,UAAU,SAAS,WAAY;MACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,UAAI,SACF,YAAW,KAAK,UAAU,UAAU,MAAM;;;;GAMrD;;CAEJ,CAAC"}
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 modifier '{{modifier}}'.\",\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,iFACH;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"}
@@ -1,7 +1,7 @@
1
1
  import { createRule } from "../create-rule.js";
2
2
  import { PRESET_MODIFIERS } from "../constants.js";
3
- import { TastyContext } from "../context.js";
4
3
  import { getKeyName, getStringValue } from "../utils.js";
4
+ import { TastyContext, styleObjectListeners } from "../context.js";
5
5
 
6
6
  //#region src/rules/valid-preset.ts
7
7
  var valid_preset_default = createRule({
@@ -59,11 +59,14 @@ var valid_preset_default = createRule({
59
59
  }
60
60
  }
61
61
  }
62
+ function handleStyleObject(node) {
63
+ checkObject(node);
64
+ }
62
65
  return {
63
66
  ImportDeclaration(node) {
64
67
  ctx.trackImport(node);
65
68
  },
66
- ObjectExpression: checkObject
69
+ ...styleObjectListeners(handleStyleObject)
67
70
  };
68
71
  }
69
72
  });
@@ -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 } 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: \"Unknown preset '{{name}}'.\",\n unknownModifier: \"Unknown preset modifier '{{modifier}}'.\",\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 parts = value.trim().split(/\\s+/);\n if (parts.length === 0) return;\n\n const [presetName, ...modifiers] = parts;\n\n if (CSS_GLOBAL_KEYWORDS.has(presetName)) return;\n\n if (\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 for (const mod of modifiers) {\n if (!PRESET_MODIFIERS.has(mod)) {\n context.report({\n node,\n messageId: 'unknownModifier',\n data: { modifier: mod },\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 return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ObjectExpression: checkObject,\n };\n },\n});\n"],"mappings":";;;;;;AAQA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU;GACR,eAAe;GACf,iBAAiB;GAClB;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,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM;AACvC,OAAI,MAAM,WAAW,EAAG;GAExB,MAAM,CAAC,YAAY,GAAG,aAAa;AAEnC,OAAI,oBAAoB,IAAI,WAAW,CAAE;AAEzC,OACE,IAAI,OAAO,QAAQ,SAAS,KAC5B,CAAC,IAAI,OAAO,QAAQ,SAAS,WAAW,CAExC,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,MAAM,YAAY;IAC3B,CAAC;AAGJ,QAAK,MAAM,OAAO,UAChB,KAAI,CAAC,iBAAiB,IAAI,IAAI,CAC5B,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,UAAU,KAAK;IACxB,CAAC;;EAKR,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;;;;AAOrD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kBAAkB;GACnB;;CAEJ,CAAC"}
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: \"Unknown preset '{{name}}'.\",\n unknownModifier: \"Unknown preset modifier '{{modifier}}'.\",\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 parts = value.trim().split(/\\s+/);\n if (parts.length === 0) return;\n\n const [presetName, ...modifiers] = parts;\n\n if (CSS_GLOBAL_KEYWORDS.has(presetName)) return;\n\n if (\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 for (const mod of modifiers) {\n if (!PRESET_MODIFIERS.has(mod)) {\n context.report({\n node,\n messageId: 'unknownModifier',\n data: { modifier: mod },\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,eAAe;GACf,iBAAiB;GAClB;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,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM;AACvC,OAAI,MAAM,WAAW,EAAG;GAExB,MAAM,CAAC,YAAY,GAAG,aAAa;AAEnC,OAAI,oBAAoB,IAAI,WAAW,CAAE;AAEzC,OACE,IAAI,OAAO,QAAQ,SAAS,KAC5B,CAAC,IAAI,OAAO,QAAQ,SAAS,WAAW,CAExC,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,MAAM,YAAY;IAC3B,CAAC;AAGJ,QAAK,MAAM,OAAO,UAChB,KAAI,CAAC,iBAAiB,IAAI,IAAI,CAC5B,SAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM,EAAE,UAAU,KAAK;IACxB,CAAC;;EAKR,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"}
@@ -1,7 +1,7 @@
1
1
  import { createRule } from "../create-rule.js";
2
2
  import { RADIUS_SHAPES } from "../constants.js";
3
- import { TastyContext } from "../context.js";
4
3
  import { getKeyName, getStringValue } from "../utils.js";
4
+ import { TastyContext, styleObjectListeners } from "../context.js";
5
5
 
6
6
  //#region src/rules/valid-radius-shape.ts
7
7
  const SHAPE_LIKE = /^[a-z]+$/;
@@ -47,27 +47,28 @@ var valid_radius_shape_default = createRule({
47
47
  for (const shape of RADIUS_SHAPES) if (shape.startsWith(input.slice(0, 3)) || input.startsWith(shape.slice(0, 3))) return shape;
48
48
  return null;
49
49
  }
50
+ function handleStyleObject(node) {
51
+ if (!ctx.isStyleObject(node)) return;
52
+ for (const prop of node.properties) {
53
+ if (prop.type !== "Property" || prop.computed) continue;
54
+ if (getKeyName(prop.key) !== "radius") continue;
55
+ const str = getStringValue(prop.value);
56
+ if (str) {
57
+ checkRadiusValue(str, prop.value);
58
+ continue;
59
+ }
60
+ if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
61
+ if (stateProp.type !== "Property") continue;
62
+ const stateStr = getStringValue(stateProp.value);
63
+ if (stateStr) checkRadiusValue(stateStr, stateProp.value);
64
+ }
65
+ }
66
+ }
50
67
  return {
51
68
  ImportDeclaration(node) {
52
69
  ctx.trackImport(node);
53
70
  },
54
- "CallExpression ObjectExpression"(node) {
55
- if (!ctx.isStyleObject(node)) return;
56
- for (const prop of node.properties) {
57
- if (prop.type !== "Property" || prop.computed) continue;
58
- if (getKeyName(prop.key) !== "radius") continue;
59
- const str = getStringValue(prop.value);
60
- if (str) {
61
- checkRadiusValue(str, prop.value);
62
- continue;
63
- }
64
- if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
65
- if (stateProp.type !== "Property") continue;
66
- const stateStr = getStringValue(stateProp.value);
67
- if (stateStr) checkRadiusValue(stateStr, stateProp.value);
68
- }
69
- }
70
- }
71
+ ...styleObjectListeners(handleStyleObject)
71
72
  };
72
73
  }
73
74
  });
@@ -1 +1 @@
1
- {"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { RADIUS_SHAPES } from '../constants.js';\n\ntype MessageIds = 'unknownShape';\n\nconst SHAPE_LIKE = /^[a-z]+$/;\n\nexport default createRule<[], MessageIds>({\n name: 'valid-radius-shape',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate special shape keywords used with the radius property',\n },\n messages: {\n unknownShape:\n \"Unknown radius shape '{{shape}}'. Valid shapes: {{valid}}.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRadiusValue(value: string, node: TSESTree.Node): void {\n const trimmed = value.trim();\n // Only check single-word values that look like keywords\n if (!SHAPE_LIKE.test(trimmed)) return;\n\n // Known valid keywords\n if (RADIUS_SHAPES.has(trimmed)) return;\n if (trimmed === 'true' || trimmed === 'false') return;\n if (trimmed === 'none' || trimmed === 'inherit' || trimmed === 'initial')\n return;\n\n // Check if it's a directional modifier (handled elsewhere)\n const 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 if (directions.has(trimmed)) return;\n\n // It looks like they tried to use a shape keyword\n const suggestion = findClosestShape(trimmed);\n const validList = [...RADIUS_SHAPES].join(', ');\n\n context.report({\n node,\n messageId: 'unknownShape',\n data: {\n shape: trimmed,\n valid:\n validList + (suggestion ? `. Did you mean '${suggestion}'?` : ''),\n },\n });\n }\n\n function findClosestShape(input: string): string | null {\n for (const shape of RADIUS_SHAPES) {\n if (\n shape.startsWith(input.slice(0, 3)) ||\n input.startsWith(shape.slice(0, 3))\n ) {\n return shape;\n }\n }\n return null;\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n 'CallExpression ObjectExpression'(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 !== 'radius') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkRadiusValue(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 checkRadiusValue(stateStr, stateProp.value);\n }\n }\n }\n }\n },\n };\n },\n});\n"],"mappings":";;;;;;AAQA,MAAM,aAAa;AAEnB,iCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iEACH;EACD,UAAU,EACR,cACE,8DACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,UAAU,MAAM,MAAM;AAE5B,OAAI,CAAC,WAAW,KAAK,QAAQ,CAAE;AAG/B,OAAI,cAAc,IAAI,QAAQ,CAAE;AAChC,OAAI,YAAY,UAAU,YAAY,QAAS;AAC/C,OAAI,YAAY,UAAU,YAAY,aAAa,YAAY,UAC7D;AAaF,OAVmB,IAAI,IAAI;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CACa,IAAI,QAAQ,CAAE;GAG7B,MAAM,aAAa,iBAAiB,QAAQ;GAC5C,MAAM,YAAY,CAAC,GAAG,cAAc,CAAC,KAAK,KAAK;AAE/C,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KACJ,OAAO;KACP,OACE,aAAa,aAAa,mBAAmB,WAAW,MAAM;KACjE;IACF,CAAC;;EAGJ,SAAS,iBAAiB,OAA8B;AACtD,QAAK,MAAM,SAAS,cAClB,KACE,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,IACnC,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,CAEnC,QAAO;AAGX,UAAO;;AAGT,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kCAAkC,MAAiC;AACjE,QAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,SAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,SAAI,KAAK,SAAS,cAAc,KAAK,SAAU;AAG/C,SADY,WAAW,KAAK,IAAI,KACpB,SAAU;KAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,SAAI,KAAK;AACP,uBAAiB,KAAK,KAAK,MAAM;AACjC;;AAGF,SAAI,KAAK,MAAM,SAAS,mBACtB,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,UAAI,UAAU,SAAS,WAAY;MACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,UAAI,SACF,kBAAiB,UAAU,UAAU,MAAM;;;;GAMtD;;CAEJ,CAAC"}
1
+ {"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.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 { RADIUS_SHAPES } from '../constants.js';\n\ntype MessageIds = 'unknownShape';\n\nconst SHAPE_LIKE = /^[a-z]+$/;\n\nexport default createRule<[], MessageIds>({\n name: 'valid-radius-shape',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate special shape keywords used with the radius property',\n },\n messages: {\n unknownShape:\n \"Unknown radius shape '{{shape}}'. Valid shapes: {{valid}}.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRadiusValue(value: string, node: TSESTree.Node): void {\n const trimmed = value.trim();\n // Only check single-word values that look like keywords\n if (!SHAPE_LIKE.test(trimmed)) return;\n\n // Known valid keywords\n if (RADIUS_SHAPES.has(trimmed)) return;\n if (trimmed === 'true' || trimmed === 'false') return;\n if (trimmed === 'none' || trimmed === 'inherit' || trimmed === 'initial')\n return;\n\n // Check if it's a directional modifier (handled elsewhere)\n const 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 if (directions.has(trimmed)) return;\n\n // It looks like they tried to use a shape keyword\n const suggestion = findClosestShape(trimmed);\n const validList = [...RADIUS_SHAPES].join(', ');\n\n context.report({\n node,\n messageId: 'unknownShape',\n data: {\n shape: trimmed,\n valid:\n validList + (suggestion ? `. Did you mean '${suggestion}'?` : ''),\n },\n });\n }\n\n function findClosestShape(input: string): string | null {\n for (const shape of RADIUS_SHAPES) {\n if (\n shape.startsWith(input.slice(0, 3)) ||\n input.startsWith(shape.slice(0, 3))\n ) {\n return shape;\n }\n }\n return null;\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 !== 'radius') continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkRadiusValue(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 checkRadiusValue(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,aAAa;AAEnB,iCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iEACH;EACD,UAAU,EACR,cACE,8DACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,iBAAiB,OAAe,MAA2B;GAClE,MAAM,UAAU,MAAM,MAAM;AAE5B,OAAI,CAAC,WAAW,KAAK,QAAQ,CAAE;AAG/B,OAAI,cAAc,IAAI,QAAQ,CAAE;AAChC,OAAI,YAAY,UAAU,YAAY,QAAS;AAC/C,OAAI,YAAY,UAAU,YAAY,aAAa,YAAY,UAC7D;AAaF,OAVmB,IAAI,IAAI;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD,CAAC,CACa,IAAI,QAAQ,CAAE;GAG7B,MAAM,aAAa,iBAAiB,QAAQ;GAC5C,MAAM,YAAY,CAAC,GAAG,cAAc,CAAC,KAAK,KAAK;AAE/C,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KACJ,OAAO;KACP,OACE,aAAa,aAAa,mBAAmB,WAAW,MAAM;KACjE;IACF,CAAC;;EAGJ,SAAS,iBAAiB,OAA8B;AACtD,QAAK,MAAM,SAAS,cAClB,KACE,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,IACnC,MAAM,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,CAEnC,QAAO;AAGX,UAAO;;EAGT,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,SAAU;IAEtB,MAAM,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,sBAAiB,KAAK,KAAK,MAAM;AACjC;;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;;;;AAOrD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { createRule } from "../create-rule.js";
2
- import { TastyContext } from "../context.js";
3
2
  import { getKeyName, getStringValue } from "../utils.js";
3
+ import { TastyContext, styleObjectListeners } from "../context.js";
4
4
 
5
5
  //#region src/rules/valid-recipe.ts
6
6
  var valid_recipe_default = createRule({
@@ -38,11 +38,14 @@ var valid_recipe_default = createRule({
38
38
  if (str) checkRecipeValue(str, prop.value);
39
39
  }
40
40
  }
41
+ function handleStyleObject(node) {
42
+ checkObject(node);
43
+ }
41
44
  return {
42
45
  ImportDeclaration(node) {
43
46
  ctx.trackImport(node);
44
47
  },
45
- ObjectExpression: checkObject
48
+ ...styleObjectListeners(handleStyleObject)
46
49
  };
47
50
  }
48
51
  });
@@ -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 } 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: \"Unknown recipe '{{name}}'.\",\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 return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ObjectExpression: checkObject,\n };\n },\n});\n"],"mappings":";;;;;AAOA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU,EACR,eAAe,8BAChB;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;;;AAKvC,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kBAAkB;GACnB;;CAEJ,CAAC"}
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: \"Unknown recipe '{{name}}'.\",\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,eAAe,8BAChB;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"}
@@ -0,0 +1,70 @@
1
+ import { createRule } from "../create-rule.js";
2
+ import { BUILT_IN_STATE_PREFIXES } from "../constants.js";
3
+ import { getKeyName, getStringValue } from "../utils.js";
4
+ import { TastyContext } from "../context.js";
5
+ import { validateStateDefinition } from "../parsers/state-key-parser.js";
6
+
7
+ //#region src/rules/valid-state-definition.ts
8
+ var valid_state_definition_default = createRule({
9
+ name: "valid-state-definition",
10
+ meta: {
11
+ type: "problem",
12
+ docs: { description: "Validate state definition values (the right-hand side of state aliases in configure() or tasty.config)" },
13
+ messages: {
14
+ invalidKeyPrefix: "State alias '{{key}}' must start with '@'.",
15
+ invalidDefinition: "{{reason}}"
16
+ },
17
+ schema: []
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ const ctx = new TastyContext(context);
22
+ function checkStateDefinitions(node) {
23
+ for (const prop of node.properties) {
24
+ if (prop.type !== "Property" || prop.computed) continue;
25
+ const key = getKeyName(prop.key);
26
+ if (key === null) continue;
27
+ if (!key.startsWith("@")) {
28
+ context.report({
29
+ node: prop.key,
30
+ messageId: "invalidKeyPrefix",
31
+ data: { key }
32
+ });
33
+ continue;
34
+ }
35
+ let isBuiltin = false;
36
+ for (const prefix of BUILT_IN_STATE_PREFIXES) if (key === prefix || key.startsWith(prefix + "(")) {
37
+ isBuiltin = true;
38
+ break;
39
+ }
40
+ if (isBuiltin) continue;
41
+ const value = getStringValue(prop.value);
42
+ if (!value) continue;
43
+ const result = validateStateDefinition(value);
44
+ for (const error of result.errors) context.report({
45
+ node: prop.value,
46
+ messageId: "invalidDefinition",
47
+ data: { reason: error.message }
48
+ });
49
+ }
50
+ }
51
+ return {
52
+ ImportDeclaration(node) {
53
+ ctx.trackImport(node);
54
+ },
55
+ "CallExpression"(node) {
56
+ if (node.callee.type !== "Identifier") return;
57
+ const imp = ctx.getImport(node.callee.name);
58
+ if (!imp) return;
59
+ if (imp.importedName !== "configure") return;
60
+ const arg = node.arguments[0];
61
+ if (arg?.type !== "ObjectExpression") return;
62
+ for (const prop of arg.properties) if (prop.type === "Property" && !prop.computed && prop.key.type === "Identifier" && prop.key.name === "states" && prop.value.type === "ObjectExpression") checkStateDefinitions(prop.value);
63
+ }
64
+ };
65
+ }
66
+ });
67
+
68
+ //#endregion
69
+ export { valid_state_definition_default as default };
70
+ //# sourceMappingURL=valid-state-definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valid-state-definition.js","names":[],"sources":["../../src/rules/valid-state-definition.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { validateStateDefinition } from '../parsers/state-key-parser.js';\nimport { BUILT_IN_STATE_PREFIXES } from '../constants.js';\n\ntype MessageIds = 'invalidKeyPrefix' | 'invalidDefinition';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-state-definition',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate state definition values (the right-hand side of state aliases in configure() or tasty.config)',\n },\n messages: {\n invalidKeyPrefix:\n \"State alias '{{key}}' must start with '@'.\",\n invalidDefinition: '{{reason}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkStateDefinitions(node: TSESTree.ObjectExpression): void {\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 // Keys should start with @\n if (!key.startsWith('@')) {\n context.report({\n node: prop.key,\n messageId: 'invalidKeyPrefix',\n data: { key },\n });\n continue;\n }\n\n // Skip built-in prefixes — they're not aliases\n let isBuiltin = false;\n for (const prefix of BUILT_IN_STATE_PREFIXES) {\n if (key === prefix || key.startsWith(prefix + '(')) {\n isBuiltin = true;\n break;\n }\n }\n if (isBuiltin) continue;\n\n // Validate the value (the state definition expression)\n const value = getStringValue(prop.value);\n if (!value) continue;\n\n const result = validateStateDefinition(value);\n for (const error of result.errors) {\n context.report({\n node: prop.value,\n messageId: 'invalidDefinition',\n data: { reason: error.message },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n /**\n * Detect configure({ states: { ... } }) calls.\n */\n 'CallExpression'(node: TSESTree.CallExpression) {\n if (node.callee.type !== 'Identifier') return;\n\n const imp = ctx.getImport(node.callee.name);\n if (!imp) return;\n\n // Only handle configure() calls\n if (imp.importedName !== 'configure') return;\n\n const arg = node.arguments[0];\n if (arg?.type !== 'ObjectExpression') return;\n\n for (const prop of arg.properties) {\n if (\n prop.type === 'Property' &&\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'states' &&\n prop.value.type === 'ObjectExpression'\n ) {\n checkStateDefinitions(prop.value);\n }\n }\n },\n };\n },\n});\n"],"mappings":";;;;;;;AASA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,0GACH;EACD,UAAU;GACR,kBACE;GACF,mBAAmB;GACpB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,sBAAsB,MAAuC;AACpE,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,CAAC,IAAI,WAAW,IAAI,EAAE;AACxB,aAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACX,MAAM,EAAE,KAAK;MACd,CAAC;AACF;;IAIF,IAAI,YAAY;AAChB,SAAK,MAAM,UAAU,wBACnB,KAAI,QAAQ,UAAU,IAAI,WAAW,SAAS,IAAI,EAAE;AAClD,iBAAY;AACZ;;AAGJ,QAAI,UAAW;IAGf,MAAM,QAAQ,eAAe,KAAK,MAAM;AACxC,QAAI,CAAC,MAAO;IAEZ,MAAM,SAAS,wBAAwB,MAAM;AAC7C,SAAK,MAAM,SAAS,OAAO,OACzB,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,QAAQ,MAAM,SAAS;KAChC,CAAC;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAMvB,iBAAiB,MAA+B;AAC9C,QAAI,KAAK,OAAO,SAAS,aAAc;IAEvC,MAAM,MAAM,IAAI,UAAU,KAAK,OAAO,KAAK;AAC3C,QAAI,CAAC,IAAK;AAGV,QAAI,IAAI,iBAAiB,YAAa;IAEtC,MAAM,MAAM,KAAK,UAAU;AAC3B,QAAI,KAAK,SAAS,mBAAoB;AAEtC,SAAK,MAAM,QAAQ,IAAI,WACrB,KACE,KAAK,SAAS,cACd,CAAC,KAAK,YACN,KAAK,IAAI,SAAS,gBAClB,KAAK,IAAI,SAAS,YAClB,KAAK,MAAM,SAAS,mBAEpB,uBAAsB,KAAK,MAAM;;GAIxC;;CAEJ,CAAC"}
@@ -1,86 +1,25 @@
1
1
  import { createRule } from "../create-rule.js";
2
- import { TastyContext } from "../context.js";
3
2
  import { getKeyName, getStringValue } from "../utils.js";
4
- import { createStateParserContext, parseStateKey, setGlobalPredefinedStates } from "@tenphi/tasty/core";
3
+ import { TastyContext, styleObjectListeners } from "../context.js";
4
+ import { parseStateKey } from "../parsers/state-key-parser.js";
5
5
 
6
6
  //#region src/rules/valid-state-key.ts
7
- function collectIssues(node, knownPredefined) {
8
- const issues = [];
9
- function walk(n) {
10
- if (n.kind === "true" || n.kind === "false") return;
11
- if (n.kind === "compound") {
12
- for (const child of n.children) walk(child);
13
- return;
14
- }
15
- switch (n.type) {
16
- case "media":
17
- if (n.subtype === "dimension" && !n.dimension && !n.lowerBound && !n.upperBound) issues.push(`Empty or invalid @media dimension query in '${n.raw}'.`);
18
- break;
19
- case "container":
20
- if (n.subtype === "dimension" && !n.dimension && !n.lowerBound && !n.upperBound) issues.push(`Empty or invalid container dimension query in '${n.raw}'.`);
21
- break;
22
- case "own":
23
- walk(n.innerCondition);
24
- break;
25
- case "parent":
26
- if ("innerCondition" in n && n.innerCondition) walk(n.innerCondition);
27
- break;
28
- case "pseudo":
29
- if (n.raw.startsWith("@") && !knownPredefined.has(n.raw)) issues.push(`Unresolvable predefined state '${n.raw}'.`);
30
- break;
31
- default: break;
32
- }
33
- }
34
- walk(node);
35
- return issues;
36
- }
37
- function hasOwnState(node) {
38
- if (node.kind === "true" || node.kind === "false") return false;
39
- if (node.kind === "compound") return node.children.some(hasOwnState);
40
- if (node.type === "own") return true;
41
- return false;
42
- }
43
- /**
44
- * Matches the same tokens as tasty's internal STATE_TOKEN_PATTERN.
45
- * Characters not covered by this pattern (excluding whitespace/commas)
46
- * are flagged as unrecognized.
47
- */
48
- const STATE_TOKEN_PATTERN = /([&|!^])|([()])|(@media:[a-z]+)|(@media\([^)]+\))|(@supports\([^()]*(?:\([^)]*\))?[^)]*\))|(@root\([^)]+\))|(@parent\([^)]+\))|(@own\([^)]+\))|(@\([^()]*(?:\([^)]*\))?[^)]*\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\^=|\$=|\*=|=)(?:"[^"]*"|'[^']*'|[^\s&|!^()]+))|([a-z][a-z0-9-]+)|(:[-a-z][a-z0-9-]*(?:\([^)]+\))?)|(\.[a-z][a-z0-9-]+)|(\[[^\]]+\])/gi;
49
- function hasUnrecognizedTokens(stateKey) {
50
- if (!stateKey.trim()) return null;
51
- const covered = /* @__PURE__ */ new Set();
52
- STATE_TOKEN_PATTERN.lastIndex = 0;
53
- let match;
54
- while ((match = STATE_TOKEN_PATTERN.exec(stateKey)) !== null) for (let i = match.index; i < match.index + match[0].length; i++) covered.add(i);
55
- const uncovered = [];
56
- for (let i = 0; i < stateKey.length; i++) {
57
- const ch = stateKey[i];
58
- if (ch === " " || ch === " " || ch === ",") continue;
59
- if (!covered.has(i)) uncovered.push(ch);
60
- }
61
- if (uncovered.length > 0) return `Unrecognized characters '${[...new Set(uncovered)].join("")}' in state key '${stateKey}'.`;
62
- return null;
63
- }
64
7
  var valid_state_key_default = createRule({
65
8
  name: "valid-state-key",
66
9
  meta: {
67
10
  type: "problem",
68
- docs: { description: "Validate state key syntax in style mapping objects using the tasty state parser" },
11
+ docs: { description: "Validate state key syntax in style mapping objects" },
69
12
  messages: {
70
- unparseable: "{{reason}}",
71
- emptyAdvancedState: "{{reason}}",
72
- unresolvablePredefined: "{{reason}}",
73
- ownOutsideSubElement: "@own() can only be used inside sub-element styles."
13
+ invalidStateKey: "{{reason}}",
14
+ ownOutsideSubElement: "@own() can only be used inside sub-element styles.",
15
+ unknownAlias: "Unknown state alias '{{alias}}'. Configured aliases: {{known}}."
74
16
  },
75
17
  schema: []
76
18
  },
77
19
  defaultOptions: [],
78
20
  create(context) {
79
21
  const ctx = new TastyContext(context);
80
- const predefinedStates = {};
81
- for (const alias of ctx.config.states) predefinedStates[alias] = alias;
82
- setGlobalPredefinedStates(predefinedStates);
83
- const knownPredefined = new Set(ctx.config.states);
22
+ const parserOpts = { knownAliases: ctx.config.states };
84
23
  function isInsideSubElement(node) {
85
24
  let current = node.parent;
86
25
  while (current) {
@@ -91,51 +30,49 @@ var valid_state_key_default = createRule({
91
30
  }
92
31
  function checkStateKey(key, keyNode, insideSubElement) {
93
32
  if (key === "") return;
94
- const tokenError = hasUnrecognizedTokens(key);
95
- if (tokenError) {
96
- context.report({
97
- node: keyNode,
98
- messageId: "unparseable",
99
- data: { reason: tokenError }
100
- });
101
- return;
102
- }
103
- const result = parseStateKey(key, { context: createStateParserContext() });
104
- if (hasOwnState(result) && !insideSubElement) context.report({
33
+ const result = parseStateKey(key, parserOpts);
34
+ for (const error of result.errors) context.report({
35
+ node: keyNode,
36
+ messageId: "invalidStateKey",
37
+ data: { reason: error.message }
38
+ });
39
+ if (result.hasOwn && !insideSubElement) context.report({
105
40
  node: keyNode,
106
41
  messageId: "ownOutsideSubElement"
107
42
  });
108
- const issues = collectIssues(result, knownPredefined);
109
- for (const reason of issues) {
110
- const messageId = reason.startsWith("Unresolvable") ? "unresolvablePredefined" : reason.startsWith("Empty") ? "emptyAdvancedState" : "unparseable";
111
- context.report({
43
+ if (ctx.config.states.length > 0) {
44
+ for (const alias of result.referencedAliases) if (!ctx.config.states.includes(alias)) context.report({
112
45
  node: keyNode,
113
- messageId,
114
- data: { reason }
46
+ messageId: "unknownAlias",
47
+ data: {
48
+ alias,
49
+ known: ctx.config.states.join(", ")
50
+ }
115
51
  });
116
52
  }
117
53
  }
54
+ function handleStyleObject(node) {
55
+ if (!ctx.isStyleObject(node)) return;
56
+ const insideSubElement = isInsideSubElement(node);
57
+ for (const prop of node.properties) {
58
+ if (prop.type !== "Property" || prop.computed) continue;
59
+ const key = getKeyName(prop.key);
60
+ if (key === null) continue;
61
+ if (/^[A-Z]/.test(key) || key.startsWith("@") || key.startsWith("&")) continue;
62
+ if (prop.value.type !== "ObjectExpression") continue;
63
+ for (const stateProp of prop.value.properties) {
64
+ if (stateProp.type !== "Property") continue;
65
+ const stateKey = !stateProp.computed ? getKeyName(stateProp.key) : getStringValue(stateProp.key);
66
+ if (stateKey === null) continue;
67
+ checkStateKey(stateKey, stateProp.key, insideSubElement);
68
+ }
69
+ }
70
+ }
118
71
  return {
119
72
  ImportDeclaration(node) {
120
73
  ctx.trackImport(node);
121
74
  },
122
- "CallExpression ObjectExpression"(node) {
123
- if (!ctx.isStyleObject(node)) return;
124
- const insideSubElement = isInsideSubElement(node);
125
- for (const prop of node.properties) {
126
- if (prop.type !== "Property" || prop.computed) continue;
127
- const key = getKeyName(prop.key);
128
- if (key === null) continue;
129
- if (/^[A-Z]/.test(key) || key.startsWith("@") || key.startsWith("&")) continue;
130
- if (prop.value.type !== "ObjectExpression") continue;
131
- for (const stateProp of prop.value.properties) {
132
- if (stateProp.type !== "Property") continue;
133
- const stateKey = !stateProp.computed ? getKeyName(stateProp.key) : getStringValue(stateProp.key);
134
- if (stateKey === null) continue;
135
- checkStateKey(stateKey, stateProp.key, insideSubElement);
136
- }
137
- }
138
- }
75
+ ...styleObjectListeners(handleStyleObject)
139
76
  };
140
77
  }
141
78
  });
@@ -1 +1 @@
1
- {"version":3,"file":"valid-state-key.js","names":[],"sources":["../../src/rules/valid-state-key.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport {\n parseStateKey,\n createStateParserContext,\n setGlobalPredefinedStates,\n} from '@tenphi/tasty/core';\nimport type { ConditionNode } from '@tenphi/tasty/core';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds =\n | 'unparseable'\n | 'emptyAdvancedState'\n | 'unresolvablePredefined'\n | 'ownOutsideSubElement';\n\nfunction collectIssues(\n node: ConditionNode,\n knownPredefined: Set<string>,\n): string[] {\n const issues: string[] = [];\n\n function walk(n: ConditionNode): void {\n if (n.kind === 'true' || n.kind === 'false') return;\n\n if (n.kind === 'compound') {\n for (const child of n.children) {\n walk(child);\n }\n return;\n }\n\n switch (n.type) {\n case 'media':\n if (\n n.subtype === 'dimension' &&\n !n.dimension &&\n !n.lowerBound &&\n !n.upperBound\n ) {\n issues.push(`Empty or invalid @media dimension query in '${n.raw}'.`);\n }\n break;\n\n case 'container':\n if (\n n.subtype === 'dimension' &&\n !n.dimension &&\n !n.lowerBound &&\n !n.upperBound\n ) {\n issues.push(\n `Empty or invalid container dimension query in '${n.raw}'.`,\n );\n }\n break;\n\n case 'own':\n walk(n.innerCondition);\n break;\n\n case 'parent':\n if ('innerCondition' in n && n.innerCondition) {\n walk(n.innerCondition as ConditionNode);\n }\n break;\n\n case 'pseudo':\n if (n.raw.startsWith('@') && !knownPredefined.has(n.raw)) {\n issues.push(`Unresolvable predefined state '${n.raw}'.`);\n }\n break;\n\n default:\n break;\n }\n }\n\n walk(node);\n return issues;\n}\n\nfunction hasOwnState(node: ConditionNode): boolean {\n if (node.kind === 'true' || node.kind === 'false') return false;\n if (node.kind === 'compound') {\n return node.children.some(hasOwnState);\n }\n if (node.type === 'own') return true;\n return false;\n}\n\n/**\n * Matches the same tokens as tasty's internal STATE_TOKEN_PATTERN.\n * Characters not covered by this pattern (excluding whitespace/commas)\n * are flagged as unrecognized.\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]+\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^)]+\\))|(@parent\\([^)]+\\))|(@own\\([^)]+\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\nfunction hasUnrecognizedTokens(stateKey: string): string | null {\n if (!stateKey.trim()) return null;\n\n const covered = new Set<number>();\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = STATE_TOKEN_PATTERN.exec(stateKey)) !== null) {\n for (let i = match.index; i < match.index + match[0].length; i++) {\n covered.add(i);\n }\n }\n\n const uncovered: string[] = [];\n for (let i = 0; i < stateKey.length; i++) {\n const ch = stateKey[i];\n if (ch === ' ' || ch === '\\t' || ch === ',') continue;\n if (!covered.has(i)) {\n uncovered.push(ch);\n }\n }\n\n if (uncovered.length > 0) {\n const chars = [...new Set(uncovered)].join('');\n return `Unrecognized characters '${chars}' in state key '${stateKey}'.`;\n }\n\n return null;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-state-key',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate state key syntax in style mapping objects using the tasty state parser',\n },\n messages: {\n unparseable: '{{reason}}',\n emptyAdvancedState: '{{reason}}',\n unresolvablePredefined: '{{reason}}',\n ownOutsideSubElement:\n '@own() can only be used inside sub-element styles.',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const predefinedStates: Record<string, string> = {};\n for (const alias of ctx.config.states) {\n predefinedStates[alias] = alias;\n }\n setGlobalPredefinedStates(predefinedStates);\n\n const knownPredefined = new Set(ctx.config.states);\n\n function isInsideSubElement(node: TSESTree.Node): boolean {\n let current: TSESTree.Node | undefined = node.parent;\n while (current) {\n if (\n current.type === 'Property' &&\n !current.computed &&\n current.key.type === 'Identifier' &&\n /^[A-Z]/.test(current.key.name)\n ) {\n return true;\n }\n current = current.parent;\n }\n return false;\n }\n\n function checkStateKey(\n key: string,\n keyNode: TSESTree.Node,\n insideSubElement: boolean,\n ): void {\n if (key === '') return;\n\n const tokenError = hasUnrecognizedTokens(key);\n if (tokenError) {\n context.report({\n node: keyNode,\n messageId: 'unparseable',\n data: { reason: tokenError },\n });\n return;\n }\n\n const parserContext = createStateParserContext();\n const result = parseStateKey(key, { context: parserContext });\n\n if (hasOwnState(result) && !insideSubElement) {\n context.report({\n node: keyNode,\n messageId: 'ownOutsideSubElement',\n });\n }\n\n const issues = collectIssues(result, knownPredefined);\n for (const reason of issues) {\n const messageId = reason.startsWith('Unresolvable')\n ? 'unresolvablePredefined'\n : reason.startsWith('Empty')\n ? 'emptyAdvancedState'\n : 'unparseable';\n\n context.report({\n node: keyNode,\n messageId,\n data: { reason },\n });\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n 'CallExpression ObjectExpression'(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n const insideSubElement = isInsideSubElement(node);\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 (/^[A-Z]/.test(key) || key.startsWith('@') || key.startsWith('&'))\n continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n\n const stateKey = !stateProp.computed\n ? getKeyName(stateProp.key)\n : getStringValue(stateProp.key);\n if (stateKey === null) continue;\n\n checkStateKey(stateKey, stateProp.key, insideSubElement);\n }\n }\n },\n };\n },\n});\n"],"mappings":";;;;;;AAiBA,SAAS,cACP,MACA,iBACU;CACV,MAAM,SAAmB,EAAE;CAE3B,SAAS,KAAK,GAAwB;AACpC,MAAI,EAAE,SAAS,UAAU,EAAE,SAAS,QAAS;AAE7C,MAAI,EAAE,SAAS,YAAY;AACzB,QAAK,MAAM,SAAS,EAAE,SACpB,MAAK,MAAM;AAEb;;AAGF,UAAQ,EAAE,MAAV;GACE,KAAK;AACH,QACE,EAAE,YAAY,eACd,CAAC,EAAE,aACH,CAAC,EAAE,cACH,CAAC,EAAE,WAEH,QAAO,KAAK,+CAA+C,EAAE,IAAI,IAAI;AAEvE;GAEF,KAAK;AACH,QACE,EAAE,YAAY,eACd,CAAC,EAAE,aACH,CAAC,EAAE,cACH,CAAC,EAAE,WAEH,QAAO,KACL,kDAAkD,EAAE,IAAI,IACzD;AAEH;GAEF,KAAK;AACH,SAAK,EAAE,eAAe;AACtB;GAEF,KAAK;AACH,QAAI,oBAAoB,KAAK,EAAE,eAC7B,MAAK,EAAE,eAAgC;AAEzC;GAEF,KAAK;AACH,QAAI,EAAE,IAAI,WAAW,IAAI,IAAI,CAAC,gBAAgB,IAAI,EAAE,IAAI,CACtD,QAAO,KAAK,kCAAkC,EAAE,IAAI,IAAI;AAE1D;GAEF,QACE;;;AAIN,MAAK,KAAK;AACV,QAAO;;AAGT,SAAS,YAAY,MAA8B;AACjD,KAAI,KAAK,SAAS,UAAU,KAAK,SAAS,QAAS,QAAO;AAC1D,KAAI,KAAK,SAAS,WAChB,QAAO,KAAK,SAAS,KAAK,YAAY;AAExC,KAAI,KAAK,SAAS,MAAO,QAAO;AAChC,QAAO;;;;;;;AAQT,MAAM,sBACJ;AAEF,SAAS,sBAAsB,UAAiC;AAC9D,KAAI,CAAC,SAAS,MAAM,CAAE,QAAO;CAE7B,MAAM,0BAAU,IAAI,KAAa;AAEjC,qBAAoB,YAAY;CAChC,IAAI;AACJ,SAAQ,QAAQ,oBAAoB,KAAK,SAAS,MAAM,KACtD,MAAK,IAAI,IAAI,MAAM,OAAO,IAAI,MAAM,QAAQ,MAAM,GAAG,QAAQ,IAC3D,SAAQ,IAAI,EAAE;CAIlB,MAAM,YAAsB,EAAE;AAC9B,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,KAAK,SAAS;AACpB,MAAI,OAAO,OAAO,OAAO,OAAQ,OAAO,IAAK;AAC7C,MAAI,CAAC,QAAQ,IAAI,EAAE,CACjB,WAAU,KAAK,GAAG;;AAItB,KAAI,UAAU,SAAS,EAErB,QAAO,4BADO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,CAAC,KAAK,GAAG,CACL,kBAAkB,SAAS;AAGtE,QAAO;;AAGT,8BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,mFACH;EACD,UAAU;GACR,aAAa;GACb,oBAAoB;GACpB,wBAAwB;GACxB,sBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,mBAA2C,EAAE;AACnD,OAAK,MAAM,SAAS,IAAI,OAAO,OAC7B,kBAAiB,SAAS;AAE5B,4BAA0B,iBAAiB;EAE3C,MAAM,kBAAkB,IAAI,IAAI,IAAI,OAAO,OAAO;EAElD,SAAS,mBAAmB,MAA8B;GACxD,IAAI,UAAqC,KAAK;AAC9C,UAAO,SAAS;AACd,QACE,QAAQ,SAAS,cACjB,CAAC,QAAQ,YACT,QAAQ,IAAI,SAAS,gBACrB,SAAS,KAAK,QAAQ,IAAI,KAAK,CAE/B,QAAO;AAET,cAAU,QAAQ;;AAEpB,UAAO;;EAGT,SAAS,cACP,KACA,SACA,kBACM;AACN,OAAI,QAAQ,GAAI;GAEhB,MAAM,aAAa,sBAAsB,IAAI;AAC7C,OAAI,YAAY;AACd,YAAQ,OAAO;KACb,MAAM;KACN,WAAW;KACX,MAAM,EAAE,QAAQ,YAAY;KAC7B,CAAC;AACF;;GAIF,MAAM,SAAS,cAAc,KAAK,EAAE,SADd,0BAA0B,EACY,CAAC;AAE7D,OAAI,YAAY,OAAO,IAAI,CAAC,iBAC1B,SAAQ,OAAO;IACb,MAAM;IACN,WAAW;IACZ,CAAC;GAGJ,MAAM,SAAS,cAAc,QAAQ,gBAAgB;AACrD,QAAK,MAAM,UAAU,QAAQ;IAC3B,MAAM,YAAY,OAAO,WAAW,eAAe,GAC/C,2BACA,OAAO,WAAW,QAAQ,GACxB,uBACA;AAEN,YAAQ,OAAO;KACb,MAAM;KACN;KACA,MAAM,EAAE,QAAQ;KACjB,CAAC;;;AAIN,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kCAAkC,MAAiC;AACjE,QAAI,CAAC,IAAI,cAAc,KAAK,CAAE;IAE9B,MAAM,mBAAmB,mBAAmB,KAAK;AAEjD,SAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,SAAI,KAAK,SAAS,cAAc,KAAK,SAAU;KAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,SAAI,QAAQ,KAAM;AAElB,SAAI,SAAS,KAAK,IAAI,IAAI,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,IAAI,CAClE;AAEF,SAAI,KAAK,MAAM,SAAS,mBAAoB;AAE5C,UAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,UAAI,UAAU,SAAS,WAAY;MAEnC,MAAM,WAAW,CAAC,UAAU,WACxB,WAAW,UAAU,IAAI,GACzB,eAAe,UAAU,IAAI;AACjC,UAAI,aAAa,KAAM;AAEvB,oBAAc,UAAU,UAAU,KAAK,iBAAiB;;;;GAI/D;;CAEJ,CAAC"}
1
+ {"version":3,"file":"valid-state-key.js","names":[],"sources":["../../src/rules/valid-state-key.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 { parseStateKey } from '../parsers/state-key-parser.js';\nimport type { StateKeyParserOptions } from '../parsers/state-key-parser.js';\n\ntype MessageIds =\n | 'invalidStateKey'\n | 'ownOutsideSubElement'\n | 'unknownAlias';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-state-key',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate state key syntax in style mapping objects',\n },\n messages: {\n invalidStateKey: '{{reason}}',\n ownOutsideSubElement:\n '@own() can only be used inside sub-element styles.',\n unknownAlias:\n \"Unknown state alias '{{alias}}'. Configured aliases: {{known}}.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n const parserOpts: StateKeyParserOptions = {\n knownAliases: ctx.config.states,\n };\n\n function isInsideSubElement(node: TSESTree.Node): boolean {\n let current: TSESTree.Node | undefined = node.parent;\n while (current) {\n if (\n current.type === 'Property' &&\n !current.computed &&\n current.key.type === 'Identifier' &&\n /^[A-Z]/.test(current.key.name)\n ) {\n return true;\n }\n current = current.parent;\n }\n return false;\n }\n\n function checkStateKey(\n key: string,\n keyNode: TSESTree.Node,\n insideSubElement: boolean,\n ): void {\n if (key === '') return;\n\n const result = parseStateKey(key, parserOpts);\n\n // Report all parse/validation errors\n for (const error of result.errors) {\n context.report({\n node: keyNode,\n messageId: 'invalidStateKey',\n data: { reason: error.message },\n });\n }\n\n // Check @own usage outside sub-element\n if (result.hasOwn && !insideSubElement) {\n context.report({\n node: keyNode,\n messageId: 'ownOutsideSubElement',\n });\n }\n\n // Check aliases against config\n if (ctx.config.states.length > 0) {\n for (const alias of result.referencedAliases) {\n if (!ctx.config.states.includes(alias)) {\n context.report({\n node: keyNode,\n messageId: 'unknownAlias',\n data: {\n alias,\n known: ctx.config.states.join(', '),\n },\n });\n }\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n const insideSubElement = isInsideSubElement(node);\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 (/^[A-Z]/.test(key) || key.startsWith('@') || key.startsWith('&'))\n continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n\n const stateKey = !stateProp.computed\n ? getKeyName(stateProp.key)\n : getStringValue(stateProp.key);\n if (stateKey === null) continue;\n\n checkStateKey(stateKey, stateProp.key, insideSubElement);\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAYA,8BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,sDACH;EACD,UAAU;GACR,iBAAiB;GACjB,sBACE;GACF,cACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,MAAM,aAAoC,EACxC,cAAc,IAAI,OAAO,QAC1B;EAED,SAAS,mBAAmB,MAA8B;GACxD,IAAI,UAAqC,KAAK;AAC9C,UAAO,SAAS;AACd,QACE,QAAQ,SAAS,cACjB,CAAC,QAAQ,YACT,QAAQ,IAAI,SAAS,gBACrB,SAAS,KAAK,QAAQ,IAAI,KAAK,CAE/B,QAAO;AAET,cAAU,QAAQ;;AAEpB,UAAO;;EAGT,SAAS,cACP,KACA,SACA,kBACM;AACN,OAAI,QAAQ,GAAI;GAEhB,MAAM,SAAS,cAAc,KAAK,WAAW;AAG7C,QAAK,MAAM,SAAS,OAAO,OACzB,SAAQ,OAAO;IACb,MAAM;IACN,WAAW;IACX,MAAM,EAAE,QAAQ,MAAM,SAAS;IAChC,CAAC;AAIJ,OAAI,OAAO,UAAU,CAAC,iBACpB,SAAQ,OAAO;IACb,MAAM;IACN,WAAW;IACZ,CAAC;AAIJ,OAAI,IAAI,OAAO,OAAO,SAAS,GAC7B;SAAK,MAAM,SAAS,OAAO,kBACzB,KAAI,CAAC,IAAI,OAAO,OAAO,SAAS,MAAM,CACpC,SAAQ,OAAO;KACb,MAAM;KACN,WAAW;KACX,MAAM;MACJ;MACA,OAAO,IAAI,OAAO,OAAO,KAAK,KAAK;MACpC;KACF,CAAC;;;EAMV,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;GAE9B,MAAM,mBAAmB,mBAAmB,KAAK;AAEjD,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,SAAS,KAAK,IAAI,IAAI,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,IAAI,CAClE;AAEF,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAE5C,SAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KAEnC,MAAM,WAAW,CAAC,UAAU,WACxB,WAAW,UAAU,IAAI,GACzB,eAAe,UAAU,IAAI;AACjC,SAAI,aAAa,KAAM;AAEvB,mBAAc,UAAU,UAAU,KAAK,iBAAiB;;;;AAK9D,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { createRule } from "../create-rule.js";
2
- import { TastyContext } from "../context.js";
3
2
  import { getKeyName, getStringValue } from "../utils.js";
3
+ import { TastyContext, styleObjectListeners } from "../context.js";
4
4
 
5
5
  //#region src/rules/valid-styles-structure.ts
6
6
  var valid_styles_structure_default = createRule({
@@ -28,48 +28,49 @@ var valid_styles_structure_default = createRule({
28
28
  if (key === "") return true;
29
29
  return STATE_KEY_PATTERNS.some((p) => p.test(key));
30
30
  }
31
- return {
32
- ImportDeclaration(node) {
33
- ctx.trackImport(node);
34
- },
35
- "CallExpression ObjectExpression"(node) {
36
- if (!ctx.isStyleObject(node)) return;
37
- for (const prop of node.properties) {
38
- if (prop.type !== "Property" || prop.computed) continue;
39
- const key = getKeyName(prop.key);
40
- if (key === null) continue;
41
- if (looksLikeStateKey(key)) {
42
- context.report({
43
- node: prop.key,
44
- messageId: "stateKeyAtTopLevel",
45
- data: { key }
46
- });
47
- continue;
48
- }
49
- if (key === "@keyframes") {
50
- if (prop.value.type !== "ObjectExpression") context.report({
51
- node: prop.value,
52
- messageId: "invalidKeyframesStructure"
53
- });
54
- continue;
55
- }
56
- if (key === "@properties") {
57
- if (prop.value.type !== "ObjectExpression") context.report({
31
+ function handleStyleObject(node) {
32
+ if (!ctx.isStyleObject(node)) return;
33
+ for (const prop of node.properties) {
34
+ if (prop.type !== "Property" || prop.computed) continue;
35
+ const key = getKeyName(prop.key);
36
+ if (key === null) continue;
37
+ if (looksLikeStateKey(key)) {
38
+ context.report({
39
+ node: prop.key,
40
+ messageId: "stateKeyAtTopLevel",
41
+ data: { key }
42
+ });
43
+ continue;
44
+ }
45
+ if (key === "@keyframes") {
46
+ if (prop.value.type !== "ObjectExpression") context.report({
47
+ node: prop.value,
48
+ messageId: "invalidKeyframesStructure"
49
+ });
50
+ continue;
51
+ }
52
+ if (key === "@properties") {
53
+ if (prop.value.type !== "ObjectExpression") context.report({
54
+ node: prop.value,
55
+ messageId: "invalidPropertiesStructure"
56
+ });
57
+ continue;
58
+ }
59
+ if (key === "recipe") {
60
+ if (getStringValue(prop.value) === null && prop.value.type !== "Literal") {
61
+ if (prop.value.type !== "TemplateLiteral" || prop.value.expressions.length > 0) context.report({
58
62
  node: prop.value,
59
- messageId: "invalidPropertiesStructure"
63
+ messageId: "recipeNotString"
60
64
  });
61
- continue;
62
- }
63
- if (key === "recipe") {
64
- if (getStringValue(prop.value) === null && prop.value.type !== "Literal") {
65
- if (prop.value.type !== "TemplateLiteral" || prop.value.expressions.length > 0) context.report({
66
- node: prop.value,
67
- messageId: "recipeNotString"
68
- });
69
- }
70
65
  }
71
66
  }
72
67
  }
68
+ }
69
+ return {
70
+ ImportDeclaration(node) {
71
+ ctx.trackImport(node);
72
+ },
73
+ ...styleObjectListeners(handleStyleObject)
73
74
  };
74
75
  }
75
76
  });
@@ -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 } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds =\n | 'stateKeyAtTopLevel'\n | 'invalidKeyframesStructure'\n | 'invalidPropertiesStructure'\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 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 return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n 'CallExpression ObjectExpression'(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 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 },\n});\n"],"mappings":";;;;;AAWA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU;GACR,oBACE;GACF,2BACE;GACF,4BACE;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;AAC/C,OAAI,QAAQ,GAAI,QAAO;AACvB,UAAO,mBAAmB,MAAM,MAAM,EAAE,KAAK,IAAI,CAAC;;AAGpD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,kCAAkC,MAAiC;AACjE,QAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,SAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,SAAI,KAAK,SAAS,cAAc,KAAK,SAAU;KAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,SAAI,QAAQ,KAAM;AAGlB,SAAI,kBAAkB,IAAI,EAAE;AAC1B,cAAQ,OAAO;OACb,MAAM,KAAK;OACX,WAAW;OACX,MAAM,EAAE,KAAK;OACd,CAAC;AACF;;AAIF,SAAI,QAAQ,cAAc;AACxB,UAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;OACb,MAAM,KAAK;OACX,WAAW;OACZ,CAAC;AAEJ;;AAIF,SAAI,QAAQ,eAAe;AACzB,UAAI,KAAK,MAAM,SAAS,mBACtB,SAAQ,OAAO;OACb,MAAM,KAAK;OACX,WAAW;OACZ,CAAC;AAEJ;;AAIF,SAAI,QAAQ,UAEV;UADY,eAAe,KAAK,MAAM,KAC1B,QAAQ,KAAK,MAAM,SAAS,WAEtC;WACE,KAAK,MAAM,SAAS,qBACpB,KAAK,MAAM,YAAY,SAAS,EAEhC,SAAQ,OAAO;QACb,MAAM,KAAK;QACX,WAAW;QACZ,CAAC;;;;;GAMb;;CAEJ,CAAC"}
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 | '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 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 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":";;;;;AAWA,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU;GACR,oBACE;GACF,2BACE;GACF,4BACE;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;AAC/C,OAAI,QAAQ,GAAI,QAAO;AACvB,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,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"}