@tenphi/eslint-plugin-tasty 1.5.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -3
- package/dist/configs.js +3 -1
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/context.js +51 -28
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/rules/consistent-token-usage.js +88 -87
- package/dist/rules/consistent-token-usage.js.map +1 -1
- package/dist/rules/no-classname-prop.js +27 -0
- package/dist/rules/no-classname-prop.js.map +1 -0
- package/dist/rules/no-important.js +23 -28
- package/dist/rules/no-important.js.map +1 -1
- package/dist/rules/no-raw-color-values.js +40 -59
- package/dist/rules/no-raw-color-values.js.map +1 -1
- package/dist/rules/no-raw-motion-duration.js +6 -24
- package/dist/rules/no-raw-motion-duration.js.map +1 -1
- package/dist/rules/no-raw-transition-duration.js +7 -49
- package/dist/rules/no-raw-transition-duration.js.map +1 -1
- package/dist/rules/no-runtime-styles-mutation.js +2 -2
- package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
- package/dist/rules/no-state-in-selector.js +1 -1
- package/dist/rules/no-state-in-selector.js.map +1 -1
- package/dist/rules/no-style-prop.js +27 -0
- package/dist/rules/no-style-prop.js.map +1 -0
- package/dist/rules/no-style-spread.js +2 -2
- package/dist/rules/no-style-spread.js.map +1 -1
- package/dist/rules/no-styles-prop.js +7 -5
- package/dist/rules/no-styles-prop.js.map +1 -1
- package/dist/rules/prefer-auto-calc.js +5 -27
- package/dist/rules/prefer-auto-calc.js.map +1 -1
- package/dist/rules/prefer-custom-property-syntax.js +12 -31
- package/dist/rules/prefer-custom-property-syntax.js.map +1 -1
- package/dist/rules/prefer-shorthand-property.js +2 -2
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- package/dist/rules/tasty-component-props.js +33 -0
- package/dist/rules/tasty-component-props.js.map +1 -0
- package/dist/rules/valid-boolean-property.js +6 -26
- package/dist/rules/valid-boolean-property.js.map +1 -1
- package/dist/rules/valid-color-token.js +12 -29
- package/dist/rules/valid-color-token.js.map +1 -1
- package/dist/rules/valid-custom-property.js +12 -28
- package/dist/rules/valid-custom-property.js.map +1 -1
- package/dist/rules/valid-custom-unit.js +10 -27
- package/dist/rules/valid-custom-unit.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +5 -27
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-preset.js +5 -29
- package/dist/rules/valid-preset.js.map +1 -1
- package/dist/rules/valid-radius-shape.js +5 -25
- package/dist/rules/valid-radius-shape.js.map +1 -1
- package/dist/rules/valid-recipe.js +5 -20
- package/dist/rules/valid-recipe.js.map +1 -1
- package/dist/rules/valid-transition.js +5 -25
- package/dist/rules/valid-transition.js.map +1 -1
- package/dist/rules/valid-value.js +5 -31
- package/dist/rules/valid-value.js.map +1 -1
- package/dist/style-values.js +79 -0
- package/dist/style-values.js.map +1 -0
- package/dist/type-aware-jsx.d.ts +9 -0
- package/dist/type-aware-jsx.js +275 -0
- package/dist/type-aware-jsx.js.map +1 -0
- package/dist/utils.js +1 -10
- package/dist/utils.js.map +1 -1
- package/dist/value-words.js +76 -0
- package/dist/value-words.js.map +1 -0
- package/package.json +6 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { RADIUS_SHAPES } from "../constants.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TastyContext } from "../context.js";
|
|
4
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
5
5
|
import { replaceStringValue } from "../fix-utils.js";
|
|
6
6
|
|
|
7
7
|
//#region src/rules/valid-radius-shape.ts
|
|
@@ -63,29 +63,9 @@ var valid_radius_shape_default = createRule({
|
|
|
63
63
|
for (const shape of RADIUS_SHAPES) if (shape.startsWith(input.slice(0, 3)) || input.startsWith(shape.slice(0, 3))) return shape;
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
if (prop.type !== "Property" || prop.computed) continue;
|
|
70
|
-
if (getKeyName(prop.key) !== "radius") continue;
|
|
71
|
-
const str = getStringValue(prop.value);
|
|
72
|
-
if (str) {
|
|
73
|
-
checkRadiusValue(str, prop.value);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
77
|
-
if (stateProp.type !== "Property") continue;
|
|
78
|
-
const stateStr = getStringValue(stateProp.value);
|
|
79
|
-
if (stateStr) checkRadiusValue(stateStr, stateProp.value);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
ImportDeclaration(node) {
|
|
85
|
-
ctx.trackImport(node);
|
|
86
|
-
},
|
|
87
|
-
...styleObjectListeners(handleStyleObject)
|
|
88
|
-
};
|
|
66
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
67
|
+
if (property === "radius") checkRadiusValue(value, node);
|
|
68
|
+
});
|
|
89
69
|
}
|
|
90
70
|
});
|
|
91
71
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext
|
|
1
|
+
{"version":3,"file":"valid-radius-shape.js","names":[],"sources":["../../src/rules/valid-radius-shape.ts"],"sourcesContent":["import type { TSESLint, TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { RADIUS_SHAPES } from '../constants.js';\nimport { replaceStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'unknownShape' | 'replaceWithShape';\n\nconst SHAPE_LIKE = /^[a-z]+$/;\n\nexport default createRule<[], MessageIds>({\n name: 'valid-radius-shape',\n meta: {\n type: 'problem',\n hasSuggestions: true,\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 replaceWithShape: \"Replace '{{shape}}' with '{{suggestion}}'\",\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 const suggest = suggestion\n ? [\n {\n messageId: 'replaceWithShape' as const,\n data: { shape: trimmed, suggestion },\n fix(fixer: TSESLint.RuleFixer) {\n return replaceStringValue(fixer, node, suggestion);\n },\n },\n ]\n : undefined;\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 suggest,\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 stringStyleValueListeners(ctx, (value, node, property) => {\n if (property === 'radius') checkRadiusValue(value, node);\n });\n },\n});\n"],"mappings":";;;;;;;AASA,MAAM,aAAa;AAEnB,iCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,iEACH;EACD,UAAU;GACR,cACE;GACF,kBAAkB;GACnB;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;GAE/C,MAAM,UAAU,aACZ,CACE;IACE,WAAW;IACX,MAAM;KAAE,OAAO;KAAS;KAAY;IACpC,IAAI,OAA2B;AAC7B,YAAO,mBAAmB,OAAO,MAAM,WAAW;;IAErD,CACF,GACD;AAEJ,WAAQ,OAAO;IACb;IACA,WAAW;IACX,MAAM;KACJ,OAAO;KACP,OACE,aAAa,aAAa,mBAAmB,WAAW,MAAM;KACjE;IACD;IACD,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,0BAA0B,MAAM,OAAO,MAAM,aAAa;AAC/D,OAAI,aAAa,SAAU,kBAAiB,OAAO,KAAK;IACxD;;CAEL,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { TastyContext } from "../context.js";
|
|
3
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
4
4
|
|
|
5
5
|
//#region src/rules/valid-recipe.ts
|
|
6
6
|
var valid_recipe_default = createRule({
|
|
@@ -29,24 +29,9 @@ var valid_recipe_default = createRule({
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
if (prop.type !== "Property" || prop.computed) continue;
|
|
36
|
-
if (getKeyName(prop.key) !== "recipe") continue;
|
|
37
|
-
const str = getStringValue(prop.value);
|
|
38
|
-
if (str) checkRecipeValue(str, prop.value);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function handleStyleObject(node) {
|
|
42
|
-
checkObject(node);
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
ImportDeclaration(node) {
|
|
46
|
-
ctx.trackImport(node);
|
|
47
|
-
},
|
|
48
|
-
...styleObjectListeners(handleStyleObject)
|
|
49
|
-
};
|
|
32
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
33
|
+
if (property === "recipe") checkRecipeValue(value, node);
|
|
34
|
+
});
|
|
50
35
|
}
|
|
51
36
|
});
|
|
52
37
|
|
|
@@ -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
|
|
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 { stringStyleValueListeners } from '../style-values.js';\n\ntype MessageIds = 'unknownRecipe';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-recipe',\n meta: {\n type: 'problem',\n docs: {\n description: 'Validate recipe property values against config',\n },\n messages: {\n unknownRecipe:\n \"Unknown recipe '{{name}}'. Add it to 'recipes' in your tasty config, or use a configured recipe.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkRecipeValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.recipes.length === 0) return;\n\n // Split by / for pre/post merge separation\n const sections = value.split('/');\n for (const section of sections) {\n const names = section.trim().split(/\\s+/);\n for (const name of names) {\n if (name.length === 0 || name === 'none') continue;\n if (!ctx.config.recipes.includes(name)) {\n context.report({\n node,\n messageId: 'unknownRecipe',\n data: { name },\n });\n }\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n if (property === 'recipe') checkRecipeValue(value, node);\n });\n },\n});\n"],"mappings":";;;;;AAOA,2BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,kDACd;EACD,UAAU,EACR,eACE,oGACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,iBAAiB,OAAe,MAA2B;AAClE,OAAI,IAAI,OAAO,QAAQ,WAAW,EAAG;GAGrC,MAAM,WAAW,MAAM,MAAM,IAAI;AACjC,QAAK,MAAM,WAAW,UAAU;IAC9B,MAAM,QAAQ,QAAQ,MAAM,CAAC,MAAM,MAAM;AACzC,SAAK,MAAM,QAAQ,OAAO;AACxB,SAAI,KAAK,WAAW,KAAK,SAAS,OAAQ;AAC1C,SAAI,CAAC,IAAI,OAAO,QAAQ,SAAS,KAAK,CACpC,SAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,MAAM;MACf,CAAC;;;;AAMV,SAAO,0BAA0B,MAAM,OAAO,MAAM,aAAa;AAC/D,OAAI,aAAa,SAAU,kBAAiB,OAAO,KAAK;IACxD;;CAEL,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { KNOWN_CSS_PROPERTIES, SEMANTIC_TRANSITIONS, TRANSITION_SEMANTIC_MAPPING } from "../constants.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TastyContext } from "../context.js";
|
|
4
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
5
5
|
import { replaceInStringValue } from "../fix-utils.js";
|
|
6
6
|
|
|
7
7
|
//#region src/rules/valid-transition.ts
|
|
@@ -89,29 +89,9 @@ var valid_transition_default = createRule({
|
|
|
89
89
|
offset += group.length + 1;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
if (prop.type !== "Property" || prop.computed) continue;
|
|
96
|
-
if (getKeyName(prop.key) !== "transition") continue;
|
|
97
|
-
const str = getStringValue(prop.value);
|
|
98
|
-
if (str) {
|
|
99
|
-
checkTransitionValue(str, prop.value);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
103
|
-
if (stateProp.type !== "Property") continue;
|
|
104
|
-
const stateStr = getStringValue(stateProp.value);
|
|
105
|
-
if (stateStr) checkTransitionValue(stateStr, stateProp.value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
ImportDeclaration(node) {
|
|
111
|
-
ctx.trackImport(node);
|
|
112
|
-
},
|
|
113
|
-
...styleObjectListeners(handleStyleObject)
|
|
114
|
-
};
|
|
92
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
93
|
+
if (property === "transition") checkTransitionValue(value, node);
|
|
94
|
+
}, { requireTastyJSX: true });
|
|
115
95
|
}
|
|
116
96
|
});
|
|
117
97
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext
|
|
1
|
+
{"version":3,"file":"valid-transition.js","names":[],"sources":["../../src/rules/valid-transition.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport {\n SEMANTIC_TRANSITIONS,\n KNOWN_CSS_PROPERTIES,\n TRANSITION_SEMANTIC_MAPPING,\n} from '../constants.js';\nimport { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds =\n | 'unknownTransition'\n | 'preferSemanticTransition'\n | 'useSemantic';\n\n/**\n * `KNOWN_CSS_PROPERTIES` is keyed by camelCase style keys, but a `transition`\n * value names *CSS* properties, which are kebab-case (`text-decoration-color`).\n * Tasty emits an unmapped name verbatim into the CSS `transition`, so the\n * kebab form is the correct spelling — normalize before the lookup.\n */\nfunction kebabToCamel(name: string): string {\n return name.replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-transition',\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'Validate transition property values use valid semantic transition names',\n },\n messages: {\n unknownTransition:\n \"Unknown transition name '{{name}}'. Use a semantic name ({{known}}) or a CSS property name.\",\n preferSemanticTransition:\n \"Transition '{{native}}' has a Tasty semantic equivalent — use '{{semantic}}' instead.\",\n useSemantic: \"Use '{{semantic}}' instead of '{{native}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkTransitionValue(value: string, node: TSESTree.Node): void {\n const groups = value.split(',');\n let offset = 0;\n\n for (const group of groups) {\n const parts = group.trim().split(/\\s+/);\n if (parts.length === 0 || parts[0] === '') {\n offset += group.length + 1;\n continue;\n }\n\n const name = parts[0];\n\n // $$ prefix is always valid (custom property reference: $$name -> --name)\n if (name.startsWith('$$')) {\n offset += group.length + 1;\n continue;\n }\n\n // ## prefix is always valid (color property reference: ##name -> --name-color)\n if (name.startsWith('##')) {\n offset += group.length + 1;\n continue;\n }\n\n const semantic = TRANSITION_SEMANTIC_MAPPING[name];\n if (semantic) {\n const nameStartInGroup = group.indexOf(name);\n const start = offset + nameStartInGroup;\n const end = start + name.length;\n context.report({\n node,\n messageId: 'preferSemanticTransition',\n data: { native: name, semantic },\n suggest: [\n {\n messageId: 'useSemantic',\n data: { native: name, semantic },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: semantic }],\n context.sourceCode,\n );\n },\n },\n ],\n });\n offset += group.length + 1;\n continue;\n }\n\n if (\n !SEMANTIC_TRANSITIONS.has(name) &&\n !KNOWN_CSS_PROPERTIES.has(name) &&\n !KNOWN_CSS_PROPERTIES.has(kebabToCamel(name)) &&\n !name.startsWith('--') &&\n name !== 'all' &&\n name !== 'none'\n ) {\n context.report({\n node,\n messageId: 'unknownTransition',\n data: {\n name,\n known: [...SEMANTIC_TRANSITIONS].join(', '),\n },\n });\n }\n\n offset += group.length + 1;\n }\n }\n\n return stringStyleValueListeners(\n ctx,\n (value, node, property) => {\n if (property === 'transition') checkTransitionValue(value, node);\n },\n { requireTastyJSX: true },\n );\n },\n});\n"],"mappings":";;;;;;;;;;;;;AAsBA,SAAS,aAAa,MAAsB;AAC1C,QAAO,KAAK,QAAQ,cAAc,GAAG,SAAiB,KAAK,aAAa,CAAC;;AAG3E,+BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,2EACH;EACD,UAAU;GACR,mBACE;GACF,0BACE;GACF,aAAa;GACd;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,qBAAqB,OAAe,MAA2B;GACtE,MAAM,SAAS,MAAM,MAAM,IAAI;GAC/B,IAAI,SAAS;AAEb,QAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,MAAM;AACvC,QAAI,MAAM,WAAW,KAAK,MAAM,OAAO,IAAI;AACzC,eAAU,MAAM,SAAS;AACzB;;IAGF,MAAM,OAAO,MAAM;AAGnB,QAAI,KAAK,WAAW,KAAK,EAAE;AACzB,eAAU,MAAM,SAAS;AACzB;;AAIF,QAAI,KAAK,WAAW,KAAK,EAAE;AACzB,eAAU,MAAM,SAAS;AACzB;;IAGF,MAAM,WAAW,4BAA4B;AAC7C,QAAI,UAAU;KACZ,MAAM,mBAAmB,MAAM,QAAQ,KAAK;KAC5C,MAAM,QAAQ,SAAS;KACvB,MAAM,MAAM,QAAQ,KAAK;AACzB,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE,QAAQ;OAAM;OAAU;MAChC,SAAS,CACP;OACE,WAAW;OACX,MAAM;QAAE,QAAQ;QAAM;QAAU;OAChC,IAAI,OAAO;AACT,eAAO,qBACL,OACA,MACA,CAAC;SAAE;SAAO;SAAK,aAAa;SAAU,CAAC,EACvC,QAAQ,WACT;;OAEJ,CACF;MACF,CAAC;AACF,eAAU,MAAM,SAAS;AACzB;;AAGF,QACE,CAAC,qBAAqB,IAAI,KAAK,IAC/B,CAAC,qBAAqB,IAAI,KAAK,IAC/B,CAAC,qBAAqB,IAAI,aAAa,KAAK,CAAC,IAC7C,CAAC,KAAK,WAAW,KAAK,IACtB,SAAS,SACT,SAAS,OAET,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ;MACA,OAAO,CAAC,GAAG,qBAAqB,CAAC,KAAK,KAAK;MAC5C;KACF,CAAC;AAGJ,cAAU,MAAM,SAAS;;;AAI7B,SAAO,0BACL,MACC,OAAO,MAAM,aAAa;AACzB,OAAI,aAAa,aAAc,sBAAqB,OAAO,KAAK;KAElE,EAAE,iBAAiB,MAAM,CAC1B;;CAEJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { TastyContext } from "../context.js";
|
|
3
|
+
import { stringStyleValueListeners } from "../style-values.js";
|
|
4
4
|
import { parseValue } from "../parsers/value-parser.js";
|
|
5
5
|
import { getExpectation } from "../property-expectations.js";
|
|
6
6
|
|
|
@@ -136,35 +136,9 @@ var valid_value_default = createRule({
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (key && (key.startsWith("$") || key.startsWith("#"))) return;
|
|
143
|
-
if (key && key.startsWith("&")) return;
|
|
144
|
-
const str = getStringValue(prop.value);
|
|
145
|
-
if (str) {
|
|
146
|
-
checkValue(str, key, prop.value);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
150
|
-
if (stateProp.type !== "Property") continue;
|
|
151
|
-
const stateStr = getStringValue(stateProp.value);
|
|
152
|
-
if (stateStr) checkValue(stateStr, key, stateProp.value);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function handleStyleObject(node) {
|
|
156
|
-
if (!ctx.isStyleObject(node)) return;
|
|
157
|
-
for (const prop of node.properties) {
|
|
158
|
-
if (prop.type !== "Property") continue;
|
|
159
|
-
processProperty(prop);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return {
|
|
163
|
-
ImportDeclaration(node) {
|
|
164
|
-
ctx.trackImport(node);
|
|
165
|
-
},
|
|
166
|
-
...styleObjectListeners(handleStyleObject)
|
|
167
|
-
};
|
|
139
|
+
return stringStyleValueListeners(ctx, (value, node, property) => {
|
|
140
|
+
if (!/^[#$]/.test(property)) checkValue(value, property, node);
|
|
141
|
+
});
|
|
168
142
|
}
|
|
169
143
|
});
|
|
170
144
|
const COLOR_FUNC_NAMES = new Set([
|
|
@@ -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 '@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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"}
|
|
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 } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.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 return stringStyleValueListeners(ctx, (value, node, property) => {\n if (!/^[#$]/.test(property)) checkValue(value, property, node);\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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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { KNOWN_CSS_PROPERTIES, KNOWN_TASTY_PROPERTIES } from "./constants.js";
|
|
2
|
+
import { getKeyName, getStringValue, unwrapExpression } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
//#region src/style-values.ts
|
|
5
|
+
/** Visit visible values without evaluating bindings, calls, or interpolations. */
|
|
6
|
+
function styleValueListeners(ctx, check, options = {}) {
|
|
7
|
+
function visit(property, node, attribute) {
|
|
8
|
+
node = unwrapExpression(node);
|
|
9
|
+
if (node.type === "ObjectExpression") {
|
|
10
|
+
for (const prop of node.properties) if (prop.type === "Property" && !prop.computed) {
|
|
11
|
+
const value = unwrapExpression(prop.value);
|
|
12
|
+
if (value.type !== "ObjectExpression") visit(property, value, attribute);
|
|
13
|
+
}
|
|
14
|
+
} else if (node.type === "ConditionalExpression") {
|
|
15
|
+
visit(property, node.consequent, attribute);
|
|
16
|
+
visit(property, node.alternate, attribute);
|
|
17
|
+
} else if (node.type === "LogicalExpression") {
|
|
18
|
+
const left = unwrapExpression(node.left);
|
|
19
|
+
const str = getStringValue(left);
|
|
20
|
+
const known = left.type === "Literal" || str !== null;
|
|
21
|
+
const value = left.type === "Literal" ? left.value : str;
|
|
22
|
+
if (node.operator === "&&") {
|
|
23
|
+
if (!known || value) visit(property, node.right, attribute);
|
|
24
|
+
} else if (known) visit(property, node.operator === "??" ? value == null ? node.right : left : value ? left : node.right, attribute);
|
|
25
|
+
else {
|
|
26
|
+
visit(property, node.left, attribute);
|
|
27
|
+
visit(property, node.right, attribute);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
const visible = node.type === "Literal" || node.type === "JSXAttribute" || getStringValue(node) !== null || node.type === "UnaryExpression" && node.argument.type === "Literal";
|
|
31
|
+
if (attribute && visible && ctx.isComponentJSXProp(attribute)) return;
|
|
32
|
+
check(property, node);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
"Program:exit"() {
|
|
37
|
+
ctx.finishJSXAnalysis();
|
|
38
|
+
options.onProgramExit?.();
|
|
39
|
+
},
|
|
40
|
+
ImportDeclaration(node) {
|
|
41
|
+
ctx.trackImport(node);
|
|
42
|
+
},
|
|
43
|
+
ObjectExpression(node) {
|
|
44
|
+
if (!ctx.isStyleObject(node)) return;
|
|
45
|
+
options.onStyleObject?.(node);
|
|
46
|
+
for (const prop of node.properties) {
|
|
47
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
48
|
+
const key = getKeyName(prop.key);
|
|
49
|
+
if (key === null || /^[A-Z@&]/.test(key) || key === "$") continue;
|
|
50
|
+
visit(key, prop.value);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
JSXAttribute(node) {
|
|
54
|
+
if (node.name.type !== "JSXIdentifier") return;
|
|
55
|
+
const key = node.name.name;
|
|
56
|
+
if (!KNOWN_TASTY_PROPERTIES.has(key) && !KNOWN_CSS_PROPERTIES.has(key) && !ctx.config.styles.includes(key)) return;
|
|
57
|
+
const opening = node.parent;
|
|
58
|
+
if (opening?.type !== "JSXOpeningElement") return;
|
|
59
|
+
let tag = opening.name;
|
|
60
|
+
while (tag.type === "JSXMemberExpression") tag = tag.object;
|
|
61
|
+
if (tag.type !== "JSXIdentifier" || !/^[A-Z]/.test(tag.name)) return;
|
|
62
|
+
if (options.requireTastyJSX && !ctx.isTastyJSXComponent(node, tag.name)) return;
|
|
63
|
+
const value = node.value?.type === "JSXExpressionContainer" ? node.value.expression : node.value;
|
|
64
|
+
if (value) visit(key, value, node);
|
|
65
|
+
else visit(key, node, node);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Adapt a node listener to the string-only rules. */
|
|
70
|
+
function stringStyleValueListeners(ctx, check, options = {}) {
|
|
71
|
+
return styleValueListeners(ctx, (property, node) => {
|
|
72
|
+
const value = getStringValue(node);
|
|
73
|
+
if (value !== null) check(value, node, property);
|
|
74
|
+
}, options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { stringStyleValueListeners, styleValueListeners };
|
|
79
|
+
//# sourceMappingURL=style-values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style-values.js","names":[],"sources":["../src/style-values.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { TastyContext } from './context.js';\nimport { KNOWN_CSS_PROPERTIES, KNOWN_TASTY_PROPERTIES } from './constants.js';\nimport { getKeyName, getStringValue, unwrapExpression } from './utils.js';\n\ninterface ValueListenerOptions {\n /** Collect declarations before checking their values (token existence rules). */\n onStyleObject?: (node: TSESTree.ObjectExpression) => void;\n /** Rewrites that depend on Tasty semantics need evidence beyond the prop name. */\n requireTastyJSX?: boolean;\n /** Deferred checks run after all declarations have been collected. */\n onProgramExit?: () => void;\n}\n\n/** Visit visible values without evaluating bindings, calls, or interpolations. */\nexport function styleValueListeners(\n ctx: TastyContext,\n check: (property: string, node: TSESTree.Node) => void,\n options: ValueListenerOptions = {},\n) {\n function visit(\n property: string,\n node: TSESTree.Node,\n attribute?: TSESTree.JSXAttribute,\n ): void {\n node = unwrapExpression(node);\n if (node.type === 'ObjectExpression') {\n // A property value object is a state map, never a sub-element here.\n for (const prop of node.properties) {\n if (prop.type === 'Property' && !prop.computed) {\n const value = unwrapExpression(prop.value);\n if (value.type !== 'ObjectExpression')\n visit(property, value, attribute);\n }\n }\n } else if (node.type === 'ConditionalExpression') {\n visit(property, node.consequent, attribute);\n visit(property, node.alternate, attribute);\n } else if (node.type === 'LogicalExpression') {\n const left = unwrapExpression(node.left);\n const str = getStringValue(left);\n const known = left.type === 'Literal' || str !== null;\n const value = left.type === 'Literal' ? left.value : str;\n if (node.operator === '&&') {\n // The left operand can only be the result when it is falsy.\n if (!known || value) visit(property, node.right, attribute);\n } else if (known) {\n visit(\n property,\n node.operator === '??'\n ? value == null\n ? node.right\n : left\n : value\n ? left\n : node.right,\n attribute,\n );\n } else {\n visit(property, node.left, attribute);\n visit(property, node.right, attribute);\n }\n } else {\n const visible =\n node.type === 'Literal' ||\n node.type === 'JSXAttribute' ||\n getStringValue(node) !== null ||\n (node.type === 'UnaryExpression' && node.argument.type === 'Literal');\n // Opaque values cannot produce a value diagnostic; avoid a compiler pass.\n if (attribute && visible && ctx.isComponentJSXProp(attribute)) return;\n check(property, node);\n }\n }\n\n return {\n 'Program:exit'() {\n ctx.finishJSXAnalysis();\n options.onProgramExit?.();\n },\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n ctx.trackImport(node);\n },\n ObjectExpression(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n options.onStyleObject?.(node);\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key === null || /^[A-Z@&]/.test(key) || key === '$') continue;\n visit(key, prop.value);\n }\n },\n JSXAttribute(node: TSESTree.JSXAttribute) {\n if (node.name.type !== 'JSXIdentifier') return;\n const key = node.name.name;\n if (\n !KNOWN_TASTY_PROPERTIES.has(key) &&\n !KNOWN_CSS_PROPERTIES.has(key) &&\n !ctx.config.styles.includes(key)\n )\n return;\n\n const opening = node.parent;\n if (opening?.type !== 'JSXOpeningElement') return;\n let tag = opening.name;\n while (tag.type === 'JSXMemberExpression') tag = tag.object;\n // Native HTML/SVG attributes contain CSS, not Tasty's value language.\n if (tag.type !== 'JSXIdentifier' || !/^[A-Z]/.test(tag.name)) return;\n if (options.requireTastyJSX && !ctx.isTastyJSXComponent(node, tag.name))\n return;\n\n const value =\n node.value?.type === 'JSXExpressionContainer'\n ? node.value.expression\n : node.value;\n if (value) visit(key, value, node);\n else visit(key, node, node); // A bare JSX attribute means boolean true.\n },\n };\n}\n\n/** Adapt a node listener to the string-only rules. */\nexport function stringStyleValueListeners(\n ctx: TastyContext,\n check: (value: string, node: TSESTree.Node, property: string) => void,\n options: ValueListenerOptions = {},\n) {\n return styleValueListeners(\n ctx,\n (property, node) => {\n const value = getStringValue(node);\n if (value !== null) check(value, node, property);\n },\n options,\n );\n}\n"],"mappings":";;;;;AAeA,SAAgB,oBACd,KACA,OACA,UAAgC,EAAE,EAClC;CACA,SAAS,MACP,UACA,MACA,WACM;AACN,SAAO,iBAAiB,KAAK;AAC7B,MAAI,KAAK,SAAS,oBAEhB;QAAK,MAAM,QAAQ,KAAK,WACtB,KAAI,KAAK,SAAS,cAAc,CAAC,KAAK,UAAU;IAC9C,MAAM,QAAQ,iBAAiB,KAAK,MAAM;AAC1C,QAAI,MAAM,SAAS,mBACjB,OAAM,UAAU,OAAO,UAAU;;aAG9B,KAAK,SAAS,yBAAyB;AAChD,SAAM,UAAU,KAAK,YAAY,UAAU;AAC3C,SAAM,UAAU,KAAK,WAAW,UAAU;aACjC,KAAK,SAAS,qBAAqB;GAC5C,MAAM,OAAO,iBAAiB,KAAK,KAAK;GACxC,MAAM,MAAM,eAAe,KAAK;GAChC,MAAM,QAAQ,KAAK,SAAS,aAAa,QAAQ;GACjD,MAAM,QAAQ,KAAK,SAAS,YAAY,KAAK,QAAQ;AACrD,OAAI,KAAK,aAAa,MAEpB;QAAI,CAAC,SAAS,MAAO,OAAM,UAAU,KAAK,OAAO,UAAU;cAClD,MACT,OACE,UACA,KAAK,aAAa,OACd,SAAS,OACP,KAAK,QACL,OACF,QACE,OACA,KAAK,OACX,UACD;QACI;AACL,UAAM,UAAU,KAAK,MAAM,UAAU;AACrC,UAAM,UAAU,KAAK,OAAO,UAAU;;SAEnC;GACL,MAAM,UACJ,KAAK,SAAS,aACd,KAAK,SAAS,kBACd,eAAe,KAAK,KAAK,QACxB,KAAK,SAAS,qBAAqB,KAAK,SAAS,SAAS;AAE7D,OAAI,aAAa,WAAW,IAAI,mBAAmB,UAAU,CAAE;AAC/D,SAAM,UAAU,KAAK;;;AAIzB,QAAO;EACL,iBAAiB;AACf,OAAI,mBAAmB;AACvB,WAAQ,iBAAiB;;EAE3B,kBAAkB,MAAkC;AAClD,OAAI,YAAY,KAAK;;EAEvB,iBAAiB,MAAiC;AAChD,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,WAAQ,gBAAgB,KAAK;AAC7B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,QAAQ,WAAW,KAAK,IAAI,IAAI,QAAQ,IAAK;AACzD,UAAM,KAAK,KAAK,MAAM;;;EAG1B,aAAa,MAA6B;AACxC,OAAI,KAAK,KAAK,SAAS,gBAAiB;GACxC,MAAM,MAAM,KAAK,KAAK;AACtB,OACE,CAAC,uBAAuB,IAAI,IAAI,IAChC,CAAC,qBAAqB,IAAI,IAAI,IAC9B,CAAC,IAAI,OAAO,OAAO,SAAS,IAAI,CAEhC;GAEF,MAAM,UAAU,KAAK;AACrB,OAAI,SAAS,SAAS,oBAAqB;GAC3C,IAAI,MAAM,QAAQ;AAClB,UAAO,IAAI,SAAS,sBAAuB,OAAM,IAAI;AAErD,OAAI,IAAI,SAAS,mBAAmB,CAAC,SAAS,KAAK,IAAI,KAAK,CAAE;AAC9D,OAAI,QAAQ,mBAAmB,CAAC,IAAI,oBAAoB,MAAM,IAAI,KAAK,CACrE;GAEF,MAAM,QACJ,KAAK,OAAO,SAAS,2BACjB,KAAK,MAAM,aACX,KAAK;AACX,OAAI,MAAO,OAAM,KAAK,OAAO,KAAK;OAC7B,OAAM,KAAK,MAAM,KAAK;;EAE9B;;;AAIH,SAAgB,0BACd,KACA,OACA,UAAgC,EAAE,EAClC;AACA,QAAO,oBACL,MACC,UAAU,SAAS;EAClB,MAAM,QAAQ,eAAe,KAAK;AAClC,MAAI,UAAU,KAAM,OAAM,OAAO,MAAM,SAAS;IAElD,QACD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/type-aware-jsx.d.ts
|
|
2
|
+
/** Experimental, independently managed TypeScript analysis for either linter. */
|
|
3
|
+
interface TypeAwareJSXOptions {
|
|
4
|
+
/** tsconfig path relative to the linter's cwd; otherwise find the nearest one. */
|
|
5
|
+
project?: string;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { TypeAwareJSXOptions };
|
|
9
|
+
//# sourceMappingURL=type-aware-jsx.d.ts.map
|