@tenphi/eslint-plugin-tasty 0.11.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/config.js +3 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +49 -7
- package/dist/constants.js.map +1 -1
- package/dist/context.js +40 -5
- package/dist/context.js.map +1 -1
- package/dist/index.js +40 -37
- package/dist/index.js.map +1 -1
- package/dist/parsers/value-parser.js +34 -0
- package/dist/parsers/value-parser.js.map +1 -1
- package/dist/property-expectations.js +12 -5
- package/dist/property-expectations.js.map +1 -1
- package/dist/rules/no-runtime-styles-mutation.js.map +1 -1
- package/dist/rules/prefer-shorthand-property.js +28 -3
- package/dist/rules/prefer-shorthand-property.js.map +1 -1
- package/dist/rules/valid-color-token.js +1 -1
- package/dist/rules/valid-color-token.js.map +1 -1
- package/dist/rules/valid-custom-property.js +1 -1
- package/dist/rules/valid-custom-property.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +76 -13
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-styles-structure.js +50 -10
- package/dist/rules/valid-styles-structure.js.map +1 -1
- package/dist/rules/valid-transition.js +10 -1
- package/dist/rules/valid-transition.js.map +1 -1
- package/dist/rules/valid-value.js +6 -3
- package/dist/rules/valid-value.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/package.json +7 -1
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\nimport type { ResolvedConfig } from './types.js';\nimport { loadConfig } from './config.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.js';\nimport { getKeyName } from './utils.js';\n\n/**\n * AST selectors that match ObjectExpressions in all known tasty style contexts:\n * call sites, variable declarations, satisfies/as expressions.\n */\nexport const STYLE_OBJECT_SELECTORS = [\n 'CallExpression ObjectExpression',\n 'VariableDeclarator > ObjectExpression',\n 'VariableDeclarator > TSSatisfiesExpression > ObjectExpression',\n 'VariableDeclarator > TSAsExpression > ObjectExpression',\n] as const;\n\n/**\n * Creates a record of ESLint listeners that all point to the same handler,\n * one entry per style-object AST selector.\n */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n const listeners: Record<string, (node: TSESTree.ObjectExpression) => void> =\n {};\n for (const selector of STYLE_OBJECT_SELECTORS) {\n listeners[selector] = handler;\n }\n return listeners;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\nconst TASTY_FUNCTION_NAMES = new Set([\n 'tasty',\n 'tastyStatic',\n 'useStyles',\n 'useGlobalStyles',\n 'configure',\n]);\n\n/**\n * Context tracker for a single file's lint pass.\n * Tracks which imports come from tasty and provides\n * helpers to determine if a node is in a tasty style context.\n */\nexport class TastyContext {\n readonly config: ResolvedConfig;\n private imports = new Map<string, TastyImport>();\n private importSources: Set<string>;\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n config?: ResolvedConfig,\n ) {\n this.config = config ?? loadConfig(context.filename);\n this.importSources = new Set([\n ...DEFAULT_IMPORT_SOURCES,\n ...this.config.importSources,\n ]);\n }\n\n trackImport(node: TSESTree.ImportDeclaration): void {\n const source = node.source.value;\n if (!this.importSources.has(source)) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportSpecifier') {\n const importedName =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (TASTY_FUNCTION_NAMES.has(importedName)) {\n this.imports.set(specifier.local.name, {\n localName: specifier.local.name,\n importedName,\n source,\n });\n }\n }\n }\n }\n\n getImport(localName: string): TastyImport | undefined {\n return this.imports.get(localName);\n }\n\n isTastyCall(node: TSESTree.CallExpression): TastyImport | undefined {\n if (node.callee.type !== 'Identifier') return undefined;\n return this.imports.get(node.callee.name);\n }\n\n /**\n * Determines whether an object expression is a tasty style object\n * by walking up the AST to find a recognized call expression.\n */\n isStyleObject(node: TSESTree.ObjectExpression): boolean {\n return this.getStyleContext(node) !== null;\n }\n\n getStyleContext(node: TSESTree.Node): {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n } | null {\n // Sub-element objects inherit their parent style object's context\n if (node.type === 'ObjectExpression') {\n const parent = node.parent;\n if (parent?.type === 'Property' && !parent.computed) {\n const key = getKeyName(parent.key);\n if (key && /^[A-Z]/.test(key)) {\n const grandparent = parent.parent;\n if (grandparent?.type === 'ObjectExpression') {\n return this.getStyleContext(grandparent);\n }\n }\n }\n }\n\n let current: TSESTree.Node | undefined = node;\n\n while (current) {\n if (current.type === 'CallExpression') {\n const imp = this.isTastyCall(current);\n if (!imp) return null;\n\n const name = imp.importedName;\n const isStaticCall = name === 'tastyStatic';\n\n if (name === 'tasty') {\n return this.getTastyCallContext(current, node);\n }\n\n if (name === 'tastyStatic') {\n return this.getTastyStaticCallContext(current, node);\n }\n\n if (name === 'useStyles') {\n if (current.arguments[0] === node) {\n return {\n type: 'useStyles',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n }\n\n if (name === 'useGlobalStyles') {\n if (current.arguments[1] === node) {\n return {\n type: 'useGlobalStyles',\n isStaticCall,\n isSelectorMode: true,\n isExtending: false,\n };\n }\n }\n\n return null;\n }\n\n if (this.isStyleVariableDeclaration(current, node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations. A `Styles` type annotation still opts a\n // differently-named variable in, below.\n if (name === 'styles' || name.endsWith('Styles')) return true;\n\n if (this.hasStylesTypeAnnotation(current)) return true;\n\n return false;\n }\n\n private hasStylesTypeAnnotation(node: TSESTree.VariableDeclarator): boolean {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n if (!annotation) return false;\n\n if (\n annotation.type === 'TSTypeReference' &&\n annotation.typeName.type === 'Identifier'\n ) {\n return /^Styles$/i.test(annotation.typeName.name);\n }\n\n return false;\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const optionsArg =\n args.length >= 2 && args[0].type !== 'ObjectExpression'\n ? args[1]\n : args[0];\n\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideStylesProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: args.length >= 2 && args[0].type !== 'ObjectExpression',\n };\n }\n\n // Check if inside variants\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideVariantsProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n return null;\n }\n\n private getTastyStaticCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tastyStatic({ ... })\n if (args.length === 1 && args[0] === targetNode) {\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = args[0].type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n };\n }\n\n return null;\n }\n\n private isInsideStylesProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'styles' &&\n prop.value === targetNode\n ) {\n return true;\n }\n }\n return false;\n }\n\n private isInsideVariantsProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'variants' &&\n prop.value.type === 'ObjectExpression'\n ) {\n for (const variantProp of prop.value.properties) {\n if (\n variantProp.type === 'Property' &&\n variantProp.value === targetNode\n ) {\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Checks if a property value node is a state mapping object\n * (i.e., an object where keys are state expressions and values are style values).\n */\n isStateMap(\n node: TSESTree.ObjectExpression,\n parentProperty: TSESTree.Property,\n ): boolean {\n const keyName = getKeyName(parentProperty.key);\n if (keyName === null) return false;\n\n // If the key starts with uppercase, it's a sub-element, not a state map\n if (/^[A-Z]/.test(keyName)) return false;\n\n // Special keys are not state maps\n if (keyName === '@keyframes' || keyName === '@properties') return false;\n\n // If the object has keys that look like state expressions, it's a state map.\n // A lone `_` (the fallback floor) or `''` (the default) is enough — both are\n // already covered by the string-literal / identifier branches below.\n return node.properties.some((prop) => {\n if (prop.type !== 'Property') return false;\n if (prop.key.type === 'Literal' && prop.key.value === '') return true;\n if (prop.key.type === 'Identifier') return true;\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {\n return true;\n }\n return false;\n });\n }\n\n /**\n * Checks if a key represents a sub-element (starts with uppercase).\n */\n isSubElementKey(key: string): boolean {\n return /^[A-Z]/.test(key);\n }\n\n /**\n * Checks if a key represents a nested selector (starts with &).\n */\n isNestedSelectorKey(key: string): boolean {\n return key.startsWith('&');\n }\n\n /**\n * Checks if a key is a custom CSS property definition ($name or $$name).\n */\n isCustomPropertyKey(key: string): boolean {\n return key.startsWith('$');\n }\n\n /**\n * Checks if a key is a color token definition (#name or ##name).\n */\n isColorTokenKey(key: string): boolean {\n return key.startsWith('#');\n }\n\n /**\n * Checks if a key is a special @ property (@keyframes, @properties).\n */\n isSpecialKey(key: string): boolean {\n return key.startsWith('@');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\nimport type { ResolvedConfig } from './types.js';\nimport { loadConfig } from './config.js';\nimport { DEFAULT_IMPORT_SOURCES, SPECIAL_STYLE_KEYS } from './constants.js';\nimport { getKeyName } from './utils.js';\n\n/**\n * AST selectors that match ObjectExpressions in all known tasty style contexts:\n * call sites, variable declarations, satisfies/as expressions.\n */\nexport const STYLE_OBJECT_SELECTORS = [\n 'CallExpression ObjectExpression',\n 'VariableDeclarator > ObjectExpression',\n 'VariableDeclarator > TSSatisfiesExpression > ObjectExpression',\n 'VariableDeclarator > TSAsExpression > ObjectExpression',\n // `<Block styles={{…}} />`. Safe to match on the prop name alone: React's own\n // prop is `style`, singular, so a plural `styles` JSX prop is a Tasty\n // convention.\n \"JSXAttribute[name.name='styles'] > JSXExpressionContainer > ObjectExpression\",\n // A Storybook story's `args.styles`. Deliberately this specific rather than\n // matching any `styles` key: plenty of unrelated libraries take a `styles`\n // option object, and a bare key carries no evidence it is Tasty's. Story files\n // are where a lot of style code is authored and neither the call-site nor the\n // variable-name heuristic reaches them — the enclosing variable is named after\n // the story, and there is no Tasty call.\n // Superset of the `args.styles` shape; `isStylesPropertyValue` is the real\n // gate, so both `styles` and `'styles'` key forms are covered.\n 'Property > ObjectExpression > Property > ObjectExpression',\n] as const;\n\n/**\n * Creates a record of ESLint listeners that all point to the same handler,\n * one entry per style-object AST selector.\n */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n const listeners: Record<string, (node: TSESTree.ObjectExpression) => void> =\n {};\n\n // Selectors overlap — `tasty({ styles: {…} })` matches both\n // `CallExpression ObjectExpression` and the `styles` key selector — so the\n // handler must run at most once per object, or every rule double-reports.\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n const once = (node: TSESTree.ObjectExpression) => {\n if (visited.has(node)) return;\n visited.add(node);\n handler(node);\n };\n\n for (const selector of STYLE_OBJECT_SELECTORS) {\n listeners[selector] = once;\n }\n\n return listeners;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\nconst TASTY_FUNCTION_NAMES = new Set([\n 'tasty',\n 'tastyStatic',\n 'useStyles',\n 'useGlobalStyles',\n 'configure',\n]);\n\n/**\n * Context tracker for a single file's lint pass.\n * Tracks which imports come from tasty and provides\n * helpers to determine if a node is in a tasty style context.\n */\nexport class TastyContext {\n readonly config: ResolvedConfig;\n private imports = new Map<string, TastyImport>();\n private importSources: Set<string>;\n\n constructor(\n private context: RuleContext<string, unknown[]>,\n config?: ResolvedConfig,\n ) {\n this.config = config ?? loadConfig(context.filename);\n this.importSources = new Set([\n ...DEFAULT_IMPORT_SOURCES,\n ...this.config.importSources,\n ]);\n }\n\n trackImport(node: TSESTree.ImportDeclaration): void {\n const source = node.source.value;\n if (!this.importSources.has(source)) return;\n\n for (const specifier of node.specifiers) {\n if (specifier.type === 'ImportSpecifier') {\n const importedName =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (TASTY_FUNCTION_NAMES.has(importedName)) {\n this.imports.set(specifier.local.name, {\n localName: specifier.local.name,\n importedName,\n source,\n });\n }\n }\n }\n }\n\n getImport(localName: string): TastyImport | undefined {\n return this.imports.get(localName);\n }\n\n isTastyCall(node: TSESTree.CallExpression): TastyImport | undefined {\n if (node.callee.type !== 'Identifier') return undefined;\n return this.imports.get(node.callee.name);\n }\n\n /**\n * Determines whether an object expression is a tasty style object\n * by walking up the AST to find a recognized call expression.\n */\n isStyleObject(node: TSESTree.ObjectExpression): boolean {\n return this.getStyleContext(node) !== null;\n }\n\n getStyleContext(node: TSESTree.Node): {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n } | null {\n // Sub-element objects inherit their parent style object's context\n if (node.type === 'ObjectExpression') {\n const parent = node.parent;\n if (parent?.type === 'Property' && !parent.computed) {\n const key = getKeyName(parent.key);\n if (key && /^[A-Z]/.test(key)) {\n const grandparent = parent.parent;\n if (grandparent?.type === 'ObjectExpression') {\n return this.getStyleContext(grandparent);\n }\n }\n }\n }\n\n // An object assigned to a `styles` key (or a `styles` JSX prop) is a style\n // object on its own terms — no enclosing tasty call or `styles`-ish variable\n // name required. Checked before the walk, which would otherwise climb past it\n // to an unrelated call or declaration and reject the whole thing.\n if (node.type === 'ObjectExpression' && this.isStylesPropertyValue(node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n let current: TSESTree.Node | undefined = node;\n\n while (current) {\n if (current.type === 'CallExpression') {\n const imp = this.isTastyCall(current);\n if (!imp) return null;\n\n const name = imp.importedName;\n const isStaticCall = name === 'tastyStatic';\n\n if (name === 'tasty') {\n return this.getTastyCallContext(current, node);\n }\n\n if (name === 'tastyStatic') {\n return this.getTastyStaticCallContext(current, node);\n }\n\n if (name === 'useStyles') {\n if (current.arguments[0] === node) {\n return {\n type: 'useStyles',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n }\n\n if (name === 'useGlobalStyles') {\n if (current.arguments[1] === node) {\n return {\n type: 'useGlobalStyles',\n isStaticCall,\n isSelectorMode: true,\n isExtending: false,\n };\n }\n }\n\n return null;\n }\n\n if (this.isStyleVariableDeclaration(current, node)) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations. A `Styles` type annotation still opts a\n // differently-named variable in, below.\n if (name === 'styles' || name.endsWith('Styles')) return true;\n\n if (this.hasStylesTypeAnnotation(current)) return true;\n\n return false;\n }\n\n private hasStylesTypeAnnotation(node: TSESTree.VariableDeclarator): boolean {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n if (!annotation) return false;\n\n if (\n annotation.type === 'TSTypeReference' &&\n annotation.typeName.type === 'Identifier'\n ) {\n return /^Styles$/i.test(annotation.typeName.name);\n }\n\n return false;\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const optionsArg =\n args.length >= 2 && args[0].type !== 'ObjectExpression'\n ? args[1]\n : args[0];\n\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideStylesProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: args.length >= 2 && args[0].type !== 'ObjectExpression',\n };\n }\n\n // Check if inside variants\n if (\n optionsArg?.type === 'ObjectExpression' &&\n this.isInsideVariantsProperty(optionsArg, targetNode)\n ) {\n return {\n type: 'tasty' as const,\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n return null;\n }\n\n private getTastyStaticCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tastyStatic({ ... })\n if (args.length === 1 && args[0] === targetNode) {\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode: false,\n isExtending: false,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = args[0].type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n };\n }\n\n return null;\n }\n\n private isInsideStylesProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'styles' &&\n prop.value === targetNode\n ) {\n return true;\n }\n }\n return false;\n }\n\n private isInsideVariantsProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'variants' &&\n prop.value.type === 'ObjectExpression'\n ) {\n for (const variantProp of prop.value.properties) {\n if (\n variantProp.type === 'Property' &&\n variantProp.value === targetNode\n ) {\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Whether this object is a `styles` JSX prop value or a Storybook `args.styles`.\n *\n * Both are unambiguous enough to stand on their own without an import-tracked\n * call or a `styles`-ish variable name. A bare `styles` key elsewhere is not —\n * see the selector list for why.\n */\n isStylesPropertyValue(node: TSESTree.ObjectExpression): boolean {\n const parent = node.parent;\n\n if (parent?.type === 'Property' && !parent.computed) {\n if (getKeyName(parent.key) !== 'styles') return false;\n\n // Only inside `args: { … }`.\n const argsObject = parent.parent;\n const argsProperty =\n argsObject?.type === 'ObjectExpression' ? argsObject.parent : undefined;\n\n return (\n argsProperty?.type === 'Property' &&\n !argsProperty.computed &&\n getKeyName(argsProperty.key) === 'args'\n );\n }\n\n if (parent?.type === 'JSXExpressionContainer') {\n const attribute = parent.parent;\n\n return (\n attribute?.type === 'JSXAttribute' &&\n attribute.name.type === 'JSXIdentifier' &&\n attribute.name.name === 'styles'\n );\n }\n\n return false;\n }\n\n /**\n * Checks if a property value node is a state mapping object\n * (i.e., an object where keys are state expressions and values are style values).\n */\n isStateMap(\n node: TSESTree.ObjectExpression,\n parentProperty: TSESTree.Property,\n ): boolean {\n const keyName = getKeyName(parentProperty.key);\n if (keyName === null) return false;\n\n // If the key starts with uppercase, it's a sub-element, not a state map\n if (/^[A-Z]/.test(keyName)) return false;\n\n // Special keys are not state maps\n // At-rule blocks are not state maps — their object values are descriptor maps.\n if (SPECIAL_STYLE_KEYS.has(keyName)) return false;\n\n // If the object has keys that look like state expressions, it's a state map.\n // A lone `_` (the fallback floor) or `''` (the default) is enough — both are\n // already covered by the string-literal / identifier branches below.\n return node.properties.some((prop) => {\n if (prop.type !== 'Property') return false;\n if (prop.key.type === 'Literal' && prop.key.value === '') return true;\n if (prop.key.type === 'Identifier') return true;\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {\n return true;\n }\n return false;\n });\n }\n\n /**\n * Checks if a key represents a sub-element (starts with uppercase).\n */\n isSubElementKey(key: string): boolean {\n return /^[A-Z]/.test(key);\n }\n\n /**\n * Checks if a key represents a nested selector (starts with &).\n */\n isNestedSelectorKey(key: string): boolean {\n return key.startsWith('&');\n }\n\n /**\n * Checks if a key is a custom CSS property definition ($name or $$name).\n */\n isCustomPropertyKey(key: string): boolean {\n return key.startsWith('$');\n }\n\n /**\n * Checks if a key is a color token definition (#name or ##name).\n */\n isColorTokenKey(key: string): boolean {\n return key.startsWith('#');\n }\n\n /**\n * Checks if a key is a special @ property (@keyframes, @property, ...).\n */\n isSpecialKey(key: string): boolean {\n return key.startsWith('@');\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -33,49 +33,52 @@ import prefer_hide_default from "./rules/prefer-hide.js";
|
|
|
33
33
|
import prefer_directional_shorthand_default from "./rules/prefer-directional-shorthand.js";
|
|
34
34
|
import prefer_longhand_property_default from "./rules/prefer-longhand-property.js";
|
|
35
35
|
import { recommended, strict } from "./configs.js";
|
|
36
|
+
import { createRequire } from "node:module";
|
|
36
37
|
|
|
37
38
|
//#region src/index.ts
|
|
39
|
+
const ruleMap = {
|
|
40
|
+
"known-property": known_property_default,
|
|
41
|
+
"valid-value": valid_value_default,
|
|
42
|
+
"valid-color-token": valid_color_token_default,
|
|
43
|
+
"valid-custom-unit": valid_custom_unit_default,
|
|
44
|
+
"valid-state-key": valid_state_key_default,
|
|
45
|
+
"valid-styles-structure": valid_styles_structure_default,
|
|
46
|
+
"no-nested-selector": no_nested_selector_default,
|
|
47
|
+
"valid-custom-property": valid_custom_property_default,
|
|
48
|
+
"valid-preset": valid_preset_default,
|
|
49
|
+
"valid-recipe": valid_recipe_default,
|
|
50
|
+
"valid-boolean-property": valid_boolean_property_default,
|
|
51
|
+
"valid-directional-modifier": valid_directional_modifier_default,
|
|
52
|
+
"valid-radius-shape": valid_radius_shape_default,
|
|
53
|
+
"no-important": no_important_default,
|
|
54
|
+
"valid-sub-element": valid_sub_element_default,
|
|
55
|
+
"no-nested-state-map": no_nested_state_map_default,
|
|
56
|
+
"static-no-dynamic-values": static_no_dynamic_values_default,
|
|
57
|
+
"static-valid-selector": static_valid_selector_default,
|
|
58
|
+
"prefer-shorthand-property": prefer_shorthand_property_default,
|
|
59
|
+
"valid-transition": valid_transition_default,
|
|
60
|
+
"require-default-state": require_default_state_default,
|
|
61
|
+
"no-unknown-state-alias": no_unknown_state_alias_default,
|
|
62
|
+
"no-raw-color-values": no_raw_color_values_default,
|
|
63
|
+
"no-styles-prop": no_styles_prop_default,
|
|
64
|
+
"consistent-token-usage": consistent_token_usage_default,
|
|
65
|
+
"no-runtime-styles-mutation": no_runtime_styles_mutation_default,
|
|
66
|
+
"valid-state-definition": valid_state_definition_default,
|
|
67
|
+
"no-own-at-root": no_own_at_root_default,
|
|
68
|
+
"valid-default-state-order": valid_default_state_order_default,
|
|
69
|
+
"prefer-auto-calc": prefer_auto_calc_default,
|
|
70
|
+
"prefer-custom-property-syntax": prefer_custom_property_syntax_default,
|
|
71
|
+
"prefer-hide": prefer_hide_default,
|
|
72
|
+
"prefer-directional-shorthand": prefer_directional_shorthand_default,
|
|
73
|
+
"prefer-longhand-property": prefer_longhand_property_default
|
|
74
|
+
};
|
|
75
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
38
76
|
const plugin = {
|
|
39
77
|
meta: {
|
|
40
78
|
name: "@tenphi/eslint-plugin-tasty",
|
|
41
|
-
version
|
|
42
|
-
},
|
|
43
|
-
rules: {
|
|
44
|
-
"known-property": known_property_default,
|
|
45
|
-
"valid-value": valid_value_default,
|
|
46
|
-
"valid-color-token": valid_color_token_default,
|
|
47
|
-
"valid-custom-unit": valid_custom_unit_default,
|
|
48
|
-
"valid-state-key": valid_state_key_default,
|
|
49
|
-
"valid-styles-structure": valid_styles_structure_default,
|
|
50
|
-
"no-nested-selector": no_nested_selector_default,
|
|
51
|
-
"valid-custom-property": valid_custom_property_default,
|
|
52
|
-
"valid-preset": valid_preset_default,
|
|
53
|
-
"valid-recipe": valid_recipe_default,
|
|
54
|
-
"valid-boolean-property": valid_boolean_property_default,
|
|
55
|
-
"valid-directional-modifier": valid_directional_modifier_default,
|
|
56
|
-
"valid-radius-shape": valid_radius_shape_default,
|
|
57
|
-
"no-important": no_important_default,
|
|
58
|
-
"valid-sub-element": valid_sub_element_default,
|
|
59
|
-
"no-nested-state-map": no_nested_state_map_default,
|
|
60
|
-
"static-no-dynamic-values": static_no_dynamic_values_default,
|
|
61
|
-
"static-valid-selector": static_valid_selector_default,
|
|
62
|
-
"prefer-shorthand-property": prefer_shorthand_property_default,
|
|
63
|
-
"valid-transition": valid_transition_default,
|
|
64
|
-
"require-default-state": require_default_state_default,
|
|
65
|
-
"no-unknown-state-alias": no_unknown_state_alias_default,
|
|
66
|
-
"no-raw-color-values": no_raw_color_values_default,
|
|
67
|
-
"no-styles-prop": no_styles_prop_default,
|
|
68
|
-
"consistent-token-usage": consistent_token_usage_default,
|
|
69
|
-
"no-runtime-styles-mutation": no_runtime_styles_mutation_default,
|
|
70
|
-
"valid-state-definition": valid_state_definition_default,
|
|
71
|
-
"no-own-at-root": no_own_at_root_default,
|
|
72
|
-
"valid-default-state-order": valid_default_state_order_default,
|
|
73
|
-
"prefer-auto-calc": prefer_auto_calc_default,
|
|
74
|
-
"prefer-custom-property-syntax": prefer_custom_property_syntax_default,
|
|
75
|
-
"prefer-hide": prefer_hide_default,
|
|
76
|
-
"prefer-directional-shorthand": prefer_directional_shorthand_default,
|
|
77
|
-
"prefer-longhand-property": prefer_longhand_property_default
|
|
79
|
+
version
|
|
78
80
|
},
|
|
81
|
+
rules: ruleMap,
|
|
79
82
|
configs: {
|
|
80
83
|
recommended: {
|
|
81
84
|
plugins: { get tasty() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["rules.knownProperty","rules.validValue","rules.validColorToken","rules.validCustomUnit","rules.validStateKey","rules.validStylesStructure","rules.noNestedSelector","rules.validCustomProperty","rules.validPreset","rules.validRecipe","rules.validBooleanProperty","rules.validDirectionalModifier","rules.validRadiusShape","rules.noImportant","rules.validSubElement","rules.noNestedStateMap","rules.staticNoDynamicValues","rules.staticValidSelector","rules.preferShorthandProperty","rules.validTransition","rules.requireDefaultState","rules.noUnknownStateAlias","rules.noRawColorValues","rules.noStylesProp","rules.consistentTokenUsage","rules.noRuntimeStylesMutation","rules.validStateDefinition","rules.noOwnAtRoot","rules.validDefaultStateOrder","rules.preferAutoCalc","rules.preferCustomPropertySyntax","rules.preferHide","rules.preferDirectionalShorthand","rules.preferLonghandProperty"],"sources":["../src/index.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\nimport * as rules from './rules/index.js';\nimport { recommended, strict } from './configs.js';\n\nconst ruleMap: Record<string, TSESLint.RuleModule<string, unknown[]>> = {\n 'known-property': rules.knownProperty,\n 'valid-value': rules.validValue,\n 'valid-color-token': rules.validColorToken,\n 'valid-custom-unit': rules.validCustomUnit,\n 'valid-state-key': rules.validStateKey,\n 'valid-styles-structure': rules.validStylesStructure,\n 'no-nested-selector': rules.noNestedSelector,\n 'valid-custom-property': rules.validCustomProperty,\n 'valid-preset': rules.validPreset,\n 'valid-recipe': rules.validRecipe,\n 'valid-boolean-property': rules.validBooleanProperty,\n 'valid-directional-modifier': rules.validDirectionalModifier,\n 'valid-radius-shape': rules.validRadiusShape,\n 'no-important': rules.noImportant,\n 'valid-sub-element': rules.validSubElement,\n 'no-nested-state-map': rules.noNestedStateMap,\n 'static-no-dynamic-values': rules.staticNoDynamicValues,\n 'static-valid-selector': rules.staticValidSelector,\n 'prefer-shorthand-property': rules.preferShorthandProperty,\n 'valid-transition': rules.validTransition,\n 'require-default-state': rules.requireDefaultState,\n 'no-unknown-state-alias': rules.noUnknownStateAlias,\n 'no-raw-color-values': rules.noRawColorValues,\n 'no-styles-prop': rules.noStylesProp,\n 'consistent-token-usage': rules.consistentTokenUsage,\n 'no-runtime-styles-mutation': rules.noRuntimeStylesMutation,\n 'valid-state-definition': rules.validStateDefinition,\n 'no-own-at-root': rules.noOwnAtRoot,\n 'valid-default-state-order': rules.validDefaultStateOrder,\n 'prefer-auto-calc': rules.preferAutoCalc,\n 'prefer-custom-property-syntax': rules.preferCustomPropertySyntax,\n 'prefer-hide': rules.preferHide,\n 'prefer-directional-shorthand': rules.preferDirectionalShorthand,\n 'prefer-longhand-property': rules.preferLonghandProperty,\n};\n\nconst plugin = {\n meta: {\n name: '@tenphi/eslint-plugin-tasty',\n version
|
|
1
|
+
{"version":3,"file":"index.js","names":["rules.knownProperty","rules.validValue","rules.validColorToken","rules.validCustomUnit","rules.validStateKey","rules.validStylesStructure","rules.noNestedSelector","rules.validCustomProperty","rules.validPreset","rules.validRecipe","rules.validBooleanProperty","rules.validDirectionalModifier","rules.validRadiusShape","rules.noImportant","rules.validSubElement","rules.noNestedStateMap","rules.staticNoDynamicValues","rules.staticValidSelector","rules.preferShorthandProperty","rules.validTransition","rules.requireDefaultState","rules.noUnknownStateAlias","rules.noRawColorValues","rules.noStylesProp","rules.consistentTokenUsage","rules.noRuntimeStylesMutation","rules.validStateDefinition","rules.noOwnAtRoot","rules.validDefaultStateOrder","rules.preferAutoCalc","rules.preferCustomPropertySyntax","rules.preferHide","rules.preferDirectionalShorthand","rules.preferLonghandProperty"],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport type { TSESLint } from '@typescript-eslint/utils';\nimport * as rules from './rules/index.js';\nimport { recommended, strict } from './configs.js';\n\nconst ruleMap: Record<string, TSESLint.RuleModule<string, unknown[]>> = {\n 'known-property': rules.knownProperty,\n 'valid-value': rules.validValue,\n 'valid-color-token': rules.validColorToken,\n 'valid-custom-unit': rules.validCustomUnit,\n 'valid-state-key': rules.validStateKey,\n 'valid-styles-structure': rules.validStylesStructure,\n 'no-nested-selector': rules.noNestedSelector,\n 'valid-custom-property': rules.validCustomProperty,\n 'valid-preset': rules.validPreset,\n 'valid-recipe': rules.validRecipe,\n 'valid-boolean-property': rules.validBooleanProperty,\n 'valid-directional-modifier': rules.validDirectionalModifier,\n 'valid-radius-shape': rules.validRadiusShape,\n 'no-important': rules.noImportant,\n 'valid-sub-element': rules.validSubElement,\n 'no-nested-state-map': rules.noNestedStateMap,\n 'static-no-dynamic-values': rules.staticNoDynamicValues,\n 'static-valid-selector': rules.staticValidSelector,\n 'prefer-shorthand-property': rules.preferShorthandProperty,\n 'valid-transition': rules.validTransition,\n 'require-default-state': rules.requireDefaultState,\n 'no-unknown-state-alias': rules.noUnknownStateAlias,\n 'no-raw-color-values': rules.noRawColorValues,\n 'no-styles-prop': rules.noStylesProp,\n 'consistent-token-usage': rules.consistentTokenUsage,\n 'no-runtime-styles-mutation': rules.noRuntimeStylesMutation,\n 'valid-state-definition': rules.validStateDefinition,\n 'no-own-at-root': rules.noOwnAtRoot,\n 'valid-default-state-order': rules.validDefaultStateOrder,\n 'prefer-auto-calc': rules.preferAutoCalc,\n 'prefer-custom-property-syntax': rules.preferCustomPropertySyntax,\n 'prefer-hide': rules.preferHide,\n 'prefer-directional-shorthand': rules.preferDirectionalShorthand,\n 'prefer-longhand-property': rules.preferLonghandProperty,\n};\n\n// Read from package.json so `meta.version` cannot drift from the released\n// version, as the previous hardcoded literal had. `../package.json` resolves to\n// the package root from both `src/` and the unbundled `dist/`.\nconst { version } = createRequire(import.meta.url)('../package.json') as {\n version: string;\n};\n\nconst plugin = {\n meta: {\n name: '@tenphi/eslint-plugin-tasty',\n version,\n },\n rules: ruleMap,\n configs: {\n recommended: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: recommended,\n },\n strict: {\n plugins: {\n get tasty() {\n return plugin;\n },\n },\n rules: strict,\n },\n },\n} satisfies TSESLint.FlatConfig.Plugin & {\n configs: Record<string, TSESLint.FlatConfig.Config>;\n};\n\nexport default plugin;\n\nexport { recommended, strict } from './configs.js';\nexport type { TastyValidationConfig, ResolvedConfig } from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAM,UAAkE;CACtE,kBAAkBA;CAClB,eAAeC;CACf,qBAAqBC;CACrB,qBAAqBC;CACrB,mBAAmBC;CACnB,0BAA0BC;CAC1B,sBAAsBC;CACtB,yBAAyBC;CACzB,gBAAgBC;CAChB,gBAAgBC;CAChB,0BAA0BC;CAC1B,8BAA8BC;CAC9B,sBAAsBC;CACtB,gBAAgBC;CAChB,qBAAqBC;CACrB,uBAAuBC;CACvB,4BAA4BC;CAC5B,yBAAyBC;CACzB,6BAA6BC;CAC7B,oBAAoBC;CACpB,yBAAyBC;CACzB,0BAA0BC;CAC1B,uBAAuBC;CACvB,kBAAkBC;CAClB,0BAA0BC;CAC1B,8BAA8BC;CAC9B,0BAA0BC;CAC1B,kBAAkBC;CAClB,6BAA6BC;CAC7B,oBAAoBC;CACpB,iCAAiCC;CACjC,eAAeC;CACf,gCAAgCC;CAChC,4BAA4BC;CAC7B;AAKD,MAAM,EAAE,YAAY,cAAc,OAAO,KAAK,IAAI,CAAC,kBAAkB;AAIrE,MAAM,SAAS;CACb,MAAM;EACJ,MAAM;EACN;EACD;CACD,OAAO;CACP,SAAS;EACP,aAAa;GACX,SAAS,EACP,IAAI,QAAQ;AACV,WAAO;MAEV;GACD,OAAO;GACR;EACD,QAAQ;GACN,SAAS,EACP,IAAI,QAAQ;AACV,WAAO;MAEV;GACD,OAAO;GACR;EACF;CACF"}
|
|
@@ -321,6 +321,10 @@ function classifyToken(raw, offset, errors, opts) {
|
|
|
321
321
|
type: "important",
|
|
322
322
|
raw: token
|
|
323
323
|
};
|
|
324
|
+
if (token.startsWith("$$") || token.startsWith("##")) {
|
|
325
|
+
const call = classifyTastyFunctionCall(token, offset, errors);
|
|
326
|
+
if (call) return call;
|
|
327
|
+
}
|
|
324
328
|
if (token.startsWith("$$")) {
|
|
325
329
|
const name = token.slice(2);
|
|
326
330
|
if (/^[a-z_][a-z0-9-_]*$/i.test(name)) return {
|
|
@@ -494,6 +498,36 @@ function classifyToken(raw, offset, errors, opts) {
|
|
|
494
498
|
raw: token
|
|
495
499
|
};
|
|
496
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* Classify a `$$name(...)` / `##name(...)` Tasty `@function` call.
|
|
503
|
+
*
|
|
504
|
+
* Returns `null` when the token is not a call, so the caller falls through to the
|
|
505
|
+
* bare `$$name` / `##name` reference handling that `transition` relies on.
|
|
506
|
+
*/
|
|
507
|
+
function classifyTastyFunctionCall(token, offset, errors) {
|
|
508
|
+
const openIdx = token.indexOf("(");
|
|
509
|
+
if (openIdx <= 2 || !token.endsWith(")")) return null;
|
|
510
|
+
const name = token.slice(2, openIdx);
|
|
511
|
+
const args = token.slice(openIdx + 1, -1);
|
|
512
|
+
if (!/^[a-z_][a-z0-9-_]*$/i.test(name)) {
|
|
513
|
+
errors.push({
|
|
514
|
+
message: `Invalid function name in '${token}'.`,
|
|
515
|
+
offset,
|
|
516
|
+
length: token.length,
|
|
517
|
+
raw: token
|
|
518
|
+
});
|
|
519
|
+
return {
|
|
520
|
+
type: "unknown",
|
|
521
|
+
raw: token
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
type: "tasty-function",
|
|
526
|
+
name,
|
|
527
|
+
args,
|
|
528
|
+
raw: token
|
|
529
|
+
};
|
|
530
|
+
}
|
|
497
531
|
function classifyColorToken(token, offset, errors) {
|
|
498
532
|
const raw = token;
|
|
499
533
|
const name = token.slice(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 { BUILT_IN_UNITS, CSS_UNITS } 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 | { 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 | { 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 (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 // 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\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 const opacity = Number(opacitySuffix);\n if (isNaN(opacity)) {\n errors.push({\n message: `Invalid opacity value '${opacitySuffix}'.`,\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n if (opacity < 0) {\n errors.push({\n message: 'Opacity cannot be negative.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n if (opacity > 100) {\n errors.push({\n message: `Opacity '${opacitySuffix}' exceeds 100.`,\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":";;;;AAqDA,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 { BUILT_IN_UNITS, CSS_UNITS } 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 | { 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 // 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 const opacity = Number(opacitySuffix);\n if (isNaN(opacity)) {\n errors.push({\n message: `Invalid opacity value '${opacitySuffix}'.`,\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n if (opacity < 0) {\n errors.push({\n message: 'Opacity cannot be negative.',\n offset,\n length: token.length,\n raw,\n });\n return { type: 'unknown', raw };\n }\n if (opacity > 100) {\n errors.push({\n message: `Opacity '${opacitySuffix}' exceeds 100.`,\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":";;;;AA2DA,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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"}
|
|
@@ -5,13 +5,20 @@ const DIRECTIONAL_MODS = [
|
|
|
5
5
|
"bottom",
|
|
6
6
|
"left"
|
|
7
7
|
];
|
|
8
|
-
const
|
|
8
|
+
const LONGHAND_MOD = "longhand";
|
|
9
|
+
const INSET_MODS = [
|
|
10
|
+
...DIRECTIONAL_MODS,
|
|
11
|
+
"dock",
|
|
12
|
+
LONGHAND_MOD
|
|
13
|
+
];
|
|
14
|
+
const BOX_DIRECTIONAL_MODS = [...DIRECTIONAL_MODS, LONGHAND_MOD];
|
|
9
15
|
const RADIUS_DIRECTIONAL_MODS = [
|
|
10
16
|
...DIRECTIONAL_MODS,
|
|
11
17
|
"top-left",
|
|
12
18
|
"top-right",
|
|
13
19
|
"bottom-left",
|
|
14
|
-
"bottom-right"
|
|
20
|
+
"bottom-right",
|
|
21
|
+
LONGHAND_MOD
|
|
15
22
|
];
|
|
16
23
|
const BORDER_STYLE_MODS = [
|
|
17
24
|
"solid",
|
|
@@ -83,7 +90,7 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
83
90
|
},
|
|
84
91
|
border: {
|
|
85
92
|
acceptsColor: true,
|
|
86
|
-
acceptsMods: [...
|
|
93
|
+
acceptsMods: [...BOX_DIRECTIONAL_MODS, ...BORDER_STYLE_MODS]
|
|
87
94
|
},
|
|
88
95
|
outline: {
|
|
89
96
|
acceptsColor: true,
|
|
@@ -101,13 +108,13 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
101
108
|
},
|
|
102
109
|
padding: {
|
|
103
110
|
acceptsColor: false,
|
|
104
|
-
acceptsMods:
|
|
111
|
+
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
105
112
|
},
|
|
106
113
|
paddingInline: VALUE_ONLY,
|
|
107
114
|
paddingBlock: VALUE_ONLY,
|
|
108
115
|
margin: {
|
|
109
116
|
acceptsColor: false,
|
|
110
|
-
acceptsMods:
|
|
117
|
+
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
111
118
|
},
|
|
112
119
|
fade: {
|
|
113
120
|
acceptsColor: true,
|
|
@@ -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// `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'];\nconst RADIUS_DIRECTIONAL_MODS = [\n ...DIRECTIONAL_MODS,\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\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'];\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\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: [...
|
|
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'];\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\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 paddingInline: VALUE_ONLY,\n paddingBlock: VALUE_ONLY,\n margin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n fade: { acceptsColor: true, acceptsMods: DIRECTIONAL_MODS },\n inset: { acceptsColor: false, acceptsMods: INSET_MODS },\n\n width: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n height: { 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: {\n acceptsColor: false,\n acceptsMods: [\n 'block',\n 'inline',\n 'inline-block',\n 'flex',\n 'inline-flex',\n 'grid',\n 'inline-grid',\n 'none',\n 'contents',\n 'table',\n 'table-row',\n 'table-cell',\n 'list-item',\n ],\n },\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":";AAsBA,MAAM,mBAAmB;CAAC;CAAO;CAAS;CAAU;CAAO;AAI3D,MAAM,eAAe;AAGrB,MAAM,aAAa;CAAC,GAAG;CAAkB;CAAQ;CAAa;AAC9D,MAAM,uBAAuB,CAAC,GAAG,kBAAkB,aAAa;AAChE,MAAM,0BAA0B;CAC9B,GAAG;CACH;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,iBAAiB;CAAC;CAAO;CAAO;CAAQ;AAC9C,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACD;AACD,MAAM,gBAAgB;CAAC;CAAU;CAAY;CAAY;CAAS;CAAS;AAE3E,MAAM,aAAkC;CACtC,cAAc;CACd,aAAa;CACd;AAED,MAAM,aAAkC;CACtC,cAAc;CACd,aAAa;CACd;AAED,MAAM,cAAmC;CACvC,cAAc;CACd,aAAa;CACd;AAED,MAAa,wBAA6D;CACxE,MAAM;EAAE,cAAc;EAAM,aAAa,CAAC,QAAQ,cAAc;EAAE;CAClE,OAAO;EAAE,cAAc;EAAM,aAAa,CAAC,QAAQ,cAAc;EAAE;CACnE,YAAY;CACZ,aAAa;CACb,QAAQ;EAAE,cAAc;EAAM,aAAa,CAAC,QAAQ;EAAE;CAEtD,QAAQ;EACN,cAAc;EACd,aAAa,CAAC,GAAG,sBAAsB,GAAG,kBAAkB;EAC7D;CACD,SAAS;EACP,cAAc;EACd,aAAa;EACd;CAED,QAAQ;EACN,cAAc;EACd,aAAa;GACX,GAAG;GACH;GACA;GACA;GACA;GACD;EACF;CAED,SAAS;EAAE,cAAc;EAAO,aAAa;EAAsB;CACnE,eAAe;CACf,cAAc;CACd,QAAQ;EAAE,cAAc;EAAO,aAAa;EAAsB;CAClE,MAAM;EAAE,cAAc;EAAM,aAAa;EAAkB;CAC3D,OAAO;EAAE,cAAc;EAAO,aAAa;EAAY;CAEvD,OAAO;EAAE,cAAc;EAAO,aAAa;EAAgB;CAC3D,QAAQ;EAAE,cAAc;EAAO,aAAa;EAAgB;CAE5D,KAAK;CACL,WAAW;CACX,QAAQ;CACR,WAAW;CACX,UAAU;CACV,YAAY;CACZ,MAAM;CACN,OAAO;CACP,QAAQ;CACR,SAAS;CACT,aAAa;CACb,WAAW;CACX,SAAS;CAET,MAAM;EAAE,cAAc;EAAO,aAAa;EAAW;CACrD,SAAS;EACP,cAAc;EACd,aAAa;GACX;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD,UAAU;EAAE,cAAc;EAAO,aAAa;EAAe;CAC7D,UAAU;EAAE,cAAc;EAAO,aAAa;EAAe;CAC9D;;;;;AAMD,SAAgB,eAAe,UAAuC;AACpE,QAAO,sBAAsB,aAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-runtime-styles-mutation.js","names":[],"sources":["../../src/rules/no-runtime-styles-mutation.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { isStaticValue } from '../utils.js';\n\ntype MessageIds = 'dynamicStyleValue';\n\nexport default createRule<[], MessageIds>({\n name: 'no-runtime-styles-mutation',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Warn when style objects contain runtime-computed values',\n },\n messages: {\n dynamicStyleValue:\n \"Style value for '{{property}}' should be static. Use the 'mods' prop, tokens, or CSS custom properties for dynamic behavior.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkProperties(\n node: TSESTree.ObjectExpression,\n parentProperty: string | null = null,\n ): void {\n for (const prop of node.properties) {\n if (prop.type === 'SpreadElement') {\n context.report({\n node: prop,\n messageId: 'dynamicStyleValue',\n data: { property: parentProperty ?? '(spread)' },\n });\n continue;\n }\n\n if (prop.type !== 'Property') continue;\n\n const key =\n !prop.computed && prop.key.type === 'Identifier'\n ? prop.key.name\n : parentProperty;\n\n // Skip sub-elements (they contain nested style objects)\n if (\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n /^[A-Z]/.test(prop.key.name)\n ) {\n if (prop.value.type === 'ObjectExpression') {\n checkProperties(prop.value, key);\n }\n continue;\n }\n\n // Skip @keyframes
|
|
1
|
+
{"version":3,"file":"no-runtime-styles-mutation.js","names":[],"sources":["../../src/rules/no-runtime-styles-mutation.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { isStaticValue } from '../utils.js';\n\ntype MessageIds = 'dynamicStyleValue';\n\nexport default createRule<[], MessageIds>({\n name: 'no-runtime-styles-mutation',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Warn when style objects contain runtime-computed values',\n },\n messages: {\n dynamicStyleValue:\n \"Style value for '{{property}}' should be static. Use the 'mods' prop, tokens, or CSS custom properties for dynamic behavior.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkProperties(\n node: TSESTree.ObjectExpression,\n parentProperty: string | null = null,\n ): void {\n for (const prop of node.properties) {\n if (prop.type === 'SpreadElement') {\n context.report({\n node: prop,\n messageId: 'dynamicStyleValue',\n data: { property: parentProperty ?? '(spread)' },\n });\n continue;\n }\n\n if (prop.type !== 'Property') continue;\n\n const key =\n !prop.computed && prop.key.type === 'Identifier'\n ? prop.key.name\n : parentProperty;\n\n // Skip sub-elements (they contain nested style objects)\n if (\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n /^[A-Z]/.test(prop.key.name)\n ) {\n if (prop.value.type === 'ObjectExpression') {\n checkProperties(prop.value, key);\n }\n continue;\n }\n\n // Skip at-rule blocks (@keyframes, @property, ...)\n if (\n !prop.computed &&\n prop.key.type === 'Identifier' &&\n prop.key.name.startsWith('@')\n ) {\n continue;\n }\n\n if (!isStaticValue(prop.value)) {\n context.report({\n node: prop.value,\n messageId: 'dynamicStyleValue',\n data: { property: key ?? '(unknown)' },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n checkProperties(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,yCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,2DACd;EACD,UAAU,EACR,mBACE,gIACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,gBACP,MACA,iBAAgC,MAC1B;AACN,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,iBAAiB;AACjC,aAAQ,OAAO;MACb,MAAM;MACN,WAAW;MACX,MAAM,EAAE,UAAU,kBAAkB,YAAY;MACjD,CAAC;AACF;;AAGF,QAAI,KAAK,SAAS,WAAY;IAE9B,MAAM,MACJ,CAAC,KAAK,YAAY,KAAK,IAAI,SAAS,eAChC,KAAK,IAAI,OACT;AAGN,QACE,CAAC,KAAK,YACN,KAAK,IAAI,SAAS,gBAClB,SAAS,KAAK,KAAK,IAAI,KAAK,EAC5B;AACA,SAAI,KAAK,MAAM,SAAS,mBACtB,iBAAgB,KAAK,OAAO,IAAI;AAElC;;AAIF,QACE,CAAC,KAAK,YACN,KAAK,IAAI,SAAS,gBAClB,KAAK,IAAI,KAAK,WAAW,IAAI,CAE7B;AAGF,QAAI,CAAC,cAAc,KAAK,MAAM,CAC5B,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,UAAU,OAAO,aAAa;KACvC,CAAC;;;EAKR,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,mBAAgB,KAAK;;AAGvB,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
2
|
import { SHORTHAND_MAPPING } from "../constants.js";
|
|
3
|
-
import { getKeyName } from "../utils.js";
|
|
3
|
+
import { getKeyName, getStringValue } from "../utils.js";
|
|
4
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
5
5
|
|
|
6
6
|
//#region src/rules/prefer-shorthand-property.ts
|
|
7
|
+
const CSS_WIDE_KEYWORDS = new Set([
|
|
8
|
+
"inherit",
|
|
9
|
+
"initial",
|
|
10
|
+
"unset",
|
|
11
|
+
"revert",
|
|
12
|
+
"revert-layer"
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* `font` resolves to `<value>, var(--font-sans, var(--font-sans-fallback))`,
|
|
16
|
+
* so it cannot express a CSS-wide keyword — `font: 'inherit'` emits
|
|
17
|
+
* `font-family: inherit, …`, which is invalid and drops the inherit.
|
|
18
|
+
*
|
|
19
|
+
* `preset` is the property that handles these: a CSS-wide keyword used as the
|
|
20
|
+
* preset name short-circuits token lookup and is emitted verbatim across the
|
|
21
|
+
* whole typography group, so `preset: 'inherit'` yields a real
|
|
22
|
+
* `font-family: inherit`. Point at that instead of `font`.
|
|
23
|
+
*/
|
|
24
|
+
function shorthandHint(key, prop) {
|
|
25
|
+
const mapping = SHORTHAND_MAPPING[key];
|
|
26
|
+
if (!mapping) return null;
|
|
27
|
+
if (key !== "fontFamily") return mapping.hint;
|
|
28
|
+
const value = getStringValue(prop.value)?.trim().toLowerCase();
|
|
29
|
+
return value && CSS_WIDE_KEYWORDS.has(value) ? `preset: '${value}'` : mapping.hint;
|
|
30
|
+
}
|
|
7
31
|
var prefer_shorthand_property_default = createRule({
|
|
8
32
|
name: "prefer-shorthand-property",
|
|
9
33
|
meta: {
|
|
@@ -23,12 +47,13 @@ var prefer_shorthand_property_default = createRule({
|
|
|
23
47
|
const key = getKeyName(prop.key);
|
|
24
48
|
if (key === null) continue;
|
|
25
49
|
const mapping = SHORTHAND_MAPPING[key];
|
|
26
|
-
|
|
50
|
+
const hint = shorthandHint(key, prop);
|
|
51
|
+
if (mapping && hint) context.report({
|
|
27
52
|
node: prop.key,
|
|
28
53
|
messageId: "preferShorthand",
|
|
29
54
|
data: {
|
|
30
55
|
native: key,
|
|
31
|
-
alternative:
|
|
56
|
+
alternative: hint
|
|
32
57
|
},
|
|
33
58
|
fix(fixer) {
|
|
34
59
|
if (!mapping.safeFix) return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const mapping = SHORTHAND_MAPPING[key];\n if (mapping) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative:
|
|
1
|
+
{"version":3,"file":"prefer-shorthand-property.js","names":[],"sources":["../../src/rules/prefer-shorthand-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue } from '../utils.js';\nimport { SHORTHAND_MAPPING } from '../constants.js';\n\ntype MessageIds = 'preferShorthand';\n\nconst CSS_WIDE_KEYWORDS = new Set([\n 'inherit',\n 'initial',\n 'unset',\n 'revert',\n 'revert-layer',\n]);\n\n/**\n * `font` resolves to `<value>, var(--font-sans, var(--font-sans-fallback))`,\n * so it cannot express a CSS-wide keyword — `font: 'inherit'` emits\n * `font-family: inherit, …`, which is invalid and drops the inherit.\n *\n * `preset` is the property that handles these: a CSS-wide keyword used as the\n * preset name short-circuits token lookup and is emitted verbatim across the\n * whole typography group, so `preset: 'inherit'` yields a real\n * `font-family: inherit`. Point at that instead of `font`.\n */\nfunction shorthandHint(key: string, prop: TSESTree.Property): string | null {\n const mapping = SHORTHAND_MAPPING[key];\n if (!mapping) return null;\n if (key !== 'fontFamily') return mapping.hint;\n\n const value = getStringValue(prop.value)?.trim().toLowerCase();\n return value && CSS_WIDE_KEYWORDS.has(value)\n ? `preset: '${value}'`\n : mapping.hint;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'prefer-shorthand-property',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Suggest tasty shorthand when a native CSS property with a tasty alternative is used',\n },\n messages: {\n preferShorthand:\n \"Prefer tasty shorthand '{{alternative}}' instead of '{{native}}'.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const mapping = SHORTHAND_MAPPING[key];\n const hint = shorthandHint(key, prop);\n if (mapping && hint) {\n context.report({\n node: prop.key,\n messageId: 'preferShorthand',\n data: { native: key, alternative: hint },\n fix(fixer) {\n // Only auto-fix the carry-over subset where the value passes\n // through unchanged (e.g. backgroundColor → fill, borderRadius →\n // radius). Directional / min/max / border-* renames change\n // semantics and stay report-only.\n if (!mapping.safeFix) return null;\n return fixer.replaceText(prop.key, mapping.property);\n },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;AAQA,MAAM,oBAAoB,IAAI,IAAI;CAChC;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;AAYF,SAAS,cAAc,KAAa,MAAwC;CAC1E,MAAM,UAAU,kBAAkB;AAClC,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,QAAQ,aAAc,QAAO,QAAQ;CAEzC,MAAM,QAAQ,eAAe,KAAK,MAAM,EAAE,MAAM,CAAC,aAAa;AAC9D,QAAO,SAAS,kBAAkB,IAAI,MAAM,GACxC,YAAY,MAAM,KAClB,QAAQ;;AAGd,wCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,uFACH;EACD,UAAU,EACR,iBACE,qEACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;AAC1D,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAE9B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;IAElB,MAAM,UAAU,kBAAkB;IAClC,MAAM,OAAO,cAAc,KAAK,KAAK;AACrC,QAAI,WAAW,KACb,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM;MAAE,QAAQ;MAAK,aAAa;MAAM;KACxC,IAAI,OAAO;AAKT,UAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,aAAO,MAAM,YAAY,KAAK,KAAK,QAAQ,SAAS;;KAEvD,CAAC;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -27,7 +27,7 @@ var valid_color_token_default = createRule({
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
function checkColorTokensInValue(value, node) {
|
|
30
|
-
const tokenRegex = /##?[a-zA-Z][a-zA-Z0-9-]*(?:\.\$?[a-zA-Z0-9-]+)
|
|
30
|
+
const tokenRegex = /##?[a-zA-Z][a-zA-Z0-9-]*(?:\.\$?[a-zA-Z0-9-]+)?(?!\()/g;
|
|
31
31
|
let match;
|
|
32
32
|
while ((match = tokenRegex.exec(value)) !== null) {
|
|
33
33
|
const token = match[0];
|