@tenphi/eslint-plugin-tasty 1.0.5 → 1.2.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 +177 -4
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/configs.js +1 -0
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +294 -38
- package/dist/constants.js.map +1 -1
- package/dist/context.js +75 -25
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/property-expectations.js +71 -2
- package/dist/property-expectations.js.map +1 -1
- package/dist/rules/consistent-token-usage.js +12 -1
- package/dist/rules/consistent-token-usage.js.map +1 -1
- package/dist/rules/motion-duration-utils.js +30 -0
- package/dist/rules/motion-duration-utils.js.map +1 -0
- package/dist/rules/no-own-at-root.js +1 -9
- package/dist/rules/no-own-at-root.js.map +1 -1
- package/dist/rules/no-raw-motion-duration.js +105 -0
- package/dist/rules/no-raw-motion-duration.js.map +1 -0
- package/dist/rules/no-raw-transition-duration.js +156 -0
- package/dist/rules/no-raw-transition-duration.js.map +1 -0
- package/dist/rules/no-unknown-state-alias.js +2 -2
- package/dist/rules/no-unknown-state-alias.js.map +1 -1
- package/dist/rules/valid-boolean-property.js +19 -8
- package/dist/rules/valid-boolean-property.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +16 -1
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-state-key.js +4 -13
- package/dist/rules/valid-state-key.js.map +1 -1
- package/dist/rules/valid-sub-element.js +5 -4
- package/dist/rules/valid-sub-element.js.map +1 -1
- package/dist/types.d.ts +14 -1
- package/dist/utils.js +8 -21
- package/dist/utils.js.map +1 -1
- package/package.json +5 -3
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 {\n DEFAULT_IMPORT_SOURCES,\n KNOWN_CSS_PROPERTIES,\n KNOWN_TASTY_PROPERTIES,\n SPECIAL_STYLE_KEYS,\n} 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 StyleContext {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n /**\n * Local name of the component this layer extends, when there is one and it is\n * an identifier. `null` covers every other case — a base definition, a\n * selector, or a layer whose base cannot be read off the AST — so a rule that\n * cares must treat `null` as \"unknown\", not as \"not extending\".\n */\n baseComponent: string | null;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\n/**\n * Whether a type annotation names tasty's `Styles` anywhere inside it.\n *\n * Walks the annotation rather than matching only the outermost reference, so a\n * wrapped tasty type (`Styles | undefined`, `Record<string, Styles>`,\n * `{ root: Styles }`) is recognised while `CSSProperties` in the same positions is\n * not. Deliberately name-based: the plugin has no type checker, and requiring one\n * would make every rule type-aware.\n */\nfunction referencesStylesType(node: TSESTree.TypeNode): boolean {\n const stack: TSESTree.Node[] = [node];\n\n while (stack.length) {\n const current = stack.pop()!;\n\n if (\n current.type === 'TSTypeReference' &&\n current.typeName.type === 'Identifier' &&\n /^Styles$/i.test(current.typeName.name)\n ) {\n return true;\n }\n\n for (const value of Object.values(\n current as unknown as Record<string, unknown>,\n )) {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && 'type' in item) {\n stack.push(item as TSESTree.Node);\n }\n }\n } else if (value && typeof value === 'object' && 'type' in value) {\n // Skip `parent` — it points back up the tree and would loop forever.\n if (value !== (current as TSESTree.Node).parent) {\n stack.push(value as TSESTree.Node);\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Whether a key could plausibly be a tasty style key.\n *\n * Covers every top-level shape tasty accepts: a style property, a sub-element\n * (capitalised), an at-rule or state/custom-property/colour key (`@`, `&`, `$`,\n * `#`), and the default-state key.\n */\nfunction isPlausibleStyleKey(key: string): boolean {\n if (key === '' || key === '_') return true;\n if (/^[A-Z]/.test(key)) return true;\n if (/^[@&$#]/.test(key)) return true;\n if (SPECIAL_STYLE_KEYS.has(key)) return true;\n\n return KNOWN_TASTY_PROPERTIES.has(key) || KNOWN_CSS_PROPERTIES.has(key);\n}\n\n/**\n * Whether an object is a map of named CSS blocks rather than a tasty styles object.\n *\n * The shape is unmistakable — `{ table: {…}, th: {…}, td: {…} }`, `{ root: {…},\n * toolbar: {…} }` — and it is what a React inline-style map looks like when it\n * carries no type annotation to opt it out. Every key is a block name that is not a\n * style key, and every value is an object.\n *\n * Requiring *every* value to be an object literal is what keeps this from swallowing\n * the case it would hurt most: `const styles = { colour: 'red' }` has a string value,\n * so it is not treated as a block map and `known-property` still reports the typo.\n * Otherwise the rule that catches a misspelled property would be silenced by the\n * misspelling.\n */\nfunction isNamedCssBlockMap(node: TSESTree.ObjectExpression): boolean {\n if (node.properties.length === 0) return false;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) return false;\n\n const key = getKeyName(prop.key);\n\n if (key === null || isPlausibleStyleKey(key)) return false;\n if (prop.value.type !== 'ObjectExpression') return false;\n }\n\n return true;\n}\n\n/**\n * The component a `styles` JSX prop overrides — `<Card styles={…} />` -> `Card`.\n * A namespaced tag (`<UI.Card />`) resolves to its root object, since that is the\n * name an import can be matched against.\n */\nfunction jsxStylesPropTarget(node: TSESTree.ObjectExpression): string | null {\n const container = node.parent;\n if (container?.type !== 'JSXExpressionContainer') return null;\n\n const attribute = container.parent;\n if (attribute?.type !== 'JSXAttribute') return null;\n\n const element = attribute.parent;\n if (element?.type !== 'JSXOpeningElement') return null;\n\n let name: TSESTree.JSXTagNameExpression = element.name;\n while (name.type === 'JSXMemberExpression') name = name.object;\n\n return name.type === 'JSXIdentifier' ? name.name : null;\n}\n\n/**\n * Strips the wrappers that carry no runtime value — `Button as any`, `Button!`,\n * `Button satisfies T`, `<T>Button` — so the expression underneath can be read.\n */\nfunction unwrapExpression(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === 'TSAsExpression' ||\n current.type === 'TSSatisfiesExpression' ||\n current.type === 'TSNonNullExpression' ||\n current.type === 'TSTypeAssertion' ||\n current.type === 'TSInstantiationExpression'\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\n/**\n * The identifier a base-component argument resolves to — `tasty(UI.Card, {…})`\n * -> `UI`, which is what the import map is keyed by.\n *\n * Type assertions are stripped on the way down. Reading `Button as any` as an\n * unknown base would quietly promote an imported component to \"owned\", which is\n * the reading that produces a warning the author cannot act on.\n */\nfunction baseComponentName(node: TSESTree.Node): string | null {\n let current = unwrapExpression(node);\n\n while (current.type === 'MemberExpression') {\n current = unwrapExpression(current.object);\n }\n\n return current.type === 'Identifier' ? current.name : null;\n}\n\n/**\n * Whether `source` is inside this project. Relative and absolute specifiers are,\n * as are the conventional in-repo aliases (`~/`, `#internal`, `@/`) — `@` alone\n * is not, since `@scope/pkg` is a published package.\n */\nfunction isInRepoSource(source: string): boolean {\n return (\n source.startsWith('.') ||\n source.startsWith('/') ||\n source.startsWith('~') ||\n source.startsWith('#') ||\n source.startsWith('@/')\n );\n}\n\n/** Matches a source against an `ownedSources` pattern, where `*` is any run of characters. */\nfunction matchesSourcePattern(pattern: string, source: string): boolean {\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, '.*')}$`).test(source);\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 moduleSources = new Map<string, string>();\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\n // Every import is recorded, tasty or not. Where a *base component* comes\n // from is what tells a rule whether the file it would send the author to\n // edit is even theirs — see `isOwnedComponent`.\n for (const specifier of node.specifiers) {\n this.moduleSources.set(specifier.local.name, source);\n }\n\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 /**\n * Whether the base component an extension layer sits on top of lives in a file\n * this project can edit — so a rule can decide whether pointing the author at\n * the base's own definition is advice they can act on.\n *\n * A name that was never imported is declared in this very file, and an unknown\n * base (`null`) counts as owned as well: staying quiet is for a base that is\n * demonstrably someone else's, never for one the plugin merely failed to read.\n */\n isOwnedComponent(localName: string | null): boolean {\n if (localName === null) return true;\n\n const source = this.moduleSources.get(localName);\n if (source === undefined) return true;\n if (isInRepoSource(source)) return true;\n\n // `ownedSources` is optional on the public `ResolvedConfig`, so a config\n // built by a consumer rather than by `loadConfig` may not carry it.\n return (this.config.ownedSources ?? []).some((pattern) =>\n matchesSourcePattern(pattern, source),\n );\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): StyleContext | 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 // A `styles` prop is an override layer by construction: the component it\n // is handed to already carries its own styles and this object lands on\n // top of them. A Storybook `args.styles` is the same prop by another name,\n // but the component behind `args` is not on the AST path, so its base\n // stays unknown.\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: true,\n baseComponent: jsxStylesPropTarget(node),\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 baseComponent: null,\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 baseComponent: null,\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 baseComponent: null,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // An explicit type annotation is authoritative, in both directions. The name\n // heuristic below is a guess; a declared type is the author stating what the\n // object is, so it wins.\n //\n // This matters because the rewrites these rules offer are only valid *inside*\n // tasty: `#purple.05` and `$font-sans` are correct tasty declarations and\n // meaningless as raw CSS. A `const styles: Record<string, CSSProperties>`\n // handed to React's `style={…}` was matched on name alone, so its plain CSS\n // longhands were reported as tasty violations — and `--fix` rewrote real\n // `var(--shadow-sm-color)` to `#shadow-sm`, which nothing resolves there, so\n // the browser dropped the declaration.\n const annotated = this.stylesTypeAnnotation(current);\n\n if (annotated !== null) return annotated;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations.\n if (name === 'styles' || name.endsWith('Styles')) {\n // The name is the weakest evidence there is, so let an unmistakable shape\n // overrule it. Without an annotation to go on, a map of named CSS blocks was\n // reported as tasty — `known-property` on every HTML tag name, each block read\n // as a state map — and `--fix` offered rewrites that are only valid inside\n // tasty.\n return !(\n targetNode.type === 'ObjectExpression' && isNamedCssBlockMap(targetNode)\n );\n }\n\n return false;\n }\n\n /**\n * Verdict from the declared type: `true` tasty, `false` explicitly not, `null`\n * when there is no annotation to go on and the caller should fall back to the\n * name.\n *\n * A `Styles` reference anywhere in the annotation counts, so wrappers like\n * `Styles | undefined` and `Record<string, Styles>` opt in — while\n * `CSSProperties`, `Record<string, CSSProperties>` and an inline literal of\n * them opt out.\n */\n private stylesTypeAnnotation(\n node: TSESTree.VariableDeclarator,\n ): boolean | null {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n\n if (!annotation) return null;\n\n return referencesStylesType(annotation);\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const isExtending =\n args.length >= 2 && unwrapExpression(args[0]).type !== 'ObjectExpression';\n const optionsArg = isExtending ? args[1] : 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,\n baseComponent: isExtending ? baseComponentName(args[0]) : null,\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 baseComponent: null,\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 baseComponent: null,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = unwrapExpression(args[0]).type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n baseComponent: isSelectorMode ? null : baseComponentName(args[0]),\n };\n }\n\n return null;\n }\n\n private isInsideStylesProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'styles' &&\n prop.value === targetNode\n ) {\n return true;\n }\n }\n return false;\n }\n\n private isInsideVariantsProperty(\n optionsObj: TSESTree.ObjectExpression,\n targetNode: TSESTree.Node,\n ): boolean {\n for (const prop of optionsObj.properties) {\n if (\n prop.type === 'Property' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 'variants' &&\n prop.value.type === 'ObjectExpression'\n ) {\n for (const variantProp of prop.value.properties) {\n if (\n variantProp.type === 'Property' &&\n variantProp.value === targetNode\n ) {\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Whether this object is a `styles` JSX prop value or a Storybook `args.styles`.\n *\n * Both are unambiguous enough to stand on their own without an import-tracked\n * call or a `styles`-ish variable name. A bare `styles` key elsewhere is not —\n * see the selector list for why.\n */\n isStylesPropertyValue(node: TSESTree.ObjectExpression): boolean {\n const parent = node.parent;\n\n if (parent?.type === 'Property' && !parent.computed) {\n if (getKeyName(parent.key) !== 'styles') return false;\n\n // Only inside `args: { … }`.\n const argsObject = parent.parent;\n const argsProperty =\n argsObject?.type === 'ObjectExpression' ? argsObject.parent : undefined;\n\n return (\n argsProperty?.type === 'Property' &&\n !argsProperty.computed &&\n getKeyName(argsProperty.key) === 'args'\n );\n }\n\n if (parent?.type === 'JSXExpressionContainer') {\n const attribute = parent.parent;\n\n return (\n attribute?.type === 'JSXAttribute' &&\n attribute.name.type === 'JSXIdentifier' &&\n attribute.name.name === 'styles'\n );\n }\n\n return false;\n }\n\n /**\n * Checks if a property value node is a state mapping object\n * (i.e., an object where keys are state expressions and values are style values).\n */\n isStateMap(\n node: TSESTree.ObjectExpression,\n parentProperty: TSESTree.Property,\n ): boolean {\n const keyName = getKeyName(parentProperty.key);\n if (keyName === null) return false;\n\n // If the key starts with uppercase, it's a sub-element, not a state map\n if (/^[A-Z]/.test(keyName)) return false;\n\n // Special keys are not state maps\n // At-rule blocks are not state maps — their object values are descriptor maps.\n if (SPECIAL_STYLE_KEYS.has(keyName)) return false;\n\n // If the object has keys that look like state expressions, it's a state map.\n // A lone `_` (the fallback floor) or `''` (the default) is enough — both are\n // already covered by the string-literal / identifier branches below.\n return node.properties.some((prop) => {\n if (prop.type !== 'Property') return false;\n if (prop.key.type === 'Literal' && prop.key.value === '') return true;\n if (prop.key.type === 'Identifier') return true;\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') {\n return true;\n }\n return false;\n });\n }\n\n /**\n * Checks if a key represents a sub-element (starts with uppercase).\n */\n isSubElementKey(key: string): boolean {\n return /^[A-Z]/.test(key);\n }\n\n /**\n * Checks if a key represents a nested selector (starts with &).\n */\n isNestedSelectorKey(key: string): boolean {\n return key.startsWith('&');\n }\n\n /**\n * Checks if a key is a custom CSS property definition ($name or $$name).\n */\n isCustomPropertyKey(key: string): boolean {\n return key.startsWith('$');\n }\n\n /**\n * Checks if a key is a color token definition (#name or ##name).\n */\n isColorTokenKey(key: string): boolean {\n return key.startsWith('#');\n }\n\n /**\n * Checks if a key is a special @ property (@keyframes, @property, ...).\n */\n isSpecialKey(key: string): boolean {\n return key.startsWith('@');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\nimport type { ResolvedConfig, StyleFunctionConfig } from './types.js';\nimport { loadConfig } from './config.js';\nimport {\n DEFAULT_IMPORT_SOURCES,\n KNOWN_CSS_PROPERTIES,\n KNOWN_TASTY_PROPERTIES,\n SPECIAL_STYLE_KEYS,\n} from './constants.js';\nimport { getKeyName, unwrapExpression } 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 StyleContext {\n type: 'tasty' | 'tastyStatic' | 'useStyles' | 'useGlobalStyles';\n isStaticCall: boolean;\n isSelectorMode: boolean;\n isExtending: boolean;\n /**\n * Local name of the component this layer extends, when there is one and it is\n * an identifier. `null` covers every other case — a base definition, a\n * selector, or a layer whose base cannot be read off the AST — so a rule that\n * cares must treat `null` as \"unknown\", not as \"not extending\".\n */\n baseComponent: string | null;\n}\n\nexport interface TastyImport {\n localName: string;\n importedName: string;\n source: string;\n}\n\n/**\n * Whether a type annotation names tasty's `Styles` anywhere inside it.\n *\n * Walks the annotation rather than matching only the outermost reference, so a\n * wrapped tasty type (`Styles | undefined`, `Record<string, Styles>`,\n * `{ root: Styles }`) is recognised while `CSSProperties` in the same positions is\n * not. Deliberately name-based: the plugin has no type checker, and requiring one\n * would make every rule type-aware.\n */\nfunction referencesStylesType(node: TSESTree.TypeNode): boolean {\n const stack: TSESTree.Node[] = [node];\n\n while (stack.length) {\n const current = stack.pop()!;\n\n if (\n current.type === 'TSTypeReference' &&\n current.typeName.type === 'Identifier' &&\n /^Styles$/i.test(current.typeName.name)\n ) {\n return true;\n }\n\n for (const value of Object.values(\n current as unknown as Record<string, unknown>,\n )) {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === 'object' && 'type' in item) {\n stack.push(item as TSESTree.Node);\n }\n }\n } else if (value && typeof value === 'object' && 'type' in value) {\n // Skip `parent` — it points back up the tree and would loop forever.\n if (value !== (current as TSESTree.Node).parent) {\n stack.push(value as TSESTree.Node);\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Whether a key could plausibly be a tasty style key.\n *\n * Covers every top-level shape tasty accepts: a style property, a sub-element\n * (capitalised), an at-rule or state/custom-property/colour key (`@`, `&`, `$`,\n * `#`), and the default-state key.\n */\nfunction isPlausibleStyleKey(key: string): boolean {\n if (key === '' || key === '_') return true;\n if (/^[A-Z]/.test(key)) return true;\n if (/^[@&$#]/.test(key)) return true;\n if (SPECIAL_STYLE_KEYS.has(key)) return true;\n\n return KNOWN_TASTY_PROPERTIES.has(key) || KNOWN_CSS_PROPERTIES.has(key);\n}\n\n/**\n * Whether an object is a map of named CSS blocks rather than a tasty styles object.\n *\n * The shape is unmistakable — `{ table: {…}, th: {…}, td: {…} }`, `{ root: {…},\n * toolbar: {…} }` — and it is what a React inline-style map looks like when it\n * carries no type annotation to opt it out. Every key is a block name that is not a\n * style key, and every value is an object.\n *\n * Requiring *every* value to be an object literal is what keeps this from swallowing\n * the case it would hurt most: `const styles = { colour: 'red' }` has a string value,\n * so it is not treated as a block map and `known-property` still reports the typo.\n * Otherwise the rule that catches a misspelled property would be silenced by the\n * misspelling.\n */\nfunction isNamedCssBlockMap(node: TSESTree.ObjectExpression): boolean {\n if (node.properties.length === 0) return false;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) return false;\n\n const key = getKeyName(prop.key);\n\n if (key === null || isPlausibleStyleKey(key)) return false;\n if (prop.value.type !== 'ObjectExpression') return false;\n }\n\n return true;\n}\n\n/**\n * The component a `styles` JSX prop overrides — `<Card styles={…} />` -> `Card`.\n * A namespaced tag (`<UI.Card />`) resolves to its root object, since that is the\n * name an import can be matched against.\n */\nfunction jsxStylesPropTarget(node: TSESTree.ObjectExpression): string | null {\n const container = node.parent;\n if (container?.type !== 'JSXExpressionContainer') return null;\n\n const attribute = container.parent;\n if (attribute?.type !== 'JSXAttribute') return null;\n\n const element = attribute.parent;\n if (element?.type !== 'JSXOpeningElement') return null;\n\n let name: TSESTree.JSXTagNameExpression = element.name;\n while (name.type === 'JSXMemberExpression') name = name.object;\n\n return name.type === 'JSXIdentifier' ? name.name : null;\n}\n\n/**\n * The identifier a base-component argument resolves to — `tasty(UI.Card, {…})`\n * -> `UI`, which is what the import map is keyed by.\n *\n * Type assertions are stripped on the way down. Reading `Button as any` as an\n * unknown base would quietly promote an imported component to \"owned\", which is\n * the reading that produces a warning the author cannot act on.\n */\nfunction baseComponentName(node: TSESTree.Node): string | null {\n let current = unwrapExpression(node);\n\n while (current.type === 'MemberExpression') {\n current = unwrapExpression(current.object);\n }\n\n return current.type === 'Identifier' ? current.name : null;\n}\n\n/**\n * Whether `source` is inside this project. Relative and absolute specifiers are,\n * as are the conventional in-repo aliases (`~/`, `#internal`, `@/`) — `@` alone\n * is not, since `@scope/pkg` is a published package.\n */\nfunction isInRepoSource(source: string): boolean {\n return (\n source.startsWith('.') ||\n source.startsWith('/') ||\n source.startsWith('~') ||\n source.startsWith('#') ||\n source.startsWith('@/')\n );\n}\n\n/** Matches a source against an `ownedSources` pattern, where `*` is any run of characters. */\nfunction matchesSourcePattern(pattern: string, source: string): boolean {\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, '.*')}$`).test(source);\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 moduleSources = new Map<string, string>();\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\n // Every import is recorded, tasty or not. Where a *base component* comes\n // from is what tells a rule whether the file it would send the author to\n // edit is even theirs — see `isOwnedComponent`.\n for (const specifier of node.specifiers) {\n this.moduleSources.set(specifier.local.name, source);\n }\n\n if (!this.importSources.has(source) || node.importKind === 'type') return;\n\n for (const specifier of node.specifiers) {\n if (\n specifier.type === 'ImportSpecifier' &&\n specifier.importKind !== 'type'\n ) {\n const importedName =\n specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n if (\n TASTY_FUNCTION_NAMES.has(importedName) ||\n Object.hasOwn(this.config.styleFunctions ?? {}, importedName)\n ) {\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 /**\n * Whether the base component an extension layer sits on top of lives in a file\n * this project can edit — so a rule can decide whether pointing the author at\n * the base's own definition is advice they can act on.\n *\n * A name that was never imported is declared in this very file, and an unknown\n * base (`null`) counts as owned as well: staying quiet is for a base that is\n * demonstrably someone else's, never for one the plugin merely failed to read.\n */\n isOwnedComponent(localName: string | null): boolean {\n if (localName === null) return true;\n\n const source = this.moduleSources.get(localName);\n if (source === undefined) return true;\n if (isInRepoSource(source)) return true;\n\n // `ownedSources` is optional on the public `ResolvedConfig`, so a config\n // built by a consumer rather than by `loadConfig` may not carry it.\n return (this.config.ownedSources ?? []).some((pattern) =>\n matchesSourcePattern(pattern, source),\n );\n }\n\n isTastyCall(node: TSESTree.CallExpression): TastyImport | undefined {\n const callee = unwrapExpression(node.callee);\n if (callee.type !== 'Identifier') return undefined;\n const imp = this.imports.get(callee.name);\n if (!imp) return undefined;\n\n // A parameter or local declaration can shadow the imported helper. Only\n // the import binding carries evidence that the call accepts Tasty styles.\n let scope = this.context.sourceCode.getScope(callee);\n while (scope) {\n const variable = scope.set.get(callee.name);\n if (variable) {\n return variable.defs.some((def) => def.type === 'ImportBinding')\n ? imp\n : undefined;\n }\n if (!scope.upper) break;\n scope = scope.upper;\n }\n return undefined;\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 private getSubElementParent(\n node: TSESTree.ObjectExpression,\n ): TSESTree.ObjectExpression | null {\n let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const parent = wrapped.parent;\n if (parent?.type !== 'Property' || parent.computed) return null;\n const key = getKeyName(parent.key);\n return key &&\n /^[A-Z]/.test(key) &&\n parent.parent.type === 'ObjectExpression'\n ? parent.parent\n : null;\n }\n\n /** Follow only sub-elements of recognized styles, never a variants container. */\n getRootStyleObject(\n node: TSESTree.ObjectExpression,\n ): TSESTree.ObjectExpression {\n let current = node;\n while (true) {\n const parent = this.getSubElementParent(current);\n if (!parent || !this.isStyleObject(parent)) return current;\n current = parent;\n }\n }\n\n /** Include helper calls nested inside a real sub-element, but not variant names. */\n isInsideSubElement(node: TSESTree.Node): boolean {\n let current = node.parent;\n while (current) {\n if (current.type === 'Property' && !current.computed) {\n const key = getKeyName(current.key);\n if (\n key &&\n /^[A-Z]/.test(key) &&\n current.parent.type === 'ObjectExpression' &&\n this.isStyleObject(current.parent)\n ) {\n return true;\n }\n }\n current = current.parent;\n }\n return false;\n }\n\n getStyleContext(node: TSESTree.Node): StyleContext | null {\n // Sub-element objects inherit their parent style object's context\n if (node.type === 'ObjectExpression') {\n const parent = this.getSubElementParent(node);\n if (parent) {\n const inherited = this.getStyleContext(parent);\n if (inherited) return inherited;\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 // A `styles` prop is an override layer by construction: the component it\n // is handed to already carries its own styles and this object lands on\n // top of them. A Storybook `args.styles` is the same prop by another name,\n // but the component behind `args` is not on the AST path, so its base\n // stays unknown.\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: true,\n baseComponent: jsxStylesPropTarget(node),\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 baseComponent: null,\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 baseComponent: null,\n };\n }\n }\n\n // Built-in signatures always take precedence over configured ones.\n const custom = this.config.styleFunctions?.[name];\n if (!TASTY_FUNCTION_NAMES.has(name) && custom) {\n return this.getCustomCallContext(current, node, custom);\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 baseComponent: null,\n };\n }\n\n current = current.parent;\n }\n\n return null;\n }\n\n private isStyleVariableDeclaration(\n current: TSESTree.Node,\n targetNode: TSESTree.Node,\n ): boolean {\n if (\n current.type !== 'VariableDeclarator' ||\n current.id.type !== 'Identifier'\n ) {\n return false;\n }\n\n let init: TSESTree.Node | null | undefined = current.init;\n while (\n init?.type === 'TSAsExpression' ||\n init?.type === 'TSSatisfiesExpression' ||\n init?.type === 'TSTypeAssertion' ||\n init?.type === 'TSNonNullExpression'\n ) {\n init = (init as TSESTree.TSAsExpression).expression;\n }\n\n if (init !== targetNode) return false;\n\n const name = current.id.name;\n\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // An explicit type annotation is authoritative, in both directions. The name\n // heuristic below is a guess; a declared type is the author stating what the\n // object is, so it wins.\n //\n // This matters because the rewrites these rules offer are only valid *inside*\n // tasty: `#purple.05` and `$font-sans` are correct tasty declarations and\n // meaningless as raw CSS. A `const styles: Record<string, CSSProperties>`\n // handed to React's `style={…}` was matched on name alone, so its plain CSS\n // longhands were reported as tasty violations — and `--fix` rewrote real\n // `var(--shadow-sm-color)` to `#shadow-sm`, which nothing resolves there, so\n // the browser dropped the declaration.\n const annotated = this.stylesTypeAnnotation(current);\n\n if (annotated !== null) return annotated;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations.\n if (name === 'styles' || name.endsWith('Styles')) {\n // The name is the weakest evidence there is, so let an unmistakable shape\n // overrule it. Without an annotation to go on, a map of named CSS blocks was\n // reported as tasty — `known-property` on every HTML tag name, each block read\n // as a state map — and `--fix` offered rewrites that are only valid inside\n // tasty.\n return !(\n targetNode.type === 'ObjectExpression' && isNamedCssBlockMap(targetNode)\n );\n }\n\n return false;\n }\n\n private getCustomCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n config: StyleFunctionConfig,\n ): StyleContext | null {\n const { argument, kind } = config;\n if (argument !== 'all' && (!Number.isInteger(argument) || argument < 0)) {\n return null;\n }\n // A preceding spread makes the runtime argument index unknown. Never apply\n // style rewrites to an object that might occupy a different parameter.\n if (\n argument !== 'all' &&\n call.arguments\n .slice(0, argument)\n .some((arg) => arg.type === 'SpreadElement')\n ) {\n return null;\n }\n\n const args =\n argument === 'all' ? call.arguments : [call.arguments[argument]];\n for (const arg of args) {\n if (!arg) continue;\n const value = unwrapExpression(arg);\n if (value.type !== 'ObjectExpression') continue;\n\n const isVariant =\n kind === 'options' && this.isInsideVariantsProperty(value, targetNode);\n const matches =\n kind === 'styles'\n ? value === targetNode\n : kind === 'options' &&\n (this.isInsideStylesProperty(value, targetNode) || isVariant);\n\n if (matches) {\n return {\n type: 'tasty',\n isStaticCall: false,\n isSelectorMode: false,\n isExtending: !isVariant && (config.partial ?? false),\n baseComponent: null,\n };\n }\n }\n\n return null;\n }\n\n /**\n * Verdict from the declared type: `true` tasty, `false` explicitly not, `null`\n * when there is no annotation to go on and the caller should fall back to the\n * name.\n *\n * A `Styles` reference anywhere in the annotation counts, so wrappers like\n * `Styles | undefined` and `Record<string, Styles>` opt in — while\n * `CSSProperties`, `Record<string, CSSProperties>` and an inline literal of\n * them opt out.\n */\n private stylesTypeAnnotation(\n node: TSESTree.VariableDeclarator,\n ): boolean | null {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n\n if (!annotation) return null;\n\n return referencesStylesType(annotation);\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const isExtending =\n args.length >= 2 && unwrapExpression(args[0]).type !== 'ObjectExpression';\n const optionsArg = isExtending ? args[1] : 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,\n baseComponent: isExtending ? baseComponentName(args[0]) : null,\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 baseComponent: null,\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 baseComponent: null,\n };\n }\n\n // tastyStatic(base, { ... }) or tastyStatic('selector', { ... })\n if (args.length === 2 && args[1] === targetNode) {\n const isSelectorMode = unwrapExpression(args[0]).type === 'Literal';\n return {\n type: 'tastyStatic' as const,\n isStaticCall: true,\n isSelectorMode,\n isExtending: !isSelectorMode,\n baseComponent: isSelectorMode ? null : baseComponentName(args[0]),\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.computed &&\n getKeyName(prop.key) === 'styles' &&\n unwrapExpression(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.computed &&\n getKeyName(prop.key) === 'variants'\n ) {\n const variants = unwrapExpression(prop.value);\n if (variants.type !== 'ObjectExpression') continue;\n for (const variantProp of variants.properties) {\n if (\n variantProp.type === 'Property' &&\n unwrapExpression(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.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { recommended, strict } from "./configs.js";
|
|
2
|
-
import { ResolvedConfig, TastyValidationConfig } from "./types.js";
|
|
2
|
+
import { ResolvedConfig, StyleFunctionConfig, TastyValidationConfig } from "./types.js";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
|
5
5
|
declare const plugin: any;
|
|
6
6
|
//#endregion
|
|
7
|
-
export { type ResolvedConfig, type TastyValidationConfig, plugin as default, recommended, strict };
|
|
7
|
+
export { type ResolvedConfig, type StyleFunctionConfig, type TastyValidationConfig, plugin as default, recommended, strict };
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ import valid_transition_default from "./rules/valid-transition.js";
|
|
|
21
21
|
import require_default_state_default from "./rules/require-default-state.js";
|
|
22
22
|
import no_unknown_state_alias_default from "./rules/no-unknown-state-alias.js";
|
|
23
23
|
import no_raw_color_values_default from "./rules/no-raw-color-values.js";
|
|
24
|
+
import no_raw_transition_duration_default from "./rules/no-raw-transition-duration.js";
|
|
25
|
+
import no_raw_motion_duration_default from "./rules/no-raw-motion-duration.js";
|
|
24
26
|
import no_styles_prop_default from "./rules/no-styles-prop.js";
|
|
25
27
|
import consistent_token_usage_default from "./rules/consistent-token-usage.js";
|
|
26
28
|
import no_runtime_styles_mutation_default from "./rules/no-runtime-styles-mutation.js";
|
|
@@ -60,6 +62,8 @@ const ruleMap = {
|
|
|
60
62
|
"require-default-state": require_default_state_default,
|
|
61
63
|
"no-unknown-state-alias": no_unknown_state_alias_default,
|
|
62
64
|
"no-raw-color-values": no_raw_color_values_default,
|
|
65
|
+
"no-raw-transition-duration": no_raw_transition_duration_default,
|
|
66
|
+
"no-raw-motion-duration": no_raw_motion_duration_default,
|
|
63
67
|
"no-styles-prop": no_styles_prop_default,
|
|
64
68
|
"consistent-token-usage": consistent_token_usage_default,
|
|
65
69
|
"no-runtime-styles-mutation": no_runtime_styles_mutation_default,
|
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 { 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 {
|
|
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.noRawTransitionDuration","rules.noRawMotionDuration","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-raw-transition-duration': rules.noRawTransitionDuration,\n 'no-raw-motion-duration': rules.noRawMotionDuration,\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 {\n TastyValidationConfig,\n ResolvedConfig,\n StyleFunctionConfig,\n} 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,8BAA8BC;CAC9B,0BAA0BC;CAC1B,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"}
|
|
@@ -37,6 +37,11 @@ const DIMENSION_MODS = [
|
|
|
37
37
|
"max",
|
|
38
38
|
"fixed"
|
|
39
39
|
];
|
|
40
|
+
const LOGICAL_EDGE_MODS = [
|
|
41
|
+
"start",
|
|
42
|
+
"end",
|
|
43
|
+
LONGHAND_MOD
|
|
44
|
+
];
|
|
40
45
|
const FLOW_MODS = [
|
|
41
46
|
"row",
|
|
42
47
|
"column",
|
|
@@ -110,8 +115,6 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
110
115
|
acceptsColor: false,
|
|
111
116
|
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
112
117
|
},
|
|
113
|
-
paddingInline: VALUE_ONLY,
|
|
114
|
-
paddingBlock: VALUE_ONLY,
|
|
115
118
|
margin: {
|
|
116
119
|
acceptsColor: false,
|
|
117
120
|
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
@@ -124,6 +127,16 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
124
127
|
acceptsColor: false,
|
|
125
128
|
acceptsMods: INSET_MODS
|
|
126
129
|
},
|
|
130
|
+
scrollMargin: {
|
|
131
|
+
acceptsColor: false,
|
|
132
|
+
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
133
|
+
},
|
|
134
|
+
scrollPadding: {
|
|
135
|
+
acceptsColor: false,
|
|
136
|
+
acceptsMods: BOX_DIRECTIONAL_MODS
|
|
137
|
+
},
|
|
138
|
+
paddingInline: VALUE_ONLY,
|
|
139
|
+
paddingBlock: VALUE_ONLY,
|
|
127
140
|
width: {
|
|
128
141
|
acceptsColor: false,
|
|
129
142
|
acceptsMods: DIMENSION_MODS
|
|
@@ -132,6 +145,62 @@ const PROPERTY_EXPECTATIONS = {
|
|
|
132
145
|
acceptsColor: false,
|
|
133
146
|
acceptsMods: DIMENSION_MODS
|
|
134
147
|
},
|
|
148
|
+
blockPadding: {
|
|
149
|
+
acceptsColor: false,
|
|
150
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
151
|
+
},
|
|
152
|
+
inlinePadding: {
|
|
153
|
+
acceptsColor: false,
|
|
154
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
155
|
+
},
|
|
156
|
+
blockMargin: {
|
|
157
|
+
acceptsColor: false,
|
|
158
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
159
|
+
},
|
|
160
|
+
inlineMargin: {
|
|
161
|
+
acceptsColor: false,
|
|
162
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
163
|
+
},
|
|
164
|
+
blockInset: {
|
|
165
|
+
acceptsColor: false,
|
|
166
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
167
|
+
},
|
|
168
|
+
inlineInset: {
|
|
169
|
+
acceptsColor: false,
|
|
170
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
171
|
+
},
|
|
172
|
+
blockScrollMargin: {
|
|
173
|
+
acceptsColor: false,
|
|
174
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
175
|
+
},
|
|
176
|
+
inlineScrollMargin: {
|
|
177
|
+
acceptsColor: false,
|
|
178
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
179
|
+
},
|
|
180
|
+
blockScrollPadding: {
|
|
181
|
+
acceptsColor: false,
|
|
182
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
183
|
+
},
|
|
184
|
+
inlineScrollPadding: {
|
|
185
|
+
acceptsColor: false,
|
|
186
|
+
acceptsMods: LOGICAL_EDGE_MODS
|
|
187
|
+
},
|
|
188
|
+
blockBorder: {
|
|
189
|
+
acceptsColor: true,
|
|
190
|
+
acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS]
|
|
191
|
+
},
|
|
192
|
+
inlineBorder: {
|
|
193
|
+
acceptsColor: true,
|
|
194
|
+
acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS]
|
|
195
|
+
},
|
|
196
|
+
blockSize: {
|
|
197
|
+
acceptsColor: false,
|
|
198
|
+
acceptsMods: DIMENSION_MODS
|
|
199
|
+
},
|
|
200
|
+
inlineSize: {
|
|
201
|
+
acceptsColor: false,
|
|
202
|
+
acceptsMods: DIMENSION_MODS
|
|
203
|
+
},
|
|
135
204
|
gap: VALUE_ONLY,
|
|
136
205
|
columnGap: VALUE_ONLY,
|
|
137
206
|
rowGap: VALUE_ONLY,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"property-expectations.js","names":[],"sources":["../src/property-expectations.ts"],"sourcesContent":["/**\n * Per-property expectations for parser bucket validation.\n *\n * After parsing a value through StyleParser.process(), each group contains\n * `colors`, `values`, and `mods` arrays. This map defines what is expected\n * for each tasty property so we can flag unexpected tokens.\n *\n * - `acceptsColor`: whether Color bucket tokens are valid\n * - `acceptsMods`: whether Mod bucket tokens are valid, and if so which ones\n * - `false` = no mods accepted (any mod is an error)\n * - `true` = any mod accepted (pass-through)\n * - `string[]` = only these specific mods are accepted\n *\n * Properties NOT listed here default to PASSTHROUGH (accept everything).\n * Only add properties that have actual restrictions.\n */\n\nexport interface PropertyExpectation {\n acceptsColor: boolean;\n acceptsMods: boolean | string[];\n}\n\nconst DIRECTIONAL_MODS = ['top', 'right', 'bottom', 'left'];\n// Output modifier accepted by every box property (`padding`, `margin`,\n// `inset`, `border`, `radius`): emit the individual CSS longhands instead of\n// the shorthand, so children can selectively inherit a single side/corner.\nconst LONGHAND_MOD = 'longhand';\n// `inset` additionally accepts `dock`: pin one edge and span its full\n// length (tasty >= 2.10). Other directional properties do not.\nconst INSET_MODS = [...DIRECTIONAL_MODS, 'dock', LONGHAND_MOD];\nconst BOX_DIRECTIONAL_MODS = [...DIRECTIONAL_MODS, LONGHAND_MOD];\nconst RADIUS_DIRECTIONAL_MODS = [\n ...DIRECTIONAL_MODS,\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n LONGHAND_MOD,\n];\nconst BORDER_STYLE_MODS = [\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'inset',\n 'outset',\n 'none',\n 'hidden',\n];\nconst DIMENSION_MODS = ['min', 'max', 'fixed'];\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
|
|
1
|
+
{"version":3,"file":"property-expectations.js","names":[],"sources":["../src/property-expectations.ts"],"sourcesContent":["/**\n * Per-property expectations for parser bucket validation.\n *\n * After parsing a value through StyleParser.process(), each group contains\n * `colors`, `values`, and `mods` arrays. This map defines what is expected\n * for each tasty property so we can flag unexpected tokens.\n *\n * - `acceptsColor`: whether Color bucket tokens are valid\n * - `acceptsMods`: whether Mod bucket tokens are valid, and if so which ones\n * - `false` = no mods accepted (any mod is an error)\n * - `true` = any mod accepted (pass-through)\n * - `string[]` = only these specific mods are accepted\n *\n * Properties NOT listed here default to PASSTHROUGH (accept everything).\n * Only add properties that have actual restrictions.\n */\n\nexport interface PropertyExpectation {\n acceptsColor: boolean;\n acceptsMods: boolean | string[];\n}\n\nconst DIRECTIONAL_MODS = ['top', 'right', 'bottom', 'left'];\n// Output modifier accepted by every box property (`padding`, `margin`,\n// `inset`, `border`, `radius`): emit the individual CSS longhands instead of\n// the shorthand, so children can selectively inherit a single side/corner.\nconst LONGHAND_MOD = 'longhand';\n// `inset` additionally accepts `dock`: pin one edge and span its full\n// length (tasty >= 2.10). Other directional properties do not.\nconst INSET_MODS = [...DIRECTIONAL_MODS, 'dock', LONGHAND_MOD];\nconst BOX_DIRECTIONAL_MODS = [...DIRECTIONAL_MODS, LONGHAND_MOD];\nconst RADIUS_DIRECTIONAL_MODS = [\n ...DIRECTIONAL_MODS,\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n LONGHAND_MOD,\n];\nconst BORDER_STYLE_MODS = [\n 'solid',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'ridge',\n 'inset',\n 'outset',\n 'none',\n 'hidden',\n];\nconst DIMENSION_MODS = ['min', 'max', 'fixed'];\n// The logical axis handlers take `start`/`end` in place of the four physical\n// sides, plus the same `longhand` output modifier — it emits the two native\n// start/end declarations instead of the axis shorthand.\nconst LOGICAL_EDGE_MODS = ['start', 'end', LONGHAND_MOD];\nconst FLOW_MODS = [\n 'row',\n 'column',\n 'wrap',\n 'nowrap',\n 'dense',\n 'row-reverse',\n 'column-reverse',\n];\nconst OVERFLOW_MODS = [\n 'visible',\n 'hidden',\n 'scroll',\n 'clip',\n 'auto',\n 'overlay',\n];\nconst POSITION_MODS = ['static', 'relative', 'absolute', 'fixed', 'sticky'];\n\nconst COLOR_ONLY: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: false,\n};\n\nconst VALUE_ONLY: PropertyExpectation = {\n acceptsColor: false,\n acceptsMods: false,\n};\n\nconst PASSTHROUGH: PropertyExpectation = {\n acceptsColor: true,\n acceptsMods: true,\n};\n\nexport const PROPERTY_EXPECTATIONS: Record<string, PropertyExpectation> = {\n fill: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n color: { acceptsColor: true, acceptsMods: ['none', 'transparent'] },\n caretColor: COLOR_ONLY,\n accentColor: COLOR_ONLY,\n shadow: { acceptsColor: true, acceptsMods: ['inset'] },\n\n border: {\n acceptsColor: true,\n acceptsMods: [...BOX_DIRECTIONAL_MODS, ...BORDER_STYLE_MODS],\n },\n outline: {\n acceptsColor: true,\n acceptsMods: BORDER_STYLE_MODS,\n },\n\n radius: {\n acceptsColor: false,\n acceptsMods: [\n ...RADIUS_DIRECTIONAL_MODS,\n 'round',\n 'ellipse',\n 'leaf',\n 'backleaf',\n ],\n },\n\n padding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n margin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n fade: { acceptsColor: true, acceptsMods: DIRECTIONAL_MODS },\n inset: { acceptsColor: false, acceptsMods: INSET_MODS },\n scrollMargin: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n scrollPadding: { acceptsColor: false, acceptsMods: BOX_DIRECTIONAL_MODS },\n\n // Native logical CSS shorthands. Tasty v3.8 stopped reading these in its\n // physical handlers, so they are ordinary CSS declarations now: one or two\n // lengths through the normal value parser, no colours and no modifiers. The\n // enhanced `blockPadding` / `inlinePadding` below are what take modifiers.\n paddingInline: VALUE_ONLY,\n paddingBlock: VALUE_ONLY,\n\n width: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n height: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n // Enhanced logical styles. Each axis/category pair mirrors its physical\n // counterpart's vocabulary with `start`/`end` in place of the four sides.\n blockPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlinePadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineInset: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollMargin: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n inlineScrollPadding: { acceptsColor: false, acceptsMods: LOGICAL_EDGE_MODS },\n blockBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n inlineBorder: {\n acceptsColor: true,\n acceptsMods: [...LOGICAL_EDGE_MODS, ...BORDER_STYLE_MODS],\n },\n blockSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n inlineSize: { acceptsColor: false, acceptsMods: DIMENSION_MODS },\n\n gap: VALUE_ONLY,\n columnGap: VALUE_ONLY,\n rowGap: VALUE_ONLY,\n flexBasis: VALUE_ONLY,\n flexGrow: VALUE_ONLY,\n flexShrink: VALUE_ONLY,\n flex: VALUE_ONLY,\n order: VALUE_ONLY,\n zIndex: VALUE_ONLY,\n opacity: VALUE_ONLY,\n aspectRatio: VALUE_ONLY,\n lineClamp: VALUE_ONLY,\n tabSize: VALUE_ONLY,\n\n flow: { acceptsColor: false, acceptsMods: FLOW_MODS },\n display: {\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":"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"}
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { createRule } from "../create-rule.js";
|
|
2
|
+
import { LOGICAL_BORDER_STYLES } from "../constants.js";
|
|
2
3
|
import { getKeyName, getStringValue } from "../utils.js";
|
|
3
4
|
import { TastyContext, styleObjectListeners } from "../context.js";
|
|
4
5
|
import { replaceInStringValue, replaceStringValue } from "../fix-utils.js";
|
|
5
6
|
|
|
6
7
|
//#region src/rules/consistent-token-usage.ts
|
|
8
|
+
/**
|
|
9
|
+
* Properties whose value carries a border width, so a `1px` in one is the
|
|
10
|
+
* `1bw` token spelled out.
|
|
11
|
+
*
|
|
12
|
+
* The logical axes belong here for the same reason `border` does: they take the
|
|
13
|
+
* same width/style/colour value, parsed by the same `parseBorderValue`. Leaving
|
|
14
|
+
* them out would make the advice depend on which axis vocabulary the author
|
|
15
|
+
* happened to use.
|
|
16
|
+
*/
|
|
17
|
+
const BORDER_WIDTH_PROPERTIES = new Set(["border", ...LOGICAL_BORDER_STYLES]);
|
|
7
18
|
const PX_TO_UNIT = {
|
|
8
19
|
"8px": "1x",
|
|
9
20
|
"16px": "2x",
|
|
@@ -74,7 +85,7 @@ var consistent_token_usage_default = createRule({
|
|
|
74
85
|
});
|
|
75
86
|
return;
|
|
76
87
|
}
|
|
77
|
-
if (property
|
|
88
|
+
if (BORDER_WIDTH_PROPERTIES.has(property) && trimmed.includes("1px")) {
|
|
78
89
|
const edits = [];
|
|
79
90
|
const re = /\b1px\b/g;
|
|
80
91
|
let match;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consistent-token-usage.js","names":[],"sources":["../../src/rules/consistent-token-usage.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 { replaceStringValue, replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'preferToken' | 'replaceWithToken';\n\nconst PX_TO_UNIT: Record<string, string> = {\n '8px': '1x',\n '16px': '2x',\n '24px': '3x',\n '32px': '4x',\n '40px': '5x',\n '48px': '6x',\n '56px': '7x',\n '64px': '8x',\n};\n\nexport default createRule<[], MessageIds>({\n name: 'consistent-token-usage',\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'Suggest using design tokens and custom units instead of raw CSS values',\n },\n messages: {\n preferToken: \"Consider using '{{suggestion}}' instead of '{{raw}}'.\",\n replaceWithToken: \"Replace '{{raw}}' with '{{suggestion}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(\n property: string,\n value: string,\n node: TSESTree.Node,\n ): void {\n const trimmed = value.trim();\n\n // Check pixel values that map to gap multiples\n if (trimmed in PX_TO_UNIT) {\n const suggestion = PX_TO_UNIT[trimmed];\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion, raw: trimmed },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw: trimmed, suggestion },\n fix(fixer) {\n return replaceStringValue(fixer, node, suggestion);\n },\n },\n ],\n });\n return;\n }\n\n // Check 6px in radius context\n if (property === 'radius' && trimmed === '6px') {\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion: '1r', raw: '6px' },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw: '6px', suggestion: '1r' },\n fix(fixer) {\n return replaceStringValue(fixer, node, '1r');\n },\n },\n ],\n });\n return;\n }\n\n // Check 1px in border context\n if (property
|
|
1
|
+
{"version":3,"file":"consistent-token-usage.js","names":[],"sources":["../../src/rules/consistent-token-usage.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 { LOGICAL_BORDER_STYLES } from '../constants.js';\nimport { replaceStringValue, replaceInStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'preferToken' | 'replaceWithToken';\n\n/**\n * Properties whose value carries a border width, so a `1px` in one is the\n * `1bw` token spelled out.\n *\n * The logical axes belong here for the same reason `border` does: they take the\n * same width/style/colour value, parsed by the same `parseBorderValue`. Leaving\n * them out would make the advice depend on which axis vocabulary the author\n * happened to use.\n */\nconst BORDER_WIDTH_PROPERTIES = new Set<string>([\n 'border',\n ...LOGICAL_BORDER_STYLES,\n]);\n\nconst PX_TO_UNIT: Record<string, string> = {\n '8px': '1x',\n '16px': '2x',\n '24px': '3x',\n '32px': '4x',\n '40px': '5x',\n '48px': '6x',\n '56px': '7x',\n '64px': '8x',\n};\n\nexport default createRule<[], MessageIds>({\n name: 'consistent-token-usage',\n meta: {\n type: 'suggestion',\n hasSuggestions: true,\n docs: {\n description:\n 'Suggest using design tokens and custom units instead of raw CSS values',\n },\n messages: {\n preferToken: \"Consider using '{{suggestion}}' instead of '{{raw}}'.\",\n replaceWithToken: \"Replace '{{raw}}' with '{{suggestion}}'\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function checkValue(\n property: string,\n value: string,\n node: TSESTree.Node,\n ): void {\n const trimmed = value.trim();\n\n // Check pixel values that map to gap multiples\n if (trimmed in PX_TO_UNIT) {\n const suggestion = PX_TO_UNIT[trimmed];\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion, raw: trimmed },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw: trimmed, suggestion },\n fix(fixer) {\n return replaceStringValue(fixer, node, suggestion);\n },\n },\n ],\n });\n return;\n }\n\n // Check 6px in radius context\n if (property === 'radius' && trimmed === '6px') {\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion: '1r', raw: '6px' },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw: '6px', suggestion: '1r' },\n fix(fixer) {\n return replaceStringValue(fixer, node, '1r');\n },\n },\n ],\n });\n return;\n }\n\n // Check 1px in border context\n if (BORDER_WIDTH_PROPERTIES.has(property) && trimmed.includes('1px')) {\n const edits: { start: number; end: number; replacement: string }[] = [];\n const re = /\\b1px\\b/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(value)) !== null) {\n edits.push({\n start: match.index,\n end: match.index + match[0].length,\n replacement: '1bw',\n });\n }\n if (edits.length === 0) return;\n context.report({\n node,\n messageId: 'preferToken',\n data: { suggestion: '1bw', raw: '1px' },\n suggest: [\n {\n messageId: 'replaceWithToken',\n data: { raw: '1px', suggestion: '1bw' },\n fix(fixer) {\n return replaceInStringValue(\n fixer,\n node,\n edits,\n context.sourceCode,\n );\n },\n },\n ],\n });\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n\n const str = getStringValue(prop.value);\n if (str) {\n checkValue(key, str, prop.value);\n continue;\n }\n\n // Only recurse into genuine state maps. Sub-element objects\n // (`Icon: { … }`) are style objects in their own right and are visited\n // separately by the listener, so treating them as state maps here both\n // double-reports and attributes the value to the wrong property.\n if (\n prop.value.type === 'ObjectExpression' &&\n ctx.isStateMap(prop.value, prop)\n ) {\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n const stateStr = getStringValue(stateProp.value);\n if (stateStr) {\n checkValue(key, stateStr, stateProp.value);\n }\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAM,0BAA0B,IAAI,IAAY,CAC9C,UACA,GAAG,sBACJ,CAAC;AAEF,MAAM,aAAqC;CACzC,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACT;AAED,qCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,gBAAgB;EAChB,MAAM,EACJ,aACE,0EACH;EACD,UAAU;GACR,aAAa;GACb,kBAAkB;GACnB;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,WACP,UACA,OACA,MACM;GACN,MAAM,UAAU,MAAM,MAAM;AAG5B,OAAI,WAAW,YAAY;IACzB,MAAM,aAAa,WAAW;AAC9B,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE;MAAY,KAAK;MAAS;KAClC,SAAS,CACP;MACE,WAAW;MACX,MAAM;OAAE,KAAK;OAAS;OAAY;MAClC,IAAI,OAAO;AACT,cAAO,mBAAmB,OAAO,MAAM,WAAW;;MAErD,CACF;KACF,CAAC;AACF;;AAIF,OAAI,aAAa,YAAY,YAAY,OAAO;AAC9C,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE,YAAY;MAAM,KAAK;MAAO;KACtC,SAAS,CACP;MACE,WAAW;MACX,MAAM;OAAE,KAAK;OAAO,YAAY;OAAM;MACtC,IAAI,OAAO;AACT,cAAO,mBAAmB,OAAO,MAAM,KAAK;;MAE/C,CACF;KACF,CAAC;AACF;;AAIF,OAAI,wBAAwB,IAAI,SAAS,IAAI,QAAQ,SAAS,MAAM,EAAE;IACpE,MAAM,QAA+D,EAAE;IACvE,MAAM,KAAK;IACX,IAAI;AACJ,YAAQ,QAAQ,GAAG,KAAK,MAAM,MAAM,KAClC,OAAM,KAAK;KACT,OAAO,MAAM;KACb,KAAK,MAAM,QAAQ,MAAM,GAAG;KAC5B,aAAa;KACd,CAAC;AAEJ,QAAI,MAAM,WAAW,EAAG;AACxB,YAAQ,OAAO;KACb;KACA,WAAW;KACX,MAAM;MAAE,YAAY;MAAO,KAAK;MAAO;KACvC,SAAS,CACP;MACE,WAAW;MACX,MAAM;OAAE,KAAK;OAAO,YAAY;OAAO;MACvC,IAAI,OAAO;AACT,cAAO,qBACL,OACA,MACA,OACA,QAAQ,WACT;;MAEJ,CACF;KACF,CAAC;;;EAIN,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,MAAM,eAAe,KAAK,MAAM;AACtC,QAAI,KAAK;AACP,gBAAW,KAAK,KAAK,KAAK,MAAM;AAChC;;AAOF,QACE,KAAK,MAAM,SAAS,sBACpB,IAAI,WAAW,KAAK,OAAO,KAAK,CAEhC,MAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KACnC,MAAM,WAAW,eAAe,UAAU,MAAM;AAChD,SAAI,SACF,YAAW,KAAK,UAAU,UAAU,MAAM;;;;AAOpD,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/rules/motion-duration-utils.ts
|
|
2
|
+
/** Numeric CSS time values. A zero duration intentionally disables motion. */
|
|
3
|
+
const TIME_LITERAL = /^(?:\d+(?:\.\d+)?|\.\d+)(?:ms|s)$/i;
|
|
4
|
+
const TIME_IN_EXPRESSION = /(?:^|[^\w.])((?:\d+(?:\.\d+)?|\.\d+)(?:ms|s))(?![\w-])/gi;
|
|
5
|
+
const TIME_EXPRESSION = /^(?:\(|calc\(|min\(|max\(|clamp\()/i;
|
|
6
|
+
const DURATION_TOKEN = /(?:^\$transition$|-transition$|duration)/i;
|
|
7
|
+
function isTimeLiteral(value) {
|
|
8
|
+
return TIME_LITERAL.test(value);
|
|
9
|
+
}
|
|
10
|
+
function isZeroTime(value) {
|
|
11
|
+
return value === "0" || isTimeLiteral(value) && Number.parseFloat(value) === 0;
|
|
12
|
+
}
|
|
13
|
+
function isNonzeroTime(value) {
|
|
14
|
+
return isTimeLiteral(value) && Number.parseFloat(value) !== 0;
|
|
15
|
+
}
|
|
16
|
+
function hasRawTimeInExpression(value) {
|
|
17
|
+
if (!isTimeExpression(value) || /\$|\bvar\(/i.test(value)) return false;
|
|
18
|
+
for (const match of value.matchAll(TIME_IN_EXPRESSION)) if (Number.parseFloat(match[1]) !== 0) return true;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function isTimeExpression(value) {
|
|
22
|
+
return TIME_EXPRESSION.test(value);
|
|
23
|
+
}
|
|
24
|
+
function getDurationTokens(tokens) {
|
|
25
|
+
return Array.isArray(tokens) ? tokens.filter((token) => token.startsWith("$") && DURATION_TOKEN.test(token)) : [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { getDurationTokens, hasRawTimeInExpression, isNonzeroTime, isTimeExpression, isTimeLiteral, isZeroTime };
|
|
30
|
+
//# sourceMappingURL=motion-duration-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion-duration-utils.js","names":[],"sources":["../../src/rules/motion-duration-utils.ts"],"sourcesContent":["/** Numeric CSS time values. A zero duration intentionally disables motion. */\nconst TIME_LITERAL = /^(?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:ms|s)$/i;\nconst TIME_IN_EXPRESSION =\n /(?:^|[^\\w.])((?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:ms|s))(?![\\w-])/gi;\nconst TIME_EXPRESSION = /^(?:\\(|calc\\(|min\\(|max\\(|clamp\\()/i;\nconst DURATION_TOKEN = /(?:^\\$transition$|-transition$|duration)/i;\n\nexport function isTimeLiteral(value: string): boolean {\n return TIME_LITERAL.test(value);\n}\n\nexport function isZeroTime(value: string): boolean {\n return (\n value === '0' || (isTimeLiteral(value) && Number.parseFloat(value) === 0)\n );\n}\n\nexport function isNonzeroTime(value: string): boolean {\n return isTimeLiteral(value) && Number.parseFloat(value) !== 0;\n}\n\nexport function hasRawTimeInExpression(value: string): boolean {\n // A variable-based expression already derives its timing from a token.\n if (!isTimeExpression(value) || /\\$|\\bvar\\(/i.test(value)) return false;\n\n for (const match of value.matchAll(TIME_IN_EXPRESSION)) {\n if (Number.parseFloat(match[1]) !== 0) return true;\n }\n return false;\n}\n\nexport function isTimeExpression(value: string): boolean {\n return TIME_EXPRESSION.test(value);\n}\n\nexport function getDurationTokens(\n tokens: false | string[] | undefined,\n): string[] {\n return Array.isArray(tokens)\n ? tokens.filter(\n (token) => token.startsWith('$') && DURATION_TOKEN.test(token),\n )\n : [];\n}\n"],"mappings":";;AACA,MAAM,eAAe;AACrB,MAAM,qBACJ;AACF,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AAEvB,SAAgB,cAAc,OAAwB;AACpD,QAAO,aAAa,KAAK,MAAM;;AAGjC,SAAgB,WAAW,OAAwB;AACjD,QACE,UAAU,OAAQ,cAAc,MAAM,IAAI,OAAO,WAAW,MAAM,KAAK;;AAI3E,SAAgB,cAAc,OAAwB;AACpD,QAAO,cAAc,MAAM,IAAI,OAAO,WAAW,MAAM,KAAK;;AAG9D,SAAgB,uBAAuB,OAAwB;AAE7D,KAAI,CAAC,iBAAiB,MAAM,IAAI,cAAc,KAAK,MAAM,CAAE,QAAO;AAElE,MAAK,MAAM,SAAS,MAAM,SAAS,mBAAmB,CACpD,KAAI,OAAO,WAAW,MAAM,GAAG,KAAK,EAAG,QAAO;AAEhD,QAAO;;AAGT,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,gBAAgB,KAAK,MAAM;;AAGpC,SAAgB,kBACd,QACU;AACV,QAAO,MAAM,QAAQ,OAAO,GACxB,OAAO,QACJ,UAAU,MAAM,WAAW,IAAI,IAAI,eAAe,KAAK,MAAM,CAC/D,GACD,EAAE"}
|
|
@@ -17,17 +17,9 @@ var no_own_at_root_default = createRule({
|
|
|
17
17
|
defaultOptions: [],
|
|
18
18
|
create(context) {
|
|
19
19
|
const ctx = new TastyContext(context);
|
|
20
|
-
function isInsideSubElement(node) {
|
|
21
|
-
let current = node.parent;
|
|
22
|
-
while (current) {
|
|
23
|
-
if (current.type === "Property" && !current.computed && current.key.type === "Identifier" && /^[A-Z]/.test(current.key.name)) return true;
|
|
24
|
-
current = current.parent;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
20
|
function handleStyleObject(node) {
|
|
29
21
|
if (!ctx.isStyleObject(node)) return;
|
|
30
|
-
if (isInsideSubElement(node)) return;
|
|
22
|
+
if (ctx.isInsideSubElement(node)) return;
|
|
31
23
|
for (const prop of node.properties) {
|
|
32
24
|
if (prop.type !== "Property" || prop.computed) continue;
|
|
33
25
|
const key = getKeyName(prop.key);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-own-at-root.js","names":[],"sources":["../../src/rules/no-own-at-root.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 { parseStateKey } from '../parsers/state-key-parser.js';\nimport { replaceStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'ownAtRoot';\n\nexport default createRule<[], MessageIds>({\n name: 'no-own-at-root',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Warn when @own() is used outside sub-element styles where it is redundant',\n },\n messages: {\n ownAtRoot:\n '@own() is redundant outside sub-element styles. Use the inner selector directly instead (e.g. :hover instead of @own(:hover)).',\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function
|
|
1
|
+
{"version":3,"file":"no-own-at-root.js","names":[],"sources":["../../src/rules/no-own-at-root.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 { parseStateKey } from '../parsers/state-key-parser.js';\nimport { replaceStringValue } from '../fix-utils.js';\n\ntype MessageIds = 'ownAtRoot';\n\nexport default createRule<[], MessageIds>({\n name: 'no-own-at-root',\n meta: {\n type: 'suggestion',\n fixable: 'code',\n docs: {\n description:\n 'Warn when @own() is used outside sub-element styles where it is redundant',\n },\n messages: {\n ownAtRoot:\n '@own() is redundant outside sub-element styles. Use the inner selector directly instead (e.g. :hover instead of @own(:hover)).',\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 if (ctx.isInsideSubElement(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 if (/^[A-Z]/.test(key) || key.startsWith('@') || key.startsWith('&'))\n continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n for (const stateProp of prop.value.properties) {\n if (stateProp.type !== 'Property') continue;\n\n const stateKey = !stateProp.computed\n ? getKeyName(stateProp.key)\n : getStringValue(stateProp.key);\n if (stateKey === null || stateKey === '' || stateKey === '_')\n continue;\n\n const result = parseStateKey(stateKey);\n\n if (result.hasOwn) {\n context.report({\n node: stateProp.key,\n messageId: 'ownAtRoot',\n fix(fixer) {\n if (!stateKey.startsWith('@own(') || !stateKey.endsWith(')')) {\n return null;\n }\n const inner = stateKey.slice(5, -1);\n if (!inner) return null;\n return replaceStringValue(fixer, stateProp.key, inner);\n },\n });\n }\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;;;AASA,6BAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,SAAS;EACT,MAAM,EACJ,aACE,6EACH;EACD,UAAU,EACR,WACE,kIACH;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,OAAI,IAAI,mBAAmB,KAAK,CAAE;AAElC,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAElB,QAAI,SAAS,KAAK,IAAI,IAAI,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,IAAI,CAClE;AAEF,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAE5C,SAAK,MAAM,aAAa,KAAK,MAAM,YAAY;AAC7C,SAAI,UAAU,SAAS,WAAY;KAEnC,MAAM,WAAW,CAAC,UAAU,WACxB,WAAW,UAAU,IAAI,GACzB,eAAe,UAAU,IAAI;AACjC,SAAI,aAAa,QAAQ,aAAa,MAAM,aAAa,IACvD;AAIF,SAFe,cAAc,SAAS,CAE3B,OACT,SAAQ,OAAO;MACb,MAAM,UAAU;MAChB,WAAW;MACX,IAAI,OAAO;AACT,WAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,SAAS,SAAS,IAAI,CAC1D,QAAO;OAET,MAAM,QAAQ,SAAS,MAAM,GAAG,GAAG;AACnC,WAAI,CAAC,MAAO,QAAO;AACnB,cAAO,mBAAmB,OAAO,UAAU,KAAK,MAAM;;MAEzD,CAAC;;;;AAMV,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
|