@tenphi/eslint-plugin-tasty 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/constants.js +8 -32
- package/dist/constants.js.map +1 -1
- package/dist/context.js +92 -42
- package/dist/context.js.map +1 -1
- package/dist/module-components.js +32 -15
- package/dist/module-components.js.map +1 -1
- package/dist/parsers/value-parser.js +6 -0
- package/dist/parsers/value-parser.js.map +1 -1
- package/dist/property-expectations.js +7 -2
- package/dist/property-expectations.js.map +1 -1
- package/dist/protected-components.js +61 -32
- package/dist/protected-components.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +1 -0
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-value.js +1 -1
- package/dist/rules/valid-value.js.map +1 -1
- package/dist/style-values.js +6 -1
- package/dist/style-values.js.map +1 -1
- package/dist/type-aware-jsx.js +10 -2
- package/dist/type-aware-jsx.js.map +1 -1
- package/dist/types.d.ts +9 -2
- package/dist/utils.js +10 -1
- package/dist/utils.js.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-parser.js","names":[],"sources":["../../src/parsers/value-parser.ts"],"sourcesContent":["import { scanTokens, checkBracketBalance } from './utils.js';\nimport {\n BUILT_IN_UNITS,\n CSS_UNITS,\n NAMED_CSS_COLORS,\n SYSTEM_COLORS,\n} from '../constants.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ValueToken =\n | { type: 'color-token'; name: string; opacity?: string; raw: string }\n | { type: 'color-ref'; name: string; raw: string }\n /** A CSS named color (`rebeccapurple`) or system color (`Canvas`). */\n | { type: 'color-keyword'; raw: string }\n | { type: 'custom-prop'; name: string; raw: string }\n | { type: 'custom-prop-ref'; name: string; raw: string }\n | { type: 'custom-unit'; value: number; unit: string; raw: string }\n | { type: 'css-unit'; value: number; unit: string; raw: string }\n | { type: 'number'; value: number; raw: string }\n | { type: 'keyword'; value: string }\n | { type: 'css-function'; name: string; args: string; raw: string }\n /**\n * A Tasty CSS `@function` call: `$$name(args)` (emits native `--name(args)`).\n * Distinct from `custom-prop-ref`, which is the bare `$$name` form that\n * `transition` uses to name a custom property.\n */\n | { type: 'tasty-function'; name: string; args: string; raw: string }\n | { type: 'string'; value: string; raw: string }\n | { type: 'important'; raw: string }\n | { type: 'group-expr'; inner: string; raw: string }\n | { type: 'unknown'; raw: string };\n\nexport interface ValueError {\n message: string;\n offset: number;\n length: number;\n raw?: string;\n}\n\nexport interface ValuePart {\n tokens: ValueToken[];\n}\n\nexport interface ValueGroup {\n parts: ValuePart[];\n}\n\nexport interface ValueParseResult {\n groups: ValueGroup[];\n errors: ValueError[];\n}\n\nexport interface ValueParserOptions {\n knownUnits?: Set<string> | string[];\n knownFuncs?: Set<string> | string[];\n skipUnitValidation?: boolean;\n skipFuncValidation?: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst RE_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z][a-z0-9]*)$/;\nconst RE_NUMBER = /^[+-]?(?:\\d*\\.\\d+|\\d+)$/;\nconst RE_CSS_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z%]+)$/;\n\nconst COLOR_FUNCS = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'device-cmyk',\n 'gray',\n 'color-mix',\n 'color-contrast',\n]);\n\nconst CSS_FUNCS = new Set([\n 'calc',\n 'calc-size',\n 'min',\n 'max',\n 'clamp',\n 'abs',\n 'sign',\n 'round',\n 'mod',\n 'rem',\n 'sin',\n 'cos',\n 'tan',\n 'asin',\n 'acos',\n 'atan',\n 'atan2',\n 'pow',\n 'sqrt',\n 'hypot',\n 'log',\n 'exp',\n 'var',\n 'env',\n 'attr',\n 'counter',\n 'counters',\n 'image-set',\n 'cross-fade',\n 'image',\n 'paint',\n 'element',\n 'linear-gradient',\n 'radial-gradient',\n 'conic-gradient',\n 'repeating-linear-gradient',\n 'repeating-radial-gradient',\n 'repeating-conic-gradient',\n 'url',\n 'fit-content',\n 'minmax',\n 'repeat',\n 'cubic-bezier',\n 'steps',\n 'linear',\n 'rotate',\n 'scale',\n 'translate',\n 'skew',\n 'matrix',\n 'perspective',\n 'rotate3d',\n 'rotateX',\n 'rotateY',\n 'rotateZ',\n 'scale3d',\n 'scaleX',\n 'scaleY',\n 'scaleZ',\n 'translate3d',\n 'translateX',\n 'translateY',\n 'translateZ',\n 'skewX',\n 'skewY',\n 'matrix3d',\n 'blur',\n 'brightness',\n 'contrast',\n 'drop-shadow',\n 'grayscale',\n 'hue-rotate',\n 'invert',\n 'opacity',\n 'saturate',\n 'sepia',\n 'polygon',\n 'circle',\n 'ellipse',\n 'inset',\n 'path',\n 'xywh',\n 'rect',\n 'ray',\n 'light-dark',\n 'anchor',\n 'anchor-size',\n 'scroll',\n 'view',\n 'symbols',\n]);\n\nconst VALUE_KEYWORDS = new Set([\n 'auto',\n 'none',\n 'normal',\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n 'max-content',\n 'min-content',\n 'fit-content',\n 'stretch',\n 'transparent',\n 'currentcolor',\n 'currentColor',\n // display\n 'block',\n 'inline',\n 'inline-block',\n 'flex',\n 'inline-flex',\n 'grid',\n 'inline-grid',\n 'contents',\n 'table',\n 'table-row',\n 'table-cell',\n 'list-item',\n 'flow-root',\n // position\n 'static',\n 'relative',\n 'absolute',\n 'fixed',\n 'sticky',\n // overflow\n 'visible',\n 'hidden',\n 'scroll',\n 'clip',\n 'overlay',\n // flex/align\n 'center',\n 'start',\n 'end',\n 'flex-start',\n 'flex-end',\n 'space-between',\n 'space-around',\n 'space-evenly',\n 'baseline',\n // flow\n 'row',\n 'column',\n 'row-reverse',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'dense',\n // border\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'outset',\n // directional\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n // radius shapes\n 'round',\n 'ellipse',\n 'leaf',\n 'backleaf',\n // dimension modifiers\n 'min',\n 'max',\n // text\n 'bold',\n 'bolder',\n 'lighter',\n 'italic',\n 'oblique',\n 'uppercase',\n 'lowercase',\n 'capitalize',\n 'line-through',\n 'underline',\n 'overline',\n 'wavy',\n // cursor\n 'pointer',\n 'default',\n 'text',\n 'move',\n 'grab',\n 'grabbing',\n 'not-allowed',\n 'crosshair',\n 'wait',\n 'help',\n 'col-resize',\n 'row-resize',\n 'n-resize',\n 's-resize',\n 'e-resize',\n 'w-resize',\n 'ne-resize',\n 'nw-resize',\n 'se-resize',\n 'sw-resize',\n 'ew-resize',\n 'ns-resize',\n 'zoom-in',\n 'zoom-out',\n // misc\n 'cover',\n 'contain',\n 'fill',\n 'no-repeat',\n 'repeat-x',\n 'repeat-y',\n 'border-box',\n 'padding-box',\n 'content-box',\n 'break-word',\n 'break-all',\n 'keep-all',\n 'anywhere',\n 'pre',\n 'pre-wrap',\n 'pre-line',\n 'balance',\n 'smooth',\n 'horizontal',\n 'vertical',\n 'both',\n 'mandatory',\n 'proximity',\n // white-space\n 'collapse',\n 'preserve',\n 'preserve-breaks',\n 'break-spaces',\n // text-wrap\n 'pretty',\n 'stable',\n // will-change\n 'transform',\n 'opacity',\n // animation\n 'infinite',\n 'alternate',\n 'alternate-reverse',\n 'reverse',\n 'forwards',\n 'backwards',\n 'running',\n 'paused',\n 'ease',\n 'ease-in',\n 'ease-out',\n 'ease-in-out',\n 'step-start',\n 'step-end',\n // scrollbar\n 'thin',\n 'always',\n 'both-edges',\n // width/height\n 'fixed',\n // shadow\n 'inset',\n // textOverflow\n 'clip',\n // other\n 'ltr',\n 'rtl',\n 'embed',\n 'isolate',\n 'isolate-override',\n 'plaintext',\n 'horizontal-tb',\n 'vertical-rl',\n 'vertical-lr',\n 'sideways-rl',\n 'sideways-lr',\n 'monospace',\n 'serif',\n 'sans-serif',\n 'cursive',\n 'fantasy',\n 'system-ui',\n 'ui-serif',\n 'ui-sans-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'to',\n // misc\n 'strong',\n 'tight',\n 'icon',\n]);\n\n// ============================================================================\n// Classifier\n// ============================================================================\n\nfunction toSet(input?: Set<string> | string[]): Set<string> {\n if (!input) return new Set();\n return input instanceof Set ? input : new Set(input);\n}\n\nfunction classifyToken(\n raw: string,\n offset: number,\n errors: ValueError[],\n opts: ValueParserOptions,\n): ValueToken {\n const token = raw.trim();\n if (!token) return { type: 'keyword', value: '' };\n\n // Quoted strings\n if (\n (token.startsWith('\"') && token.endsWith('\"')) ||\n (token.startsWith(\"'\") && token.endsWith(\"'\"))\n ) {\n return { type: 'string', value: token.slice(1, -1), raw: token };\n }\n\n // !important\n if (token === '!important') {\n return { type: 'important', raw: token };\n }\n\n // Double prefix: $$name(...) is a Tasty `@function` call; bare $$name is the\n // custom-property reference `transition` uses. The call form must be checked\n // first, or the bare-name regex rejects `$$negative(10px)` outright.\n if (token.startsWith('$$') || token.startsWith('##')) {\n const call = classifyTastyFunctionCall(token, offset, errors);\n if (call) return call;\n }\n\n // Double prefix: $$name (custom property reference for transitions)\n if (token.startsWith('$$')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'custom-prop-ref', name, raw: token };\n }\n errors.push({\n message: `Invalid custom property reference '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Double prefix: ##name (color reference for transitions)\n if (token.startsWith('##')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'color-ref', name, raw: token };\n }\n errors.push({\n message: `Invalid color reference '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Custom property: $name\n if (token.startsWith('$')) {\n const name = token.slice(1);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'custom-prop', name, raw: token };\n }\n errors.push({\n message: `Invalid custom property syntax '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Color token: #name, #name.N, #name.$prop, or bare # (error)\n if (token.startsWith('#')) {\n if (token.length === 1) {\n errors.push({\n message: 'Empty color token name.',\n offset,\n length: 1,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n return classifyColorToken(token, offset, errors);\n }\n\n // Parenthesized expression: (expr) — fallback, auto-calc, etc.\n if (token.startsWith('(') && token.endsWith(')')) {\n return { type: 'group-expr', inner: token.slice(1, -1), raw: token };\n }\n\n // Function call: name(...)\n const openIdx = token.indexOf('(');\n if (openIdx > 0 && token.endsWith(')')) {\n const fname = token.slice(0, openIdx);\n const args = token.slice(openIdx + 1, -1);\n\n if (opts.skipFuncValidation) {\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n const knownFuncs = toSet(opts.knownFuncs);\n\n if (\n COLOR_FUNCS.has(fname) ||\n CSS_FUNCS.has(fname) ||\n knownFuncs.has(fname)\n ) {\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n // Unknown function — still return as css-function but flag it\n errors.push({\n message: `Unknown function '${fname}()'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n // Unit number: 2x, 1.5r, 10px, 50%, 1fr\n const unitMatch = token.match(RE_UNIT_NUM);\n if (unitMatch) {\n const unit = unitMatch[1];\n const numVal = parseFloat(token.slice(0, -unit.length));\n\n if (opts.skipUnitValidation) {\n return { type: 'custom-unit', value: numVal, unit, raw: token };\n }\n\n const knownUnits = toSet(opts.knownUnits);\n\n if (BUILT_IN_UNITS.has(unit) || knownUnits.has(unit)) {\n return { type: 'custom-unit', value: numVal, unit, raw: token };\n }\n if (CSS_UNITS.has(unit)) {\n return { type: 'css-unit', value: numVal, unit, raw: token };\n }\n\n errors.push({\n message: `Unknown unit '${unit}' in '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // CSS unit with % (RE_UNIT_NUM doesn't match % since it expects alpha)\n const cssUnitMatch = token.match(RE_CSS_UNIT_NUM);\n if (cssUnitMatch) {\n const unit = cssUnitMatch[1];\n const numVal = parseFloat(token.slice(0, -unit.length));\n if (CSS_UNITS.has(unit)) {\n return { type: 'css-unit', value: numVal, unit, raw: token };\n }\n }\n\n // Plain number\n if (RE_NUMBER.test(token)) {\n return { type: 'number', value: parseFloat(token), raw: token };\n }\n\n // Tasty merge directive (@inherit)\n if (token === '@inherit') {\n return { type: 'keyword', value: token };\n }\n\n // Known keyword\n if (VALUE_KEYWORDS.has(token) || VALUE_KEYWORDS.has(token.toLowerCase())) {\n return { type: 'keyword', value: token };\n }\n\n // Color keyword: no token syntax, so the keyword itself is the color.\n // Matched case-insensitively, as CSS and Tasty match them.\n const lower = token.toLowerCase();\n if (NAMED_CSS_COLORS.has(lower) || SYSTEM_COLORS.has(lower)) {\n return { type: 'color-keyword', raw: token };\n }\n\n // CSS custom property function var(--name)\n if (token.startsWith('var(') && token.endsWith(')')) {\n return {\n type: 'css-function',\n name: 'var',\n args: token.slice(4, -1),\n raw: token,\n };\n }\n\n // Unknown token\n return { type: 'unknown', raw: token };\n}\n\n/**\n * Classify a `$$name(...)` / `##name(...)` Tasty `@function` call.\n *\n * Returns `null` when the token is not a call, so the caller falls through to the\n * bare `$$name` / `##name` reference handling that `transition` relies on.\n */\nfunction classifyTastyFunctionCall(\n token: string,\n offset: number,\n errors: ValueError[],\n): ValueToken | null {\n const openIdx = token.indexOf('(');\n\n if (openIdx <= 2 || !token.endsWith(')')) return null;\n\n const name = token.slice(2, openIdx);\n const args = token.slice(openIdx + 1, -1);\n\n if (!/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n errors.push({\n message: `Invalid function name in '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n return { type: 'tasty-function', name, args, raw: token };\n}\n\nfunction classifyColorToken(\n token: string,\n offset: number,\n errors: ValueError[],\n): ValueToken {\n const raw = token;\n\n // Strip leading #\n const name = token.slice(1);\n\n if (name.length === 0) {\n errors.push({\n message: 'Empty color token name.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Check for opacity suffix\n const dotIndex = name.indexOf('.');\n if (dotIndex !== -1) {\n const tokenName = name.slice(0, dotIndex);\n const opacitySuffix = name.slice(dotIndex + 1);\n\n if (tokenName.length === 0) {\n errors.push({\n message: 'Empty color token name before opacity.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Dynamic opacity from CSS custom property\n if (opacitySuffix.startsWith('$')) {\n return {\n type: 'color-token',\n name: tokenName,\n opacity: opacitySuffix,\n raw,\n };\n }\n\n if (opacitySuffix.length === 0) {\n errors.push({\n message: 'Trailing dot with no opacity value.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Tasty reads the digits as a decimal fraction (`.5` and `.50` are 50%,\n // `.150` is 15%), so any digit string is in range.\n if (!/^\\d+$/.test(opacitySuffix)) {\n errors.push({\n message: /^-\\d+$/.test(opacitySuffix)\n ? 'Opacity cannot be negative.'\n : `Invalid opacity value '${opacitySuffix}'.`,\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n return {\n type: 'color-token',\n name: tokenName,\n opacity: opacitySuffix,\n raw,\n };\n }\n\n return { type: 'color-token', name, raw };\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Parse a style value string into typed tokens organized by\n * comma-separated groups and slash-separated parts.\n */\nexport function parseValue(\n src: string,\n opts: ValueParserOptions = {},\n): ValueParseResult {\n const errors: ValueError[] = [];\n\n // Check bracket balance first\n const bracketError = checkBracketBalance(src);\n if (bracketError) {\n errors.push({\n message: bracketError.message,\n offset: bracketError.position,\n length: 1,\n });\n return { groups: [], errors };\n }\n\n const scanned = scanTokens(src);\n\n const groups: ValueGroup[] = [];\n let currentParts: ValuePart[] = [];\n let currentTokens: ValueToken[] = [];\n\n const endPart = () => {\n if (currentTokens.length > 0) {\n currentParts.push({ tokens: currentTokens });\n currentTokens = [];\n }\n };\n\n const endGroup = () => {\n endPart();\n if (currentParts.length > 0) {\n groups.push({ parts: currentParts });\n } else {\n groups.push({ parts: [{ tokens: [] }] });\n }\n currentParts = [];\n };\n\n for (const scanned_token of scanned) {\n if (scanned_token.value) {\n const classified = classifyToken(\n scanned_token.value,\n scanned_token.offset,\n errors,\n opts,\n );\n currentTokens.push(classified);\n }\n if (scanned_token.isSlash) endPart();\n if (scanned_token.isComma) endGroup();\n }\n\n // Push final group\n if (\n currentTokens.length > 0 ||\n currentParts.length > 0 ||\n groups.length === 0\n ) {\n endGroup();\n }\n\n return { groups, errors };\n}\n\n/**\n * Extract all tokens of a specific type from a parse result.\n */\nexport function extractTokensByType<T extends ValueToken['type']>(\n result: ValueParseResult,\n type: T,\n): Extract<ValueToken, { type: T }>[] {\n const tokens: Extract<ValueToken, { type: T }>[] = [];\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n if (token.type === type) {\n tokens.push(token as Extract<ValueToken, { type: T }>);\n }\n }\n }\n }\n return tokens;\n}\n\n/**\n * Get a flat list of all tokens from a parse result.\n */\nexport function flattenTokens(result: ValueParseResult): ValueToken[] {\n const tokens: ValueToken[] = [];\n for (const group of result.groups) {\n for (const part of group.parts) {\n tokens.push(...part.tokens);\n }\n }\n return tokens;\n}\n"],"mappings":";;;;AAkEA,MAAM,cAAc;AACpB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAExB,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,YAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;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;AAEF,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;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"}
|
|
1
|
+
{"version":3,"file":"value-parser.js","names":[],"sources":["../../src/parsers/value-parser.ts"],"sourcesContent":["import { scanTokens, checkBracketBalance } from './utils.js';\nimport {\n BUILT_IN_UNITS,\n CSS_UNITS,\n NAMED_CSS_COLORS,\n SYSTEM_COLORS,\n} from '../constants.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ValueToken =\n | { type: 'color-token'; name: string; opacity?: string; raw: string }\n | { type: 'color-ref'; name: string; raw: string }\n /** A CSS named color (`rebeccapurple`) or system color (`Canvas`). */\n | { type: 'color-keyword'; raw: string }\n | { type: 'custom-prop'; name: string; raw: string }\n | { type: 'custom-prop-ref'; name: string; raw: string }\n | { type: 'custom-unit'; value: number; unit: string; raw: string }\n | { type: 'css-unit'; value: number; unit: string; raw: string }\n | { type: 'number'; value: number; raw: string }\n /** An unspaced `<ratio>`, such as `16/10`; the spaced form is two parts. */\n | { type: 'ratio'; raw: string }\n | { type: 'keyword'; value: string }\n | { type: 'css-function'; name: string; args: string; raw: string }\n /**\n * A Tasty CSS `@function` call: `$$name(args)` (emits native `--name(args)`).\n * Distinct from `custom-prop-ref`, which is the bare `$$name` form that\n * `transition` uses to name a custom property.\n */\n | { type: 'tasty-function'; name: string; args: string; raw: string }\n | { type: 'string'; value: string; raw: string }\n | { type: 'important'; raw: string }\n | { type: 'group-expr'; inner: string; raw: string }\n | { type: 'unknown'; raw: string };\n\nexport interface ValueError {\n message: string;\n offset: number;\n length: number;\n raw?: string;\n}\n\nexport interface ValuePart {\n tokens: ValueToken[];\n}\n\nexport interface ValueGroup {\n parts: ValuePart[];\n}\n\nexport interface ValueParseResult {\n groups: ValueGroup[];\n errors: ValueError[];\n}\n\nexport interface ValueParserOptions {\n knownUnits?: Set<string> | string[];\n knownFuncs?: Set<string> | string[];\n skipUnitValidation?: boolean;\n skipFuncValidation?: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst RE_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z][a-z0-9]*)$/;\nconst RE_NUMBER = /^[+-]?(?:\\d*\\.\\d+|\\d+)$/;\nconst RE_CSS_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z%]+)$/;\n/** An unspaced `<ratio>`, such as `16/10`. */\nconst RE_RATIO = /^(?:\\d*\\.\\d+|\\d+)\\/(?:\\d*\\.\\d+|\\d+)$/;\n\nconst COLOR_FUNCS = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'device-cmyk',\n 'gray',\n 'color-mix',\n 'color-contrast',\n]);\n\nconst CSS_FUNCS = new Set([\n 'calc',\n 'calc-size',\n 'min',\n 'max',\n 'clamp',\n 'abs',\n 'sign',\n 'round',\n 'mod',\n 'rem',\n 'sin',\n 'cos',\n 'tan',\n 'asin',\n 'acos',\n 'atan',\n 'atan2',\n 'pow',\n 'sqrt',\n 'hypot',\n 'log',\n 'exp',\n 'var',\n 'env',\n 'attr',\n 'counter',\n 'counters',\n 'image-set',\n 'cross-fade',\n 'image',\n 'paint',\n 'element',\n 'linear-gradient',\n 'radial-gradient',\n 'conic-gradient',\n 'repeating-linear-gradient',\n 'repeating-radial-gradient',\n 'repeating-conic-gradient',\n 'url',\n 'fit-content',\n 'minmax',\n 'repeat',\n 'cubic-bezier',\n 'steps',\n 'linear',\n 'rotate',\n 'scale',\n 'translate',\n 'skew',\n 'matrix',\n 'perspective',\n 'rotate3d',\n 'rotateX',\n 'rotateY',\n 'rotateZ',\n 'scale3d',\n 'scaleX',\n 'scaleY',\n 'scaleZ',\n 'translate3d',\n 'translateX',\n 'translateY',\n 'translateZ',\n 'skewX',\n 'skewY',\n 'matrix3d',\n 'blur',\n 'brightness',\n 'contrast',\n 'drop-shadow',\n 'grayscale',\n 'hue-rotate',\n 'invert',\n 'opacity',\n 'saturate',\n 'sepia',\n 'polygon',\n 'circle',\n 'ellipse',\n 'inset',\n 'path',\n 'xywh',\n 'rect',\n 'ray',\n 'light-dark',\n 'anchor',\n 'anchor-size',\n 'scroll',\n 'view',\n 'symbols',\n]);\n\nconst VALUE_KEYWORDS = new Set([\n 'auto',\n 'none',\n 'normal',\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n 'max-content',\n 'min-content',\n 'fit-content',\n 'stretch',\n 'transparent',\n 'currentcolor',\n 'currentColor',\n // display\n 'block',\n 'inline',\n 'inline-block',\n 'flex',\n 'inline-flex',\n 'grid',\n 'inline-grid',\n 'contents',\n 'table',\n 'table-row',\n 'table-cell',\n 'list-item',\n 'flow-root',\n // position\n 'static',\n 'relative',\n 'absolute',\n 'fixed',\n 'sticky',\n // overflow\n 'visible',\n 'hidden',\n 'scroll',\n 'clip',\n 'overlay',\n // flex/align\n 'center',\n 'start',\n 'end',\n 'flex-start',\n 'flex-end',\n 'space-between',\n 'space-around',\n 'space-evenly',\n 'baseline',\n // flow\n 'row',\n 'column',\n 'row-reverse',\n 'column-reverse',\n 'wrap',\n 'nowrap',\n 'dense',\n // border\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'outset',\n // directional\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n // radius shapes\n 'round',\n 'ellipse',\n 'leaf',\n 'backleaf',\n // dimension modifiers\n 'min',\n 'max',\n // text\n 'bold',\n 'bolder',\n 'lighter',\n 'italic',\n 'oblique',\n 'uppercase',\n 'lowercase',\n 'capitalize',\n 'line-through',\n 'underline',\n 'overline',\n 'wavy',\n // cursor\n 'pointer',\n 'default',\n 'text',\n 'move',\n 'grab',\n 'grabbing',\n 'not-allowed',\n 'crosshair',\n 'wait',\n 'help',\n 'col-resize',\n 'row-resize',\n 'n-resize',\n 's-resize',\n 'e-resize',\n 'w-resize',\n 'ne-resize',\n 'nw-resize',\n 'se-resize',\n 'sw-resize',\n 'ew-resize',\n 'ns-resize',\n 'zoom-in',\n 'zoom-out',\n // misc\n 'cover',\n 'contain',\n 'fill',\n 'no-repeat',\n 'repeat-x',\n 'repeat-y',\n 'border-box',\n 'padding-box',\n 'content-box',\n 'break-word',\n 'break-all',\n 'keep-all',\n 'anywhere',\n 'pre',\n 'pre-wrap',\n 'pre-line',\n 'balance',\n 'smooth',\n 'horizontal',\n 'vertical',\n 'both',\n 'mandatory',\n 'proximity',\n // white-space\n 'collapse',\n 'preserve',\n 'preserve-breaks',\n 'break-spaces',\n // text-wrap\n 'pretty',\n 'stable',\n // will-change\n 'transform',\n 'opacity',\n // animation\n 'infinite',\n 'alternate',\n 'alternate-reverse',\n 'reverse',\n 'forwards',\n 'backwards',\n 'running',\n 'paused',\n 'ease',\n 'ease-in',\n 'ease-out',\n 'ease-in-out',\n 'step-start',\n 'step-end',\n // scrollbar\n 'thin',\n 'always',\n 'both-edges',\n // width/height\n 'fixed',\n // shadow\n 'inset',\n // textOverflow\n 'clip',\n // other\n 'ltr',\n 'rtl',\n 'embed',\n 'isolate',\n 'isolate-override',\n 'plaintext',\n 'horizontal-tb',\n 'vertical-rl',\n 'vertical-lr',\n 'sideways-rl',\n 'sideways-lr',\n 'monospace',\n 'serif',\n 'sans-serif',\n 'cursive',\n 'fantasy',\n 'system-ui',\n 'ui-serif',\n 'ui-sans-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'to',\n // misc\n 'strong',\n 'tight',\n 'icon',\n]);\n\n// ============================================================================\n// Classifier\n// ============================================================================\n\nfunction toSet(input?: Set<string> | string[]): Set<string> {\n if (!input) return new Set();\n return input instanceof Set ? input : new Set(input);\n}\n\nfunction classifyToken(\n raw: string,\n offset: number,\n errors: ValueError[],\n opts: ValueParserOptions,\n): ValueToken {\n const token = raw.trim();\n if (!token) return { type: 'keyword', value: '' };\n\n // Quoted strings\n if (\n (token.startsWith('\"') && token.endsWith('\"')) ||\n (token.startsWith(\"'\") && token.endsWith(\"'\"))\n ) {\n return { type: 'string', value: token.slice(1, -1), raw: token };\n }\n\n // !important\n if (token === '!important') {\n return { type: 'important', raw: token };\n }\n\n // Double prefix: $$name(...) is a Tasty `@function` call; bare $$name is the\n // custom-property reference `transition` uses. The call form must be checked\n // first, or the bare-name regex rejects `$$negative(10px)` outright.\n if (token.startsWith('$$') || token.startsWith('##')) {\n const call = classifyTastyFunctionCall(token, offset, errors);\n if (call) return call;\n }\n\n // Double prefix: $$name (custom property reference for transitions)\n if (token.startsWith('$$')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'custom-prop-ref', name, raw: token };\n }\n errors.push({\n message: `Invalid custom property reference '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Double prefix: ##name (color reference for transitions)\n if (token.startsWith('##')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'color-ref', name, raw: token };\n }\n errors.push({\n message: `Invalid color reference '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Custom property: $name\n if (token.startsWith('$')) {\n const name = token.slice(1);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { type: 'custom-prop', name, raw: token };\n }\n errors.push({\n message: `Invalid custom property syntax '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // Color token: #name, #name.N, #name.$prop, or bare # (error)\n if (token.startsWith('#')) {\n if (token.length === 1) {\n errors.push({\n message: 'Empty color token name.',\n offset,\n length: 1,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n return classifyColorToken(token, offset, errors);\n }\n\n // Parenthesized expression: (expr) — fallback, auto-calc, etc.\n if (token.startsWith('(') && token.endsWith(')')) {\n return { type: 'group-expr', inner: token.slice(1, -1), raw: token };\n }\n\n // Function call: name(...)\n const openIdx = token.indexOf('(');\n if (openIdx > 0 && token.endsWith(')')) {\n const fname = token.slice(0, openIdx);\n const args = token.slice(openIdx + 1, -1);\n\n if (opts.skipFuncValidation) {\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n const knownFuncs = toSet(opts.knownFuncs);\n\n if (\n COLOR_FUNCS.has(fname) ||\n CSS_FUNCS.has(fname) ||\n knownFuncs.has(fname)\n ) {\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n // Unknown function — still return as css-function but flag it\n errors.push({\n message: `Unknown function '${fname}()'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'css-function', name: fname, args, raw: token };\n }\n\n // Unit number: 2x, 1.5r, 10px, 50%, 1fr\n const unitMatch = token.match(RE_UNIT_NUM);\n if (unitMatch) {\n const unit = unitMatch[1];\n const numVal = parseFloat(token.slice(0, -unit.length));\n\n if (opts.skipUnitValidation) {\n return { type: 'custom-unit', value: numVal, unit, raw: token };\n }\n\n const knownUnits = toSet(opts.knownUnits);\n\n if (BUILT_IN_UNITS.has(unit) || knownUnits.has(unit)) {\n return { type: 'custom-unit', value: numVal, unit, raw: token };\n }\n if (CSS_UNITS.has(unit)) {\n return { type: 'css-unit', value: numVal, unit, raw: token };\n }\n\n errors.push({\n message: `Unknown unit '${unit}' in '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n // CSS unit with % (RE_UNIT_NUM doesn't match % since it expects alpha)\n const cssUnitMatch = token.match(RE_CSS_UNIT_NUM);\n if (cssUnitMatch) {\n const unit = cssUnitMatch[1];\n const numVal = parseFloat(token.slice(0, -unit.length));\n if (CSS_UNITS.has(unit)) {\n return { type: 'css-unit', value: numVal, unit, raw: token };\n }\n }\n\n // Plain number\n if (RE_NUMBER.test(token)) {\n return { type: 'number', value: parseFloat(token), raw: token };\n }\n\n // Unspaced ratio: 16/10\n if (RE_RATIO.test(token)) {\n return { type: 'ratio', raw: token };\n }\n\n // Tasty merge directive (@inherit)\n if (token === '@inherit') {\n return { type: 'keyword', value: token };\n }\n\n // Known keyword\n if (VALUE_KEYWORDS.has(token) || VALUE_KEYWORDS.has(token.toLowerCase())) {\n return { type: 'keyword', value: token };\n }\n\n // Color keyword: no token syntax, so the keyword itself is the color.\n // Matched case-insensitively, as CSS and Tasty match them.\n const lower = token.toLowerCase();\n if (NAMED_CSS_COLORS.has(lower) || SYSTEM_COLORS.has(lower)) {\n return { type: 'color-keyword', raw: token };\n }\n\n // CSS custom property function var(--name)\n if (token.startsWith('var(') && token.endsWith(')')) {\n return {\n type: 'css-function',\n name: 'var',\n args: token.slice(4, -1),\n raw: token,\n };\n }\n\n // Unknown token\n return { type: 'unknown', raw: token };\n}\n\n/**\n * Classify a `$$name(...)` / `##name(...)` Tasty `@function` call.\n *\n * Returns `null` when the token is not a call, so the caller falls through to the\n * bare `$$name` / `##name` reference handling that `transition` relies on.\n */\nfunction classifyTastyFunctionCall(\n token: string,\n offset: number,\n errors: ValueError[],\n): ValueToken | null {\n const openIdx = token.indexOf('(');\n\n if (openIdx <= 2 || !token.endsWith(')')) return null;\n\n const name = token.slice(2, openIdx);\n const args = token.slice(openIdx + 1, -1);\n\n if (!/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n errors.push({\n message: `Invalid function name in '${token}'.`,\n offset,\n length: token.length,\n raw: token,\n });\n return { type: 'unknown', raw: token };\n }\n\n return { type: 'tasty-function', name, args, raw: token };\n}\n\nfunction classifyColorToken(\n token: string,\n offset: number,\n errors: ValueError[],\n): ValueToken {\n const raw = token;\n\n // Strip leading #\n const name = token.slice(1);\n\n if (name.length === 0) {\n errors.push({\n message: 'Empty color token name.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Check for opacity suffix\n const dotIndex = name.indexOf('.');\n if (dotIndex !== -1) {\n const tokenName = name.slice(0, dotIndex);\n const opacitySuffix = name.slice(dotIndex + 1);\n\n if (tokenName.length === 0) {\n errors.push({\n message: 'Empty color token name before opacity.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Dynamic opacity from CSS custom property\n if (opacitySuffix.startsWith('$')) {\n return {\n type: 'color-token',\n name: tokenName,\n opacity: opacitySuffix,\n raw,\n };\n }\n\n if (opacitySuffix.length === 0) {\n errors.push({\n message: 'Trailing dot with no opacity value.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n // Tasty reads the digits as a decimal fraction (`.5` and `.50` are 50%,\n // `.150` is 15%), so any digit string is in range.\n if (!/^\\d+$/.test(opacitySuffix)) {\n errors.push({\n message: /^-\\d+$/.test(opacitySuffix)\n ? 'Opacity cannot be negative.'\n : `Invalid opacity value '${opacitySuffix}'.`,\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n\n return {\n type: 'color-token',\n name: tokenName,\n opacity: opacitySuffix,\n raw,\n };\n }\n\n return { type: 'color-token', name, raw };\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Parse a style value string into typed tokens organized by\n * comma-separated groups and slash-separated parts.\n */\nexport function parseValue(\n src: string,\n opts: ValueParserOptions = {},\n): ValueParseResult {\n const errors: ValueError[] = [];\n\n // Check bracket balance first\n const bracketError = checkBracketBalance(src);\n if (bracketError) {\n errors.push({\n message: bracketError.message,\n offset: bracketError.position,\n length: 1,\n });\n return { groups: [], errors };\n }\n\n const scanned = scanTokens(src);\n\n const groups: ValueGroup[] = [];\n let currentParts: ValuePart[] = [];\n let currentTokens: ValueToken[] = [];\n\n const endPart = () => {\n if (currentTokens.length > 0) {\n currentParts.push({ tokens: currentTokens });\n currentTokens = [];\n }\n };\n\n const endGroup = () => {\n endPart();\n if (currentParts.length > 0) {\n groups.push({ parts: currentParts });\n } else {\n groups.push({ parts: [{ tokens: [] }] });\n }\n currentParts = [];\n };\n\n for (const scanned_token of scanned) {\n if (scanned_token.value) {\n const classified = classifyToken(\n scanned_token.value,\n scanned_token.offset,\n errors,\n opts,\n );\n currentTokens.push(classified);\n }\n if (scanned_token.isSlash) endPart();\n if (scanned_token.isComma) endGroup();\n }\n\n // Push final group\n if (\n currentTokens.length > 0 ||\n currentParts.length > 0 ||\n groups.length === 0\n ) {\n endGroup();\n }\n\n return { groups, errors };\n}\n\n/**\n * Extract all tokens of a specific type from a parse result.\n */\nexport function extractTokensByType<T extends ValueToken['type']>(\n result: ValueParseResult,\n type: T,\n): Extract<ValueToken, { type: T }>[] {\n const tokens: Extract<ValueToken, { type: T }>[] = [];\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n if (token.type === type) {\n tokens.push(token as Extract<ValueToken, { type: T }>);\n }\n }\n }\n }\n return tokens;\n}\n\n/**\n * Get a flat list of all tokens from a parse result.\n */\nexport function flattenTokens(result: ValueParseResult): ValueToken[] {\n const tokens: ValueToken[] = [];\n for (const group of result.groups) {\n for (const part of group.parts) {\n tokens.push(...part.tokens);\n }\n }\n return tokens;\n}\n"],"mappings":";;;;AAoEA,MAAM,cAAc;AACpB,MAAM,YAAY;AAClB,MAAM,kBAAkB;;AAExB,MAAM,WAAW;AAEjB,MAAM,cAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,YAAY,IAAI,IAAI;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;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;AAEF,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAC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"}
|
|
@@ -115,7 +115,9 @@ const DISPLAY_MODS = [
|
|
|
115
115
|
"inline-table",
|
|
116
116
|
"inline-flex",
|
|
117
117
|
"inline-grid",
|
|
118
|
-
"inline-grid-lanes"
|
|
118
|
+
"inline-grid-lanes",
|
|
119
|
+
"-webkit-box",
|
|
120
|
+
"-webkit-inline-box"
|
|
119
121
|
];
|
|
120
122
|
const PROPERTY_EXPECTATIONS = {
|
|
121
123
|
fill: {
|
|
@@ -250,7 +252,10 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
250
252
|
order: VALUE_ONLY,
|
|
251
253
|
zIndex: VALUE_ONLY,
|
|
252
254
|
opacity: VALUE_ONLY,
|
|
253
|
-
aspectRatio:
|
|
255
|
+
aspectRatio: {
|
|
256
|
+
...VALUE_ONLY,
|
|
257
|
+
acceptsRatio: true
|
|
258
|
+
},
|
|
254
259
|
lineClamp: VALUE_ONLY,
|
|
255
260
|
tabSize: VALUE_ONLY,
|
|
256
261
|
flow: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"property-expectations.js","names":[],"sources":["../src/property-expectations.ts"],"sourcesContent":["/**\n * Per-property expectations for parser bucket validation.\n *\n * After parsing a value through StyleParser.process(), each group contains\n * `colors`, `values`, and `mods` arrays. This map defines what is expected\n * for each tasty property so we can flag unexpected tokens.\n *\n * - `acceptsColor`: whether Color bucket tokens are valid\n * - `acceptsMods`: whether Mod bucket tokens are valid, and if so which ones\n * - `false` = no mods accepted (any mod is an error)\n * - `true` = any mod accepted (pass-through)\n * - `string[]` = only these specific mods are accepted\n *\n * Properties NOT listed here default to PASSTHROUGH (accept everything).\n * Only add properties that have actual restrictions.\n */\n\nexport interface PropertyExpectation {\n acceptsColor: boolean;\n acceptsMods: boolean | string[];\n}\n\nconst DIRECTIONAL_MODS = ['top', 'right', 'bottom', 'left'];\n// Output modifier accepted by every box property (`padding`, `margin`,\n// `inset`, `border`, `radius`): emit the individual CSS longhands instead of\n// the shorthand, so children can selectively inherit a single side/corner.\nconst LONGHAND_MOD = 'longhand';\n// `inset` additionally accepts `dock`: pin one edge and span its full\n// length (tasty >= 2.10). Other directional properties do not.\nconst INSET_MODS = [...DIRECTIONAL_MODS, 'dock', LONGHAND_MOD];\nconst BOX_DIRECTIONAL_MODS = [...DIRECTIONAL_MODS, LONGHAND_MOD];\nconst RADIUS_DIRECTIONAL_MODS = [\n ...DIRECTIONAL_MODS,\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n LONGHAND_MOD,\n];\nconst BORDER_STYLE_MODS = [\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'inset',\n 'outset',\n 'none',\n 'hidden',\n];\nconst DIMENSION_MODS = ['min', 'max', 'fixed'];\n// The logical axis handlers take `start`/`end` in place of the four physical\n// sides, plus the same `longhand` output modifier — it emits the two native\n// start/end declarations instead of the axis shorthand.\nconst LOGICAL_EDGE_MODS = ['start', 'end', LONGHAND_MOD];\nconst FLOW_MODS = [\n 'row',\n 'column',\n 'wrap',\n 'nowrap',\n 'dense',\n 'row-reverse',\n 'column-reverse',\n];\nconst OVERFLOW_MODS = [\n 'visible',\n 'hidden',\n 'scroll',\n 'clip',\n 'auto',\n 'overlay',\n];\nconst POSITION_MODS = ['static', 'relative', 'absolute', 'fixed', 'sticky'];\n\nconst COLOR_ONLY: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: false,\n};\n\nconst VALUE_ONLY: PropertyExpectation = {\n acceptsColor: false,\n acceptsMods: false,\n};\n\nconst PASSTHROUGH: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: true,\n};\n\n/**\n * Every `display` keyword, one word at a time: values are checked word by\n * word, so the two-value syntax (`block flex`, `inline flow-root`,\n * `list-item block`) is covered by its outside, inside and `list-item` words.\n * `grid-lanes` is CSS Grid Level 3 masonry.\n */\nconst DISPLAY_MODS = [\n // <display-outside>\n 'block',\n 'inline',\n 'run-in',\n // <display-inside>\n 'flow',\n 'flow-root',\n 'table',\n 'flex',\n 'grid',\n 'grid-lanes',\n 'ruby',\n 'math',\n // <display-listitem>\n 'list-item',\n // <display-internal>\n 'table-row-group',\n 'table-header-group',\n 'table-footer-group',\n 'table-row',\n 'table-cell',\n 'table-column-group',\n 'table-column',\n 'table-caption',\n 'ruby-base',\n 'ruby-text',\n 'ruby-base-container',\n 'ruby-text-container',\n // <display-box>\n 'contents',\n 'none',\n // <display-legacy>\n 'inline-block',\n 'inline-table',\n 'inline-flex',\n 'inline-grid',\n 'inline-grid-lanes',\n];\n\nexport const PROPERTY_EXPECTATIONS: Record<string, PropertyExpectation> = {\n fill: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n color: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n caretColor: COLOR_ONLY,\n accentColor: COLOR_ONLY,\n shadow: { acceptsColor: true, acceptsMods: ['inset'] },\n\n border: {\n acceptsColor: true,\n acceptsMods: [...BOX_DIRECTIONAL_MODS, ...BORDER_STYLE_MODS],\n },\n outline: {\n acceptsColor: true,\n acceptsMods: BORDER_STYLE_MODS,\n },\n\n radius: {\n acceptsColor: false,\n acceptsMods: [\n ...RADIUS_DIRECTIONAL_MODS,\n 'round',\n 'ellipse',\n 'leaf',\n 'backleaf',\n ],\n },\n\n padding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n margin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n fade: { acceptsColor: true, acceptsMods: DIRECTIONAL_MODS },\n inset: { acceptsColor: false, acceptsMods: INSET_MODS },\n scrollMargin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n scrollPadding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n\n // Native logical CSS shorthands. Tasty v3.8 stopped reading these in its\n // physical handlers, so they are ordinary CSS declarations now: one or two\n // lengths through the normal value parser, no colours and no modifiers. The\n // enhanced `blockPadding` / `inlinePadding` below are what take modifiers.\n paddingInline: VALUE_ONLY,\n paddingBlock: VALUE_ONLY,\n\n width: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n height: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n // Enhanced logical styles. Each axis/category pair mirrors its physical\n // counterpart's vocabulary with `start`/`end` in place of the four sides.\n blockPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlinePadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n inlineBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n blockSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n inlineSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n gap: VALUE_ONLY,\n columnGap: VALUE_ONLY,\n rowGap: VALUE_ONLY,\n flexBasis: VALUE_ONLY,\n flexGrow: VALUE_ONLY,\n flexShrink: VALUE_ONLY,\n flex: VALUE_ONLY,\n order: VALUE_ONLY,\n zIndex: VALUE_ONLY,\n opacity: VALUE_ONLY,\n aspectRatio: VALUE_ONLY,\n lineClamp: VALUE_ONLY,\n tabSize: VALUE_ONLY,\n\n flow: { acceptsColor: false, acceptsMods: FLOW_MODS },\n display: { acceptsColor: false, acceptsMods: DISPLAY_MODS },\n overflow: { acceptsColor: false, acceptsMods: OVERFLOW_MODS },\n position: { acceptsColor: false, acceptsMods: POSITION_MODS },\n};\n\n/**\n * Get expectations for a property. Properties not in the map\n * are treated as passthrough (accept everything).\n */\nexport function getExpectation(property: string): PropertyExpectation {\n return PROPERTY_EXPECTATIONS[property] ?? PASSTHROUGH;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"property-expectations.js","names":[],"sources":["../src/property-expectations.ts"],"sourcesContent":["/**\n * Per-property expectations for parser bucket validation.\n *\n * After parsing a value through StyleParser.process(), each group contains\n * `colors`, `values`, and `mods` arrays. This map defines what is expected\n * for each tasty property so we can flag unexpected tokens.\n *\n * - `acceptsColor`: whether Color bucket tokens are valid\n * - `acceptsMods`: whether Mod bucket tokens are valid, and if so which ones\n * - `false` = no mods accepted (any mod is an error)\n * - `true` = any mod accepted (pass-through)\n * - `string[]` = only these specific mods are accepted\n *\n * Properties NOT listed here default to PASSTHROUGH (accept everything).\n * Only add properties that have actual restrictions.\n */\n\nexport interface PropertyExpectation {\n acceptsColor: boolean;\n acceptsMods: boolean | string[];\n /** Whether an unspaced `<ratio>` token (`16/10`) is a value here. */\n acceptsRatio?: boolean;\n}\n\nconst DIRECTIONAL_MODS = ['top', 'right', 'bottom', 'left'];\n// Output modifier accepted by every box property (`padding`, `margin`,\n// `inset`, `border`, `radius`): emit the individual CSS longhands instead of\n// the shorthand, so children can selectively inherit a single side/corner.\nconst LONGHAND_MOD = 'longhand';\n// `inset` additionally accepts `dock`: pin one edge and span its full\n// length (tasty >= 2.10). Other directional properties do not.\nconst INSET_MODS = [...DIRECTIONAL_MODS, 'dock', LONGHAND_MOD];\nconst BOX_DIRECTIONAL_MODS = [...DIRECTIONAL_MODS, LONGHAND_MOD];\nconst RADIUS_DIRECTIONAL_MODS = [\n ...DIRECTIONAL_MODS,\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n LONGHAND_MOD,\n];\nconst BORDER_STYLE_MODS = [\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'inset',\n 'outset',\n 'none',\n 'hidden',\n];\nconst DIMENSION_MODS = ['min', 'max', 'fixed'];\n// The logical axis handlers take `start`/`end` in place of the four physical\n// sides, plus the same `longhand` output modifier — it emits the two native\n// start/end declarations instead of the axis shorthand.\nconst LOGICAL_EDGE_MODS = ['start', 'end', LONGHAND_MOD];\nconst FLOW_MODS = [\n 'row',\n 'column',\n 'wrap',\n 'nowrap',\n 'dense',\n 'row-reverse',\n 'column-reverse',\n];\nconst OVERFLOW_MODS = [\n 'visible',\n 'hidden',\n 'scroll',\n 'clip',\n 'auto',\n 'overlay',\n];\nconst POSITION_MODS = ['static', 'relative', 'absolute', 'fixed', 'sticky'];\n\nconst COLOR_ONLY: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: false,\n};\n\nconst VALUE_ONLY: PropertyExpectation = {\n acceptsColor: false,\n acceptsMods: false,\n};\n\nconst PASSTHROUGH: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: true,\n};\n\n/**\n * Every `display` keyword, one word at a time: values are checked word by\n * word, so the two-value syntax (`block flex`, `inline flow-root`,\n * `list-item block`) is covered by its outside, inside and `list-item` words.\n * `grid-lanes` is CSS Grid Level 3 masonry.\n */\nconst DISPLAY_MODS = [\n // <display-outside>\n 'block',\n 'inline',\n 'run-in',\n // <display-inside>\n 'flow',\n 'flow-root',\n 'table',\n 'flex',\n 'grid',\n 'grid-lanes',\n 'ruby',\n 'math',\n // <display-listitem>\n 'list-item',\n // <display-internal>\n 'table-row-group',\n 'table-header-group',\n 'table-footer-group',\n 'table-row',\n 'table-cell',\n 'table-column-group',\n 'table-column',\n 'table-caption',\n 'ruby-base',\n 'ruby-text',\n 'ruby-base-container',\n 'ruby-text-container',\n // <display-box>\n 'contents',\n 'none',\n // <display-legacy>\n 'inline-block',\n 'inline-table',\n 'inline-flex',\n 'inline-grid',\n 'inline-grid-lanes',\n // The old flexbox boxes, kept because `-webkit-line-clamp` only truncates\n // inside one.\n '-webkit-box',\n '-webkit-inline-box',\n];\n\nexport const PROPERTY_EXPECTATIONS: Record<string, PropertyExpectation> = {\n fill: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n color: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n caretColor: COLOR_ONLY,\n accentColor: COLOR_ONLY,\n shadow: { acceptsColor: true, acceptsMods: ['inset'] },\n\n border: {\n acceptsColor: true,\n acceptsMods: [...BOX_DIRECTIONAL_MODS, ...BORDER_STYLE_MODS],\n },\n outline: {\n acceptsColor: true,\n acceptsMods: BORDER_STYLE_MODS,\n },\n\n radius: {\n acceptsColor: false,\n acceptsMods: [\n ...RADIUS_DIRECTIONAL_MODS,\n 'round',\n 'ellipse',\n 'leaf',\n 'backleaf',\n ],\n },\n\n padding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n margin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n fade: { acceptsColor: true, acceptsMods: DIRECTIONAL_MODS },\n inset: { acceptsColor: false, acceptsMods: INSET_MODS },\n scrollMargin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n scrollPadding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n\n // Native logical CSS shorthands. Tasty v3.8 stopped reading these in its\n // physical handlers, so they are ordinary CSS declarations now: one or two\n // lengths through the normal value parser, no colours and no modifiers. The\n // enhanced `blockPadding` / `inlinePadding` below are what take modifiers.\n paddingInline: VALUE_ONLY,\n paddingBlock: VALUE_ONLY,\n\n width: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n height: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n // Enhanced logical styles. Each axis/category pair mirrors its physical\n // counterpart's vocabulary with `start`/`end` in place of the four sides.\n blockPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlinePadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n inlineBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n blockSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n inlineSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n gap: VALUE_ONLY,\n columnGap: VALUE_ONLY,\n rowGap: VALUE_ONLY,\n flexBasis: VALUE_ONLY,\n flexGrow: VALUE_ONLY,\n flexShrink: VALUE_ONLY,\n flex: VALUE_ONLY,\n order: VALUE_ONLY,\n zIndex: VALUE_ONLY,\n opacity: VALUE_ONLY,\n // `<ratio>` allows optional whitespace around the slash: `16/10`, `16 / 10`.\n aspectRatio: { ...VALUE_ONLY, acceptsRatio: true },\n lineClamp: VALUE_ONLY,\n tabSize: VALUE_ONLY,\n\n flow: { acceptsColor: false, acceptsMods: FLOW_MODS },\n display: { acceptsColor: false, acceptsMods: DISPLAY_MODS },\n overflow: { acceptsColor: false, acceptsMods: OVERFLOW_MODS },\n position: { acceptsColor: false, acceptsMods: POSITION_MODS },\n};\n\n/**\n * Get expectations for a property. Properties not in the map\n * are treated as passthrough (accept everything).\n */\nexport function getExpectation(property: string): PropertyExpectation {\n return PROPERTY_EXPECTATIONS[property] ?? PASSTHROUGH;\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { DIMENSION_STYLES, POSITION_STYLES } from "./constants.js";
|
|
2
|
-
import { unwrapExpression, wildcardPattern } from "./utils.js";
|
|
3
|
-
import { resolveModuleExportOrigin } from "./module-components.js";
|
|
2
|
+
import { unwrapExpression, valueDefinition, wildcardPattern } from "./utils.js";
|
|
3
|
+
import { resolveModuleExportOrigin, withMembers } from "./module-components.js";
|
|
4
4
|
|
|
5
5
|
//#region src/protected-components.ts
|
|
6
|
+
function namePattern(pattern) {
|
|
7
|
+
return {
|
|
8
|
+
regex: wildcardPattern(pattern),
|
|
9
|
+
member: pattern.includes(".")
|
|
10
|
+
};
|
|
11
|
+
}
|
|
6
12
|
const STYLE_PROP_GROUPS = new Map([["position", POSITION_STYLES], ["dimension", DIMENSION_STYLES]]);
|
|
7
13
|
const entriesByConfig = /* @__PURE__ */ new WeakMap();
|
|
8
14
|
function readEntries(config) {
|
|
@@ -22,8 +28,8 @@ function readEntries(config) {
|
|
|
22
28
|
};
|
|
23
29
|
return {
|
|
24
30
|
source: wildcardPattern(entry.source),
|
|
25
|
-
include: strings("include").map(
|
|
26
|
-
exclude: strings("exclude").map(
|
|
31
|
+
include: strings("include").map(namePattern),
|
|
32
|
+
exclude: strings("exclude").map(namePattern),
|
|
27
33
|
allowedStyleProps: new Set(strings("allowedStyleProps").flatMap((name) => STYLE_PROP_GROUPS.get(name) ?? [name]))
|
|
28
34
|
};
|
|
29
35
|
});
|
|
@@ -35,10 +41,10 @@ function importedName(specifier) {
|
|
|
35
41
|
}
|
|
36
42
|
/**
|
|
37
43
|
* Resolves JSX tags and `tasty()` bases to the `protectedComponents` entry they
|
|
38
|
-
* name, by the name their source exports them under
|
|
39
|
-
* (`
|
|
40
|
-
* wrappers and relative re-exports are followed; relative
|
|
41
|
-
* `typescript` package, as for other cross-file recognition.
|
|
44
|
+
* name, by the name their source exports them under plus any members read off
|
|
45
|
+
* it (`Button.Group`). Aliases, namespace members (`UI.Button`), `const`
|
|
46
|
+
* aliases, `tasty()` wrappers and relative re-exports are followed; relative
|
|
47
|
+
* modules need the `typescript` package, as for other cross-file recognition.
|
|
42
48
|
*/
|
|
43
49
|
var ProtectedComponents = class {
|
|
44
50
|
entries;
|
|
@@ -61,41 +67,61 @@ var ProtectedComponents = class {
|
|
|
61
67
|
resolveTag(name) {
|
|
62
68
|
if (name.type === "JSXIdentifier" && !/^[A-Z]/.test(name.name)) return null;
|
|
63
69
|
let root = name;
|
|
64
|
-
|
|
70
|
+
const members = [];
|
|
65
71
|
while (root.type === "JSXMemberExpression") {
|
|
66
|
-
|
|
72
|
+
members.unshift(root.property.name);
|
|
67
73
|
root = root.object;
|
|
68
74
|
}
|
|
69
75
|
if (root.type !== "JSXIdentifier") return null;
|
|
70
|
-
return this.match(this.resolveBinding(root, root.name,
|
|
76
|
+
return this.match(this.resolveBinding(root, root.name, members, /* @__PURE__ */ new Set()));
|
|
71
77
|
}
|
|
72
78
|
/** The protected component an expression, such as a `tasty()` base, refers to. */
|
|
73
79
|
resolveExpression(node) {
|
|
74
|
-
return this.match(this.origin(node,
|
|
80
|
+
return this.match(this.origin(node, [], /* @__PURE__ */ new Set()));
|
|
75
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* The most specific name decides: an entry that names a member path
|
|
84
|
+
* (`Button.Group`, `Button.*`) in `include` or `exclude` wins over one that
|
|
85
|
+
* matches only the export, so a sub-component that is a separate component
|
|
86
|
+
* can follow its own entry. Longer paths are tried first.
|
|
87
|
+
*/
|
|
76
88
|
match(origin) {
|
|
77
89
|
if (!origin) return null;
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
const entries = this.entries.filter((entry) => entry.source.test(origin.source));
|
|
91
|
+
const protect = (name, entry) => ({
|
|
92
|
+
name,
|
|
93
|
+
source: origin.source,
|
|
94
|
+
wrapped: origin.wrapped,
|
|
81
95
|
allowedStyleProps: entry.allowedStyleProps
|
|
82
|
-
}
|
|
96
|
+
});
|
|
97
|
+
const parts = [origin.name, ...origin.members];
|
|
98
|
+
for (let length = parts.length; length > 1; length--) {
|
|
99
|
+
const path = parts.slice(0, length).join(".");
|
|
100
|
+
const names = (patterns) => patterns.some((pattern) => pattern.member && pattern.regex.test(path));
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
if (names(entry.exclude)) return null;
|
|
103
|
+
if (names(entry.include)) return protect(path, entry);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const matches = (patterns) => patterns.some((pattern) => !pattern.member && pattern.regex.test(origin.name));
|
|
107
|
+
const entry = entries.find((candidate) => matches(candidate.include) && !matches(candidate.exclude));
|
|
108
|
+
return entry ? protect(origin.name, entry) : null;
|
|
83
109
|
}
|
|
84
|
-
origin(node,
|
|
110
|
+
origin(node, members, visited) {
|
|
85
111
|
const value = unwrapExpression(node);
|
|
86
|
-
if (value.type === "Identifier") return this.resolveBinding(value, value.name,
|
|
112
|
+
if (value.type === "Identifier") return this.resolveBinding(value, value.name, members, visited);
|
|
87
113
|
if (value.type === "MemberExpression") {
|
|
88
114
|
let root = value;
|
|
89
|
-
|
|
115
|
+
const path = [...members];
|
|
90
116
|
while (root.type === "MemberExpression") {
|
|
91
117
|
if (root.computed || root.property.type !== "Identifier") return null;
|
|
92
|
-
|
|
118
|
+
path.unshift(root.property.name);
|
|
93
119
|
root = unwrapExpression(root.object);
|
|
94
120
|
}
|
|
95
|
-
return root.type === "Identifier" ? this.resolveBinding(root, root.name,
|
|
121
|
+
return root.type === "Identifier" ? this.resolveBinding(root, root.name, path, visited) : null;
|
|
96
122
|
}
|
|
97
|
-
if (
|
|
98
|
-
const origin = this.origin(value.arguments[0],
|
|
123
|
+
if (members.length === 0 && value.type === "CallExpression" && this.isWrapperCall(value)) {
|
|
124
|
+
const origin = this.origin(value.arguments[0], [], visited);
|
|
99
125
|
return origin && {
|
|
100
126
|
...origin,
|
|
101
127
|
wrapped: true
|
|
@@ -103,29 +129,31 @@ var ProtectedComponents = class {
|
|
|
103
129
|
}
|
|
104
130
|
return null;
|
|
105
131
|
}
|
|
106
|
-
/** `
|
|
107
|
-
resolveBinding(reference, localName,
|
|
132
|
+
/** `members` are the properties read off the binding, as in `UI.Button.Group`. */
|
|
133
|
+
resolveBinding(reference, localName, members, visited) {
|
|
108
134
|
let scope = this.context.sourceCode.getScope(reference);
|
|
109
135
|
while (scope && !scope.set.has(localName)) scope = scope.upper;
|
|
110
136
|
const variable = scope?.set.get(localName);
|
|
111
|
-
|
|
137
|
+
const def = variable && valueDefinition(variable);
|
|
138
|
+
if (!variable || !def) return null;
|
|
112
139
|
let cache = this.bindings.get(variable);
|
|
113
140
|
if (!cache) {
|
|
114
141
|
cache = /* @__PURE__ */ new Map();
|
|
115
142
|
this.bindings.set(variable, cache);
|
|
116
143
|
}
|
|
117
|
-
const key =
|
|
144
|
+
const key = members.join(".");
|
|
118
145
|
if (cache.has(key)) return cache.get(key);
|
|
119
|
-
const result = this.resolveDefinition(
|
|
146
|
+
const result = this.resolveDefinition(def, members, visited);
|
|
120
147
|
cache.set(key, result);
|
|
121
148
|
return result;
|
|
122
149
|
}
|
|
123
|
-
resolveDefinition(def,
|
|
150
|
+
resolveDefinition(def, members, visited) {
|
|
124
151
|
if (def.type === "ImportBinding") {
|
|
125
152
|
if (def.parent.type !== "ImportDeclaration" || def.parent.importKind === "type") return null;
|
|
126
153
|
const specifier = def.node;
|
|
127
154
|
let name;
|
|
128
|
-
|
|
155
|
+
let rest = members;
|
|
156
|
+
if (specifier.type === "ImportNamespaceSpecifier") [name, ...rest] = members;
|
|
129
157
|
else if (specifier.type === "ImportDefaultSpecifier") name = "default";
|
|
130
158
|
else if (specifier.type === "ImportSpecifier") {
|
|
131
159
|
if (specifier.importKind === "type") return null;
|
|
@@ -136,13 +164,14 @@ var ProtectedComponents = class {
|
|
|
136
164
|
if (!source.startsWith(".")) return {
|
|
137
165
|
source,
|
|
138
166
|
name,
|
|
167
|
+
members: rest,
|
|
139
168
|
wrapped: false
|
|
140
169
|
};
|
|
141
|
-
return this.moduleOrigin(source, name);
|
|
170
|
+
return withMembers(this.moduleOrigin(source, name), rest);
|
|
142
171
|
}
|
|
143
172
|
if (def.type !== "Variable" || def.parent.kind !== "const" || def.node.id.type !== "Identifier" || !def.node.init || visited.has(def.node)) return null;
|
|
144
173
|
visited.add(def.node);
|
|
145
|
-
return this.origin(def.node.init,
|
|
174
|
+
return this.origin(def.node.init, members, visited);
|
|
146
175
|
}
|
|
147
176
|
moduleOrigin(source, exportName) {
|
|
148
177
|
const key = `${source}\0${exportName}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protected-components.js","names":[],"sources":["../src/protected-components.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext, Scope } from '@typescript-eslint/utils/ts-eslint';\nimport type { TastyContext } from './context.js';\nimport type { ResolvedConfig } from './types.js';\nimport { DIMENSION_STYLES, POSITION_STYLES } from './constants.js';\nimport {\n type ModuleExportOrigin,\n resolveModuleExportOrigin,\n} from './module-components.js';\nimport { unwrapExpression, wildcardPattern } from './utils.js';\n\n/** A component resolved to a `protectedComponents` entry. */\nexport interface ProtectedComponent {\n /** The name its source exports it under: `Button` for `<UI.Button>` or an alias. */\n name: string;\n source: string;\n /** Whether a `tasty()` wrapper sits between the reference and the export. */\n wrapped: boolean;\n allowedStyleProps: ReadonlySet<string>;\n}\n\ninterface Entry {\n source: RegExp;\n include: RegExp[];\n exclude: RegExp[];\n allowedStyleProps: ReadonlySet<string>;\n}\n\nconst STYLE_PROP_GROUPS = new Map<string, readonly string[]>([\n ['position', POSITION_STYLES],\n ['dimension', DIMENSION_STYLES],\n]);\n\nconst entriesByConfig = new WeakMap<ResolvedConfig, Entry[]>();\n\nfunction readEntries(config: ResolvedConfig): Entry[] {\n let entries = entriesByConfig.get(config);\n if (entries) return entries;\n\n const list: unknown = config.protectedComponents ?? [];\n if (!Array.isArray(list)) {\n throw new Error('tasty: protectedComponents must be an array.');\n }\n entries = list.map((item: unknown, index): Entry => {\n const at = `tasty: protectedComponents[${index}]`;\n if (\n !item ||\n typeof item !== 'object' ||\n typeof (item as { source?: unknown }).source !== 'string'\n ) {\n throw new Error(`${at} must be an object with a string 'source'.`);\n }\n const entry = item as Record<string, unknown>;\n const strings = (key: string): string[] => {\n const value = entry[key];\n if (value === undefined) return key === 'include' ? ['*'] : [];\n if (\n !Array.isArray(value) ||\n value.some((name) => typeof name !== 'string')\n ) {\n throw new Error(`${at}.${key} must be an array of strings.`);\n }\n return value as string[];\n };\n return {\n source: wildcardPattern(entry.source as string),\n include: strings('include').map(wildcardPattern),\n exclude: strings('exclude').map(wildcardPattern),\n allowedStyleProps: new Set(\n strings('allowedStyleProps').flatMap(\n (name) => STYLE_PROP_GROUPS.get(name) ?? [name],\n ),\n ),\n };\n });\n entriesByConfig.set(config, entries);\n return entries;\n}\n\nfunction importedName(specifier: TSESTree.ImportSpecifier): string {\n return specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n}\n\n/**\n * Resolves JSX tags and `tasty()` bases to the `protectedComponents` entry they\n * name, by the name their source exports them under. Aliases, namespace members\n * (`UI.Button`, and `Text.Strong` as `Text`), `const` aliases, `tasty()`\n * wrappers and relative re-exports are followed; relative modules need the\n * `typescript` package, as for other cross-file recognition.\n */\nexport class ProtectedComponents {\n private entries: Entry[];\n private bindings = new WeakMap<\n Scope.Variable,\n Map<string, ModuleExportOrigin | null>\n >();\n private modules = new Map<string, ModuleExportOrigin | null>();\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n private ctx: TastyContext,\n ) {\n this.entries = readEntries(ctx.config);\n }\n\n get enabled(): boolean {\n return this.entries.length > 0;\n }\n\n /** `tasty(Base, ...)`: a Tasty wrapper of an existing component. */\n isWrapperCall(node: TSESTree.CallExpression): boolean {\n const base = node.arguments[0];\n return (\n this.ctx.isTastyCall(node)?.importedName === 'tasty' &&\n base !== undefined &&\n base.type !== 'SpreadElement' &&\n unwrapExpression(base).type !== 'ObjectExpression'\n );\n }\n\n /** The protected component a JSX tag name refers to. */\n resolveTag(name: TSESTree.JSXTagNameExpression): ProtectedComponent | null {\n // `<button>` is a native element whatever a `button` binding holds.\n if (name.type === 'JSXIdentifier' && !/^[A-Z]/.test(name.name)) return null;\n let root = name;\n let member: string | undefined;\n while (root.type === 'JSXMemberExpression') {\n member = root.property.name;\n root = root.object;\n }\n if (root.type !== 'JSXIdentifier') return null;\n return this.match(this.resolveBinding(root, root.name, member, new Set()));\n }\n\n /** The protected component an expression, such as a `tasty()` base, refers to. */\n resolveExpression(node: TSESTree.Node): ProtectedComponent | null {\n return this.match(this.origin(node, undefined, new Set()));\n }\n\n private match(origin: ModuleExportOrigin | null): ProtectedComponent | null {\n if (!origin) return null;\n const entry = this.entries.find(\n (candidate) =>\n candidate.source.test(origin.source) &&\n candidate.include.some((pattern) => pattern.test(origin.name)) &&\n !candidate.exclude.some((pattern) => pattern.test(origin.name)),\n );\n return entry\n ? { ...origin, allowedStyleProps: entry.allowedStyleProps }\n : null;\n }\n\n private origin(\n node: TSESTree.Node,\n member: string | undefined,\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n const value = unwrapExpression(node);\n if (value.type === 'Identifier') {\n return this.resolveBinding(value, value.name, member, visited);\n }\n if (value.type === 'MemberExpression') {\n let root: TSESTree.Node = value;\n let first = member;\n while (root.type === 'MemberExpression') {\n if (root.computed || root.property.type !== 'Identifier') return null;\n first = root.property.name;\n root = unwrapExpression(root.object);\n }\n return root.type === 'Identifier'\n ? this.resolveBinding(root, root.name, first, visited)\n : null;\n }\n if (\n member === undefined &&\n value.type === 'CallExpression' &&\n this.isWrapperCall(value)\n ) {\n const origin = this.origin(value.arguments[0], undefined, visited);\n return origin && { ...origin, wrapped: true };\n }\n return null;\n }\n\n /** `member` is the first property read off the binding, as in `UI.Button`. */\n private resolveBinding(\n reference: TSESTree.Node,\n localName: string,\n member: string | undefined,\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n let scope: Scope.Scope | null = this.context.sourceCode.getScope(reference);\n while (scope && !scope.set.has(localName)) scope = scope.upper;\n const variable = scope?.set.get(localName);\n if (!variable || variable.defs.length !== 1) return null;\n\n let cache = this.bindings.get(variable);\n if (!cache) {\n cache = new Map();\n this.bindings.set(variable, cache);\n }\n const key = member ?? '';\n if (cache.has(key)) return cache.get(key)!;\n\n const result = this.resolveDefinition(variable.defs[0], member, visited);\n cache.set(key, result);\n return result;\n }\n\n private resolveDefinition(\n def: Scope.Definition,\n member: string | undefined,\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n if (def.type === 'ImportBinding') {\n if (\n def.parent.type !== 'ImportDeclaration' ||\n def.parent.importKind === 'type'\n ) {\n return null;\n }\n const specifier = def.node;\n let name: string | undefined;\n if (specifier.type === 'ImportNamespaceSpecifier') name = member;\n else if (specifier.type === 'ImportDefaultSpecifier') name = 'default';\n else if (specifier.type === 'ImportSpecifier') {\n if (specifier.importKind === 'type') return null;\n name = importedName(specifier);\n }\n if (name === undefined) return null;\n\n const source = def.parent.source.value;\n if (!source.startsWith('.')) return { source, name, wrapped: false };\n return this.moduleOrigin(source, name);\n }\n\n if (\n def.type !== 'Variable' ||\n def.parent.kind !== 'const' ||\n def.node.id.type !== 'Identifier' ||\n !def.node.init ||\n visited.has(def.node)\n ) {\n return null;\n }\n visited.add(def.node);\n return this.origin(def.node.init, member, visited);\n }\n\n private moduleOrigin(\n source: string,\n exportName: string,\n ): ModuleExportOrigin | null {\n const key = `${source}\\0${exportName}`;\n if (!this.modules.has(key)) {\n this.modules.set(\n key,\n resolveModuleExportOrigin(this.context.filename, source, exportName, {\n importSources: this.ctx.trackedSources,\n isComponentFactory: (name) => name === 'tasty',\n isOriginSource: (candidate) =>\n this.entries.some((entry) => entry.source.test(candidate)),\n }),\n );\n }\n return this.modules.get(key)!;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"protected-components.js","names":[],"sources":["../src/protected-components.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext, Scope } from '@typescript-eslint/utils/ts-eslint';\nimport type { TastyContext } from './context.js';\nimport type { ResolvedConfig } from './types.js';\nimport { DIMENSION_STYLES, POSITION_STYLES } from './constants.js';\nimport {\n type ModuleExportOrigin,\n resolveModuleExportOrigin,\n withMembers,\n} from './module-components.js';\nimport { unwrapExpression, valueDefinition, wildcardPattern } from './utils.js';\n\n/** A component resolved to a `protectedComponents` entry. */\nexport interface ProtectedComponent {\n /**\n * The name it is protected under: the export name (`Button` for `<UI.Button>`,\n * an alias or `<Button.Split>`), or the member path an entry names, such as\n * `Button.Group`.\n */\n name: string;\n source: string;\n /** Whether a `tasty()` wrapper sits between the reference and the export. */\n wrapped: boolean;\n allowedStyleProps: ReadonlySet<string>;\n}\n\ninterface Pattern {\n regex: RegExp;\n /** Names a member path, such as `Button.Group` or `Button.*`. */\n member: boolean;\n}\n\ninterface Entry {\n source: RegExp;\n include: Pattern[];\n exclude: Pattern[];\n allowedStyleProps: ReadonlySet<string>;\n}\n\nfunction namePattern(pattern: string): Pattern {\n return { regex: wildcardPattern(pattern), member: pattern.includes('.') };\n}\n\nconst STYLE_PROP_GROUPS = new Map<string, readonly string[]>([\n ['position', POSITION_STYLES],\n ['dimension', DIMENSION_STYLES],\n]);\n\nconst entriesByConfig = new WeakMap<ResolvedConfig, Entry[]>();\n\nfunction readEntries(config: ResolvedConfig): Entry[] {\n let entries = entriesByConfig.get(config);\n if (entries) return entries;\n\n const list: unknown = config.protectedComponents ?? [];\n if (!Array.isArray(list)) {\n throw new Error('tasty: protectedComponents must be an array.');\n }\n entries = list.map((item: unknown, index): Entry => {\n const at = `tasty: protectedComponents[${index}]`;\n if (\n !item ||\n typeof item !== 'object' ||\n typeof (item as { source?: unknown }).source !== 'string'\n ) {\n throw new Error(`${at} must be an object with a string 'source'.`);\n }\n const entry = item as Record<string, unknown>;\n const strings = (key: string): string[] => {\n const value = entry[key];\n if (value === undefined) return key === 'include' ? ['*'] : [];\n if (\n !Array.isArray(value) ||\n value.some((name) => typeof name !== 'string')\n ) {\n throw new Error(`${at}.${key} must be an array of strings.`);\n }\n return value as string[];\n };\n return {\n source: wildcardPattern(entry.source as string),\n include: strings('include').map(namePattern),\n exclude: strings('exclude').map(namePattern),\n allowedStyleProps: new Set(\n strings('allowedStyleProps').flatMap(\n (name) => STYLE_PROP_GROUPS.get(name) ?? [name],\n ),\n ),\n };\n });\n entriesByConfig.set(config, entries);\n return entries;\n}\n\nfunction importedName(specifier: TSESTree.ImportSpecifier): string {\n return specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n}\n\n/**\n * Resolves JSX tags and `tasty()` bases to the `protectedComponents` entry they\n * name, by the name their source exports them under plus any members read off\n * it (`Button.Group`). Aliases, namespace members (`UI.Button`), `const`\n * aliases, `tasty()` wrappers and relative re-exports are followed; relative\n * modules need the `typescript` package, as for other cross-file recognition.\n */\nexport class ProtectedComponents {\n private entries: Entry[];\n private bindings = new WeakMap<\n Scope.Variable,\n Map<string, ModuleExportOrigin | null>\n >();\n private modules = new Map<string, ModuleExportOrigin | null>();\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n private ctx: TastyContext,\n ) {\n this.entries = readEntries(ctx.config);\n }\n\n get enabled(): boolean {\n return this.entries.length > 0;\n }\n\n /** `tasty(Base, ...)`: a Tasty wrapper of an existing component. */\n isWrapperCall(node: TSESTree.CallExpression): boolean {\n const base = node.arguments[0];\n return (\n this.ctx.isTastyCall(node)?.importedName === 'tasty' &&\n base !== undefined &&\n base.type !== 'SpreadElement' &&\n unwrapExpression(base).type !== 'ObjectExpression'\n );\n }\n\n /** The protected component a JSX tag name refers to. */\n resolveTag(name: TSESTree.JSXTagNameExpression): ProtectedComponent | null {\n // `<button>` is a native element whatever a `button` binding holds.\n if (name.type === 'JSXIdentifier' && !/^[A-Z]/.test(name.name)) return null;\n let root = name;\n const members: string[] = [];\n while (root.type === 'JSXMemberExpression') {\n members.unshift(root.property.name);\n root = root.object;\n }\n if (root.type !== 'JSXIdentifier') return null;\n return this.match(this.resolveBinding(root, root.name, members, new Set()));\n }\n\n /** The protected component an expression, such as a `tasty()` base, refers to. */\n resolveExpression(node: TSESTree.Node): ProtectedComponent | null {\n return this.match(this.origin(node, [], new Set()));\n }\n\n /**\n * The most specific name decides: an entry that names a member path\n * (`Button.Group`, `Button.*`) in `include` or `exclude` wins over one that\n * matches only the export, so a sub-component that is a separate component\n * can follow its own entry. Longer paths are tried first.\n */\n private match(origin: ModuleExportOrigin | null): ProtectedComponent | null {\n if (!origin) return null;\n const entries = this.entries.filter((entry) =>\n entry.source.test(origin.source),\n );\n const protect = (name: string, entry: Entry): ProtectedComponent => ({\n name,\n source: origin.source,\n wrapped: origin.wrapped,\n allowedStyleProps: entry.allowedStyleProps,\n });\n\n const parts = [origin.name, ...origin.members];\n for (let length = parts.length; length > 1; length--) {\n const path = parts.slice(0, length).join('.');\n const names = (patterns: Pattern[]) =>\n patterns.some((pattern) => pattern.member && pattern.regex.test(path));\n for (const entry of entries) {\n if (names(entry.exclude)) return null;\n if (names(entry.include)) return protect(path, entry);\n }\n }\n\n const matches = (patterns: Pattern[]) =>\n patterns.some(\n (pattern) => !pattern.member && pattern.regex.test(origin.name),\n );\n const entry = entries.find(\n (candidate) => matches(candidate.include) && !matches(candidate.exclude),\n );\n return entry ? protect(origin.name, entry) : null;\n }\n\n private origin(\n node: TSESTree.Node,\n members: string[],\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n const value = unwrapExpression(node);\n if (value.type === 'Identifier') {\n return this.resolveBinding(value, value.name, members, visited);\n }\n if (value.type === 'MemberExpression') {\n let root: TSESTree.Node = value;\n const path = [...members];\n while (root.type === 'MemberExpression') {\n if (root.computed || root.property.type !== 'Identifier') return null;\n path.unshift(root.property.name);\n root = unwrapExpression(root.object);\n }\n return root.type === 'Identifier'\n ? this.resolveBinding(root, root.name, path, visited)\n : null;\n }\n if (\n members.length === 0 &&\n value.type === 'CallExpression' &&\n this.isWrapperCall(value)\n ) {\n const origin = this.origin(value.arguments[0], [], visited);\n return origin && { ...origin, wrapped: true };\n }\n return null;\n }\n\n /** `members` are the properties read off the binding, as in `UI.Button.Group`. */\n private resolveBinding(\n reference: TSESTree.Node,\n localName: string,\n members: string[],\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n let scope: Scope.Scope | null = this.context.sourceCode.getScope(reference);\n while (scope && !scope.set.has(localName)) scope = scope.upper;\n const variable = scope?.set.get(localName);\n const def = variable && valueDefinition(variable);\n if (!variable || !def) return null;\n\n let cache = this.bindings.get(variable);\n if (!cache) {\n cache = new Map();\n this.bindings.set(variable, cache);\n }\n const key = members.join('.');\n if (cache.has(key)) return cache.get(key)!;\n\n const result = this.resolveDefinition(def, members, visited);\n cache.set(key, result);\n return result;\n }\n\n private resolveDefinition(\n def: Scope.Definition,\n members: string[],\n visited: Set<TSESTree.Node>,\n ): ModuleExportOrigin | null {\n if (def.type === 'ImportBinding') {\n if (\n def.parent.type !== 'ImportDeclaration' ||\n def.parent.importKind === 'type'\n ) {\n return null;\n }\n const specifier = def.node;\n let name: string | undefined;\n let rest = members;\n if (specifier.type === 'ImportNamespaceSpecifier') {\n [name, ...rest] = members;\n } else if (specifier.type === 'ImportDefaultSpecifier') name = 'default';\n else if (specifier.type === 'ImportSpecifier') {\n if (specifier.importKind === 'type') return null;\n name = importedName(specifier);\n }\n if (name === undefined) return null;\n\n const source = def.parent.source.value;\n if (!source.startsWith('.')) {\n return { source, name, members: rest, wrapped: false };\n }\n return withMembers(this.moduleOrigin(source, name), rest);\n }\n\n if (\n def.type !== 'Variable' ||\n def.parent.kind !== 'const' ||\n def.node.id.type !== 'Identifier' ||\n !def.node.init ||\n visited.has(def.node)\n ) {\n return null;\n }\n visited.add(def.node);\n return this.origin(def.node.init, members, visited);\n }\n\n private moduleOrigin(\n source: string,\n exportName: string,\n ): ModuleExportOrigin | null {\n const key = `${source}\\0${exportName}`;\n if (!this.modules.has(key)) {\n this.modules.set(\n key,\n resolveModuleExportOrigin(this.context.filename, source, exportName, {\n importSources: this.ctx.trackedSources,\n isComponentFactory: (name) => name === 'tasty',\n isOriginSource: (candidate) =>\n this.entries.some((entry) => entry.source.test(candidate)),\n }),\n );\n }\n return this.modules.get(key)!;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-directional-modifier.js","names":[],"sources":["../../src/rules/valid-directional-modifier.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport {\n DIRECTIONAL_MODIFIERS,\n DOCK_PROPERTIES,\n SINGLE_VALUE_DIRECTIONAL_PROPERTIES,\n} from '../constants.js';\nimport { parseValue } from '../parsers/index.js';\nimport type { ValueToken } from '../parsers/index.js';\n\ntype MessageIds = 'invalidDirectionalModifier' | 'tooManyValues';\n\n/**\n * Every direction word the rule recognises, across physical and logical\n * vocabularies. A word here is read as a *modifier*; whether it is accepted is\n * then decided per property by `DIRECTIONAL_MODIFIERS`.\n *\n * `start` and `end` are ordinary CSS *values* for a long tail of properties\n * (`textAlign: 'start'`, `justifyContent: 'end'`), which is exactly what the\n * `DIRECTIONAL_MODIFIERS` gate in `checkValue` exists to keep out: only the\n * properties with a directional vocabulary are ever inspected. Inside that set\n * no property takes `start`/`end` as a value, so reading them as modifiers is\n * safe — and it is what turns `padding: '1x start'` (a physical property given\n * a logical edge, which the handler drops) into a report.\n */\nconst ALL_DIRECTIONS = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'start',\n 'end',\n]);\n\n/**\n * Token types that count as a *value* rather than a modifier.\n *\n * Deliberately excludes `keyword`: direction words, `solid`, `inherit` and every\n * other bare word classify as keywords, and a CSS-wide keyword alongside a\n * direction (`padding: 'inherit top'`) is legal while carrying no value of its own.\n *\n * Also excludes colors: a `fade` group carries a width plus up to two mask\n * colors, so colors must not count toward the one-value limit.\n */\nconst VALUE_TOKEN_TYPES = new Set<ValueToken['type']>([\n 'custom-unit',\n 'css-unit',\n 'number',\n 'custom-prop',\n 'custom-prop-ref',\n 'css-function',\n 'tasty-function',\n 'group-expr',\n]);\n\n/** The source text of a token. `keyword` tokens carry `value` rather than `raw`. */\nfunction tokenText(token: ValueToken): string {\n if ('raw' in token && typeof token.raw === 'string') return token.raw;\n if (token.type === 'keyword') return token.value;\n\n return '';\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-directional-modifier',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate directional modifiers: only on properties that support them, and one value per group',\n },\n messages: {\n invalidDirectionalModifier:\n \"Modifier '{{modifier}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n tooManyValues:\n 'A group that names directions takes {{limit}}, but \\'{{group}}\\' has {{count}}. Values and modifiers are parsed into separate buckets, so their order is not recoverable — use comma-separated groups for per-side values, e.g. \"2x top, 4x right\".',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(\n property: string,\n value: string,\n node: TSESTree.Node,\n ): void {\n const allowedMods = DIRECTIONAL_MODIFIERS[property];\n // Only properties with a directional vocabulary are checked.\n //\n // This gate looks like it makes the \"wrong property\" case unreachable, and\n // it does — but removing it is not the fix. Direction words are ordinary CSS\n // *values* for a long tail of properties: `verticalAlign: 'bottom'`,\n // `textAlign: 'left'`, `transformOrigin: 'top center'`,\n // `backgroundPosition`, `objectPosition`, `float`, `clear`,\n // `scrollSnapAlign`, and `transition` (whose value names properties, which\n // can themselves be `left`/`top`). Checking every property reported all of\n // those as errors across a real design system. Catching `fill: '#purple\n // top'` is not worth an allowlist of every property that takes a\n // positional keyword.\n if (!allowedMods) return;\n\n // `parseValue` splits on commas (groups) and spaced slashes (parts), which\n // is what makes a per-group arity check possible at all.\n const { groups } = parseValue(value, {\n skipUnitValidation: true,\n skipFuncValidation: true,\n });\n\n for (const group of groups) {\n const tokens = group.parts.flatMap((part) => part.tokens);\n const directions: string[] = [];\n let valueCount = 0;\n let hasSpanModifier = false;\n\n for (const token of tokens) {\n if (VALUE_TOKEN_TYPES.has(token.type)) {\n valueCount++;\n continue;\n }\n\n const word = tokenText(token);\n\n if (ALL_DIRECTIONS.has(word)) {\n directions.push(word);\n } else if (word === 'dock' && DOCK_PROPERTIES.has(property)) {\n hasSpanModifier = true;\n }\n }\n\n if (directions.length === 0) continue;\n\n let reportedUnknown = false;\n for (const direction of directions) {\n if (!allowedMods.has(direction)) {\n context.report({\n node,\n messageId: 'invalidDirectionalModifier',\n data: {\n property,\n modifier: direction,\n accepted: [...allowedMods].join(', '),\n },\n });\n reportedUnknown = true;\n }\n }\n\n // Don't pile an arity complaint on top of an already-invalid modifier.\n if (reportedUnknown) continue;\n\n if (!SINGLE_VALUE_DIRECTIONAL_PROPERTIES.has(property)) continue;\n\n // Values and modifiers are bucketed separately by the parser, so the\n // pairing a reader infers from source order does not survive parsing: a\n // group naming directions carries one value, applied to every direction it\n // names. `dock` is the exception — its second value insets the sides the\n // dock spans.\n const limit = hasSpanModifier ? 2 : 1;\n\n if (valueCount > limit) {\n context.report({\n node,\n messageId: 'tooManyValues',\n data: {\n group: tokens.map(tokenText).filter(Boolean).join(' '),\n count: `${valueCount}`,\n limit: hasSpanModifier ? 'at most two values' : 'a single value',\n },\n });\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n checkValue(property, value, node);\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;AAYF,MAAM,oBAAoB,IAAI,IAAwB;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,SAAS,UAAU,OAA2B;AAC5C,KAAI,SAAS,SAAS,OAAO,MAAM,QAAQ,SAAU,QAAO,MAAM;AAClE,KAAI,MAAM,SAAS,UAAW,QAAO,MAAM;AAE3C,QAAO;;AAGT,yCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iGACH;EACD,UAAU;GACR,4BACE;GACF,eACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;GACN,MAAM,cAAc,sBAAsB;AAa1C,OAAI,CAAC,YAAa;GAIlB,MAAM,EAAE,WAAW,WAAW,OAAO;IACnC,oBAAoB;IACpB,oBAAoB;IACrB,CAAC;AAEF,QAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,SAAS,MAAM,MAAM,SAAS,SAAS,KAAK,OAAO;IACzD,MAAM,aAAuB,EAAE;IAC/B,IAAI,aAAa;IACjB,IAAI,kBAAkB;AAEtB,SAAK,MAAM,SAAS,QAAQ;AAC1B,SAAI,kBAAkB,IAAI,MAAM,KAAK,EAAE;AACrC;AACA;;KAGF,MAAM,OAAO,UAAU,MAAM;AAE7B,SAAI,eAAe,IAAI,KAAK,CAC1B,YAAW,KAAK,KAAK;cACZ,SAAS,UAAU,gBAAgB,IAAI,SAAS,CACzD,mBAAkB;;AAItB,QAAI,WAAW,WAAW,EAAG;IAE7B,IAAI,kBAAkB;AACtB,SAAK,MAAM,aAAa,WACtB,KAAI,CAAC,YAAY,IAAI,UAAU,EAAE;AAC/B,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OACJ;OACA,UAAU;OACV,UAAU,CAAC,GAAG,YAAY,CAAC,KAAK,KAAK;OACtC;MACF,CAAC;AACF,uBAAkB;;AAKtB,QAAI,gBAAiB;AAErB,QAAI,CAAC,oCAAoC,IAAI,SAAS,CAAE;AASxD,QAAI,cAFU,kBAAkB,IAAI,GAGlC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;MACtD,OAAO,GAAG;MACV,OAAO,kBAAkB,uBAAuB;MACjD;KACF,CAAC;;;AAKR,SAAO,0BAA0B,MAAM,OAAO,MAAM,aAAa;AAC/D,cAAW,UAAU,OAAO,KAAK;IACjC;;CAEL,CAAC"}
|
|
1
|
+
{"version":3,"file":"valid-directional-modifier.js","names":[],"sources":["../../src/rules/valid-directional-modifier.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport {\n DIRECTIONAL_MODIFIERS,\n DOCK_PROPERTIES,\n SINGLE_VALUE_DIRECTIONAL_PROPERTIES,\n} from '../constants.js';\nimport { parseValue } from '../parsers/index.js';\nimport type { ValueToken } from '../parsers/index.js';\n\ntype MessageIds = 'invalidDirectionalModifier' | 'tooManyValues';\n\n/**\n * Every direction word the rule recognises, across physical and logical\n * vocabularies. A word here is read as a *modifier*; whether it is accepted is\n * then decided per property by `DIRECTIONAL_MODIFIERS`.\n *\n * `start` and `end` are ordinary CSS *values* for a long tail of properties\n * (`textAlign: 'start'`, `justifyContent: 'end'`), which is exactly what the\n * `DIRECTIONAL_MODIFIERS` gate in `checkValue` exists to keep out: only the\n * properties with a directional vocabulary are ever inspected. Inside that set\n * no property takes `start`/`end` as a value, so reading them as modifiers is\n * safe — and it is what turns `padding: '1x start'` (a physical property given\n * a logical edge, which the handler drops) into a report.\n */\nconst ALL_DIRECTIONS = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'start',\n 'end',\n]);\n\n/**\n * Token types that count as a *value* rather than a modifier.\n *\n * Deliberately excludes `keyword`: direction words, `solid`, `inherit` and every\n * other bare word classify as keywords, and a CSS-wide keyword alongside a\n * direction (`padding: 'inherit top'`) is legal while carrying no value of its own.\n *\n * Also excludes colors: a `fade` group carries a width plus up to two mask\n * colors, so colors must not count toward the one-value limit.\n */\nconst VALUE_TOKEN_TYPES = new Set<ValueToken['type']>([\n 'custom-unit',\n 'css-unit',\n 'number',\n 'ratio',\n 'custom-prop',\n 'custom-prop-ref',\n 'css-function',\n 'tasty-function',\n 'group-expr',\n]);\n\n/** The source text of a token. `keyword` tokens carry `value` rather than `raw`. */\nfunction tokenText(token: ValueToken): string {\n if ('raw' in token && typeof token.raw === 'string') return token.raw;\n if (token.type === 'keyword') return token.value;\n\n return '';\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-directional-modifier',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate directional modifiers: only on properties that support them, and one value per group',\n },\n messages: {\n invalidDirectionalModifier:\n \"Modifier '{{modifier}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n tooManyValues:\n 'A group that names directions takes {{limit}}, but \\'{{group}}\\' has {{count}}. Values and modifiers are parsed into separate buckets, so their order is not recoverable — use comma-separated groups for per-side values, e.g. \"2x top, 4x right\".',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(\n property: string,\n value: string,\n node: TSESTree.Node,\n ): void {\n const allowedMods = DIRECTIONAL_MODIFIERS[property];\n // Only properties with a directional vocabulary are checked.\n //\n // This gate looks like it makes the \"wrong property\" case unreachable, and\n // it does — but removing it is not the fix. Direction words are ordinary CSS\n // *values* for a long tail of properties: `verticalAlign: 'bottom'`,\n // `textAlign: 'left'`, `transformOrigin: 'top center'`,\n // `backgroundPosition`, `objectPosition`, `float`, `clear`,\n // `scrollSnapAlign`, and `transition` (whose value names properties, which\n // can themselves be `left`/`top`). Checking every property reported all of\n // those as errors across a real design system. Catching `fill: '#purple\n // top'` is not worth an allowlist of every property that takes a\n // positional keyword.\n if (!allowedMods) return;\n\n // `parseValue` splits on commas (groups) and spaced slashes (parts), which\n // is what makes a per-group arity check possible at all.\n const { groups } = parseValue(value, {\n skipUnitValidation: true,\n skipFuncValidation: true,\n });\n\n for (const group of groups) {\n const tokens = group.parts.flatMap((part) => part.tokens);\n const directions: string[] = [];\n let valueCount = 0;\n let hasSpanModifier = false;\n\n for (const token of tokens) {\n if (VALUE_TOKEN_TYPES.has(token.type)) {\n valueCount++;\n continue;\n }\n\n const word = tokenText(token);\n\n if (ALL_DIRECTIONS.has(word)) {\n directions.push(word);\n } else if (word === 'dock' && DOCK_PROPERTIES.has(property)) {\n hasSpanModifier = true;\n }\n }\n\n if (directions.length === 0) continue;\n\n let reportedUnknown = false;\n for (const direction of directions) {\n if (!allowedMods.has(direction)) {\n context.report({\n node,\n messageId: 'invalidDirectionalModifier',\n data: {\n property,\n modifier: direction,\n accepted: [...allowedMods].join(', '),\n },\n });\n reportedUnknown = true;\n }\n }\n\n // Don't pile an arity complaint on top of an already-invalid modifier.\n if (reportedUnknown) continue;\n\n if (!SINGLE_VALUE_DIRECTIONAL_PROPERTIES.has(property)) continue;\n\n // Values and modifiers are bucketed separately by the parser, so the\n // pairing a reader infers from source order does not survive parsing: a\n // group naming directions carries one value, applied to every direction it\n // names. `dock` is the exception — its second value insets the sides the\n // dock spans.\n const limit = hasSpanModifier ? 2 : 1;\n\n if (valueCount > limit) {\n context.report({\n node,\n messageId: 'tooManyValues',\n data: {\n group: tokens.map(tokenText).filter(Boolean).join(' '),\n count: `${valueCount}`,\n limit: hasSpanModifier ? 'at most two values' : 'a single value',\n },\n });\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n checkValue(property, value, node);\n });\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;AAYF,MAAM,oBAAoB,IAAI,IAAwB;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,SAAS,UAAU,OAA2B;AAC5C,KAAI,SAAS,SAAS,OAAO,MAAM,QAAQ,SAAU,QAAO,MAAM;AAClE,KAAI,MAAM,SAAS,UAAW,QAAO,MAAM;AAE3C,QAAO;;AAGT,yCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,iGACH;EACD,UAAU;GACR,4BACE;GACF,eACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;GACN,MAAM,cAAc,sBAAsB;AAa1C,OAAI,CAAC,YAAa;GAIlB,MAAM,EAAE,WAAW,WAAW,OAAO;IACnC,oBAAoB;IACpB,oBAAoB;IACrB,CAAC;AAEF,QAAK,MAAM,SAAS,QAAQ;IAC1B,MAAM,SAAS,MAAM,MAAM,SAAS,SAAS,KAAK,OAAO;IACzD,MAAM,aAAuB,EAAE;IAC/B,IAAI,aAAa;IACjB,IAAI,kBAAkB;AAEtB,SAAK,MAAM,SAAS,QAAQ;AAC1B,SAAI,kBAAkB,IAAI,MAAM,KAAK,EAAE;AACrC;AACA;;KAGF,MAAM,OAAO,UAAU,MAAM;AAE7B,SAAI,eAAe,IAAI,KAAK,CAC1B,YAAW,KAAK,KAAK;cACZ,SAAS,UAAU,gBAAgB,IAAI,SAAS,CACzD,mBAAkB;;AAItB,QAAI,WAAW,WAAW,EAAG;IAE7B,IAAI,kBAAkB;AACtB,SAAK,MAAM,aAAa,WACtB,KAAI,CAAC,YAAY,IAAI,UAAU,EAAE;AAC/B,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OACJ;OACA,UAAU;OACV,UAAU,CAAC,GAAG,YAAY,CAAC,KAAK,KAAK;OACtC;MACF,CAAC;AACF,uBAAkB;;AAKtB,QAAI,gBAAiB;AAErB,QAAI,CAAC,oCAAoC,IAAI,SAAS,CAAE;AASxD,QAAI,cAFU,kBAAkB,IAAI,GAGlC,SAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MACJ,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;MACtD,OAAO,GAAG;MACV,OAAO,kBAAkB,uBAAuB;MACjD;KACF,CAAC;;;AAKR,SAAO,0BAA0B,MAAM,OAAO,MAAM,aAAa;AAC/D,cAAW,UAAU,OAAO,KAAK;IACjC;;CAEL,CAAC"}
|
|
@@ -112,7 +112,7 @@ var valid_value_default = createRule({
|
|
|
112
112
|
continue;
|
|
113
113
|
}
|
|
114
114
|
if ((token.type === "unknown" || token.type === "css-function") && "raw" in token && token.raw && erroredRaws.has(token.raw)) continue;
|
|
115
|
-
if (token.type === "unknown") {
|
|
115
|
+
if (token.type === "unknown" || token.type === "ratio" && !expectation.acceptsRatio) {
|
|
116
116
|
const raw = token.raw;
|
|
117
117
|
if (expectation.acceptsMods === true) {} else if (expectation.acceptsMods === false) context.report({
|
|
118
118
|
node,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken:\n \"Unknown token '{{token}}' in '{{property}}' value. Check for a typo.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.functions === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.functions)) {\n opts.knownFuncs = new Set(ctx.config.functions);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens and keywords in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token' ||\n token.type === 'color-ref' ||\n token.type === 'color-keyword')\n ) {\n const colorName =\n token.type === 'color-token'\n ? `#${token.name}`\n : token.type === 'color-ref'\n ? `##${token.name}`\n : token.raw;\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: colorName },\n });\n continue;\n }\n\n // Check color functions in non-color properties\n if (\n !expectation.acceptsColor &&\n token.type === 'css-function' &&\n isColorFunction(token.name)\n ) {\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: `${token.name}()` },\n });\n continue;\n }\n\n // Skip tokens already reported via parser errors\n if (\n (token.type === 'unknown' || token.type === 'css-function') &&\n 'raw' in token &&\n token.raw &&\n erroredRaws.has(token.raw)\n ) {\n continue;\n }\n\n // Check unknown tokens against property expectations\n if (token.type === 'unknown') {\n const raw = token.raw;\n\n if (expectation.acceptsMods === true) {\n // Passthrough: accept any unknown token\n } else if (expectation.acceptsMods === false) {\n context.report({\n node,\n messageId: 'unexpectedMod',\n data: { property, mod: raw },\n });\n } else if (Array.isArray(expectation.acceptsMods)) {\n if (!expectation.acceptsMods.includes(raw)) {\n context.report({\n node,\n messageId: 'invalidMod',\n data: {\n property,\n mod: raw,\n accepted: expectation.acceptsMods.join(', '),\n },\n });\n }\n }\n }\n }\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n if (!/^[#$]/.test(property)) checkValue(value, property, node);\n });\n },\n});\n\nconst COLOR_FUNC_NAMES = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'color-mix',\n 'color-contrast',\n]);\n\nfunction isColorFunction(name: string): boolean {\n return COLOR_FUNC_NAMES.has(name);\n}\n"],"mappings":";;;;;;;AAQA,MAAM,kBAAkB,IAAI,IAAI;CAC9B;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;AAWF,0BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,8FACH;EACD,UAAU;GACR,kBAAkB;GAClB,qBACE;GACF,eACE;GACF,iBACE;GACF,YACE;GACF,cACE;GACF,YAAY;GACb;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,gBAAoC;GAC3C,MAAM,OAA2B,EAAE;AACnC,OAAI,IAAI,OAAO,UAAU,MACvB,MAAK,qBAAqB;YACjB,MAAM,QAAQ,IAAI,OAAO,MAAM,CACxC,MAAK,aAAa,IAAI,IAAI,IAAI,OAAO,MAAM;AAE7C,OAAI,IAAI,OAAO,cAAc,MAC3B,MAAK,qBAAqB;YACjB,MAAM,QAAQ,IAAI,OAAO,UAAU,CAC5C,MAAK,aAAa,IAAI,IAAI,IAAI,OAAO,UAAU;AAEjD,UAAO;;EAGT,SAAS,WACP,OACA,UACA,MACM;AACN,OAAI,YAAY,gBAAgB,IAAI,SAAS,CAAE;GAE/C,MAAM,SAAS,WAAW,OAAO,eAAe,CAAC;GAEjD,MAAM,8BAAc,IAAI,KAAa;AAGrC,QAAK,MAAM,SAAS,OAAO,QAAQ;AACjC,QAAI,MAAM,QAAQ,SAAS,YAAY,EAAE;AACvC,aAAQ,OAAO;MAAE;MAAM,WAAW;MAAoB,CAAC;AACvD;;AAEF,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM,EAAE,SAAS,MAAM,SAAS;KACjC,CAAC;AACF,QAAI,MAAM,IACR,aAAY,IAAI,MAAM,IAAI;;AAI9B,OAAI,CAAC,SAAU;GAEf,MAAM,cAAc,eAAe,SAAS;AAE5C,QAAK,MAAM,SAAS,OAAO,OACzB,MAAK,MAAM,QAAQ,MAAM,MACvB,MAAK,MAAM,SAAS,KAAK,QAAQ;AAE/B,QAAI,MAAM,SAAS,aAAa;AAC9B,aAAQ,OAAO;MAAE;MAAM,WAAW;MAAuB,CAAC;AAC1D;;AAIF,QACE,CAAC,YAAY,iBACZ,MAAM,SAAS,iBACd,MAAM,SAAS,eACf,MAAM,SAAS,kBACjB;KACA,MAAM,YACJ,MAAM,SAAS,gBACX,IAAI,MAAM,SACV,MAAM,SAAS,cACb,KAAK,MAAM,SACX,MAAM;AACd,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE;OAAU,OAAO;OAAW;MACrC,CAAC;AACF;;AAIF,QACE,CAAC,YAAY,gBACb,MAAM,SAAS,kBACf,gBAAgB,MAAM,KAAK,EAC3B;AACA,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM;OAAE;OAAU,OAAO,GAAG,MAAM,KAAK;OAAK;MAC7C,CAAC;AACF;;AAIF,SACG,MAAM,SAAS,aAAa,MAAM,SAAS,mBAC5C,SAAS,SACT,MAAM,OACN,YAAY,IAAI,MAAM,IAAI,CAE1B;
|
|
1
|
+
{"version":3,"file":"valid-value.js","names":[],"sources":["../../src/rules/valid-value.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { parseValue } from '../parsers/value-parser.js';\nimport type { ValueParserOptions } from '../parsers/value-parser.js';\nimport { getExpectation } from '../property-expectations.js';\n\nconst SKIP_PROPERTIES = new Set([\n 'recipe',\n 'preset',\n 'transition',\n '@keyframes',\n '@property',\n '@font-face',\n '@counter-style',\n '@function',\n 'content',\n 'animation',\n 'animationName',\n 'gridArea',\n 'gridAreas',\n 'gridColumn',\n 'gridColumnStart',\n 'gridColumnEnd',\n 'gridRow',\n 'gridRowStart',\n 'gridRowEnd',\n 'gridTemplate',\n 'gridTemplateAreas',\n 'gridTemplateColumns',\n 'gridTemplateRows',\n 'listStyle',\n 'willChange',\n]);\n\ntype MessageIds =\n | 'unbalancedParens'\n | 'importantNotAllowed'\n | 'unexpectedMod'\n | 'unexpectedColor'\n | 'invalidMod'\n | 'unknownToken'\n | 'parseError';\n\nexport default createRule<[], MessageIds>({\n name: 'valid-value',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Parse style values through the value parser and validate against per-property expectations',\n },\n messages: {\n unbalancedParens: 'Unbalanced parentheses in value.',\n importantNotAllowed:\n 'Do not use !important in tasty styles. Use state specificity instead.',\n unexpectedMod:\n \"Unrecognized token '{{mod}}' in '{{property}}' value. This may be a typo.\",\n unexpectedColor:\n \"Property '{{property}}' does not accept color tokens, but found '{{color}}'.\",\n invalidMod:\n \"Modifier '{{mod}}' is not valid for '{{property}}'. Accepted: {{accepted}}.\",\n unknownToken:\n \"Unknown token '{{token}}' in '{{property}}' value. Check for a typo.\",\n parseError: '{{message}}',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function getParserOpts(): ValueParserOptions {\n const opts: ValueParserOptions = {};\n if (ctx.config.units === false) {\n opts.skipUnitValidation = true;\n } else if (Array.isArray(ctx.config.units)) {\n opts.knownUnits = new Set(ctx.config.units);\n }\n if (ctx.config.functions === false) {\n opts.skipFuncValidation = true;\n } else if (Array.isArray(ctx.config.functions)) {\n opts.knownFuncs = new Set(ctx.config.functions);\n }\n return opts;\n }\n\n function checkValue(\n value: string,\n property: string | null,\n node: TSESTree.Node,\n ): void {\n if (property && SKIP_PROPERTIES.has(property)) return;\n\n const result = parseValue(value, getParserOpts());\n\n const erroredRaws = new Set<string>();\n\n // Report parser-level errors (bracket balance, unknown units/functions)\n for (const error of result.errors) {\n if (error.message.includes('parenthes')) {\n context.report({ node, messageId: 'unbalancedParens' });\n return;\n }\n context.report({\n node,\n messageId: 'parseError',\n data: { message: error.message },\n });\n if (error.raw) {\n erroredRaws.add(error.raw);\n }\n }\n\n if (!property) return;\n\n const expectation = getExpectation(property);\n\n for (const group of result.groups) {\n for (const part of group.parts) {\n for (const token of part.tokens) {\n // Check !important\n if (token.type === 'important') {\n context.report({ node, messageId: 'importantNotAllowed' });\n continue;\n }\n\n // Check color tokens and keywords in non-color properties\n if (\n !expectation.acceptsColor &&\n (token.type === 'color-token' ||\n token.type === 'color-ref' ||\n token.type === 'color-keyword')\n ) {\n const colorName =\n token.type === 'color-token'\n ? `#${token.name}`\n : token.type === 'color-ref'\n ? `##${token.name}`\n : token.raw;\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: colorName },\n });\n continue;\n }\n\n // Check color functions in non-color properties\n if (\n !expectation.acceptsColor &&\n token.type === 'css-function' &&\n isColorFunction(token.name)\n ) {\n context.report({\n node,\n messageId: 'unexpectedColor',\n data: { property, color: `${token.name}()` },\n });\n continue;\n }\n\n // Skip tokens already reported via parser errors\n if (\n (token.type === 'unknown' || token.type === 'css-function') &&\n 'raw' in token &&\n token.raw &&\n erroredRaws.has(token.raw)\n ) {\n continue;\n }\n\n // Check unknown tokens against property expectations. A ratio\n // outside `aspectRatio` is as unrecognized as any other word.\n if (\n token.type === 'unknown' ||\n (token.type === 'ratio' && !expectation.acceptsRatio)\n ) {\n const raw = token.raw;\n\n if (expectation.acceptsMods === true) {\n // Passthrough: accept any unknown token\n } else if (expectation.acceptsMods === false) {\n context.report({\n node,\n messageId: 'unexpectedMod',\n data: { property, mod: raw },\n });\n } else if (Array.isArray(expectation.acceptsMods)) {\n if (!expectation.acceptsMods.includes(raw)) {\n context.report({\n node,\n messageId: 'invalidMod',\n data: {\n property,\n mod: raw,\n accepted: expectation.acceptsMods.join(', '),\n },\n });\n }\n }\n }\n }\n }\n }\n }\n\n return stringStyleValueListeners(ctx, (value, node, property) => {\n if (!/^[#$]/.test(property)) checkValue(value, property, node);\n });\n },\n});\n\nconst COLOR_FUNC_NAMES = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'color-mix',\n 'color-contrast',\n]);\n\nfunction isColorFunction(name: string): boolean {\n return COLOR_FUNC_NAMES.has(name);\n}\n"],"mappings":"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"}
|
package/dist/style-values.js
CHANGED
|
@@ -28,7 +28,11 @@ function styleValueListeners(ctx, check, options = {}) {
|
|
|
28
28
|
}
|
|
29
29
|
} else {
|
|
30
30
|
const visible = node.type === "Literal" || node.type === "JSXAttribute" || getStringValue(node) !== null || node.type === "UnaryExpression" && node.argument.type === "Literal";
|
|
31
|
-
if (attribute && visible
|
|
31
|
+
if (attribute && visible) {
|
|
32
|
+
const kind = ctx.jsxPropKind(attribute);
|
|
33
|
+
if (kind === "component") return;
|
|
34
|
+
if (kind === "unknown" && ctx.isExternalJSXElement(attribute.parent)) return;
|
|
35
|
+
}
|
|
32
36
|
check(property, node);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -59,6 +63,7 @@ function styleValueListeners(ctx, check, options = {}) {
|
|
|
59
63
|
let tag = opening.name;
|
|
60
64
|
while (tag.type === "JSXMemberExpression") tag = tag.object;
|
|
61
65
|
if (tag.type !== "JSXIdentifier" || !/^[A-Z]/.test(tag.name)) return;
|
|
66
|
+
if (!ctx.typeAwareJSX && ctx.isExternalJSXElement(opening)) return;
|
|
62
67
|
if (options.requireTastyJSX && !ctx.isTastyJSXComponent(node, tag.name)) return;
|
|
63
68
|
const value = node.value?.type === "JSXExpressionContainer" ? node.value.expression : node.value;
|
|
64
69
|
if (value) visit(key, value, node);
|