@tenphi/eslint-plugin-tasty 1.5.1 → 1.5.2
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
CHANGED
|
@@ -192,7 +192,7 @@ for typing shared signatures.
|
|
|
192
192
|
| `tasty/require-default-state` | error | Missing default (`''`) or fallback floor (`_`) key in state mappings (skipped for extending calls) |
|
|
193
193
|
| `tasty/no-own-at-root` | warn | `@own()` used at root level where it is redundant |
|
|
194
194
|
| `tasty/valid-default-state-order` | warn | Misplaced default (`''`) or redundant `''` when only `_` is present |
|
|
195
|
-
| `tasty/prefer-shorthand-property` | warn | Use Tasty shorthand instead of native CSS properties (`backgroundColor` → `fill`, `paddingBlock` → `blockPadding`, etc.).
|
|
195
|
+
| `tasty/prefer-shorthand-property` | warn | Use Tasty shorthand instead of native CSS properties (`backgroundColor` → `fill`, `paddingBlock` → `blockPadding`, etc.). Report-only warnings explain how to override one part through a token, with concrete min/max dimension examples. Extension warnings point at the base component and are skipped over bases you cannot edit (see `ownedSources`; [details](docs/rules/prefer-shorthand-property.md)) |
|
|
196
196
|
| `tasty/no-raw-color-values` | warn | Raw hex/rgb/`okhsl`/`okhst`/`oklch`/named colors instead of `#color` tokens |
|
|
197
197
|
| `tasty/no-raw-transition-duration` | warn | Hardcoded `transition` duration (`fill 0.2s`) in Tasty styles or a local `tasty()` component's `transition` prop. Suggests a configured duration token or the implicit per-name timing; zero and delays are left alone |
|
|
198
198
|
| `tasty/no-runtime-styles-mutation` | warn | JavaScript variables, calls, conditionals, computed keys, or interpolated templates in Tasty style values; use states and tokens instead |
|
|
@@ -21,12 +21,31 @@ const CSS_WIDE_KEYWORDS = new Set([
|
|
|
21
21
|
* whole typography group, so `preset: 'inherit'` yields a real
|
|
22
22
|
* `font-family: inherit`. Point at that instead of `font`.
|
|
23
23
|
*/
|
|
24
|
-
function
|
|
24
|
+
function shorthandMapping(key, prop) {
|
|
25
25
|
const mapping = SHORTHAND_MAPPING[key];
|
|
26
26
|
if (!mapping) return null;
|
|
27
|
-
if (key !== "fontFamily") return mapping
|
|
27
|
+
if (key !== "fontFamily") return mapping;
|
|
28
28
|
const value = getStringValue(prop.value)?.trim().toLowerCase();
|
|
29
|
-
return value && CSS_WIDE_KEYWORDS.has(value) ?
|
|
29
|
+
return value && CSS_WIDE_KEYWORDS.has(value) ? {
|
|
30
|
+
...mapping,
|
|
31
|
+
property: "preset",
|
|
32
|
+
hint: `preset: '${value}'`
|
|
33
|
+
} : mapping;
|
|
34
|
+
}
|
|
35
|
+
function tokenOverrideHint(key, property, styleCtx, isSubElement) {
|
|
36
|
+
const usesTokensProp = styleCtx.type === "tasty";
|
|
37
|
+
const original = styleCtx.isExtending ? usesTokensProp ? `base component's` : `base style definition's` : "original";
|
|
38
|
+
const warning = styleCtx.isExtending ? "" : ` Replacing '${property}' can reset its other values.`;
|
|
39
|
+
const defaultHint = usesTokensProp && isSubElement ? " Declare the default on the component root so this sub-element inherits the token." : "";
|
|
40
|
+
const dimension = /^(min|max)(Width|Height|BlockSize|InlineSize)$/.exec(key);
|
|
41
|
+
if (dimension) {
|
|
42
|
+
const token = `$${key.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`;
|
|
43
|
+
return `${warning} To override only '${key}', expose a token with a default in the ${original} '${property}' (e.g. ${property}: '${dimension[1] === "min" ? `${token} auto initial` : `initial auto ${token}`}'), keep its other values, and ${usesTokensProp ? `set tokens={{ '${token}': '20x' }}` : `set the '${token.replace("$", "--")}' CSS custom property on the target element to a CSS value (e.g. '160px')`}.${defaultHint}`;
|
|
44
|
+
}
|
|
45
|
+
const override = usesTokensProp ? `set it via the 'tokens' prop` : "set its CSS custom property on the target element";
|
|
46
|
+
if (property === "preset") return `${warning} For independent typography overrides, use the CSS value tokens referenced by a named preset in the ${original} styles, keep its other values, and ${override}. Keep preset names and modifiers static.${defaultHint}`;
|
|
47
|
+
if (key === "scrollbarWidth" || key === "scrollbarGutter") return `${warning} For independent overrides, expose the value as a token with a default in the ${original} '${key}' longhand, keep its other values, and ${override}. '${property}' width and gutter modifiers are static.${defaultHint}`;
|
|
48
|
+
return `${warning} For independent overrides, expose the part you need to change as a token with a default in the ${original} '${property}', keep its other values, and ${override}.${defaultHint}`;
|
|
30
49
|
}
|
|
31
50
|
var prefer_shorthand_property_default = createRule({
|
|
32
51
|
name: "prefer-shorthand-property",
|
|
@@ -35,8 +54,8 @@ var prefer_shorthand_property_default = createRule({
|
|
|
35
54
|
fixable: "code",
|
|
36
55
|
docs: { description: "Suggest tasty shorthand when a native CSS property with a tasty alternative is used" },
|
|
37
56
|
messages: {
|
|
38
|
-
preferShorthand: "
|
|
39
|
-
preferShorthandExtending: "'{{native}}'
|
|
57
|
+
preferShorthand: "When defining the complete style, prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.{{overrideHint}}",
|
|
58
|
+
preferShorthandExtending: "'{{native}}' changes one part of the base styles. '{{alternative}}' would replace the whole '{{property}}'.{{overrideHint}}"
|
|
40
59
|
},
|
|
41
60
|
schema: []
|
|
42
61
|
},
|
|
@@ -46,13 +65,14 @@ var prefer_shorthand_property_default = createRule({
|
|
|
46
65
|
function handleStyleObject(node) {
|
|
47
66
|
const styleCtx = ctx.getStyleContext(node);
|
|
48
67
|
if (!styleCtx) return;
|
|
68
|
+
const isSubElement = ctx.isInsideSubElement(node);
|
|
49
69
|
for (const prop of node.properties) {
|
|
50
70
|
if (prop.type !== "Property" || prop.computed) continue;
|
|
51
71
|
const key = getKeyName(prop.key);
|
|
52
72
|
if (key === null) continue;
|
|
53
|
-
const mapping =
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
const mapping = shorthandMapping(key, prop);
|
|
74
|
+
if (mapping) {
|
|
75
|
+
const hint = mapping.hint;
|
|
56
76
|
if (styleCtx.isExtending) {
|
|
57
77
|
if (!ctx.isOwnedComponent(styleCtx.baseComponent)) continue;
|
|
58
78
|
context.report({
|
|
@@ -61,7 +81,8 @@ var prefer_shorthand_property_default = createRule({
|
|
|
61
81
|
data: {
|
|
62
82
|
native: key,
|
|
63
83
|
alternative: hint,
|
|
64
|
-
property: mapping.property
|
|
84
|
+
property: mapping.property,
|
|
85
|
+
overrideHint: tokenOverrideHint(key, mapping.property, styleCtx, isSubElement)
|
|
65
86
|
}
|
|
66
87
|
});
|
|
67
88
|
continue;
|
|
@@ -71,7 +92,8 @@ var prefer_shorthand_property_default = createRule({
|
|
|
71
92
|
messageId: "preferShorthand",
|
|
72
93
|
data: {
|
|
73
94
|
native: key,
|
|
74
|
-
alternative: hint
|
|
95
|
+
alternative: hint,
|
|
96
|
+
overrideHint: mapping.safeFix ? "" : tokenOverrideHint(key, mapping.property, styleCtx, isSubElement)
|
|
75
97
|
},
|
|
76
98
|
fix(fixer) {
|
|
77
99
|
if (!mapping.safeFix) return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand' | 'preferShorthandExtending';\n\nconst CSS_WIDE_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n]);\n\n/**\n * `font` resolves to `<value>, var(--font-sans, var(--font-sans-fallback))`,\n * so it cannot express a CSS-wide keyword — `font: 'inherit'` emits\n * `font-family: inherit, …`, which is invalid and drops the inherit.\n *\n * `preset` is the property that handles these: a CSS-wide keyword used as the\n * preset name short-circuits token lookup and is emitted verbatim across the\n * whole typography group, so `preset: 'inherit'` yields a real\n * `font-family: inherit`. Point at that instead of `font`.\n */\nfunction
|
|
1
|
+
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport type { StyleContext } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand' | 'preferShorthandExtending';\n\nconst CSS_WIDE_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n]);\n\n/**\n * `font` resolves to `<value>, var(--font-sans, var(--font-sans-fallback))`,\n * so it cannot express a CSS-wide keyword — `font: 'inherit'` emits\n * `font-family: inherit, …`, which is invalid and drops the inherit.\n *\n * `preset` is the property that handles these: a CSS-wide keyword used as the\n * preset name short-circuits token lookup and is emitted verbatim across the\n * whole typography group, so `preset: 'inherit'` yields a real\n * `font-family: inherit`. Point at that instead of `font`.\n */\nfunction shorthandMapping(key: string, prop: TSESTree.Property) {\n const mapping = SHORTHAND_MAPPING[key];\n if (!mapping) return null;\n if (key !== 'fontFamily') return mapping;\n\n const value = getStringValue(prop.value)?.trim().toLowerCase();\n return value && CSS_WIDE_KEYWORDS.has(value)\n ? { ...mapping, property: 'preset', hint: `preset: '${value}'` }\n : mapping;\n}\n\nfunction tokenOverrideHint(\n key: string,\n property: string,\n styleCtx: StyleContext,\n isSubElement: boolean,\n): string {\n const usesTokensProp = styleCtx.type === 'tasty';\n const original = styleCtx.isExtending\n ? usesTokensProp\n ? `base component's`\n : `base style definition's`\n : 'original';\n const warning = styleCtx.isExtending\n ? ''\n : ` Replacing '${property}' can reset its other values.`;\n const defaultHint =\n usesTokensProp && isSubElement\n ? ' Declare the default on the component root so this sub-element inherits the token.'\n : '';\n const dimension = /^(min|max)(Width|Height|BlockSize|InlineSize)$/.exec(key);\n\n if (dimension) {\n const token = `$${key.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`)}`;\n const values =\n dimension[1] === 'min'\n ? `${token} auto initial`\n : `initial auto ${token}`;\n const override = usesTokensProp\n ? `set tokens={{ '${token}': '20x' }}`\n : `set the '${token.replace('$', '--')}' CSS custom property on the target element to a CSS value (e.g. '160px')`;\n\n return `${warning} To override only '${key}', expose a token with a default in the ${original} '${property}' (e.g. ${property}: '${values}'), keep its other values, and ${override}.${defaultHint}`;\n }\n\n const override = usesTokensProp\n ? `set it via the 'tokens' prop`\n : 'set its CSS custom property on the target element';\n\n if (property === 'preset') {\n return `${warning} For independent typography overrides, use the CSS value tokens referenced by a named preset in the ${original} styles, keep its other values, and ${override}. Keep preset names and modifiers static.${defaultHint}`;\n }\n\n if (key === 'scrollbarWidth' || key === 'scrollbarGutter') {\n return `${warning} For independent overrides, expose the value as a token with a default in the ${original} '${key}' longhand, keep its other values, and ${override}. '${property}' width and gutter modifiers are static.${defaultHint}`;\n }\n\n return `${warning} For independent overrides, expose the part you need to change as a token with a default in the ${original} '${property}', keep its other values, and ${override}.${defaultHint}`;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"When defining the complete style, prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.{{overrideHint}}\",\n preferShorthandExtending:\n \"'{{native}}' changes one part of the base styles. '{{alternative}}' would replace the whole '{{property}}'.{{overrideHint}}\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n const styleCtx = ctx.getStyleContext(node);\n if (!styleCtx) return;\n const isSubElement = ctx.isInsideSubElement(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const mapping = shorthandMapping(key, prop);\n if (mapping) {\n const hint = mapping.hint;\n // An extension layer merges per key, so renaming the key replaces the\n // base component's whole `mapping.property` instead of patching the\n // one part written here — `paddingTop: '2x'` -> `padding: '2x top'`\n // drops the base's other three edges to 0. The token seam belongs in\n // the base component, which is another file and may not even be the\n // author's to edit, so this stays a report with no fix.\n if (styleCtx.isExtending) {\n // The token seam has to be added to the base component's own\n // definition, so the suggestion is only actionable when that file\n // belongs to this project. Over an imported base — a UI kit, say —\n // the author's remaining options are the longhand they already\n // wrote or a rewrite that would clobber the base, and a warning\n // recommending neither is just noise. `ownedSources` opts a\n // published-from-this-monorepo design system back in.\n if (!ctx.isOwnedComponent(styleCtx.baseComponent)) continue;\n\n context.report({\n node: prop.key,\n messageId: 'preferShorthandExtending',\n data: {\n native: key,\n alternative: hint,\n property: mapping.property,\n overrideHint: tokenOverrideHint(\n key,\n mapping.property,\n styleCtx,\n isSubElement,\n ),\n },\n });\n\n continue;\n }\n\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: {\n native: key,\n alternative: hint,\n overrideHint: mapping.safeFix\n ? ''\n : tokenOverrideHint(\n key,\n mapping.property,\n styleCtx,\n isSubElement,\n ),\n },\n fix(fixer) {\n // Only auto-fix the carry-over subset where the value passes\n // through unchanged (e.g. backgroundColor → fill, borderRadius →\n // radius). Directional / min/max / border-* renames change\n // semantics and stay report-only.\n if (!mapping.safeFix) return null;\n return fixer.replaceText(prop.key, mapping.property);\n },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":"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"}
|