@tenphi/eslint-plugin-tasty 0.11.4 → 1.0.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.
@@ -1,7 +1,8 @@
1
1
  import { createRule } from "../create-rule.js";
2
- import { DIRECTIONAL_MODIFIERS } from "../constants.js";
2
+ import { DIRECTIONAL_MODIFIERS, DOCK_PROPERTIES, SINGLE_VALUE_DIRECTIONAL_PROPERTIES } from "../constants.js";
3
3
  import { getKeyName, getStringValue } from "../utils.js";
4
4
  import { TastyContext, styleObjectListeners } from "../context.js";
5
+ import { parseValue } from "../parsers/value-parser.js";
5
6
 
6
7
  //#region src/rules/valid-directional-modifier.ts
7
8
  const ALL_DIRECTIONS = new Set([
@@ -14,28 +15,90 @@ const ALL_DIRECTIONS = new Set([
14
15
  "bottom-left",
15
16
  "bottom-right"
16
17
  ]);
18
+ /**
19
+ * Token types that count as a *value* rather than a modifier.
20
+ *
21
+ * Deliberately excludes `keyword`: direction words, `solid`, `inherit` and every
22
+ * other bare word classify as keywords, and a CSS-wide keyword alongside a
23
+ * direction (`padding: 'inherit top'`) is legal while carrying no value of its own.
24
+ *
25
+ * Also excludes colors: a `fade` group carries a width plus up to two mask
26
+ * colors, so colors must not count toward the one-value limit.
27
+ */
28
+ const VALUE_TOKEN_TYPES = new Set([
29
+ "custom-unit",
30
+ "css-unit",
31
+ "number",
32
+ "custom-prop",
33
+ "custom-prop-ref",
34
+ "css-function",
35
+ "tasty-function",
36
+ "group-expr"
37
+ ]);
38
+ /** The source text of a token. `keyword` tokens carry `value` rather than `raw`. */
39
+ function tokenText(token) {
40
+ if ("raw" in token && typeof token.raw === "string") return token.raw;
41
+ if (token.type === "keyword") return token.value;
42
+ return "";
43
+ }
17
44
  var valid_directional_modifier_default = createRule({
18
45
  name: "valid-directional-modifier",
19
46
  meta: {
20
47
  type: "problem",
21
- docs: { description: "Validate that directional modifiers are used only on properties that support them" },
22
- messages: { invalidDirectionalModifier: "Property '{{property}}' does not support directional modifiers. They work on: border, radius, padding, margin, fade, inset." },
48
+ docs: { description: "Validate directional modifiers: only on properties that support them, and one value per group" },
49
+ messages: {
50
+ invalidDirectionalModifier: "Modifier '{{modifier}}' is not valid for '{{property}}'. Accepted: {{accepted}}.",
51
+ tooManyValues: "A group that names directions takes {{limit}}, but '{{group}}' has {{count}}. Values and modifiers are parsed into separate buckets, so their order is not recoverable — use comma-separated groups for per-side values, e.g. \"2x top, 4x right\"."
52
+ },
23
53
  schema: []
24
54
  },
25
55
  defaultOptions: [],
26
56
  create(context) {
27
57
  const ctx = new TastyContext(context);
28
58
  function checkValue(property, value, node) {
29
- const tokens = value.trim().split(/\s+/);
30
- for (const token of tokens) {
31
- if (!ALL_DIRECTIONS.has(token)) continue;
32
- const allowedMods = DIRECTIONAL_MODIFIERS[property];
33
- if (!allowedMods || !allowedMods.has(token)) context.report({
59
+ const allowedMods = DIRECTIONAL_MODIFIERS[property];
60
+ if (!allowedMods) return;
61
+ const { groups } = parseValue(value, {
62
+ skipUnitValidation: true,
63
+ skipFuncValidation: true
64
+ });
65
+ for (const group of groups) {
66
+ const tokens = group.parts.flatMap((part) => part.tokens);
67
+ const directions = [];
68
+ let valueCount = 0;
69
+ let hasSpanModifier = false;
70
+ for (const token of tokens) {
71
+ if (VALUE_TOKEN_TYPES.has(token.type)) {
72
+ valueCount++;
73
+ continue;
74
+ }
75
+ const word = tokenText(token);
76
+ if (ALL_DIRECTIONS.has(word)) directions.push(word);
77
+ else if (word === "dock" && DOCK_PROPERTIES.has(property)) hasSpanModifier = true;
78
+ }
79
+ if (directions.length === 0) continue;
80
+ let reportedUnknown = false;
81
+ for (const direction of directions) if (!allowedMods.has(direction)) {
82
+ context.report({
83
+ node,
84
+ messageId: "invalidDirectionalModifier",
85
+ data: {
86
+ property,
87
+ modifier: direction,
88
+ accepted: [...allowedMods].join(", ")
89
+ }
90
+ });
91
+ reportedUnknown = true;
92
+ }
93
+ if (reportedUnknown) continue;
94
+ if (!SINGLE_VALUE_DIRECTIONAL_PROPERTIES.has(property)) continue;
95
+ if (valueCount > (hasSpanModifier ? 2 : 1)) context.report({
34
96
  node,
35
- messageId: "invalidDirectionalModifier",
97
+ messageId: "tooManyValues",
36
98
  data: {
37
- property,
38
- modifier: token
99
+ group: tokens.map(tokenText).filter(Boolean).join(" "),
100
+ count: `${valueCount}`,
101
+ limit: hasSpanModifier ? "at most two values" : "a single value"
39
102
  }
40
103
  });
41
104
  }
@@ -46,13 +109,13 @@ var valid_directional_modifier_default = createRule({
46
109
  if (prop.type !== "Property" || prop.computed) continue;
47
110
  const key = getKeyName(prop.key);
48
111
  if (key === null) continue;
49
- if (!(key in DIRECTIONAL_MODIFIERS)) continue;
112
+ if (key === "$" || key.startsWith("@") || key.startsWith("$") || key.startsWith("#") || key.startsWith("&") || /^[A-Z]/.test(key)) continue;
50
113
  const str = getStringValue(prop.value);
51
114
  if (str) {
52
115
  checkValue(key, str, prop.value);
53
116
  continue;
54
117
  }
55
- if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
118
+ if (prop.value.type === "ObjectExpression" && ctx.isStateMap(prop.value, prop)) for (const stateProp of prop.value.properties) {
56
119
  if (stateProp.type !== "Property") continue;
57
120
  const stateStr = getStringValue(stateProp.value);
58
121
  if (stateStr) checkValue(key, stateStr, stateProp.value);
@@ -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 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"}
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 {\n DIRECTIONAL_MODIFIERS,\n DOCK_PROPERTIES,\n SINGLE_VALUE_DIRECTIONAL_PROPERTIES,\n} from '../constants.js';\nimport { parseValue } from '../parsers/index.js';\nimport type { ValueToken } from '../parsers/index.js';\n\ntype MessageIds = 'invalidDirectionalModifier' | 'tooManyValues';\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\n/**\n * Token types that count as a *value* rather than a modifier.\n *\n * Deliberately excludes `keyword`: direction words, `solid`, `inherit` and every\n * other bare word classify as keywords, and a CSS-wide keyword alongside a\n * direction (`padding: 'inherit top'`) is legal while carrying no value of its own.\n *\n * Also excludes colors: a `fade` group carries a width plus up to two mask\n * colors, so colors must not count toward the one-value limit.\n */\nconst VALUE_TOKEN_TYPES = new Set<ValueToken['type']>([\n 'custom-unit',\n 'css-unit',\n 'number',\n 'custom-prop',\n 'custom-prop-ref',\n 'css-function',\n 'tasty-function',\n 'group-expr',\n]);\n\n/** The source text of a token. `keyword` tokens carry `value` rather than `raw`. */\nfunction tokenText(token: ValueToken): string {\n if ('raw' in token && typeof token.raw === 'string') return token.raw;\n if (token.type === 'keyword') return token.value;\n\n return '';\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-directional-modifier',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate directional modifiers: only on properties that support them, and one value per group',\n },\n messages: {\n invalidDirectionalModifier:\n \"Modifier '{{modifier}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n tooManyValues:\n 'A group that names directions takes {{limit}}, but \\'{{group}}\\' has {{count}}. Values and modifiers are parsed into separate buckets, so their order is not recoverable — use comma-separated groups for per-side values, e.g. \"2x top, 4x right\".',\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 allowedMods = DIRECTIONAL_MODIFIERS[property];\n // Only properties with a directional vocabulary are checked.\n //\n // This gate looks like it makes the \"wrong property\" case unreachable, and\n // it does — but removing it is not the fix. Direction words are ordinary CSS\n // *values* for a long tail of properties: `verticalAlign: 'bottom'`,\n // `textAlign: 'left'`, `transformOrigin: 'top center'`,\n // `backgroundPosition`, `objectPosition`, `float`, `clear`,\n // `scrollSnapAlign`, and `transition` (whose value names properties, which\n // can themselves be `left`/`top`). Checking every property reported all of\n // those as errors across a real design system. Catching `fill: '#purple\n // top'` is not worth an allowlist of every property that takes a\n // positional keyword.\n if (!allowedMods) return;\n\n // `parseValue` splits on commas (groups) and spaced slashes (parts), which\n // is what makes a per-group arity check possible at all.\n const { groups } = parseValue(value, {\n skipUnitValidation: true,\n skipFuncValidation: true,\n });\n\n for (const group of groups) {\n const tokens = group.parts.flatMap((part) => part.tokens);\n const directions: string[] = [];\n let valueCount = 0;\n let hasSpanModifier = false;\n\n for (const token of tokens) {\n if (VALUE_TOKEN_TYPES.has(token.type)) {\n valueCount++;\n continue;\n }\n\n const word = tokenText(token);\n\n if (ALL_DIRECTIONS.has(word)) {\n directions.push(word);\n } else if (word === 'dock' && DOCK_PROPERTIES.has(property)) {\n hasSpanModifier = true;\n }\n }\n\n if (directions.length === 0) continue;\n\n let reportedUnknown = false;\n for (const direction of directions) {\n if (!allowedMods.has(direction)) {\n context.report({\n node,\n messageId: 'invalidDirectionalModifier',\n data: {\n property,\n modifier: direction,\n accepted: [...allowedMods].join(', '),\n },\n });\n reportedUnknown = true;\n }\n }\n\n // Don't pile an arity complaint on top of an already-invalid modifier.\n if (reportedUnknown) continue;\n\n if (!SINGLE_VALUE_DIRECTIONAL_PROPERTIES.has(property)) continue;\n\n // Values and modifiers are bucketed separately by the parser, so the\n // pairing a reader infers from source order does not survive parsing: a\n // group naming directions carries one value, applied to every direction it\n // names. `dock` is the exception — its second value insets the sides the\n // dock spans.\n const limit = hasSpanModifier ? 2 : 1;\n\n if (valueCount > limit) {\n context.report({\n node,\n messageId: 'tooManyValues',\n data: {\n group: tokens.map(tokenText).filter(Boolean).join(' '),\n count: `${valueCount}`,\n limit: hasSpanModifier ? 'at most two values' : 'a single value',\n },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n // Sub-elements, at-rules, token declarations, nested selectors, and the\n // `$` affix (which holds a selector, not a value) are not style values.\n if (\n key === '$' ||\n key.startsWith('@') ||\n key.startsWith('$') ||\n key.startsWith('#') ||\n key.startsWith('&') ||\n /^[A-Z]/.test(key)\n ) {\n continue;\n }\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 (\n prop.value.type === 'ObjectExpression' &&\n ctx.isStateMap(prop.value, prop)\n ) {\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":"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"}
@@ -1,19 +1,35 @@
1
1
  import { createRule } from "../create-rule.js";
2
+ import { RENAMED_SPECIAL_STYLE_KEYS } from "../constants.js";
2
3
  import { getKeyName, getStringValue } from "../utils.js";
3
4
  import { TastyContext, styleObjectListeners } from "../context.js";
4
5
 
5
6
  //#region src/rules/valid-styles-structure.ts
7
+ /**
8
+ * Whether an at-rule value is *definitely* not the descriptor object it must be.
9
+ *
10
+ * Only literal shapes are conclusive. An identifier, member expression, or call
11
+ * can perfectly well evaluate to an object — `'@font-face': fontFaceMap` is valid
12
+ * — so anything the plugin cannot see into is left alone.
13
+ */
14
+ function isDefinitelyNotAnObject(node) {
15
+ let current = node;
16
+ while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = current.expression;
17
+ return current.type === "Literal" || current.type === "TemplateLiteral" || current.type === "ArrayExpression";
18
+ }
6
19
  var valid_styles_structure_default = createRule({
7
20
  name: "valid-styles-structure",
8
21
  meta: {
9
22
  type: "problem",
23
+ fixable: "code",
10
24
  docs: { description: "Validate overall structure of styles object passed to tasty APIs" },
11
25
  messages: {
12
26
  stateKeyAtTopLevel: "State key '{{key}}' at top level is not valid. State maps belong inside property values, not at the root of the styles object.",
13
27
  invalidKeyframesStructure: "@keyframes value must be an object of { name: { step: styles } }.",
14
- invalidPropertiesStructure: "@properties value must be an object of { name: { syntax, inherits, initialValue } }.",
15
- invalidFontFaceStructure: "@fontFace value must be an object of { familyName: descriptors | descriptors[] }.",
16
- invalidCounterStyleStructure: "@counterStyle value must be an object of { name: descriptors }.",
28
+ invalidPropertiesStructure: "'@property' value must be an object of { name: { syntax, inherits, initialValue } }.",
29
+ invalidFontFaceStructure: "'@font-face' value must be an object of { familyName: descriptors | descriptors[] }.",
30
+ invalidCounterStyleStructure: "'@counter-style' value must be an object of { name: descriptors }.",
31
+ invalidFunctionStructure: "'@function' value must be an object of { '$$name': { args, result } }.",
32
+ renamedSpecialKey: "'{{oldKey}}' was renamed to '{{newKey}}' in Tasty v3 — at-rule keys now match the CSS at-rule names Tasty emits.",
17
33
  recipeNotString: "'recipe' value must be a string."
18
34
  },
19
35
  schema: []
@@ -44,34 +60,58 @@ var valid_styles_structure_default = createRule({
44
60
  });
45
61
  continue;
46
62
  }
63
+ const renamedTo = key ? RENAMED_SPECIAL_STYLE_KEYS[key] : void 0;
64
+ if (renamedTo) {
65
+ const keyNode = prop.key;
66
+ context.report({
67
+ node: keyNode,
68
+ messageId: "renamedSpecialKey",
69
+ data: {
70
+ oldKey: key,
71
+ newKey: renamedTo
72
+ },
73
+ fix(fixer) {
74
+ const quote = keyNode.type === "Literal" && typeof keyNode.raw === "string" && keyNode.raw.startsWith("\"") ? "\"" : "'";
75
+ return fixer.replaceText(keyNode, `${quote}${renamedTo}${quote}`);
76
+ }
77
+ });
78
+ continue;
79
+ }
47
80
  if (key === "@keyframes") {
48
- if (prop.value.type !== "ObjectExpression") context.report({
81
+ if (isDefinitelyNotAnObject(prop.value)) context.report({
49
82
  node: prop.value,
50
83
  messageId: "invalidKeyframesStructure"
51
84
  });
52
85
  continue;
53
86
  }
54
- if (key === "@properties") {
55
- if (prop.value.type !== "ObjectExpression") context.report({
87
+ if (key === "@property") {
88
+ if (isDefinitelyNotAnObject(prop.value)) context.report({
56
89
  node: prop.value,
57
90
  messageId: "invalidPropertiesStructure"
58
91
  });
59
92
  continue;
60
93
  }
61
- if (key === "@fontFace") {
62
- if (prop.value.type !== "ObjectExpression") context.report({
94
+ if (key === "@font-face") {
95
+ if (isDefinitelyNotAnObject(prop.value)) context.report({
63
96
  node: prop.value,
64
97
  messageId: "invalidFontFaceStructure"
65
98
  });
66
99
  continue;
67
100
  }
68
- if (key === "@counterStyle") {
69
- if (prop.value.type !== "ObjectExpression") context.report({
101
+ if (key === "@counter-style") {
102
+ if (isDefinitelyNotAnObject(prop.value)) context.report({
70
103
  node: prop.value,
71
104
  messageId: "invalidCounterStyleStructure"
72
105
  });
73
106
  continue;
74
107
  }
108
+ if (key === "@function") {
109
+ if (isDefinitelyNotAnObject(prop.value)) context.report({
110
+ node: prop.value,
111
+ messageId: "invalidFunctionStructure"
112
+ });
113
+ continue;
114
+ }
75
115
  if (key === "recipe") {
76
116
  if (getStringValue(prop.value) === null && prop.value.type !== "Literal") {
77
117
  if (prop.value.type !== "TemplateLiteral" || prop.value.expressions.length > 0) context.report({
@@ -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 // '' 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
+ {"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 { RENAMED_SPECIAL_STYLE_KEYS } from '../constants.js';\nimport { getKeyName, getStringValue } from '../utils.js';\n\ntype MessageIds =\n | 'stateKeyAtTopLevel'\n | 'invalidKeyframesStructure'\n | 'invalidPropertiesStructure'\n | 'invalidFontFaceStructure'\n | 'invalidCounterStyleStructure'\n | 'invalidFunctionStructure'\n | 'renamedSpecialKey'\n | 'recipeNotString';\n\n/**\n * Whether an at-rule value is *definitely* not the descriptor object it must be.\n *\n * Only literal shapes are conclusive. An identifier, member expression, or call\n * can perfectly well evaluate to an object — `'@font-face': fontFaceMap` is valid\n * — so anything the plugin cannot see into is left alone.\n */\nfunction isDefinitelyNotAnObject(node: TSESTree.Node): boolean {\n let current = node;\n\n // Look through `as` / `satisfies` wrappers.\n while (\n current.type === 'TSAsExpression' ||\n current.type === 'TSSatisfiesExpression' ||\n current.type === 'TSNonNullExpression'\n ) {\n current = current.expression;\n }\n\n return (\n current.type === 'Literal' ||\n current.type === 'TemplateLiteral' ||\n current.type === 'ArrayExpression'\n );\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-styles-structure',\n meta: {\n type: 'problem',\n fixable: 'code',\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 \"'@property' value must be an object of { name: { syntax, inherits, initialValue } }.\",\n invalidFontFaceStructure:\n \"'@font-face' value must be an object of { familyName: descriptors | descriptors[] }.\",\n invalidCounterStyleStructure:\n \"'@counter-style' value must be an object of { name: descriptors }.\",\n invalidFunctionStructure:\n \"'@function' value must be an object of { '$$name': { args, result } }.\",\n renamedSpecialKey:\n \"'{{oldKey}}' was renamed to '{{newKey}}' in Tasty v3 — at-rule keys now match the CSS at-rule names Tasty emits.\",\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 // Report the v2 camelCase at-rule spellings, with a fix.\n const renamedTo = key ? RENAMED_SPECIAL_STYLE_KEYS[key] : undefined;\n if (renamedTo) {\n const keyNode = prop.key;\n context.report({\n node: keyNode,\n messageId: 'renamedSpecialKey',\n data: { oldKey: key, newKey: renamedTo },\n fix(fixer) {\n // Always emit a quoted key: the new spellings are kebab-case and so\n // are not valid bare identifiers.\n const quote =\n keyNode.type === 'Literal' &&\n typeof keyNode.raw === 'string' &&\n keyNode.raw.startsWith('\"')\n ? '\"'\n : \"'\";\n\n return fixer.replaceText(keyNode, `${quote}${renamedTo}${quote}`);\n },\n });\n continue;\n }\n\n // Validate @keyframes structure\n if (key === '@keyframes') {\n if (isDefinitelyNotAnObject(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'invalidKeyframesStructure',\n });\n }\n continue;\n }\n\n // Validate @property structure\n if (key === '@property') {\n if (isDefinitelyNotAnObject(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'invalidPropertiesStructure',\n });\n }\n continue;\n }\n\n // Validate @font-face structure\n if (key === '@font-face') {\n if (isDefinitelyNotAnObject(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'invalidFontFaceStructure',\n });\n }\n continue;\n }\n\n // Validate @counter-style structure\n if (key === '@counter-style') {\n if (isDefinitelyNotAnObject(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'invalidCounterStyleStructure',\n });\n }\n continue;\n }\n\n // Validate @function structure\n if (key === '@function') {\n if (isDefinitelyNotAnObject(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'invalidFunctionStructure',\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":";;;;;;;;;;;;;AAuBA,SAAS,wBAAwB,MAA8B;CAC7D,IAAI,UAAU;AAGd,QACE,QAAQ,SAAS,oBACjB,QAAQ,SAAS,2BACjB,QAAQ,SAAS,sBAEjB,WAAU,QAAQ;AAGpB,QACE,QAAQ,SAAS,aACjB,QAAQ,SAAS,qBACjB,QAAQ,SAAS;;AAIrB,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,oEACH;EACD,UAAU;GACR,oBACE;GACF,2BACE;GACF,4BACE;GACF,0BACE;GACF,8BACE;GACF,0BACE;GACF,mBACE;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;;IAIF,MAAM,YAAY,MAAM,2BAA2B,OAAO;AAC1D,QAAI,WAAW;KACb,MAAM,UAAU,KAAK;AACrB,aAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM;OAAE,QAAQ;OAAK,QAAQ;OAAW;MACxC,IAAI,OAAO;OAGT,MAAM,QACJ,QAAQ,SAAS,aACjB,OAAO,QAAQ,QAAQ,YACvB,QAAQ,IAAI,WAAW,KAAI,GACvB,OACA;AAEN,cAAO,MAAM,YAAY,SAAS,GAAG,QAAQ,YAAY,QAAQ;;MAEpE,CAAC;AACF;;AAIF,QAAI,QAAQ,cAAc;AACxB,SAAI,wBAAwB,KAAK,MAAM,CACrC,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,aAAa;AACvB,SAAI,wBAAwB,KAAK,MAAM,CACrC,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,cAAc;AACxB,SAAI,wBAAwB,KAAK,MAAM,CACrC,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,kBAAkB;AAC5B,SAAI,wBAAwB,KAAK,MAAM,CACrC,SAAQ,OAAO;MACb,MAAM,KAAK;MACX,WAAW;MACZ,CAAC;AAEJ;;AAIF,QAAI,QAAQ,aAAa;AACvB,SAAI,wBAAwB,KAAK,MAAM,CACrC,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"}
@@ -10,7 +10,10 @@ const SKIP_PROPERTIES = new Set([
10
10
  "preset",
11
11
  "transition",
12
12
  "@keyframes",
13
- "@properties",
13
+ "@property",
14
+ "@font-face",
15
+ "@counter-style",
16
+ "@function",
14
17
  "content",
15
18
  "animation",
16
19
  "animationName",
@@ -52,8 +55,8 @@ var valid_value_default = createRule({
52
55
  const opts = {};
53
56
  if (ctx.config.units === false) opts.skipUnitValidation = true;
54
57
  else if (Array.isArray(ctx.config.units)) opts.knownUnits = new Set(ctx.config.units);
55
- if (ctx.config.funcs === false) opts.skipFuncValidation = true;
56
- else if (Array.isArray(ctx.config.funcs)) opts.knownFuncs = new Set(ctx.config.funcs);
58
+ if (ctx.config.functions === false) opts.skipFuncValidation = true;
59
+ else if (Array.isArray(ctx.config.functions)) opts.knownFuncs = new Set(ctx.config.functions);
57
60
  return opts;
58
61
  }
59
62
  function checkValue(value, property, node) {
@@ -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:\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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"}
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 '@property',\n '@font-face',\n '@counter-style',\n '@function',\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.functions === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.functions)) {\n opts.knownFuncs = new Set(ctx.config.functions);\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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"}
package/dist/types.d.ts CHANGED
@@ -3,6 +3,14 @@ interface TastyValidationConfig {
3
3
  extends?: string;
4
4
  tokens?: false | string[];
5
5
  units?: false | string[];
6
+ /**
7
+ * Valid custom parse-function names. Matches Tasty v3's `functions` config key.
8
+ *
9
+ * `funcs` is the v2 spelling and is still read as a deprecated alias so a shared
10
+ * `tasty.config.ts` keeps working through the upgrade.
11
+ */
12
+ functions?: false | string[];
13
+ /** @deprecated Renamed to `functions` in Tasty v3. */
6
14
  funcs?: false | string[];
7
15
  states?: string[];
8
16
  presets?: string[];
@@ -13,7 +21,7 @@ interface TastyValidationConfig {
13
21
  interface ResolvedConfig {
14
22
  tokens: false | string[];
15
23
  units: false | string[];
16
- funcs: false | string[];
24
+ functions: false | string[];
17
25
  states: string[];
18
26
  presets: string[];
19
27
  recipes: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/eslint-plugin-tasty",
3
- "version": "0.11.4",
3
+ "version": "1.0.0",
4
4
  "description": "ESLint plugin for validating tasty() and tastyStatic() style objects",
5
5
  "type": "module",
6
6
  "exports": {
@@ -39,6 +39,7 @@
39
39
  },
40
40
  "homepage": "https://github.com/tenphi/eslint-plugin-tasty#readme",
41
41
  "peerDependencies": {
42
+ "@tenphi/tasty": ">=3",
42
43
  "eslint": ">=8.0.0"
43
44
  },
44
45
  "dependencies": {
@@ -60,6 +61,11 @@
60
61
  "typescript-eslint": "^8.56.0",
61
62
  "vitest": "^4.0.18"
62
63
  },
64
+ "peerDependenciesMeta": {
65
+ "@tenphi/tasty": {
66
+ "optional": true
67
+ }
68
+ },
63
69
  "scripts": {
64
70
  "build": "tsdown",
65
71
  "test": "vitest run",