@tenphi/eslint-plugin-tasty 1.0.0 → 1.0.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/dist/constants.js
CHANGED
|
@@ -1254,7 +1254,80 @@ const COLOR_BEARING_PROPERTIES = new Set([
|
|
|
1254
1254
|
"floodColor",
|
|
1255
1255
|
"lightingColor"
|
|
1256
1256
|
]);
|
|
1257
|
+
/**
|
|
1258
|
+
* Properties where tasty does NOT expand a `$name` custom-property reference.
|
|
1259
|
+
*
|
|
1260
|
+
* These have their own style handlers that pass the value through verbatim, so
|
|
1261
|
+
* `fontFamily: '$font-sans'` emits a literal `font-family: $font-sans` — an
|
|
1262
|
+
* invalid declaration the browser drops. Rewriting `var(--font-sans)` to
|
|
1263
|
+
* `$font-sans` here silently deletes the style.
|
|
1264
|
+
*
|
|
1265
|
+
* Note that the colour properties (`color`, `fill`, `backgroundColor`) are in
|
|
1266
|
+
* this list but NOT in {@link PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION}: they
|
|
1267
|
+
* expand `#token` and not `$name`. The two sets are genuinely different.
|
|
1268
|
+
*
|
|
1269
|
+
* Derived from the runtime, not by hand — `constants.round-trip.test.ts` rebuilds
|
|
1270
|
+
* both sets against the installed `@tenphi/tasty` and fails if either drifts.
|
|
1271
|
+
*/
|
|
1272
|
+
const PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION = new Set([
|
|
1273
|
+
"align",
|
|
1274
|
+
"alignContent",
|
|
1275
|
+
"alignItems",
|
|
1276
|
+
"backgroundAttachment",
|
|
1277
|
+
"backgroundClip",
|
|
1278
|
+
"backgroundColor",
|
|
1279
|
+
"backgroundOrigin",
|
|
1280
|
+
"backgroundRepeat",
|
|
1281
|
+
"color",
|
|
1282
|
+
"display",
|
|
1283
|
+
"fill",
|
|
1284
|
+
"font",
|
|
1285
|
+
"fontFamily",
|
|
1286
|
+
"justify",
|
|
1287
|
+
"justifyContent",
|
|
1288
|
+
"justifyItems",
|
|
1289
|
+
"overflow",
|
|
1290
|
+
"placeContent",
|
|
1291
|
+
"placeItems",
|
|
1292
|
+
"textTransform",
|
|
1293
|
+
"whiteSpace"
|
|
1294
|
+
]);
|
|
1295
|
+
/**
|
|
1296
|
+
* Properties where tasty does NOT expand a `#name` colour token.
|
|
1297
|
+
*
|
|
1298
|
+
* Mostly dimension and keyword properties: `fontSize: '#accent'` is meaningless,
|
|
1299
|
+
* so the handler leaves the value alone and the declaration is dropped. Suggesting
|
|
1300
|
+
* `#x` for a `var(--x-color)` inside one of these would break it.
|
|
1301
|
+
*
|
|
1302
|
+
* See {@link PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION} for why this is a
|
|
1303
|
+
* separate list rather than the same one.
|
|
1304
|
+
*/
|
|
1305
|
+
const PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION = new Set([
|
|
1306
|
+
"align",
|
|
1307
|
+
"alignContent",
|
|
1308
|
+
"alignItems",
|
|
1309
|
+
"backgroundAttachment",
|
|
1310
|
+
"backgroundClip",
|
|
1311
|
+
"backgroundOrigin",
|
|
1312
|
+
"backgroundRepeat",
|
|
1313
|
+
"display",
|
|
1314
|
+
"font",
|
|
1315
|
+
"fontFamily",
|
|
1316
|
+
"fontSize",
|
|
1317
|
+
"fontWeight",
|
|
1318
|
+
"justify",
|
|
1319
|
+
"justifyContent",
|
|
1320
|
+
"justifyItems",
|
|
1321
|
+
"letterSpacing",
|
|
1322
|
+
"lineHeight",
|
|
1323
|
+
"outlineOffset",
|
|
1324
|
+
"overflow",
|
|
1325
|
+
"placeContent",
|
|
1326
|
+
"placeItems",
|
|
1327
|
+
"textTransform",
|
|
1328
|
+
"whiteSpace"
|
|
1329
|
+
]);
|
|
1257
1330
|
|
|
1258
1331
|
//#endregion
|
|
1259
|
-
export { BOOLEAN_TRUE_PROPERTIES, BUILT_IN_STATE_PREFIXES, BUILT_IN_UNITS, COLOR_BEARING_PROPERTIES, CSS_UNITS, DEFAULT_IMPORT_SOURCES, DIRECTIONAL_BOX, DIRECTIONAL_MODIFIERS, DOCK_PROPERTIES, KNOWN_CSS_PROPERTIES, KNOWN_PSEUDO_CLASSES, KNOWN_TASTY_PROPERTIES, LONGHAND_MAPPING, NAMED_CSS_COLORS, PRESET_MODIFIERS, RADIUS_SHAPES, RENAMED_SPECIAL_STYLE_KEYS, SEMANTIC_TRANSITIONS, SHORTHAND_MAPPING, SINGLE_VALUE_DIRECTIONAL_PROPERTIES, SPECIAL_STYLE_KEYS, TRANSITION_SEMANTIC_MAPPING };
|
|
1332
|
+
export { BOOLEAN_TRUE_PROPERTIES, BUILT_IN_STATE_PREFIXES, BUILT_IN_UNITS, COLOR_BEARING_PROPERTIES, CSS_UNITS, DEFAULT_IMPORT_SOURCES, DIRECTIONAL_BOX, DIRECTIONAL_MODIFIERS, DOCK_PROPERTIES, KNOWN_CSS_PROPERTIES, KNOWN_PSEUDO_CLASSES, KNOWN_TASTY_PROPERTIES, LONGHAND_MAPPING, NAMED_CSS_COLORS, PRESET_MODIFIERS, PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION, PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION, RADIUS_SHAPES, RENAMED_SPECIAL_STYLE_KEYS, SEMANTIC_TRANSITIONS, SHORTHAND_MAPPING, SINGLE_VALUE_DIRECTIONAL_PROPERTIES, SPECIAL_STYLE_KEYS, TRANSITION_SEMANTIC_MAPPING };
|
|
1260
1333
|
//# sourceMappingURL=constants.js.map
|
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Built-in tasty style properties that are always valid as style keys.\n */\nexport const KNOWN_TASTY_PROPERTIES = new Set([\n 'display',\n 'font',\n 'preset',\n 'hide',\n 'whiteSpace',\n 'opacity',\n 'transition',\n 'gridArea',\n 'order',\n 'gridColumn',\n 'gridRow',\n 'placeSelf',\n 'alignSelf',\n 'justifySelf',\n 'zIndex',\n 'margin',\n 'inset',\n 'position',\n 'padding',\n 'paddingInline',\n 'paddingBlock',\n 'overflow',\n 'scrollbar',\n 'textAlign',\n 'border',\n 'radius',\n 'shadow',\n 'outline',\n 'color',\n 'fill',\n 'fade',\n 'image',\n 'textTransform',\n 'fontWeight',\n 'fontStyle',\n 'width',\n 'height',\n 'flexBasis',\n 'flexGrow',\n 'flexShrink',\n 'flex',\n 'flow',\n 'placeItems',\n 'placeContent',\n 'alignItems',\n 'alignContent',\n 'justifyItems',\n 'justifyContent',\n 'align',\n 'justify',\n 'gap',\n 'columnGap',\n 'rowGap',\n 'gridColumns',\n 'gridRows',\n 'gridTemplate',\n 'gridAreas',\n 'recipe',\n 'textOverflow',\n 'textDecoration',\n 'animation',\n 'cursor',\n 'pointerEvents',\n 'userSelect',\n 'transform',\n 'transformOrigin',\n 'filter',\n 'backdropFilter',\n 'mixBlendMode',\n 'objectFit',\n 'objectPosition',\n 'resize',\n 'appearance',\n 'listStyle',\n 'listStyleType',\n 'content',\n 'boxSizing',\n 'verticalAlign',\n 'wordBreak',\n 'overflowWrap',\n 'hyphens',\n 'tabSize',\n 'direction',\n 'unicodeBidi',\n 'writingMode',\n 'lineClamp',\n 'aspectRatio',\n 'contain',\n 'container',\n 'containerType',\n 'containerName',\n 'interpolateSize',\n 'willChange',\n 'isolation',\n 'touchAction',\n 'scrollBehavior',\n 'scrollSnapType',\n 'scrollSnapAlign',\n 'caretColor',\n 'accentColor',\n 'colorScheme',\n]);\n\n/**\n * Special top-level keys that are valid but not regular style properties.\n *\n * These match the real CSS at-rule names Tasty emits, so they are kebab-case.\n */\nexport const SPECIAL_STYLE_KEYS = new Set([\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n]);\n\n/**\n * At-rule style keys renamed in Tasty v3, mapped old spelling -> new.\n *\n * The v2 spellings were camelCase; v3 uses the CSS at-rule names Tasty already\n * emitted. Reported (and auto-fixed) so upgrading is mechanical.\n */\nexport const RENAMED_SPECIAL_STYLE_KEYS: Record<string, string> = {\n '@properties': '@property',\n '@fontFace': '@font-face',\n '@counterStyle': '@counter-style',\n};\n\n/**\n * CSS property names (common subset for validation).\n * When a key is camelCase and matches a known CSS property, it's valid.\n */\nexport const KNOWN_CSS_PROPERTIES = new Set([\n 'all',\n 'animation',\n 'animationDelay',\n 'animationDirection',\n 'animationDuration',\n 'animationFillMode',\n 'animationIterationCount',\n 'animationName',\n 'animationPlayState',\n 'animationTimingFunction',\n 'appearance',\n 'aspectRatio',\n 'backdropFilter',\n 'backfaceVisibility',\n 'background',\n 'backgroundAttachment',\n 'backgroundBlendMode',\n 'backgroundClip',\n 'backgroundColor',\n 'backgroundImage',\n 'backgroundOrigin',\n 'backgroundPosition',\n 'backgroundRepeat',\n 'backgroundSize',\n 'blockSize',\n 'border',\n 'borderBlock',\n 'borderBlockColor',\n 'borderBlockEnd',\n 'borderBlockEndColor',\n 'borderBlockEndStyle',\n 'borderBlockEndWidth',\n 'borderBlockStart',\n 'borderBlockStartColor',\n 'borderBlockStartStyle',\n 'borderBlockStartWidth',\n 'borderBlockStyle',\n 'borderBlockWidth',\n 'borderBottom',\n 'borderBottomColor',\n 'borderBottomLeftRadius',\n 'borderBottomRightRadius',\n 'borderBottomStyle',\n 'borderBottomWidth',\n 'borderCollapse',\n 'borderColor',\n 'borderImage',\n 'borderInline',\n 'borderInlineColor',\n 'borderInlineEnd',\n 'borderInlineStart',\n 'borderInlineStyle',\n 'borderInlineWidth',\n 'borderLeft',\n 'borderLeftColor',\n 'borderLeftStyle',\n 'borderLeftWidth',\n 'borderRadius',\n 'borderRight',\n 'borderRightColor',\n 'borderRightStyle',\n 'borderRightWidth',\n 'borderSpacing',\n 'borderStyle',\n 'borderTop',\n 'borderTopColor',\n 'borderTopLeftRadius',\n 'borderTopRightRadius',\n 'borderTopStyle',\n 'borderTopWidth',\n 'borderWidth',\n 'bottom',\n 'boxDecorationBreak',\n 'boxShadow',\n 'boxSizing',\n 'breakAfter',\n 'breakBefore',\n 'breakInside',\n 'captionSide',\n 'caretColor',\n 'clear',\n 'clip',\n 'clipPath',\n 'clipRule',\n 'color',\n 'colorScheme',\n 'columnCount',\n 'columnFill',\n 'columnGap',\n 'columnRule',\n 'columnRuleColor',\n 'columnRuleStyle',\n 'columnRuleWidth',\n 'columnSpan',\n 'columnWidth',\n 'columns',\n 'contain',\n 'containerName',\n 'containerType',\n 'content',\n 'contentVisibility',\n 'counterIncrement',\n 'counterReset',\n 'counterSet',\n 'cursor',\n 'direction',\n 'display',\n 'dominantBaseline',\n 'emptyCells',\n 'fillOpacity',\n 'fillRule',\n 'filter',\n 'flex',\n 'flexBasis',\n 'flexDirection',\n 'flexFlow',\n 'flexGrow',\n 'flexShrink',\n 'flexWrap',\n 'float',\n 'floodColor',\n 'floodOpacity',\n 'font',\n 'fontFamily',\n 'fontFeatureSettings',\n 'fontKerning',\n 'fontOpticalSizing',\n 'fontSize',\n 'fontSizeAdjust',\n 'fontStretch',\n 'fontStyle',\n 'fontSynthesis',\n 'fontVariant',\n 'fontVariantAlternates',\n 'fontVariantCaps',\n 'fontVariantEastAsian',\n 'fontVariantLigatures',\n 'fontVariantNumeric',\n 'fontVariantPosition',\n 'fontWeight',\n 'gap',\n 'grid',\n 'gridArea',\n 'gridAutoColumns',\n 'gridAutoFlow',\n 'gridAutoRows',\n 'gridColumn',\n 'gridColumnEnd',\n 'gridColumnStart',\n 'gridRow',\n 'gridRowEnd',\n 'gridRowStart',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'height',\n 'hyphens',\n 'imageRendering',\n 'inlineSize',\n 'inset',\n 'insetBlock',\n 'insetBlockEnd',\n 'insetBlockStart',\n 'insetInline',\n 'insetInlineEnd',\n 'insetInlineStart',\n 'isolation',\n 'justifyContent',\n 'justifyItems',\n 'justifySelf',\n 'left',\n 'letterSpacing',\n 'lightingColor',\n 'lineBreak',\n 'lineHeight',\n 'listStyle',\n 'listStyleImage',\n 'listStylePosition',\n 'listStyleType',\n 'marker',\n 'markerEnd',\n 'markerMid',\n 'markerStart',\n 'margin',\n 'marginBlock',\n 'marginBlockEnd',\n 'marginBlockStart',\n 'marginBottom',\n 'marginInline',\n 'marginInlineEnd',\n 'marginInlineStart',\n 'marginLeft',\n 'marginRight',\n 'marginTop',\n 'maskImage',\n 'maxBlockSize',\n 'maxHeight',\n 'maxInlineSize',\n 'maxWidth',\n 'minBlockSize',\n 'minHeight',\n 'minInlineSize',\n 'minWidth',\n 'mixBlendMode',\n 'objectFit',\n 'objectPosition',\n 'opacity',\n 'order',\n 'orphans',\n 'outline',\n 'outlineColor',\n 'outlineOffset',\n 'outlineStyle',\n 'outlineWidth',\n 'overflow',\n 'overflowAnchor',\n 'overflowWrap',\n 'overflowX',\n 'overflowY',\n 'overscrollBehavior',\n 'padding',\n 'paddingBlock',\n 'paddingBlockEnd',\n 'paddingBlockStart',\n 'paddingBottom',\n 'paddingInline',\n 'paddingInlineEnd',\n 'paddingInlineStart',\n 'paddingLeft',\n 'paddingRight',\n 'paddingTop',\n 'perspective',\n 'perspectiveOrigin',\n 'placeContent',\n 'placeItems',\n 'placeSelf',\n 'pointerEvents',\n 'position',\n 'quotes',\n 'resize',\n 'right',\n 'rotate',\n 'rowGap',\n 'scale',\n 'scrollBehavior',\n 'scrollMargin',\n 'scrollMarginBlock',\n 'scrollMarginBlockEnd',\n 'scrollMarginBlockStart',\n 'scrollMarginBottom',\n 'scrollMarginInline',\n 'scrollMarginInlineEnd',\n 'scrollMarginInlineStart',\n 'scrollMarginLeft',\n 'scrollMarginRight',\n 'scrollMarginTop',\n 'scrollPadding',\n 'scrollPaddingBlock',\n 'scrollPaddingBlockEnd',\n 'scrollPaddingBlockStart',\n 'scrollPaddingBottom',\n 'scrollPaddingInline',\n 'scrollPaddingInlineEnd',\n 'scrollPaddingInlineStart',\n 'scrollPaddingLeft',\n 'scrollPaddingRight',\n 'scrollPaddingTop',\n 'scrollSnapAlign',\n 'scrollSnapStop',\n 'scrollSnapType',\n 'scrollbarColor',\n 'scrollbarGutter',\n 'scrollbarWidth',\n 'shapeOutside',\n 'shapeRendering',\n 'stopColor',\n 'stopOpacity',\n 'stroke',\n 'strokeDasharray',\n 'strokeDashoffset',\n 'strokeLinecap',\n 'strokeLinejoin',\n 'strokeMiterlimit',\n 'strokeOpacity',\n 'strokeWidth',\n 'tabSize',\n 'tableLayout',\n 'textAlign',\n 'textAlignLast',\n 'textAnchor',\n 'textCombineUpright',\n 'textDecoration',\n 'textDecorationColor',\n 'textDecorationLine',\n 'textDecorationSkipInk',\n 'textDecorationStyle',\n 'textDecorationThickness',\n 'textEmphasis',\n 'textIndent',\n 'textOrientation',\n 'textOverflow',\n 'textRendering',\n 'textShadow',\n 'textSizeAdjust',\n 'textTransform',\n 'textUnderlineOffset',\n 'textUnderlinePosition',\n 'textWrap',\n 'top',\n 'touchAction',\n 'transform',\n 'transformOrigin',\n 'transformStyle',\n 'transition',\n 'transitionDelay',\n 'transitionDuration',\n 'transitionProperty',\n 'transitionTimingFunction',\n 'translate',\n 'unicodeBidi',\n 'userSelect',\n 'verticalAlign',\n 'visibility',\n 'whiteSpace',\n 'widows',\n 'width',\n 'willChange',\n 'wordBreak',\n 'wordSpacing',\n 'writingMode',\n 'zIndex',\n]);\n\n/**\n * Built-in custom units recognized by the tasty parser.\n */\nexport const BUILT_IN_UNITS = new Set(['x', 'r', 'cr', 'bw', 'ow', 'lh', 'sf']);\n\n/**\n * Standard CSS units (always valid).\n */\nexport const CSS_UNITS = new Set([\n 'px',\n 'em',\n 'rem',\n '%',\n 'vw',\n 'vh',\n 'vmin',\n 'vmax',\n 'ch',\n 'ex',\n 'cm',\n 'mm',\n 'in',\n 'pt',\n 'pc',\n 'fr',\n 'deg',\n 'rad',\n 'turn',\n 'grad',\n 's',\n 'ms',\n 'dpi',\n 'dpcm',\n 'dppx',\n 'svw',\n 'svh',\n 'lvw',\n 'lvh',\n 'dvw',\n 'dvh',\n 'cqw',\n 'cqh',\n 'cqi',\n 'cqb',\n 'cqmin',\n 'cqmax',\n 'cap',\n 'ic',\n 'rlh',\n 'vi',\n 'vb',\n]);\n\n/**\n * Properties that accept `true` as a value (means \"use default\").\n */\nexport const BOOLEAN_TRUE_PROPERTIES = new Set([\n 'border',\n 'radius',\n 'padding',\n 'margin',\n 'gap',\n 'fill',\n 'color',\n 'shadow',\n 'outline',\n 'inset',\n 'width',\n 'height',\n // `fade: true` falls back to `calc(2 * var(--gap))` — a token-following\n // default no explicit value can express, so it is genuinely useful.\n 'fade',\n 'hide',\n 'preset',\n 'font',\n 'scrollbar',\n]);\n\n/**\n * Directional modifiers and which properties accept them.\n */\nexport const DIRECTIONAL_MODIFIERS: Record<string, Set<string>> = {\n border: new Set(['top', 'right', 'bottom', 'left']),\n radius: new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n ]),\n padding: new Set(['top', 'right', 'bottom', 'left']),\n margin: new Set(['top', 'right', 'bottom', 'left']),\n fade: new Set(['top', 'right', 'bottom', 'left']),\n scrollMargin: new Set(['top', 'right', 'bottom', 'left']),\n inset: new Set(['top', 'right', 'bottom', 'left', 'dock']),\n};\n\n/**\n * Properties where a group naming directions carries exactly **one** value.\n *\n * Tasty v3 dropped the positional multi-value form for these: values and\n * modifiers are bucketed separately per comma group, so `'2x 4x top right'` and\n * `'2x top 4x right'` were the same input and the pairing depended on modifier\n * order. Per-side values now come from comma groups.\n *\n * Deliberately excludes `border` and `radius`. A `border` group legitimately\n * carries width + style + color, and a `radius` group can carry two values with\n * `leaf`/`backleaf`; neither takes the positional-per-side form, so neither is\n * subject to the rule.\n */\nexport const SINGLE_VALUE_DIRECTIONAL_PROPERTIES = new Set([\n 'padding',\n 'margin',\n 'inset',\n 'scrollMargin',\n 'fade',\n]);\n\n/**\n * Position names a 4-value box maps onto, in source order.\n */\nexport const BOX_SIDES = ['top', 'right', 'bottom', 'left'] as const;\nexport const BOX_CORNERS = [\n 'top-left',\n 'top-right',\n 'bottom-right',\n 'bottom-left',\n] as const;\n\nexport interface DirectionalBoxConfig {\n /**\n * The token that marks an unset position — dropping it from the shorthand\n * leaves the emitted CSS unchanged. This is the CSS initial value of the\n * longhand, which is not always `0`: `inset` initialises to `auto`, so a `0`\n * there is a real offset the shorthand must not drop.\n */\n identity: string;\n /** Modifier names for each of the four positions, in source order. */\n positions: readonly string[];\n /**\n * Whether `eslint --fix` may apply the rewrite. False when the rewrite is\n * still an improvement but changes what the browser renders, so a human has\n * to opt in.\n */\n fixable: boolean;\n /**\n * Set when the 4-value form is not interpreted per-position at all, so the\n * current output is wrong rather than merely verbose. Surfaced in the report.\n */\n note?: string;\n}\n\n/**\n * Properties whose 4-value box syntax has a directional-shorthand equivalent.\n *\n * `radius` uses corner names rather than sides: a directional modifier\n * addresses the corner *pair* along an edge, so only `top-left` & co. can\n * express a single corner (requires `@tenphi/tasty` >= 2.10).\n */\nexport const DIRECTIONAL_BOX: Record<string, DirectionalBoxConfig> = {\n margin: { identity: '0', positions: BOX_SIDES, fixable: true },\n padding: { identity: '0', positions: BOX_SIDES, fixable: true },\n inset: { identity: 'auto', positions: BOX_SIDES, fixable: true },\n radius: { identity: '0', positions: BOX_CORNERS, fixable: true },\n fade: { identity: '0', positions: BOX_SIDES, fixable: true },\n // Four `border` tokens parse as a single border value (width/style/color),\n // not as a box: `border: '0 0 1bw 0'` emits `border: 0 solid …` — no border\n // on any side. The directional form is what the author meant, but applying\n // it changes rendering, so it stays a manual fix.\n border: {\n identity: '0',\n positions: BOX_SIDES,\n fixable: false,\n note: 'four border tokens parse as one border value, so the current style sets no border at all',\n },\n};\n\n/**\n * Properties that support a `dock` modifier — pin one edge and span its full\n * length. Lets a 4-value box with a single `identity` side collapse, e.g.\n * `inset: 'auto 0 0 0'` -> `inset: '0 bottom dock'`.\n * Requires `@tenphi/tasty` >= 2.10.\n */\nexport const DOCK_PROPERTIES = new Set(['inset']);\n\n/**\n * Valid radius shape keywords.\n */\nexport const RADIUS_SHAPES = new Set(['round', 'ellipse', 'leaf', 'backleaf']);\n\n/**\n * Known semantic transition names.\n */\nexport const SEMANTIC_TRANSITIONS = new Set([\n 'fade',\n 'fill',\n 'border',\n 'radius',\n 'shadow',\n 'preset',\n 'gap',\n 'theme',\n 'color',\n 'outline',\n 'dimension',\n 'flow',\n 'inset',\n 'text',\n 'opacity',\n 'translate',\n 'rotate',\n 'scale',\n 'filter',\n 'image',\n 'background',\n 'width',\n 'height',\n 'zIndex',\n]);\n\n/**\n * Mapping of native CSS properties to tasty shorthand alternatives.\n */\nexport const SHORTHAND_MAPPING: Record<\n string,\n { property: string; hint: string; safeFix?: boolean }\n> = {\n backgroundColor: { property: 'fill', hint: \"fill: '...'\", safeFix: true },\n borderColor: { property: 'border', hint: \"border: '...'\" },\n borderWidth: { property: 'border', hint: \"border: '...'\" },\n borderStyle: { property: 'border', hint: \"border: '...'\" },\n borderTop: { property: 'border', hint: \"border: '... top'\" },\n borderRight: { property: 'border', hint: \"border: '... right'\" },\n borderBottom: { property: 'border', hint: \"border: '... bottom'\" },\n borderLeft: { property: 'border', hint: \"border: '... left'\" },\n borderRadius: { property: 'radius', hint: \"radius: '...'\", safeFix: true },\n maxWidth: { property: 'width', hint: \"width: 'max ...'\" },\n minWidth: { property: 'width', hint: \"width: 'min ...'\" },\n maxHeight: { property: 'height', hint: \"height: 'max ...'\" },\n minHeight: { property: 'height', hint: \"height: 'min ...'\" },\n flexDirection: { property: 'flow', hint: \"flow: '...'\" },\n flexWrap: { property: 'flow', hint: \"flow: '...'\" },\n flexFlow: { property: 'flow', hint: \"flow: '...'\" },\n gridAutoFlow: { property: 'flow', hint: \"flow: '...'\" },\n outlineOffset: { property: 'outline', hint: \"outline: '... / offset'\" },\n paddingTop: { property: 'padding', hint: \"padding: '... top'\" },\n paddingRight: { property: 'padding', hint: \"padding: '... right'\" },\n paddingBottom: { property: 'padding', hint: \"padding: '... bottom'\" },\n paddingLeft: { property: 'padding', hint: \"padding: '... left'\" },\n marginTop: { property: 'margin', hint: \"margin: '... top'\" },\n marginRight: { property: 'margin', hint: \"margin: '... right'\" },\n marginBottom: { property: 'margin', hint: \"margin: '... bottom'\" },\n marginLeft: { property: 'margin', hint: \"margin: '... left'\" },\n fontSize: { property: 'preset', hint: \"preset: '...'\" },\n fontWeight: {\n property: 'preset',\n hint: \"preset: 'name / strong' (with strong modifier)\",\n },\n lineHeight: {\n property: 'preset',\n hint: \"preset: 'name / tight' (with tight modifier)\",\n },\n boxShadow: { property: 'shadow', hint: \"shadow: '...'\" },\n backgroundImage: { property: 'image', hint: \"image: '...'\" },\n fontFamily: { property: 'font', hint: \"font: '...'\" },\n fontStyle: {\n property: 'preset',\n hint: \"preset: 'name / italic' (with italic modifier)\",\n },\n letterSpacing: { property: 'preset', hint: \"preset: '...'\" },\n textTransform: { property: 'preset', hint: \"preset: '...'\" },\n flexGap: { property: 'gap', hint: \"gap: '...'\" },\n scrollbarWidth: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n scrollbarColor: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n scrollbarGutter: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n gridTemplateColumns: {\n property: 'gridColumns',\n hint: \"gridColumns: '...'\",\n },\n gridTemplateRows: { property: 'gridRows', hint: \"gridRows: '...'\" },\n gridTemplateAreas: { property: 'gridAreas', hint: \"gridAreas: '...'\" },\n top: { property: 'inset', hint: \"inset: '... top'\" },\n right: { property: 'inset', hint: \"inset: '... right'\" },\n bottom: { property: 'inset', hint: \"inset: '... bottom'\" },\n left: { property: 'inset', hint: \"inset: '... left'\" },\n outlineColor: { property: 'outline', hint: \"outline: '...'\" },\n outlineWidth: { property: 'outline', hint: \"outline: '...'\" },\n outlineStyle: { property: 'outline', hint: \"outline: '...'\" },\n lineClamp: {\n property: 'textOverflow',\n hint: \"textOverflow: 'ellipsis / {lines}'\",\n },\n};\n\n/**\n * CSS shorthands whose *longhands* are the preferred Tasty form — the reverse\n * of `SHORTHAND_MAPPING`.\n *\n * `flex` is lossy in a way the longhands are not: it resets the two components\n * you did not write to values that are not their CSS initial. `flex: '0'`\n * expands to `flex-grow: 0; flex-shrink: 1; flex-basis: 0%`, so it cannot\n * express \"do not shrink\" (`flexShrink: 0`) at all, and it silently changes\n * `flex-basis`. Writing the longhands keeps each component independent, which\n * also lets them carry separate state maps.\n */\nexport const LONGHAND_MAPPING: Record<\n string,\n { longhands: string[]; hint: string }\n> = {\n flex: {\n longhands: ['flexGrow', 'flexShrink', 'flexBasis'],\n hint: 'flexGrow / flexShrink / flexBasis',\n },\n};\n\n/**\n * CSS transition property names mapped to Tasty semantic transition names.\n */\nexport const TRANSITION_SEMANTIC_MAPPING: Record<string, string> = {\n backgroundColor: 'fill',\n 'background-color': 'fill',\n backgroundImage: 'image',\n 'background-image': 'image',\n borderRadius: 'radius',\n 'border-radius': 'radius',\n borderColor: 'border',\n 'border-color': 'border',\n borderWidth: 'border',\n 'border-width': 'border',\n borderStyle: 'border',\n 'border-style': 'border',\n boxShadow: 'shadow',\n 'box-shadow': 'shadow',\n outlineColor: 'outline',\n 'outline-color': 'outline',\n outlineWidth: 'outline',\n 'outline-width': 'outline',\n outlineOffset: 'outline',\n 'outline-offset': 'outline',\n gridTemplateColumns: 'gridColumns',\n 'grid-template-columns': 'gridColumns',\n gridTemplateRows: 'gridRows',\n 'grid-template-rows': 'gridRows',\n flexDirection: 'flow',\n 'flex-direction': 'flow',\n gridAutoFlow: 'flow',\n 'grid-auto-flow': 'flow',\n fontSize: 'preset',\n 'font-size': 'preset',\n lineHeight: 'preset',\n 'line-height': 'preset',\n letterSpacing: 'preset',\n 'letter-spacing': 'preset',\n};\n\n/**\n * Known preset modifiers.\n */\nexport const PRESET_MODIFIERS = new Set([\n 'strong',\n 'bold',\n 'italic',\n 'icon',\n 'tight',\n]);\n\n/**\n * Default import sources for tasty.\n */\nexport const DEFAULT_IMPORT_SOURCES = ['@tenphi/tasty', '@tenphi/tasty/static'];\n\n/**\n * Built-in state prefixes that are always valid (not aliases).\n */\nexport const BUILT_IN_STATE_PREFIXES = new Set([\n '@media',\n '@root',\n '@parent',\n '@own',\n '@supports',\n '@starting',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n]);\n\n/**\n * Known CSS pseudo-classes.\n */\nexport const KNOWN_PSEUDO_CLASSES = new Set([\n ':hover',\n ':focus',\n ':focus-visible',\n ':focus-within',\n ':active',\n ':visited',\n ':link',\n ':checked',\n ':disabled',\n ':enabled',\n ':empty',\n ':first-child',\n ':last-child',\n ':first-of-type',\n ':last-of-type',\n ':only-child',\n ':only-of-type',\n ':root',\n ':target',\n ':valid',\n ':invalid',\n ':required',\n ':optional',\n ':read-only',\n ':read-write',\n ':placeholder-shown',\n ':autofill',\n ':default',\n ':indeterminate',\n ':in-range',\n ':out-of-range',\n ':any-link',\n ':local-link',\n ':is',\n ':not',\n ':where',\n ':has',\n ':nth-child',\n ':nth-last-child',\n ':nth-of-type',\n ':nth-last-of-type',\n '::before',\n '::after',\n '::placeholder',\n '::selection',\n '::first-line',\n '::first-letter',\n '::marker',\n '::backdrop',\n]);\n\n/**\n * CSS named colors (level 1–4) that should be replaced by `#color` tokens.\n * `transparent` and `currentcolor` are excluded — they have their own token\n * suggestions (`#clear`, `#current`) handled by `prefer-custom-property-syntax`.\n */\nexport const NAMED_CSS_COLORS = new Set<string>([\n 'aliceblue',\n 'antiquewhite',\n 'aqua',\n 'aquamarine',\n 'azure',\n 'beige',\n 'bisque',\n 'black',\n 'blanchedalmond',\n 'blue',\n 'blueviolet',\n 'brown',\n 'burlywood',\n 'cadetblue',\n 'chartreuse',\n 'chocolate',\n 'coral',\n 'cornflowerblue',\n 'cornsilk',\n 'crimson',\n 'cyan',\n 'darkblue',\n 'darkcyan',\n 'darkgoldenrod',\n 'darkgray',\n 'darkgreen',\n 'darkgrey',\n 'darkkhaki',\n 'darkmagenta',\n 'darkolivegreen',\n 'darkorange',\n 'darkorchid',\n 'darkred',\n 'darksalmon',\n 'darkseagreen',\n 'darkslateblue',\n 'darkslategray',\n 'darkslategrey',\n 'darkturquoise',\n 'darkviolet',\n 'deeppink',\n 'deepskyblue',\n 'dimgray',\n 'dimgrey',\n 'dodgerblue',\n 'firebrick',\n 'floralwhite',\n 'forestgreen',\n 'fuchsia',\n 'gainsboro',\n 'ghostwhite',\n 'gold',\n 'goldenrod',\n 'gray',\n 'green',\n 'greenyellow',\n 'grey',\n 'honeydew',\n 'hotpink',\n 'indianred',\n 'indigo',\n 'ivory',\n 'khaki',\n 'lavender',\n 'lavenderblush',\n 'lawngreen',\n 'lemonchiffon',\n 'lightblue',\n 'lightcoral',\n 'lightcyan',\n 'lightgoldenrodyellow',\n 'lightgray',\n 'lightgreen',\n 'lightgrey',\n 'lightpink',\n 'lightsalmon',\n 'lightseagreen',\n 'lightskyblue',\n 'lightslategray',\n 'lightslategrey',\n 'lightsteelblue',\n 'lightyellow',\n 'lime',\n 'limegreen',\n 'linen',\n 'magenta',\n 'maroon',\n 'mediumaquamarine',\n 'mediumblue',\n 'mediumorchid',\n 'mediumpurple',\n 'mediumseagreen',\n 'mediumslateblue',\n 'mediumspringgreen',\n 'mediumturquoise',\n 'mediumvioletred',\n 'midnightblue',\n 'mintcream',\n 'mistyrose',\n 'moccasin',\n 'navajowhite',\n 'navy',\n 'oldlace',\n 'olive',\n 'olivedrab',\n 'orange',\n 'orangered',\n 'orchid',\n 'palegoldenrod',\n 'palegreen',\n 'paleturquoise',\n 'palevioletred',\n 'papayawhip',\n 'peachpuff',\n 'peru',\n 'pink',\n 'plum',\n 'powderblue',\n 'purple',\n 'rebeccapurple',\n 'red',\n 'rosybrown',\n 'royalblue',\n 'saddlebrown',\n 'salmon',\n 'sandybrown',\n 'seagreen',\n 'seashell',\n 'sienna',\n 'silver',\n 'skyblue',\n 'slateblue',\n 'slategray',\n 'slategrey',\n 'snow',\n 'springgreen',\n 'steelblue',\n 'tan',\n 'teal',\n 'thistle',\n 'tomato',\n 'turquoise',\n 'violet',\n 'wheat',\n 'white',\n 'whitesmoke',\n 'yellow',\n 'yellowgreen',\n]);\n\n/**\n * Properties that accept color values. Used to scope named-color checks so\n * string properties like `content` or `cursor` don't false-positive.\n */\nexport const COLOR_BEARING_PROPERTIES = new Set<string>([\n // Tasty enhanced properties\n 'fill',\n 'color',\n 'caretColor',\n 'accentColor',\n 'shadow',\n 'border',\n 'outline',\n 'fade',\n // CSS background\n 'background',\n 'backgroundColor',\n // CSS border colors\n 'borderColor',\n 'borderTopColor',\n 'borderRightColor',\n 'borderBottomColor',\n 'borderLeftColor',\n // CSS shadows\n 'boxShadow',\n 'textShadow',\n // CSS text decoration\n 'textDecorationColor',\n // CSS outline / misc\n 'outlineColor',\n 'scrollbarColor',\n 'columnRuleColor',\n // SVG\n 'stroke',\n 'stopColor',\n 'floodColor',\n 'lightingColor',\n]);\n"],"mappings":";;;;AAGA,MAAa,yBAAyB,IAAI,IAAI;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,MAAa,qBAAqB,IAAI,IAAI;CACxC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,MAAa,6BAAqD;CAChE,eAAe;CACf,aAAa;CACb,iBAAiB;CAClB;;;;;AAMD,MAAa,uBAAuB,IAAI,IAAI;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,iBAAiB,I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CACjB,oBAAoB;CACpB,iBAAiB;CACjB,oBAAoB;CACpB,cAAc;CACd,iBAAiB;CACjB,aAAa;CACb,gBAAgB;CAChB,aAAa;CACb,gBAAgB;CAChB,aAAa;CACb,gBAAgB;CAChB,WAAW;CACX,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,cAAc;CACd,iBAAiB;CACjB,eAAe;CACf,kBAAkB;CAClB,qBAAqB;CACrB,yBAAyB;CACzB,kBAAkB;CAClB,sBAAsB;CACtB,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,kBAAkB;CAClB,UAAU;CACV,aAAa;CACb,YAAY;CACZ,eAAe;CACf,eAAe;CACf,kBAAkB;CACnB;;;;AAKD,MAAa,mBAAmB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,yBAAyB,CAAC,iBAAiB,uBAAuB;;;;AAK/E,MAAa,0BAA0B,IAAI,IAAI;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,uBAAuB,IAAI,IAAI;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,MAAa,mBAAmB,IAAI,IAAY;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;AAMF,MAAa,2BAA2B,IAAI,IAAY;CAEtD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CAEA;CACA;CACA;CAEA;CACA;CACA;CACA;CACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Built-in tasty style properties that are always valid as style keys.\n */\nexport const KNOWN_TASTY_PROPERTIES = new Set([\n 'display',\n 'font',\n 'preset',\n 'hide',\n 'whiteSpace',\n 'opacity',\n 'transition',\n 'gridArea',\n 'order',\n 'gridColumn',\n 'gridRow',\n 'placeSelf',\n 'alignSelf',\n 'justifySelf',\n 'zIndex',\n 'margin',\n 'inset',\n 'position',\n 'padding',\n 'paddingInline',\n 'paddingBlock',\n 'overflow',\n 'scrollbar',\n 'textAlign',\n 'border',\n 'radius',\n 'shadow',\n 'outline',\n 'color',\n 'fill',\n 'fade',\n 'image',\n 'textTransform',\n 'fontWeight',\n 'fontStyle',\n 'width',\n 'height',\n 'flexBasis',\n 'flexGrow',\n 'flexShrink',\n 'flex',\n 'flow',\n 'placeItems',\n 'placeContent',\n 'alignItems',\n 'alignContent',\n 'justifyItems',\n 'justifyContent',\n 'align',\n 'justify',\n 'gap',\n 'columnGap',\n 'rowGap',\n 'gridColumns',\n 'gridRows',\n 'gridTemplate',\n 'gridAreas',\n 'recipe',\n 'textOverflow',\n 'textDecoration',\n 'animation',\n 'cursor',\n 'pointerEvents',\n 'userSelect',\n 'transform',\n 'transformOrigin',\n 'filter',\n 'backdropFilter',\n 'mixBlendMode',\n 'objectFit',\n 'objectPosition',\n 'resize',\n 'appearance',\n 'listStyle',\n 'listStyleType',\n 'content',\n 'boxSizing',\n 'verticalAlign',\n 'wordBreak',\n 'overflowWrap',\n 'hyphens',\n 'tabSize',\n 'direction',\n 'unicodeBidi',\n 'writingMode',\n 'lineClamp',\n 'aspectRatio',\n 'contain',\n 'container',\n 'containerType',\n 'containerName',\n 'interpolateSize',\n 'willChange',\n 'isolation',\n 'touchAction',\n 'scrollBehavior',\n 'scrollSnapType',\n 'scrollSnapAlign',\n 'caretColor',\n 'accentColor',\n 'colorScheme',\n]);\n\n/**\n * Special top-level keys that are valid but not regular style properties.\n *\n * These match the real CSS at-rule names Tasty emits, so they are kebab-case.\n */\nexport const SPECIAL_STYLE_KEYS = new Set([\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n]);\n\n/**\n * At-rule style keys renamed in Tasty v3, mapped old spelling -> new.\n *\n * The v2 spellings were camelCase; v3 uses the CSS at-rule names Tasty already\n * emitted. Reported (and auto-fixed) so upgrading is mechanical.\n */\nexport const RENAMED_SPECIAL_STYLE_KEYS: Record<string, string> = {\n '@properties': '@property',\n '@fontFace': '@font-face',\n '@counterStyle': '@counter-style',\n};\n\n/**\n * CSS property names (common subset for validation).\n * When a key is camelCase and matches a known CSS property, it's valid.\n */\nexport const KNOWN_CSS_PROPERTIES = new Set([\n 'all',\n 'animation',\n 'animationDelay',\n 'animationDirection',\n 'animationDuration',\n 'animationFillMode',\n 'animationIterationCount',\n 'animationName',\n 'animationPlayState',\n 'animationTimingFunction',\n 'appearance',\n 'aspectRatio',\n 'backdropFilter',\n 'backfaceVisibility',\n 'background',\n 'backgroundAttachment',\n 'backgroundBlendMode',\n 'backgroundClip',\n 'backgroundColor',\n 'backgroundImage',\n 'backgroundOrigin',\n 'backgroundPosition',\n 'backgroundRepeat',\n 'backgroundSize',\n 'blockSize',\n 'border',\n 'borderBlock',\n 'borderBlockColor',\n 'borderBlockEnd',\n 'borderBlockEndColor',\n 'borderBlockEndStyle',\n 'borderBlockEndWidth',\n 'borderBlockStart',\n 'borderBlockStartColor',\n 'borderBlockStartStyle',\n 'borderBlockStartWidth',\n 'borderBlockStyle',\n 'borderBlockWidth',\n 'borderBottom',\n 'borderBottomColor',\n 'borderBottomLeftRadius',\n 'borderBottomRightRadius',\n 'borderBottomStyle',\n 'borderBottomWidth',\n 'borderCollapse',\n 'borderColor',\n 'borderImage',\n 'borderInline',\n 'borderInlineColor',\n 'borderInlineEnd',\n 'borderInlineStart',\n 'borderInlineStyle',\n 'borderInlineWidth',\n 'borderLeft',\n 'borderLeftColor',\n 'borderLeftStyle',\n 'borderLeftWidth',\n 'borderRadius',\n 'borderRight',\n 'borderRightColor',\n 'borderRightStyle',\n 'borderRightWidth',\n 'borderSpacing',\n 'borderStyle',\n 'borderTop',\n 'borderTopColor',\n 'borderTopLeftRadius',\n 'borderTopRightRadius',\n 'borderTopStyle',\n 'borderTopWidth',\n 'borderWidth',\n 'bottom',\n 'boxDecorationBreak',\n 'boxShadow',\n 'boxSizing',\n 'breakAfter',\n 'breakBefore',\n 'breakInside',\n 'captionSide',\n 'caretColor',\n 'clear',\n 'clip',\n 'clipPath',\n 'clipRule',\n 'color',\n 'colorScheme',\n 'columnCount',\n 'columnFill',\n 'columnGap',\n 'columnRule',\n 'columnRuleColor',\n 'columnRuleStyle',\n 'columnRuleWidth',\n 'columnSpan',\n 'columnWidth',\n 'columns',\n 'contain',\n 'containerName',\n 'containerType',\n 'content',\n 'contentVisibility',\n 'counterIncrement',\n 'counterReset',\n 'counterSet',\n 'cursor',\n 'direction',\n 'display',\n 'dominantBaseline',\n 'emptyCells',\n 'fillOpacity',\n 'fillRule',\n 'filter',\n 'flex',\n 'flexBasis',\n 'flexDirection',\n 'flexFlow',\n 'flexGrow',\n 'flexShrink',\n 'flexWrap',\n 'float',\n 'floodColor',\n 'floodOpacity',\n 'font',\n 'fontFamily',\n 'fontFeatureSettings',\n 'fontKerning',\n 'fontOpticalSizing',\n 'fontSize',\n 'fontSizeAdjust',\n 'fontStretch',\n 'fontStyle',\n 'fontSynthesis',\n 'fontVariant',\n 'fontVariantAlternates',\n 'fontVariantCaps',\n 'fontVariantEastAsian',\n 'fontVariantLigatures',\n 'fontVariantNumeric',\n 'fontVariantPosition',\n 'fontWeight',\n 'gap',\n 'grid',\n 'gridArea',\n 'gridAutoColumns',\n 'gridAutoFlow',\n 'gridAutoRows',\n 'gridColumn',\n 'gridColumnEnd',\n 'gridColumnStart',\n 'gridRow',\n 'gridRowEnd',\n 'gridRowStart',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'height',\n 'hyphens',\n 'imageRendering',\n 'inlineSize',\n 'inset',\n 'insetBlock',\n 'insetBlockEnd',\n 'insetBlockStart',\n 'insetInline',\n 'insetInlineEnd',\n 'insetInlineStart',\n 'isolation',\n 'justifyContent',\n 'justifyItems',\n 'justifySelf',\n 'left',\n 'letterSpacing',\n 'lightingColor',\n 'lineBreak',\n 'lineHeight',\n 'listStyle',\n 'listStyleImage',\n 'listStylePosition',\n 'listStyleType',\n 'marker',\n 'markerEnd',\n 'markerMid',\n 'markerStart',\n 'margin',\n 'marginBlock',\n 'marginBlockEnd',\n 'marginBlockStart',\n 'marginBottom',\n 'marginInline',\n 'marginInlineEnd',\n 'marginInlineStart',\n 'marginLeft',\n 'marginRight',\n 'marginTop',\n 'maskImage',\n 'maxBlockSize',\n 'maxHeight',\n 'maxInlineSize',\n 'maxWidth',\n 'minBlockSize',\n 'minHeight',\n 'minInlineSize',\n 'minWidth',\n 'mixBlendMode',\n 'objectFit',\n 'objectPosition',\n 'opacity',\n 'order',\n 'orphans',\n 'outline',\n 'outlineColor',\n 'outlineOffset',\n 'outlineStyle',\n 'outlineWidth',\n 'overflow',\n 'overflowAnchor',\n 'overflowWrap',\n 'overflowX',\n 'overflowY',\n 'overscrollBehavior',\n 'padding',\n 'paddingBlock',\n 'paddingBlockEnd',\n 'paddingBlockStart',\n 'paddingBottom',\n 'paddingInline',\n 'paddingInlineEnd',\n 'paddingInlineStart',\n 'paddingLeft',\n 'paddingRight',\n 'paddingTop',\n 'perspective',\n 'perspectiveOrigin',\n 'placeContent',\n 'placeItems',\n 'placeSelf',\n 'pointerEvents',\n 'position',\n 'quotes',\n 'resize',\n 'right',\n 'rotate',\n 'rowGap',\n 'scale',\n 'scrollBehavior',\n 'scrollMargin',\n 'scrollMarginBlock',\n 'scrollMarginBlockEnd',\n 'scrollMarginBlockStart',\n 'scrollMarginBottom',\n 'scrollMarginInline',\n 'scrollMarginInlineEnd',\n 'scrollMarginInlineStart',\n 'scrollMarginLeft',\n 'scrollMarginRight',\n 'scrollMarginTop',\n 'scrollPadding',\n 'scrollPaddingBlock',\n 'scrollPaddingBlockEnd',\n 'scrollPaddingBlockStart',\n 'scrollPaddingBottom',\n 'scrollPaddingInline',\n 'scrollPaddingInlineEnd',\n 'scrollPaddingInlineStart',\n 'scrollPaddingLeft',\n 'scrollPaddingRight',\n 'scrollPaddingTop',\n 'scrollSnapAlign',\n 'scrollSnapStop',\n 'scrollSnapType',\n 'scrollbarColor',\n 'scrollbarGutter',\n 'scrollbarWidth',\n 'shapeOutside',\n 'shapeRendering',\n 'stopColor',\n 'stopOpacity',\n 'stroke',\n 'strokeDasharray',\n 'strokeDashoffset',\n 'strokeLinecap',\n 'strokeLinejoin',\n 'strokeMiterlimit',\n 'strokeOpacity',\n 'strokeWidth',\n 'tabSize',\n 'tableLayout',\n 'textAlign',\n 'textAlignLast',\n 'textAnchor',\n 'textCombineUpright',\n 'textDecoration',\n 'textDecorationColor',\n 'textDecorationLine',\n 'textDecorationSkipInk',\n 'textDecorationStyle',\n 'textDecorationThickness',\n 'textEmphasis',\n 'textIndent',\n 'textOrientation',\n 'textOverflow',\n 'textRendering',\n 'textShadow',\n 'textSizeAdjust',\n 'textTransform',\n 'textUnderlineOffset',\n 'textUnderlinePosition',\n 'textWrap',\n 'top',\n 'touchAction',\n 'transform',\n 'transformOrigin',\n 'transformStyle',\n 'transition',\n 'transitionDelay',\n 'transitionDuration',\n 'transitionProperty',\n 'transitionTimingFunction',\n 'translate',\n 'unicodeBidi',\n 'userSelect',\n 'verticalAlign',\n 'visibility',\n 'whiteSpace',\n 'widows',\n 'width',\n 'willChange',\n 'wordBreak',\n 'wordSpacing',\n 'writingMode',\n 'zIndex',\n]);\n\n/**\n * Built-in custom units recognized by the tasty parser.\n */\nexport const BUILT_IN_UNITS = new Set(['x', 'r', 'cr', 'bw', 'ow', 'lh', 'sf']);\n\n/**\n * Standard CSS units (always valid).\n */\nexport const CSS_UNITS = new Set([\n 'px',\n 'em',\n 'rem',\n '%',\n 'vw',\n 'vh',\n 'vmin',\n 'vmax',\n 'ch',\n 'ex',\n 'cm',\n 'mm',\n 'in',\n 'pt',\n 'pc',\n 'fr',\n 'deg',\n 'rad',\n 'turn',\n 'grad',\n 's',\n 'ms',\n 'dpi',\n 'dpcm',\n 'dppx',\n 'svw',\n 'svh',\n 'lvw',\n 'lvh',\n 'dvw',\n 'dvh',\n 'cqw',\n 'cqh',\n 'cqi',\n 'cqb',\n 'cqmin',\n 'cqmax',\n 'cap',\n 'ic',\n 'rlh',\n 'vi',\n 'vb',\n]);\n\n/**\n * Properties that accept `true` as a value (means \"use default\").\n */\nexport const BOOLEAN_TRUE_PROPERTIES = new Set([\n 'border',\n 'radius',\n 'padding',\n 'margin',\n 'gap',\n 'fill',\n 'color',\n 'shadow',\n 'outline',\n 'inset',\n 'width',\n 'height',\n // `fade: true` falls back to `calc(2 * var(--gap))` — a token-following\n // default no explicit value can express, so it is genuinely useful.\n 'fade',\n 'hide',\n 'preset',\n 'font',\n 'scrollbar',\n]);\n\n/**\n * Directional modifiers and which properties accept them.\n */\nexport const DIRECTIONAL_MODIFIERS: Record<string, Set<string>> = {\n border: new Set(['top', 'right', 'bottom', 'left']),\n radius: new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n ]),\n padding: new Set(['top', 'right', 'bottom', 'left']),\n margin: new Set(['top', 'right', 'bottom', 'left']),\n fade: new Set(['top', 'right', 'bottom', 'left']),\n scrollMargin: new Set(['top', 'right', 'bottom', 'left']),\n inset: new Set(['top', 'right', 'bottom', 'left', 'dock']),\n};\n\n/**\n * Properties where a group naming directions carries exactly **one** value.\n *\n * Tasty v3 dropped the positional multi-value form for these: values and\n * modifiers are bucketed separately per comma group, so `'2x 4x top right'` and\n * `'2x top 4x right'` were the same input and the pairing depended on modifier\n * order. Per-side values now come from comma groups.\n *\n * Deliberately excludes `border` and `radius`. A `border` group legitimately\n * carries width + style + color, and a `radius` group can carry two values with\n * `leaf`/`backleaf`; neither takes the positional-per-side form, so neither is\n * subject to the rule.\n */\nexport const SINGLE_VALUE_DIRECTIONAL_PROPERTIES = new Set([\n 'padding',\n 'margin',\n 'inset',\n 'scrollMargin',\n 'fade',\n]);\n\n/**\n * Position names a 4-value box maps onto, in source order.\n */\nexport const BOX_SIDES = ['top', 'right', 'bottom', 'left'] as const;\nexport const BOX_CORNERS = [\n 'top-left',\n 'top-right',\n 'bottom-right',\n 'bottom-left',\n] as const;\n\nexport interface DirectionalBoxConfig {\n /**\n * The token that marks an unset position — dropping it from the shorthand\n * leaves the emitted CSS unchanged. This is the CSS initial value of the\n * longhand, which is not always `0`: `inset` initialises to `auto`, so a `0`\n * there is a real offset the shorthand must not drop.\n */\n identity: string;\n /** Modifier names for each of the four positions, in source order. */\n positions: readonly string[];\n /**\n * Whether `eslint --fix` may apply the rewrite. False when the rewrite is\n * still an improvement but changes what the browser renders, so a human has\n * to opt in.\n */\n fixable: boolean;\n /**\n * Set when the 4-value form is not interpreted per-position at all, so the\n * current output is wrong rather than merely verbose. Surfaced in the report.\n */\n note?: string;\n}\n\n/**\n * Properties whose 4-value box syntax has a directional-shorthand equivalent.\n *\n * `radius` uses corner names rather than sides: a directional modifier\n * addresses the corner *pair* along an edge, so only `top-left` & co. can\n * express a single corner (requires `@tenphi/tasty` >= 2.10).\n */\nexport const DIRECTIONAL_BOX: Record<string, DirectionalBoxConfig> = {\n margin: { identity: '0', positions: BOX_SIDES, fixable: true },\n padding: { identity: '0', positions: BOX_SIDES, fixable: true },\n inset: { identity: 'auto', positions: BOX_SIDES, fixable: true },\n radius: { identity: '0', positions: BOX_CORNERS, fixable: true },\n fade: { identity: '0', positions: BOX_SIDES, fixable: true },\n // Four `border` tokens parse as a single border value (width/style/color),\n // not as a box: `border: '0 0 1bw 0'` emits `border: 0 solid …` — no border\n // on any side. The directional form is what the author meant, but applying\n // it changes rendering, so it stays a manual fix.\n border: {\n identity: '0',\n positions: BOX_SIDES,\n fixable: false,\n note: 'four border tokens parse as one border value, so the current style sets no border at all',\n },\n};\n\n/**\n * Properties that support a `dock` modifier — pin one edge and span its full\n * length. Lets a 4-value box with a single `identity` side collapse, e.g.\n * `inset: 'auto 0 0 0'` -> `inset: '0 bottom dock'`.\n * Requires `@tenphi/tasty` >= 2.10.\n */\nexport const DOCK_PROPERTIES = new Set(['inset']);\n\n/**\n * Valid radius shape keywords.\n */\nexport const RADIUS_SHAPES = new Set(['round', 'ellipse', 'leaf', 'backleaf']);\n\n/**\n * Known semantic transition names.\n */\nexport const SEMANTIC_TRANSITIONS = new Set([\n 'fade',\n 'fill',\n 'border',\n 'radius',\n 'shadow',\n 'preset',\n 'gap',\n 'theme',\n 'color',\n 'outline',\n 'dimension',\n 'flow',\n 'inset',\n 'text',\n 'opacity',\n 'translate',\n 'rotate',\n 'scale',\n 'filter',\n 'image',\n 'background',\n 'width',\n 'height',\n 'zIndex',\n]);\n\n/**\n * Mapping of native CSS properties to tasty shorthand alternatives.\n */\nexport const SHORTHAND_MAPPING: Record<\n string,\n { property: string; hint: string; safeFix?: boolean }\n> = {\n backgroundColor: { property: 'fill', hint: \"fill: '...'\", safeFix: true },\n borderColor: { property: 'border', hint: \"border: '...'\" },\n borderWidth: { property: 'border', hint: \"border: '...'\" },\n borderStyle: { property: 'border', hint: \"border: '...'\" },\n borderTop: { property: 'border', hint: \"border: '... top'\" },\n borderRight: { property: 'border', hint: \"border: '... right'\" },\n borderBottom: { property: 'border', hint: \"border: '... bottom'\" },\n borderLeft: { property: 'border', hint: \"border: '... left'\" },\n borderRadius: { property: 'radius', hint: \"radius: '...'\", safeFix: true },\n maxWidth: { property: 'width', hint: \"width: 'max ...'\" },\n minWidth: { property: 'width', hint: \"width: 'min ...'\" },\n maxHeight: { property: 'height', hint: \"height: 'max ...'\" },\n minHeight: { property: 'height', hint: \"height: 'min ...'\" },\n flexDirection: { property: 'flow', hint: \"flow: '...'\" },\n flexWrap: { property: 'flow', hint: \"flow: '...'\" },\n flexFlow: { property: 'flow', hint: \"flow: '...'\" },\n gridAutoFlow: { property: 'flow', hint: \"flow: '...'\" },\n outlineOffset: { property: 'outline', hint: \"outline: '... / offset'\" },\n paddingTop: { property: 'padding', hint: \"padding: '... top'\" },\n paddingRight: { property: 'padding', hint: \"padding: '... right'\" },\n paddingBottom: { property: 'padding', hint: \"padding: '... bottom'\" },\n paddingLeft: { property: 'padding', hint: \"padding: '... left'\" },\n marginTop: { property: 'margin', hint: \"margin: '... top'\" },\n marginRight: { property: 'margin', hint: \"margin: '... right'\" },\n marginBottom: { property: 'margin', hint: \"margin: '... bottom'\" },\n marginLeft: { property: 'margin', hint: \"margin: '... left'\" },\n fontSize: { property: 'preset', hint: \"preset: '...'\" },\n fontWeight: {\n property: 'preset',\n hint: \"preset: 'name / strong' (with strong modifier)\",\n },\n lineHeight: {\n property: 'preset',\n hint: \"preset: 'name / tight' (with tight modifier)\",\n },\n boxShadow: { property: 'shadow', hint: \"shadow: '...'\" },\n backgroundImage: { property: 'image', hint: \"image: '...'\" },\n fontFamily: { property: 'font', hint: \"font: '...'\" },\n fontStyle: {\n property: 'preset',\n hint: \"preset: 'name / italic' (with italic modifier)\",\n },\n letterSpacing: { property: 'preset', hint: \"preset: '...'\" },\n textTransform: { property: 'preset', hint: \"preset: '...'\" },\n flexGap: { property: 'gap', hint: \"gap: '...'\" },\n scrollbarWidth: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n scrollbarColor: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n scrollbarGutter: { property: 'scrollbar', hint: \"scrollbar: '...'\" },\n gridTemplateColumns: {\n property: 'gridColumns',\n hint: \"gridColumns: '...'\",\n },\n gridTemplateRows: { property: 'gridRows', hint: \"gridRows: '...'\" },\n gridTemplateAreas: { property: 'gridAreas', hint: \"gridAreas: '...'\" },\n top: { property: 'inset', hint: \"inset: '... top'\" },\n right: { property: 'inset', hint: \"inset: '... right'\" },\n bottom: { property: 'inset', hint: \"inset: '... bottom'\" },\n left: { property: 'inset', hint: \"inset: '... left'\" },\n outlineColor: { property: 'outline', hint: \"outline: '...'\" },\n outlineWidth: { property: 'outline', hint: \"outline: '...'\" },\n outlineStyle: { property: 'outline', hint: \"outline: '...'\" },\n lineClamp: {\n property: 'textOverflow',\n hint: \"textOverflow: 'ellipsis / {lines}'\",\n },\n};\n\n/**\n * CSS shorthands whose *longhands* are the preferred Tasty form — the reverse\n * of `SHORTHAND_MAPPING`.\n *\n * `flex` is lossy in a way the longhands are not: it resets the two components\n * you did not write to values that are not their CSS initial. `flex: '0'`\n * expands to `flex-grow: 0; flex-shrink: 1; flex-basis: 0%`, so it cannot\n * express \"do not shrink\" (`flexShrink: 0`) at all, and it silently changes\n * `flex-basis`. Writing the longhands keeps each component independent, which\n * also lets them carry separate state maps.\n */\nexport const LONGHAND_MAPPING: Record<\n string,\n { longhands: string[]; hint: string }\n> = {\n flex: {\n longhands: ['flexGrow', 'flexShrink', 'flexBasis'],\n hint: 'flexGrow / flexShrink / flexBasis',\n },\n};\n\n/**\n * CSS transition property names mapped to Tasty semantic transition names.\n */\nexport const TRANSITION_SEMANTIC_MAPPING: Record<string, string> = {\n backgroundColor: 'fill',\n 'background-color': 'fill',\n backgroundImage: 'image',\n 'background-image': 'image',\n borderRadius: 'radius',\n 'border-radius': 'radius',\n borderColor: 'border',\n 'border-color': 'border',\n borderWidth: 'border',\n 'border-width': 'border',\n borderStyle: 'border',\n 'border-style': 'border',\n boxShadow: 'shadow',\n 'box-shadow': 'shadow',\n outlineColor: 'outline',\n 'outline-color': 'outline',\n outlineWidth: 'outline',\n 'outline-width': 'outline',\n outlineOffset: 'outline',\n 'outline-offset': 'outline',\n gridTemplateColumns: 'gridColumns',\n 'grid-template-columns': 'gridColumns',\n gridTemplateRows: 'gridRows',\n 'grid-template-rows': 'gridRows',\n flexDirection: 'flow',\n 'flex-direction': 'flow',\n gridAutoFlow: 'flow',\n 'grid-auto-flow': 'flow',\n fontSize: 'preset',\n 'font-size': 'preset',\n lineHeight: 'preset',\n 'line-height': 'preset',\n letterSpacing: 'preset',\n 'letter-spacing': 'preset',\n};\n\n/**\n * Known preset modifiers.\n */\nexport const PRESET_MODIFIERS = new Set([\n 'strong',\n 'bold',\n 'italic',\n 'icon',\n 'tight',\n]);\n\n/**\n * Default import sources for tasty.\n */\nexport const DEFAULT_IMPORT_SOURCES = ['@tenphi/tasty', '@tenphi/tasty/static'];\n\n/**\n * Built-in state prefixes that are always valid (not aliases).\n */\nexport const BUILT_IN_STATE_PREFIXES = new Set([\n '@media',\n '@root',\n '@parent',\n '@own',\n '@supports',\n '@starting',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n]);\n\n/**\n * Known CSS pseudo-classes.\n */\nexport const KNOWN_PSEUDO_CLASSES = new Set([\n ':hover',\n ':focus',\n ':focus-visible',\n ':focus-within',\n ':active',\n ':visited',\n ':link',\n ':checked',\n ':disabled',\n ':enabled',\n ':empty',\n ':first-child',\n ':last-child',\n ':first-of-type',\n ':last-of-type',\n ':only-child',\n ':only-of-type',\n ':root',\n ':target',\n ':valid',\n ':invalid',\n ':required',\n ':optional',\n ':read-only',\n ':read-write',\n ':placeholder-shown',\n ':autofill',\n ':default',\n ':indeterminate',\n ':in-range',\n ':out-of-range',\n ':any-link',\n ':local-link',\n ':is',\n ':not',\n ':where',\n ':has',\n ':nth-child',\n ':nth-last-child',\n ':nth-of-type',\n ':nth-last-of-type',\n '::before',\n '::after',\n '::placeholder',\n '::selection',\n '::first-line',\n '::first-letter',\n '::marker',\n '::backdrop',\n]);\n\n/**\n * CSS named colors (level 1–4) that should be replaced by `#color` tokens.\n * `transparent` and `currentcolor` are excluded — they have their own token\n * suggestions (`#clear`, `#current`) handled by `prefer-custom-property-syntax`.\n */\nexport const NAMED_CSS_COLORS = new Set<string>([\n 'aliceblue',\n 'antiquewhite',\n 'aqua',\n 'aquamarine',\n 'azure',\n 'beige',\n 'bisque',\n 'black',\n 'blanchedalmond',\n 'blue',\n 'blueviolet',\n 'brown',\n 'burlywood',\n 'cadetblue',\n 'chartreuse',\n 'chocolate',\n 'coral',\n 'cornflowerblue',\n 'cornsilk',\n 'crimson',\n 'cyan',\n 'darkblue',\n 'darkcyan',\n 'darkgoldenrod',\n 'darkgray',\n 'darkgreen',\n 'darkgrey',\n 'darkkhaki',\n 'darkmagenta',\n 'darkolivegreen',\n 'darkorange',\n 'darkorchid',\n 'darkred',\n 'darksalmon',\n 'darkseagreen',\n 'darkslateblue',\n 'darkslategray',\n 'darkslategrey',\n 'darkturquoise',\n 'darkviolet',\n 'deeppink',\n 'deepskyblue',\n 'dimgray',\n 'dimgrey',\n 'dodgerblue',\n 'firebrick',\n 'floralwhite',\n 'forestgreen',\n 'fuchsia',\n 'gainsboro',\n 'ghostwhite',\n 'gold',\n 'goldenrod',\n 'gray',\n 'green',\n 'greenyellow',\n 'grey',\n 'honeydew',\n 'hotpink',\n 'indianred',\n 'indigo',\n 'ivory',\n 'khaki',\n 'lavender',\n 'lavenderblush',\n 'lawngreen',\n 'lemonchiffon',\n 'lightblue',\n 'lightcoral',\n 'lightcyan',\n 'lightgoldenrodyellow',\n 'lightgray',\n 'lightgreen',\n 'lightgrey',\n 'lightpink',\n 'lightsalmon',\n 'lightseagreen',\n 'lightskyblue',\n 'lightslategray',\n 'lightslategrey',\n 'lightsteelblue',\n 'lightyellow',\n 'lime',\n 'limegreen',\n 'linen',\n 'magenta',\n 'maroon',\n 'mediumaquamarine',\n 'mediumblue',\n 'mediumorchid',\n 'mediumpurple',\n 'mediumseagreen',\n 'mediumslateblue',\n 'mediumspringgreen',\n 'mediumturquoise',\n 'mediumvioletred',\n 'midnightblue',\n 'mintcream',\n 'mistyrose',\n 'moccasin',\n 'navajowhite',\n 'navy',\n 'oldlace',\n 'olive',\n 'olivedrab',\n 'orange',\n 'orangered',\n 'orchid',\n 'palegoldenrod',\n 'palegreen',\n 'paleturquoise',\n 'palevioletred',\n 'papayawhip',\n 'peachpuff',\n 'peru',\n 'pink',\n 'plum',\n 'powderblue',\n 'purple',\n 'rebeccapurple',\n 'red',\n 'rosybrown',\n 'royalblue',\n 'saddlebrown',\n 'salmon',\n 'sandybrown',\n 'seagreen',\n 'seashell',\n 'sienna',\n 'silver',\n 'skyblue',\n 'slateblue',\n 'slategray',\n 'slategrey',\n 'snow',\n 'springgreen',\n 'steelblue',\n 'tan',\n 'teal',\n 'thistle',\n 'tomato',\n 'turquoise',\n 'violet',\n 'wheat',\n 'white',\n 'whitesmoke',\n 'yellow',\n 'yellowgreen',\n]);\n\n/**\n * Properties that accept color values. Used to scope named-color checks so\n * string properties like `content` or `cursor` don't false-positive.\n */\nexport const COLOR_BEARING_PROPERTIES = new Set<string>([\n // Tasty enhanced properties\n 'fill',\n 'color',\n 'caretColor',\n 'accentColor',\n 'shadow',\n 'border',\n 'outline',\n 'fade',\n // CSS background\n 'background',\n 'backgroundColor',\n // CSS border colors\n 'borderColor',\n 'borderTopColor',\n 'borderRightColor',\n 'borderBottomColor',\n 'borderLeftColor',\n // CSS shadows\n 'boxShadow',\n 'textShadow',\n // CSS text decoration\n 'textDecorationColor',\n // CSS outline / misc\n 'outlineColor',\n 'scrollbarColor',\n 'columnRuleColor',\n // SVG\n 'stroke',\n 'stopColor',\n 'floodColor',\n 'lightingColor',\n]);\n\n/**\n * Properties where tasty does NOT expand a `$name` custom-property reference.\n *\n * These have their own style handlers that pass the value through verbatim, so\n * `fontFamily: '$font-sans'` emits a literal `font-family: $font-sans` — an\n * invalid declaration the browser drops. Rewriting `var(--font-sans)` to\n * `$font-sans` here silently deletes the style.\n *\n * Note that the colour properties (`color`, `fill`, `backgroundColor`) are in\n * this list but NOT in {@link PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION}: they\n * expand `#token` and not `$name`. The two sets are genuinely different.\n *\n * Derived from the runtime, not by hand — `constants.round-trip.test.ts` rebuilds\n * both sets against the installed `@tenphi/tasty` and fails if either drifts.\n */\nexport const PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION = new Set([\n 'align',\n 'alignContent',\n 'alignItems',\n 'backgroundAttachment',\n 'backgroundClip',\n 'backgroundColor',\n 'backgroundOrigin',\n 'backgroundRepeat',\n 'color',\n 'display',\n 'fill',\n 'font',\n 'fontFamily',\n 'justify',\n 'justifyContent',\n 'justifyItems',\n 'overflow',\n 'placeContent',\n 'placeItems',\n 'textTransform',\n 'whiteSpace',\n]);\n\n/**\n * Properties where tasty does NOT expand a `#name` colour token.\n *\n * Mostly dimension and keyword properties: `fontSize: '#accent'` is meaningless,\n * so the handler leaves the value alone and the declaration is dropped. Suggesting\n * `#x` for a `var(--x-color)` inside one of these would break it.\n *\n * See {@link PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION} for why this is a\n * separate list rather than the same one.\n */\nexport const PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION = new Set([\n 'align',\n 'alignContent',\n 'alignItems',\n 'backgroundAttachment',\n 'backgroundClip',\n 'backgroundOrigin',\n 'backgroundRepeat',\n 'display',\n 'font',\n 'fontFamily',\n 'fontSize',\n 'fontWeight',\n 'justify',\n 'justifyContent',\n 'justifyItems',\n 'letterSpacing',\n 'lineHeight',\n 'outlineOffset',\n 'overflow',\n 'placeContent',\n 'placeItems',\n 'textTransform',\n 'whiteSpace',\n]);\n"],"mappings":";;;;AAGA,MAAa,yBAAyB,IAAI,IAAI;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,MAAa,qBAAqB,IAAI,IAAI;CACxC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,MAAa,6BAAqD;CAChE,eAAe;CACf,aAAa;CACb,iBAAiB;CAClB;;;;;AAMD,MAAa,uBAAuB,IAAI,IAAI;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,iBAAiB,I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CACjB,oBAAoB;CACpB,iBAAiB;CACjB,oBAAoB;CACpB,cAAc;CACd,iBAAiB;CACjB,aAAa;CACb,gBAAgB;CAChB,aAAa;CACb,gBAAgB;CAChB,aAAa;CACb,gBAAgB;CAChB,WAAW;CACX,cAAc;CACd,cAAc;CACd,iBAAiB;CACjB,cAAc;CACd,iBAAiB;CACjB,eAAe;CACf,kBAAkB;CAClB,qBAAqB;CACrB,yBAAyB;CACzB,kBAAkB;CAClB,sBAAsB;CACtB,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,kBAAkB;CAClB,UAAU;CACV,aAAa;CACb,YAAY;CACZ,eAAe;CACf,eAAe;CACf,kBAAkB;CACnB;;;;AAKD,MAAa,mBAAmB,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,yBAAyB,CAAC,iBAAiB,uBAAuB;;;;AAK/E,MAAa,0BAA0B,IAAI,IAAI;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;AAKF,MAAa,uBAAuB,IAAI,IAAI;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,MAAa,mBAAmB,IAAI,IAAY;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;AAMF,MAAa,2BAA2B,IAAI,IAAY;CAEtD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CAEA;CACA;CACA;CAEA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,+CAA+C,IAAI,IAAI;CAClE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;AAYF,MAAa,2CAA2C,IAAI,IAAI;CAC9D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC"}
|
package/dist/context.js
CHANGED
|
@@ -30,6 +30,28 @@ function styleObjectListeners(handler) {
|
|
|
30
30
|
for (const selector of STYLE_OBJECT_SELECTORS) listeners[selector] = once;
|
|
31
31
|
return listeners;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a type annotation names tasty's `Styles` anywhere inside it.
|
|
35
|
+
*
|
|
36
|
+
* Walks the annotation rather than matching only the outermost reference, so a
|
|
37
|
+
* wrapped tasty type (`Styles | undefined`, `Record<string, Styles>`,
|
|
38
|
+
* `{ root: Styles }`) is recognised while `CSSProperties` in the same positions is
|
|
39
|
+
* not. Deliberately name-based: the plugin has no type checker, and requiring one
|
|
40
|
+
* would make every rule type-aware.
|
|
41
|
+
*/
|
|
42
|
+
function referencesStylesType(node) {
|
|
43
|
+
const stack = [node];
|
|
44
|
+
while (stack.length) {
|
|
45
|
+
const current = stack.pop();
|
|
46
|
+
if (current.type === "TSTypeReference" && current.typeName.type === "Identifier" && /^Styles$/i.test(current.typeName.name)) return true;
|
|
47
|
+
for (const value of Object.values(current)) if (Array.isArray(value)) {
|
|
48
|
+
for (const item of value) if (item && typeof item === "object" && "type" in item) stack.push(item);
|
|
49
|
+
} else if (value && typeof value === "object" && "type" in value) {
|
|
50
|
+
if (value !== current.parent) stack.push(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
33
55
|
const TASTY_FUNCTION_NAMES = new Set([
|
|
34
56
|
"tasty",
|
|
35
57
|
"tastyStatic",
|
|
@@ -138,15 +160,25 @@ var TastyContext = class {
|
|
|
138
160
|
if (init !== targetNode) return false;
|
|
139
161
|
const name = current.id.name;
|
|
140
162
|
if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;
|
|
163
|
+
const annotated = this.stylesTypeAnnotation(current);
|
|
164
|
+
if (annotated !== null) return annotated;
|
|
141
165
|
if (name === "styles" || name.endsWith("Styles")) return true;
|
|
142
|
-
if (this.hasStylesTypeAnnotation(current)) return true;
|
|
143
166
|
return false;
|
|
144
167
|
}
|
|
145
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Verdict from the declared type: `true` tasty, `false` explicitly not, `null`
|
|
170
|
+
* when there is no annotation to go on and the caller should fall back to the
|
|
171
|
+
* name.
|
|
172
|
+
*
|
|
173
|
+
* A `Styles` reference anywhere in the annotation counts, so wrappers like
|
|
174
|
+
* `Styles | undefined` and `Record<string, Styles>` opt in — while
|
|
175
|
+
* `CSSProperties`, `Record<string, CSSProperties>` and an inline literal of
|
|
176
|
+
* them opt out.
|
|
177
|
+
*/
|
|
178
|
+
stylesTypeAnnotation(node) {
|
|
146
179
|
const annotation = node.id.typeAnnotation?.typeAnnotation;
|
|
147
|
-
if (!annotation) return
|
|
148
|
-
|
|
149
|
-
return false;
|
|
180
|
+
if (!annotation) return null;
|
|
181
|
+
return referencesStylesType(annotation);
|
|
150
182
|
}
|
|
151
183
|
getTastyCallContext(call, targetNode) {
|
|
152
184
|
const args = call.arguments;
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\nimport type { ResolvedConfig } from './types.js';\nimport { loadConfig } from './config.js';\nimport { DEFAULT_IMPORT_SOURCES, SPECIAL_STYLE_KEYS } from './constants.js';\nimport { getKeyName } from './utils.js';\n\n/**\n * AST selectors that match ObjectExpressions in all known tasty style contexts:\n * call sites, variable declarations, satisfies/as expressions.\n */\nexport const STYLE_OBJECT_SELECTORS = [\n 'CallExpression ObjectExpression',\n 'VariableDeclarator > ObjectExpression',\n 'VariableDeclarator > TSSatisfiesExpression > ObjectExpression',\n 'VariableDeclarator > TSAsExpression > ObjectExpression',\n // `<Block styles={{…}} />`. Safe to match on the prop name alone: React's own\n // prop is `style`, singular, so a plural `styles` JSX prop is a Tasty\n // convention.\n \"JSXAttribute[name.name='styles'] > JSXExpressionContainer > ObjectExpression\",\n // A Storybook story's `args.styles`. Deliberately this specific rather than\n // matching any `styles` key: plenty of unrelated libraries take a `styles`\n // option object, and a bare key carries no evidence it is Tasty's. Story files\n // are where a lot of style code is authored and neither the call-site nor the\n // variable-name heuristic reaches them — the enclosing variable is named after\n // the story, and there is no Tasty call.\n // Superset of the `args.styles` shape; `isStylesPropertyValue` is the real\n // gate, so both `styles` and `'styles'` key forms are covered.\n 'Property > ObjectExpression > Property > ObjectExpression',\n] as const;\n\n/**\n * Creates a record of ESLint listeners that all point to the same handler,\n * one entry per style-object AST selector.\n */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n const listeners: Record<string, (node: TSESTree.ObjectExpression) => void> =\n {};\n\n // Selectors overlap — `tasty({ styles: {…} })` matches both\n // `CallExpression ObjectExpression` and the `styles` key selector — so the\n // handler must run at most once per object, or every rule double-reports.\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n const once = (node: TSESTree.ObjectExpression) => {\n if (visited.has(node)) return;\n visited.add(node);\n handler(node);\n };\n\n for (const selector of STYLE_OBJECT_SELECTORS) {\n listeners[selector] = once;\n }\n\n return listeners;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\nconst TASTY_FUNCTION_NAMES = new Set([\n 'tasty',\n 'tastyStatic',\n 'useStyles',\n 'useGlobalStyles',\n 'configure',\n]);\n\n/**\n * Context tracker for a single file's lint pass.\n * Tracks which imports come from tasty and provides\n * helpers to determine if a node is in a tasty style context.\n */\nexport class TastyContext {\n readonly config: ResolvedConfig;\n private imports = new Map<string, TastyImport>();\n private importSources: Set<string>;\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n config?: ResolvedConfig,\n ) {\n this.config = config ?? loadConfig(context.filename);\n this.importSources = new Set([\n ...DEFAULT_IMPORT_SOURCES,\n ...this.config.importSources,\n ]);\n }\n\n trackImport(node: TSESTree.ImportDeclaration): void {\n const source = node.source.value;\n if (!this.importSources.has(source)) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportSpecifier') {\n const importedName =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (TASTY_FUNCTION_NAMES.has(importedName)) {\n this.imports.set(specifier.local.name, {\n localName: specifier.local.name,\n importedName,\n source,\n });\n }\n }\n }\n }\n\n getImport(localName: string): TastyImport | undefined {\n return this.imports.get(localName);\n }\n\n isTastyCall(node: TSESTree.CallExpression): TastyImport | undefined {\n if (node.callee.type !== 'Identifier') return undefined;\n return this.imports.get(node.callee.name);\n }\n\n /**\n * Determines whether an object expression is a tasty style object\n * by walking up the AST to find a recognized call expression.\n */\n isStyleObject(node: TSESTree.ObjectExpression): boolean {\n return this.getStyleContext(node) !== null;\n }\n\n getStyleContext(node: TSESTree.Node): {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n } | null {\n // Sub-element objects inherit their parent style object's context\n if (node.type === 'ObjectExpression') {\n const parent = node.parent;\n if (parent?.type === 'Property' && !parent.computed) {\n const key = getKeyName(parent.key);\n if (key && /^[A-Z]/.test(key)) {\n const grandparent = parent.parent;\n if (grandparent?.type === 'ObjectExpression') {\n return this.getStyleContext(grandparent);\n }\n }\n }\n }\n\n // An object assigned to a `styles` key (or a `styles` JSX prop) is a style\n // object on its own terms — no enclosing tasty call or `styles`-ish variable\n // name required. Checked before the walk, which would otherwise climb past it\n // to an unrelated call or declaration and reject the whole thing.\n if (node.type === 'ObjectExpression' && this.isStylesPropertyValue(node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n let current: TSESTree.Node | undefined = node;\n\n while (current) {\n if (current.type === 'CallExpression') {\n const imp = this.isTastyCall(current);\n if (!imp) return null;\n\n const name = imp.importedName;\n const isStaticCall = name === 'tastyStatic';\n\n if (name === 'tasty') {\n return this.getTastyCallContext(current, node);\n }\n\n if (name === 'tastyStatic') {\n return this.getTastyStaticCallContext(current, node);\n }\n\n if (name === 'useStyles') {\n if (current.arguments[0] === node) {\n return {\n type: 'useStyles',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n }\n\n if (name === 'useGlobalStyles') {\n if (current.arguments[1] === node) {\n return {\n type: 'useGlobalStyles',\n isStaticCall,\n isSelectorMode: true,\n isExtending: false,\n };\n }\n }\n\n return null;\n }\n\n if (this.isStyleVariableDeclaration(current, node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations. A `Styles` type annotation still opts a\n // differently-named variable in, below.\n if (name === 'styles' || name.endsWith('Styles')) return true;\n\n if (this.hasStylesTypeAnnotation(current)) return true;\n\n return false;\n }\n\n private hasStylesTypeAnnotation(node: TSESTree.VariableDeclarator): boolean {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n if (!annotation) return false;\n\n if (\n annotation.type === 'TSTypeReference' &&\n annotation.typeName.type === 'Identifier'\n ) {\n return /^Styles$/i.test(annotation.typeName.name);\n }\n\n return false;\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const optionsArg =\n args.length >= 2 && args[0].type !== 'ObjectExpression'\n ? args[1]\n : args[0];\n\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideStylesProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: args.length >= 2 && args[0].type !== 'ObjectExpression',\n };\n }\n\n // Check if inside variants\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideVariantsProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n return null;\n }\n\n private getTastyStaticCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tastyStatic({ ... })\n if (args.length === 1 && args[0] === targetNode) {\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = args[0].type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n };\n }\n\n return null;\n }\n\n private isInsideStylesProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'styles' &&\n prop.value === targetNode\n ) {\n return true;\n }\n }\n return false;\n }\n\n private isInsideVariantsProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'variants' &&\n prop.value.type === 'ObjectExpression'\n ) {\n for (const variantProp of prop.value.properties) {\n if (\n variantProp.type === 'Property' &&\n variantProp.value === targetNode\n ) {\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Whether this object is a `styles` JSX prop value or a Storybook `args.styles`.\n *\n * Both are unambiguous enough to stand on their own without an import-tracked\n * call or a `styles`-ish variable name. A bare `styles` key elsewhere is not —\n * see the selector list for why.\n */\n isStylesPropertyValue(node: TSESTree.ObjectExpression): boolean {\n const parent = node.parent;\n\n if (parent?.type === 'Property' && !parent.computed) {\n if (getKeyName(parent.key) !== 'styles') return false;\n\n // Only inside `args: { … }`.\n const argsObject = parent.parent;\n const argsProperty =\n argsObject?.type === 'ObjectExpression' ? argsObject.parent : undefined;\n\n return (\n argsProperty?.type === 'Property' &&\n !argsProperty.computed &&\n getKeyName(argsProperty.key) === 'args'\n );\n }\n\n if (parent?.type === 'JSXExpressionContainer') {\n const attribute = parent.parent;\n\n return (\n attribute?.type === 'JSXAttribute' &&\n attribute.name.type === 'JSXIdentifier' &&\n attribute.name.name === 'styles'\n );\n }\n\n return false;\n }\n\n /**\n * Checks if a property value node is a state mapping object\n * (i.e., an object where keys are state expressions and values are style values).\n */\n isStateMap(\n node: TSESTree.ObjectExpression,\n parentProperty: TSESTree.Property,\n ): boolean {\n const keyName = getKeyName(parentProperty.key);\n if (keyName === null) return false;\n\n // If the key starts with uppercase, it's a sub-element, not a state map\n if (/^[A-Z]/.test(keyName)) return false;\n\n // Special keys are not state maps\n // At-rule blocks are not state maps — their object values are descriptor maps.\n if (SPECIAL_STYLE_KEYS.has(keyName)) return false;\n\n // If the object has keys that look like state expressions, it's a state map.\n // A lone `_` (the fallback floor) or `''` (the default) is enough — both are\n // already covered by the string-literal / identifier branches below.\n return node.properties.some((prop) => {\n if (prop.type !== 'Property') return false;\n if (prop.key.type === 'Literal' && prop.key.value === '') return true;\n if (prop.key.type === 'Identifier') return true;\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {\n return true;\n }\n return false;\n });\n }\n\n /**\n * Checks if a key represents a sub-element (starts with uppercase).\n */\n isSubElementKey(key: string): boolean {\n return /^[A-Z]/.test(key);\n }\n\n /**\n * Checks if a key represents a nested selector (starts with &).\n */\n isNestedSelectorKey(key: string): boolean {\n return key.startsWith('&');\n }\n\n /**\n * Checks if a key is a custom CSS property definition ($name or $$name).\n */\n isCustomPropertyKey(key: string): boolean {\n return key.startsWith('$');\n }\n\n /**\n * Checks if a key is a color token definition (#name or ##name).\n */\n isColorTokenKey(key: string): boolean {\n return key.startsWith('#');\n }\n\n /**\n * Checks if a key is a special @ property (@keyframes, @property, ...).\n */\n isSpecialKey(key: string): boolean {\n return key.startsWith('@');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\nimport type { ResolvedConfig } from './types.js';\nimport { loadConfig } from './config.js';\nimport { DEFAULT_IMPORT_SOURCES, SPECIAL_STYLE_KEYS } from './constants.js';\nimport { getKeyName } from './utils.js';\n\n/**\n * AST selectors that match ObjectExpressions in all known tasty style contexts:\n * call sites, variable declarations, satisfies/as expressions.\n */\nexport const STYLE_OBJECT_SELECTORS = [\n 'CallExpression ObjectExpression',\n 'VariableDeclarator > ObjectExpression',\n 'VariableDeclarator > TSSatisfiesExpression > ObjectExpression',\n 'VariableDeclarator > TSAsExpression > ObjectExpression',\n // `<Block styles={{…}} />`. Safe to match on the prop name alone: React's own\n // prop is `style`, singular, so a plural `styles` JSX prop is a Tasty\n // convention.\n \"JSXAttribute[name.name='styles'] > JSXExpressionContainer > ObjectExpression\",\n // A Storybook story's `args.styles`. Deliberately this specific rather than\n // matching any `styles` key: plenty of unrelated libraries take a `styles`\n // option object, and a bare key carries no evidence it is Tasty's. Story files\n // are where a lot of style code is authored and neither the call-site nor the\n // variable-name heuristic reaches them — the enclosing variable is named after\n // the story, and there is no Tasty call.\n // Superset of the `args.styles` shape; `isStylesPropertyValue` is the real\n // gate, so both `styles` and `'styles'` key forms are covered.\n 'Property > ObjectExpression > Property > ObjectExpression',\n] as const;\n\n/**\n * Creates a record of ESLint listeners that all point to the same handler,\n * one entry per style-object AST selector.\n */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n const listeners: Record<string, (node: TSESTree.ObjectExpression) => void> =\n {};\n\n // Selectors overlap — `tasty({ styles: {…} })` matches both\n // `CallExpression ObjectExpression` and the `styles` key selector — so the\n // handler must run at most once per object, or every rule double-reports.\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n const once = (node: TSESTree.ObjectExpression) => {\n if (visited.has(node)) return;\n visited.add(node);\n handler(node);\n };\n\n for (const selector of STYLE_OBJECT_SELECTORS) {\n listeners[selector] = once;\n }\n\n return listeners;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\n/**\n * Whether a type annotation names tasty's `Styles` anywhere inside it.\n *\n * Walks the annotation rather than matching only the outermost reference, so a\n * wrapped tasty type (`Styles | undefined`, `Record<string, Styles>`,\n * `{ root: Styles }`) is recognised while `CSSProperties` in the same positions is\n * not. Deliberately name-based: the plugin has no type checker, and requiring one\n * would make every rule type-aware.\n */\nfunction referencesStylesType(node: TSESTree.TypeNode): boolean {\n const stack: TSESTree.Node[] = [node];\n\n while (stack.length) {\n const current = stack.pop()!;\n\n if (\n current.type === 'TSTypeReference' &&\n current.typeName.type === 'Identifier' &&\n /^Styles$/i.test(current.typeName.name)\n ) {\n return true;\n }\n\n for (const value of Object.values(\n current as unknown as Record<string, unknown>,\n )) {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && 'type' in item) {\n stack.push(item as TSESTree.Node);\n }\n }\n } else if (value && typeof value === 'object' && 'type' in value) {\n // Skip `parent` — it points back up the tree and would loop forever.\n if (value !== (current as TSESTree.Node).parent) {\n stack.push(value as TSESTree.Node);\n }\n }\n }\n }\n\n return false;\n}\n\nconst TASTY_FUNCTION_NAMES = new Set([\n 'tasty',\n 'tastyStatic',\n 'useStyles',\n 'useGlobalStyles',\n 'configure',\n]);\n\n/**\n * Context tracker for a single file's lint pass.\n * Tracks which imports come from tasty and provides\n * helpers to determine if a node is in a tasty style context.\n */\nexport class TastyContext {\n readonly config: ResolvedConfig;\n private imports = new Map<string, TastyImport>();\n private importSources: Set<string>;\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n config?: ResolvedConfig,\n ) {\n this.config = config ?? loadConfig(context.filename);\n this.importSources = new Set([\n ...DEFAULT_IMPORT_SOURCES,\n ...this.config.importSources,\n ]);\n }\n\n trackImport(node: TSESTree.ImportDeclaration): void {\n const source = node.source.value;\n if (!this.importSources.has(source)) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportSpecifier') {\n const importedName =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (TASTY_FUNCTION_NAMES.has(importedName)) {\n this.imports.set(specifier.local.name, {\n localName: specifier.local.name,\n importedName,\n source,\n });\n }\n }\n }\n }\n\n getImport(localName: string): TastyImport | undefined {\n return this.imports.get(localName);\n }\n\n isTastyCall(node: TSESTree.CallExpression): TastyImport | undefined {\n if (node.callee.type !== 'Identifier') return undefined;\n return this.imports.get(node.callee.name);\n }\n\n /**\n * Determines whether an object expression is a tasty style object\n * by walking up the AST to find a recognized call expression.\n */\n isStyleObject(node: TSESTree.ObjectExpression): boolean {\n return this.getStyleContext(node) !== null;\n }\n\n getStyleContext(node: TSESTree.Node): {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n } | null {\n // Sub-element objects inherit their parent style object's context\n if (node.type === 'ObjectExpression') {\n const parent = node.parent;\n if (parent?.type === 'Property' && !parent.computed) {\n const key = getKeyName(parent.key);\n if (key && /^[A-Z]/.test(key)) {\n const grandparent = parent.parent;\n if (grandparent?.type === 'ObjectExpression') {\n return this.getStyleContext(grandparent);\n }\n }\n }\n }\n\n // An object assigned to a `styles` key (or a `styles` JSX prop) is a style\n // object on its own terms — no enclosing tasty call or `styles`-ish variable\n // name required. Checked before the walk, which would otherwise climb past it\n // to an unrelated call or declaration and reject the whole thing.\n if (node.type === 'ObjectExpression' && this.isStylesPropertyValue(node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n let current: TSESTree.Node | undefined = node;\n\n while (current) {\n if (current.type === 'CallExpression') {\n const imp = this.isTastyCall(current);\n if (!imp) return null;\n\n const name = imp.importedName;\n const isStaticCall = name === 'tastyStatic';\n\n if (name === 'tasty') {\n return this.getTastyCallContext(current, node);\n }\n\n if (name === 'tastyStatic') {\n return this.getTastyStaticCallContext(current, node);\n }\n\n if (name === 'useStyles') {\n if (current.arguments[0] === node) {\n return {\n type: 'useStyles',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n }\n\n if (name === 'useGlobalStyles') {\n if (current.arguments[1] === node) {\n return {\n type: 'useGlobalStyles',\n isStaticCall,\n isSelectorMode: true,\n isExtending: false,\n };\n }\n }\n\n return null;\n }\n\n if (this.isStyleVariableDeclaration(current, node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // An explicit type annotation is authoritative, in both directions. The name\n // heuristic below is a guess; a declared type is the author stating what the\n // object is, so it wins.\n //\n // This matters because the rewrites these rules offer are only valid *inside*\n // tasty: `#purple.05` and `$font-sans` are correct tasty declarations and\n // meaningless as raw CSS. A `const styles: Record<string, CSSProperties>`\n // handed to React's `style={…}` was matched on name alone, so its plain CSS\n // longhands were reported as tasty violations — and `--fix` rewrote real\n // `var(--shadow-sm-color)` to `#shadow-sm`, which nothing resolves there, so\n // the browser dropped the declaration.\n const annotated = this.stylesTypeAnnotation(current);\n\n if (annotated !== null) return annotated;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations.\n if (name === 'styles' || name.endsWith('Styles')) return true;\n\n return false;\n }\n\n /**\n * Verdict from the declared type: `true` tasty, `false` explicitly not, `null`\n * when there is no annotation to go on and the caller should fall back to the\n * name.\n *\n * A `Styles` reference anywhere in the annotation counts, so wrappers like\n * `Styles | undefined` and `Record<string, Styles>` opt in — while\n * `CSSProperties`, `Record<string, CSSProperties>` and an inline literal of\n * them opt out.\n */\n private stylesTypeAnnotation(\n node: TSESTree.VariableDeclarator,\n ): boolean | null {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n\n if (!annotation) return null;\n\n return referencesStylesType(annotation);\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const optionsArg =\n args.length >= 2 && args[0].type !== 'ObjectExpression'\n ? args[1]\n : args[0];\n\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideStylesProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: args.length >= 2 && args[0].type !== 'ObjectExpression',\n };\n }\n\n // Check if inside variants\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideVariantsProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n return null;\n }\n\n private getTastyStaticCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tastyStatic({ ... })\n if (args.length === 1 && args[0] === targetNode) {\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = args[0].type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n };\n }\n\n return null;\n }\n\n private isInsideStylesProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'styles' &&\n prop.value === targetNode\n ) {\n return true;\n }\n }\n return false;\n }\n\n private isInsideVariantsProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'variants' &&\n prop.value.type === 'ObjectExpression'\n ) {\n for (const variantProp of prop.value.properties) {\n if (\n variantProp.type === 'Property' &&\n variantProp.value === targetNode\n ) {\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Whether this object is a `styles` JSX prop value or a Storybook `args.styles`.\n *\n * Both are unambiguous enough to stand on their own without an import-tracked\n * call or a `styles`-ish variable name. A bare `styles` key elsewhere is not —\n * see the selector list for why.\n */\n isStylesPropertyValue(node: TSESTree.ObjectExpression): boolean {\n const parent = node.parent;\n\n if (parent?.type === 'Property' && !parent.computed) {\n if (getKeyName(parent.key) !== 'styles') return false;\n\n // Only inside `args: { … }`.\n const argsObject = parent.parent;\n const argsProperty =\n argsObject?.type === 'ObjectExpression' ? argsObject.parent : undefined;\n\n return (\n argsProperty?.type === 'Property' &&\n !argsProperty.computed &&\n getKeyName(argsProperty.key) === 'args'\n );\n }\n\n if (parent?.type === 'JSXExpressionContainer') {\n const attribute = parent.parent;\n\n return (\n attribute?.type === 'JSXAttribute' &&\n attribute.name.type === 'JSXIdentifier' &&\n attribute.name.name === 'styles'\n );\n }\n\n return false;\n }\n\n /**\n * Checks if a property value node is a state mapping object\n * (i.e., an object where keys are state expressions and values are style values).\n */\n isStateMap(\n node: TSESTree.ObjectExpression,\n parentProperty: TSESTree.Property,\n ): boolean {\n const keyName = getKeyName(parentProperty.key);\n if (keyName === null) return false;\n\n // If the key starts with uppercase, it's a sub-element, not a state map\n if (/^[A-Z]/.test(keyName)) return false;\n\n // Special keys are not state maps\n // At-rule blocks are not state maps — their object values are descriptor maps.\n if (SPECIAL_STYLE_KEYS.has(keyName)) return false;\n\n // If the object has keys that look like state expressions, it's a state map.\n // A lone `_` (the fallback floor) or `''` (the default) is enough — both are\n // already covered by the string-literal / identifier branches below.\n return node.properties.some((prop) => {\n if (prop.type !== 'Property') return false;\n if (prop.key.type === 'Literal' && prop.key.value === '') return true;\n if (prop.key.type === 'Identifier') return true;\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {\n return true;\n }\n return false;\n });\n }\n\n /**\n * Checks if a key represents a sub-element (starts with uppercase).\n */\n isSubElementKey(key: string): boolean {\n return /^[A-Z]/.test(key);\n }\n\n /**\n * Checks if a key represents a nested selector (starts with &).\n */\n isNestedSelectorKey(key: string): boolean {\n return key.startsWith('&');\n }\n\n /**\n * Checks if a key is a custom CSS property definition ($name or $$name).\n */\n isCustomPropertyKey(key: string): boolean {\n return key.startsWith('$');\n }\n\n /**\n * Checks if a key is a color token definition (#name or ##name).\n */\n isColorTokenKey(key: string): boolean {\n return key.startsWith('#');\n }\n\n /**\n * Checks if a key is a special @ property (@keyframes, @property, ...).\n */\n isSpecialKey(key: string): boolean {\n return key.startsWith('@');\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION, PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION } from "../constants.js";
|
|
2
3
|
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
5
|
import { replaceInStringValue } from "../fix-utils.js";
|
|
5
6
|
|
|
6
7
|
//#region src/rules/prefer-custom-property-syntax.ts
|
|
7
8
|
const VAR_REGEX = /var\(\s*--([a-zA-Z0-9_-]+)\s*(?:,\s*([^)]+))?\s*\)/g;
|
|
8
|
-
const COLOR_PROP_REGEX = /(?<![$#])\$([a-zA-Z][a-zA-Z0-9_-]*)-color
|
|
9
|
+
const COLOR_PROP_REGEX = /(?<![$#])\$([a-zA-Z][a-zA-Z0-9_-]*)-color(?![a-zA-Z0-9_-])(\.[0-9]+|\.\$[a-zA-Z][a-zA-Z0-9_-]*)?/g;
|
|
9
10
|
const KEYWORD_REGEX = /\b(transparent|currentColor)\b/gi;
|
|
10
11
|
function normalizeFallback(fallback) {
|
|
11
12
|
const trimmed = fallback.trim();
|
|
@@ -45,7 +46,22 @@ var prefer_custom_property_syntax_default = createRule({
|
|
|
45
46
|
defaultOptions: [],
|
|
46
47
|
create(context) {
|
|
47
48
|
const ctx = new TastyContext(context);
|
|
48
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Whether rewriting to `suggestion` actually round-trips for `property`.
|
|
51
|
+
*
|
|
52
|
+
* Every rewrite this rule offers has to render to the same CSS as what it
|
|
53
|
+
* replaces. Tasty expands `$name` and `#name` per property, not globally, so
|
|
54
|
+
* a suggestion that is correct on `gap` can silently delete the declaration on
|
|
55
|
+
* `fontFamily`. Suppress the report entirely rather than offer a broken fix —
|
|
56
|
+
* the rule is autofixable and runs unattended in pre-commit hooks.
|
|
57
|
+
*/
|
|
58
|
+
function roundTrips(property, suggestion) {
|
|
59
|
+
if (suggestion.startsWith("$")) return !PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION.has(property);
|
|
60
|
+
if (suggestion.startsWith("#")) return !PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION.has(property);
|
|
61
|
+
if (suggestion.startsWith("(")) return roundTrips(property, suggestion.slice(1));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function checkValue(value, node, property) {
|
|
49
65
|
const varSpans = [];
|
|
50
66
|
VAR_REGEX.lastIndex = 0;
|
|
51
67
|
let match;
|
|
@@ -56,6 +72,7 @@ var prefer_custom_property_syntax_default = createRule({
|
|
|
56
72
|
const suggestion = suggestVarSyntax(name, fallback);
|
|
57
73
|
const start = match.index;
|
|
58
74
|
const end = start + raw.length;
|
|
75
|
+
if (!roundTrips(property, suggestion)) continue;
|
|
59
76
|
if (match.index !== void 0) varSpans.push({
|
|
60
77
|
start: match.index,
|
|
61
78
|
end: match.index + raw.length
|
|
@@ -82,6 +99,7 @@ var prefer_custom_property_syntax_default = createRule({
|
|
|
82
99
|
const suggestion = `#${match[1]}${match[2] ?? ""}`;
|
|
83
100
|
const start = match.index;
|
|
84
101
|
const end = start + raw.length;
|
|
102
|
+
if (!roundTrips(property, suggestion)) continue;
|
|
85
103
|
context.report({
|
|
86
104
|
node,
|
|
87
105
|
messageId: "preferColorToken",
|
|
@@ -105,6 +123,7 @@ var prefer_custom_property_syntax_default = createRule({
|
|
|
105
123
|
if (isInsideSpans(start, end, varSpans)) continue;
|
|
106
124
|
const lower = match[0].toLowerCase();
|
|
107
125
|
const replacement = lower === "transparent" ? "#clear" : "#current";
|
|
126
|
+
if (!roundTrips(property, replacement)) continue;
|
|
108
127
|
context.report({
|
|
109
128
|
node,
|
|
110
129
|
messageId: lower === "transparent" ? "preferClearToken" : "preferCurrentToken",
|
|
@@ -127,13 +146,13 @@ var prefer_custom_property_syntax_default = createRule({
|
|
|
127
146
|
if (/^[A-Z@&$#]/.test(key)) continue;
|
|
128
147
|
const str = getStringValue(prop.value);
|
|
129
148
|
if (str) {
|
|
130
|
-
checkValue(str, prop.value);
|
|
149
|
+
checkValue(str, prop.value, key);
|
|
131
150
|
continue;
|
|
132
151
|
}
|
|
133
152
|
if (prop.value.type === "ObjectExpression") for (const stateProp of prop.value.properties) {
|
|
134
153
|
if (stateProp.type !== "Property") continue;
|
|
135
154
|
const stateStr = getStringValue(stateProp.value);
|
|
136
|
-
if (stateStr) checkValue(stateStr, stateProp.value);
|
|
155
|
+
if (stateStr) checkValue(stateStr, stateProp.value, key);
|
|
137
156
|
}
|
|
138
157
|
}
|
|
139
158
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-custom-property-syntax.js","names":[],"sources":["../../src/rules/prefer-custom-property-syntax.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 { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds =\n | 'preferCustomPropertySyntax'\n | 'preferColorToken'\n | 'preferClearToken'\n | 'preferCurrentToken';\n\nconst VAR_REGEX = /var\\(\\s*--([a-zA-Z0-9_-]+)\\s*(?:,\\s*([^)]+))?\\s*\\)/g;\n// `$x-color` → `#x`. Negative lookbehind avoids `$$func` / `##transition`.\n// `\\b` after `-color` avoids matching `$x-colorful`. Optional opacity suffix.\nconst COLOR_PROP_REGEX =\n /(?<![$#])\\$([a-zA-Z][a-zA-Z0-9_-]*)-color\\b(\\.[0-9]+|\\.\\$[a-zA-Z][a-zA-Z0-9_-]*)?/g;\nconst KEYWORD_REGEX = /\\b(transparent|currentColor)\\b/gi;\n\nfunction normalizeFallback(fallback: string): string {\n const trimmed = fallback.trim();\n const lower = trimmed.toLowerCase();\n if (lower === 'transparent') return '#clear';\n if (lower === 'currentcolor') return '#current';\n return trimmed;\n}\n\nfunction suggestVarSyntax(name: string, fallback?: string): string {\n if (name.endsWith('-color')) {\n const tokenName = name.slice(0, -'-color'.length);\n const tastyToken = `#${tokenName}`;\n if (fallback) {\n return `(${tastyToken}, ${normalizeFallback(fallback)})`;\n }\n return tastyToken;\n }\n\n const propToken = `$${name}`;\n if (fallback) {\n return `(${propToken}, ${normalizeFallback(fallback)})`;\n }\n return propToken;\n}\n\ninterface Span {\n start: number;\n end: number;\n}\n\nfunction isInsideSpans(start: number, end: number, spans: Span[]): boolean {\n for (const span of spans) {\n if (start >= span.start && end <= span.end) return true;\n }\n return false;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-custom-property-syntax',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Prefer Tasty token syntax ($prop, #color, #clear, #current) over raw CSS (var(), $x-color, transparent, currentColor)',\n },\n messages: {\n preferCustomPropertySyntax:\n \"Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'.\",\n preferColorToken:\n \"Use color token '{{suggestion}}' instead of custom property reference '{{raw}}'.\",\n preferClearToken:\n \"Use the '#clear' token instead of the 'transparent' keyword.\",\n preferCurrentToken:\n \"Use the '#current' token instead of the 'currentColor' keyword.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(value: string, node: TSESTree.Node): void {\n // Pass 1: var(--x) / var(--x, fallback) → $x / (#x, fallback).\n // Records spans so standalone keywords inside var() aren't double-reported.\n const varSpans: Span[] = [];\n VAR_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = VAR_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const fallback = match[2];\n const suggestion = suggestVarSyntax(name, fallback);\n const start = match.index;\n const end = start + raw.length;\n\n if (match.index !== undefined) {\n varSpans.push({\n start: match.index,\n end: match.index + raw.length,\n });\n }\n\n context.report({\n node,\n messageId: 'preferCustomPropertySyntax',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 2: $x-color → #x.\n COLOR_PROP_REGEX.lastIndex = 0;\n while ((match = COLOR_PROP_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const opacity = match[2] ?? '';\n const suggestion = `#${name}${opacity}`;\n const start = match.index;\n const end = start + raw.length;\n\n context.report({\n node,\n messageId: 'preferColorToken',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 3: standalone transparent / currentColor (skip inside var()).\n KEYWORD_REGEX.lastIndex = 0;\n while ((match = KEYWORD_REGEX.exec(value)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (isInsideSpans(start, end, varSpans)) continue;\n\n const lower = match[0].toLowerCase();\n const replacement = lower === 'transparent' ? '#clear' : '#current';\n context.report({\n node,\n messageId:\n lower === 'transparent' ? 'preferClearToken' : 'preferCurrentToken',\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement }],\n context.sourceCode,\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 if (/^[A-Z@&$#]/.test(key)) continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(str, prop.value);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkValue(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
|
+
{"version":3,"file":"prefer-custom-property-syntax.js","names":[],"sources":["../../src/rules/prefer-custom-property-syntax.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport {\n PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION,\n PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION,\n} from '../constants.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds =\n | 'preferCustomPropertySyntax'\n | 'preferColorToken'\n | 'preferClearToken'\n | 'preferCurrentToken';\n\nconst VAR_REGEX = /var\\(\\s*--([a-zA-Z0-9_-]+)\\s*(?:,\\s*([^)]+))?\\s*\\)/g;\n// `$x-color` → `#x`. Negative lookbehind avoids `$$func` / `##transition`.\n// Optional opacity suffix.\n//\n// `-color` must END the identifier, hence the lookahead rather than `\\b`. `\\b`\n// matches between `color` and a following `-`, so `$purple-color-rgb` matched as\n// `$purple-color` and was rewritten to `#purple-rgb` — a property that does not\n// exist. That bit in practice: `var(--purple-color-rgb)` became `$purple-color-rgb`\n// on one --fix pass and `#purple-rgb` on the next, and the declaration was dropped\n// at computed-value time with no error anywhere.\nconst COLOR_PROP_REGEX =\n /(?<![$#])\\$([a-zA-Z][a-zA-Z0-9_-]*)-color(?![a-zA-Z0-9_-])(\\.[0-9]+|\\.\\$[a-zA-Z][a-zA-Z0-9_-]*)?/g;\nconst KEYWORD_REGEX = /\\b(transparent|currentColor)\\b/gi;\n\nfunction normalizeFallback(fallback: string): string {\n const trimmed = fallback.trim();\n const lower = trimmed.toLowerCase();\n if (lower === 'transparent') return '#clear';\n if (lower === 'currentcolor') return '#current';\n return trimmed;\n}\n\nfunction suggestVarSyntax(name: string, fallback?: string): string {\n if (name.endsWith('-color')) {\n const tokenName = name.slice(0, -'-color'.length);\n const tastyToken = `#${tokenName}`;\n if (fallback) {\n return `(${tastyToken}, ${normalizeFallback(fallback)})`;\n }\n return tastyToken;\n }\n\n const propToken = `$${name}`;\n if (fallback) {\n return `(${propToken}, ${normalizeFallback(fallback)})`;\n }\n return propToken;\n}\n\ninterface Span {\n start: number;\n end: number;\n}\n\nfunction isInsideSpans(start: number, end: number, spans: Span[]): boolean {\n for (const span of spans) {\n if (start >= span.start && end <= span.end) return true;\n }\n return false;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-custom-property-syntax',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Prefer Tasty token syntax ($prop, #color, #clear, #current) over raw CSS (var(), $x-color, transparent, currentColor)',\n },\n messages: {\n preferCustomPropertySyntax:\n \"Use Tasty token syntax '{{suggestion}}' instead of '{{raw}}'.\",\n preferColorToken:\n \"Use color token '{{suggestion}}' instead of custom property reference '{{raw}}'.\",\n preferClearToken:\n \"Use the '#clear' token instead of the 'transparent' keyword.\",\n preferCurrentToken:\n \"Use the '#current' token instead of the 'currentColor' keyword.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n /**\n * Whether rewriting to `suggestion` actually round-trips for `property`.\n *\n * Every rewrite this rule offers has to render to the same CSS as what it\n * replaces. Tasty expands `$name` and `#name` per property, not globally, so\n * a suggestion that is correct on `gap` can silently delete the declaration on\n * `fontFamily`. Suppress the report entirely rather than offer a broken fix —\n * the rule is autofixable and runs unattended in pre-commit hooks.\n */\n function roundTrips(property: string, suggestion: string): boolean {\n if (suggestion.startsWith('$')) {\n return !PROPERTIES_WITHOUT_CUSTOM_PROPERTY_EXPANSION.has(property);\n }\n\n if (suggestion.startsWith('#')) {\n return !PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION.has(property);\n }\n\n // `(token, fallback)` auto-calc form — the token inside decides.\n if (suggestion.startsWith('(')) {\n const inner = suggestion.slice(1);\n\n return roundTrips(property, inner);\n }\n\n return true;\n }\n\n function checkValue(\n value: string,\n node: TSESTree.Node,\n property: string,\n ): void {\n // Pass 1: var(--x) / var(--x, fallback) → $x / (#x, fallback).\n // Records spans so standalone keywords inside var() aren't double-reported.\n const varSpans: Span[] = [];\n VAR_REGEX.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = VAR_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const fallback = match[2];\n const suggestion = suggestVarSyntax(name, fallback);\n const start = match.index;\n const end = start + raw.length;\n\n if (!roundTrips(property, suggestion)) continue;\n\n if (match.index !== undefined) {\n varSpans.push({\n start: match.index,\n end: match.index + raw.length,\n });\n }\n\n context.report({\n node,\n messageId: 'preferCustomPropertySyntax',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 2: $x-color → #x.\n COLOR_PROP_REGEX.lastIndex = 0;\n while ((match = COLOR_PROP_REGEX.exec(value)) !== null) {\n const raw = match[0];\n const name = match[1];\n const opacity = match[2] ?? '';\n const suggestion = `#${name}${opacity}`;\n const start = match.index;\n const end = start + raw.length;\n\n if (!roundTrips(property, suggestion)) continue;\n\n context.report({\n node,\n messageId: 'preferColorToken',\n data: { raw, suggestion },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement: suggestion }],\n context.sourceCode,\n );\n },\n });\n }\n\n // Pass 3: standalone transparent / currentColor (skip inside var()).\n KEYWORD_REGEX.lastIndex = 0;\n while ((match = KEYWORD_REGEX.exec(value)) !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (isInsideSpans(start, end, varSpans)) continue;\n\n const lower = match[0].toLowerCase();\n const replacement = lower === 'transparent' ? '#clear' : '#current';\n\n if (!roundTrips(property, replacement)) continue;\n\n context.report({\n node,\n messageId:\n lower === 'transparent' ? 'preferClearToken' : 'preferCurrentToken',\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n [{ start, end, replacement }],\n context.sourceCode,\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 if (/^[A-Z@&$#]/.test(key)) continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(str, prop.value, key);\n continue;\n }\n\n if (prop.value.type === 'ObjectExpression') {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n // State maps keep the outer property's expansion rules.\n checkValue(stateStr, stateProp.value, key);\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/eslint-plugin-tasty",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "ESLint plugin for validating tasty() and tastyStatic() style objects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,12 +50,14 @@
|
|
|
50
50
|
"@changesets/changelog-github": "^0.5.2",
|
|
51
51
|
"@changesets/cli": "^2.29.8",
|
|
52
52
|
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@tenphi/tasty": "^3.0.1",
|
|
53
54
|
"@types/node": "^22.0.0",
|
|
54
55
|
"@typescript-eslint/parser": "^8.56.1",
|
|
55
56
|
"@typescript-eslint/rule-tester": "^8.56.1",
|
|
56
57
|
"eslint": "^10.0.0",
|
|
57
58
|
"eslint-config-prettier": "^10.1.8",
|
|
58
59
|
"prettier": "^3.8.1",
|
|
60
|
+
"react": "^19.2.8",
|
|
59
61
|
"tsdown": "^0.20.3",
|
|
60
62
|
"typescript": "^5.9.3",
|
|
61
63
|
"typescript-eslint": "^8.56.0",
|