@tenphi/eslint-plugin-tasty 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -189,8 +189,8 @@ for typing shared signatures.
189
189
  | `tasty/prefer-element-selector` | warn | Prefer element names such as `Primary > Search` over exact `data-element` attributes in sub-element `$` selectors (safely autofixable; [details](docs/rules/prefer-element-selector.md)) |
190
190
  | `tasty/static-no-dynamic-values` | error | Dynamic values in `tastyStatic()` |
191
191
  | `tasty/static-valid-selector` | error | Invalid selector in `tastyStatic(selector, ...)` |
192
- | `tasty/require-default-state` | error | Missing default (`''`) or fallback floor (`_`) key in state mappings (skipped for extending calls) |
193
- | `tasty/no-own-at-root` | warn | `@own()` used at root level where it is redundant |
192
+ | `tasty/require-default-state` | error | Missing default (`''`) or fallback floor (`_`) key in state mappings (skipped for extending calls and maps that spread another) |
193
+ | `tasty/no-own-at-root` | warn | `@own()` used at root level where it is redundant (not in shared style constants, which may be spread into a sub-element) |
194
194
  | `tasty/no-ignored-pseudo-element-state` | error | States Tasty ignores on a pseudo-element, such as `@own([open])` on `$: 'a::before'`: attributes, modifiers, classes, and tree-structural pseudo-classes ([details](docs/rules/no-ignored-pseudo-element-state.md)) |
195
195
  | `tasty/valid-default-state-order` | warn | Misplaced default (`''`) or redundant `''` when only `_` is present |
196
196
  | `tasty/prefer-shorthand-property` | warn | Use Tasty shorthand instead of native CSS properties (`backgroundColor` → `fill`, `paddingBlock` → `blockPadding`, etc.). Report-only warnings explain how to override one part through a token, with concrete min/max dimension examples. Extension warnings point at the base component and are skipped over bases you cannot edit (see `ownedSources`; [details](docs/rules/prefer-shorthand-property.md)) |
@@ -375,9 +375,11 @@ these props requires choosing the component's token/state/extension contract, so
375
375
  these rules provide guidance without automatic fixes.
376
376
 
377
377
  Recognition uses local `const` Tasty/configured-options factory results, their
378
- `const` aliases/sub-elements, and components imported from `importSources`. Add
379
- your design-system module to `importSources` to enable checks on imported
380
- components. Native elements, unrelated components, shadowed bindings, and mutable
378
+ `const` aliases/sub-elements, and components imported from `importSources`.
379
+ Exports of relative modules (`import { Panel } from './styled'`) count when they
380
+ resolve to one of these, through re-exports too; following them needs the
381
+ `typescript` package. Add your design-system package to `importSources` to enable checks
382
+ on components imported from it. Native elements, unrelated components, shadowed bindings, and mutable
381
383
  aliases are excluded. Opaque spread bindings are not evaluated. Third-party
382
384
  styling integrations and edge-case instance overrides need an explicit local
383
385
  ignore with a reason, as described above.
package/dist/context.js CHANGED
@@ -2,6 +2,7 @@ import { DEFAULT_IMPORT_SOURCES, KNOWN_CSS_PROPERTIES, KNOWN_TASTY_PROPERTIES, S
2
2
  import { loadConfig } from "./config.js";
3
3
  import { getKeyName, unwrapExpression } from "./utils.js";
4
4
  import { finishJSXAnalysis, jsxPropKind } from "./type-aware-jsx.js";
5
+ import { isModuleTastyComponent } from "./module-components.js";
5
6
 
6
7
  //#region src/context.ts
7
8
  /** One listener per object; context recognition remains the caller's gate. */
@@ -12,9 +13,9 @@ function styleObjectListeners(handler) {
12
13
  * Whether a type annotation names tasty's `Styles` anywhere inside it.
13
14
  *
14
15
  * Walks the annotation rather than matching only the outermost reference, so a
15
- * wrapped tasty type (`Styles | undefined`, `Record<string, Styles>`,
16
- * `{ root: Styles }`) is recognised while `CSSProperties` in the same positions is
17
- * not. Deliberately name-based: the plugin has no type checker, and requiring one
16
+ * wrapped tasty type (`Styles | undefined`, `Array<Styles>`) is recognised while
17
+ * `CSSProperties` in the same positions is not. Maps of styles are told apart
18
+ * first by `stylesAnnotationShape`. Deliberately name-based: the plugin has no type checker, and requiring one
18
19
  * would make every rule type-aware.
19
20
  */
20
21
  function referencesStylesType(node) {
@@ -30,6 +31,43 @@ function referencesStylesType(node) {
30
31
  }
31
32
  return false;
32
33
  }
34
+ /** Utility types whose result has the same shape as their argument. */
35
+ const SHAPE_PRESERVING_TYPES = new Set([
36
+ "Readonly",
37
+ "Partial",
38
+ "Required",
39
+ "NonNullable"
40
+ ]);
41
+ /**
42
+ * What a `Styles`-bearing annotation declares: `'styles'` for a style object,
43
+ * `'map'` for an object whose *values* are style objects (`Record<K, Styles>`,
44
+ * `{ [k: string]: Styles }`, `{ root: Styles }`), `null` when `Styles` does not
45
+ * appear at all.
46
+ *
47
+ * The distinction matters for a map: its keys are variant or block names, so
48
+ * checking the map itself as a style object reports every one of them as an
49
+ * unknown property, while the style objects it holds go unchecked.
50
+ */
51
+ function stylesAnnotationShape(node) {
52
+ if (node.type === "TSUnionType" || node.type === "TSIntersectionType") {
53
+ const shapes = node.types.map(stylesAnnotationShape);
54
+ if (shapes.includes("styles")) return "styles";
55
+ return shapes.includes("map") ? "map" : null;
56
+ }
57
+ if (node.type === "TSTypeReference" && node.typeName.type === "Identifier") {
58
+ const name = node.typeName.name;
59
+ const args = node.typeArguments?.params ?? [];
60
+ if (/^Styles$/i.test(name)) return "styles";
61
+ if (name === "Record" && args.length === 2) {
62
+ if (stylesAnnotationShape(args[1]) === "styles") return "map";
63
+ }
64
+ if (SHAPE_PRESERVING_TYPES.has(name) && args.length === 1) return stylesAnnotationShape(args[0]);
65
+ }
66
+ if (node.type === "TSTypeLiteral") {
67
+ if (node.members.some((member) => (member.type === "TSIndexSignature" || member.type === "TSPropertySignature") && member.typeAnnotation !== void 0 && stylesAnnotationShape(member.typeAnnotation.typeAnnotation) === "styles")) return "map";
68
+ }
69
+ return referencesStylesType(node) ? "styles" : null;
70
+ }
33
71
  /**
34
72
  * Whether a key could plausibly be a tasty style key.
35
73
  *
@@ -129,6 +167,7 @@ var TastyContext = class {
129
167
  imports = /* @__PURE__ */ new Map();
130
168
  moduleSources = /* @__PURE__ */ new Map();
131
169
  importSources;
170
+ relativeComponents = /* @__PURE__ */ new Map();
132
171
  constructor(context, config) {
133
172
  this.context = context;
134
173
  this.config = config ?? loadConfig(context.filename);
@@ -193,7 +232,12 @@ var TastyContext = class {
193
232
  while (scope) {
194
233
  const variable = scope.set.get(localName);
195
234
  if (variable) return variable.defs.some((def) => {
196
- if (def.type === "ImportBinding") return def.parent.type === "ImportDeclaration" && def.parent.importKind !== "type" && this.importSources.has(def.parent.source.value) && (def.node.type !== "ImportSpecifier" || def.node.importKind !== "type");
235
+ if (def.type === "ImportBinding") {
236
+ if (def.parent.type !== "ImportDeclaration" || def.parent.importKind === "type" || def.node.type === "ImportSpecifier" && def.node.importKind === "type") return false;
237
+ const source = def.parent.source.value;
238
+ if (this.importSources.has(source)) return true;
239
+ return this.isRelativeTastyImport(source, def.node);
240
+ }
197
241
  if (def.type !== "Variable") return false;
198
242
  const declaration = def.node;
199
243
  if (declaration.id.type !== "Identifier" || declaration.parent.kind !== "const" || !declaration.init || visited.has(declaration)) return false;
@@ -221,6 +265,25 @@ var TastyContext = class {
221
265
  return resolve(node, name);
222
266
  }
223
267
  /**
268
+ * Whether a binding imported from a relative module names a Tasty component
269
+ * declared there — `import { Panel } from './styled'`. A namespace import has
270
+ * no single export to follow.
271
+ */
272
+ isRelativeTastyImport(source, specifier) {
273
+ if (specifier.type !== "ImportSpecifier" && specifier.type !== "ImportDefaultSpecifier") return false;
274
+ const exportName = specifier.type === "ImportDefaultSpecifier" ? "default" : specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
275
+ const key = `${source}\0${exportName}`;
276
+ let result = this.relativeComponents.get(key);
277
+ if (result === void 0) {
278
+ result = isModuleTastyComponent(this.context.filename, source, exportName, {
279
+ importSources: this.importSources,
280
+ isComponentFactory: (name) => name === "tasty" || this.config.styleFunctions?.[name]?.kind === "options"
281
+ });
282
+ this.relativeComponents.set(key, result);
283
+ }
284
+ return result;
285
+ }
286
+ /**
224
287
  * Determines whether an object expression is a tasty style object
225
288
  * by walking up the AST to find a recognized call expression.
226
289
  */
@@ -244,6 +307,23 @@ var TastyContext = class {
244
307
  current = parent;
245
308
  }
246
309
  }
310
+ /**
311
+ * Whether the style object this node belongs to is declared on its own — a
312
+ * `const CELL_STYLES: Styles` or a value of a `Record<K, Styles>` — rather
313
+ * than handed to a call or prop. Such styles can be spread anywhere,
314
+ * including into a sub-element, so their root is not necessarily the
315
+ * component's root.
316
+ */
317
+ isSharedStyleObject(node) {
318
+ const outermost = (inner) => {
319
+ let wrapped = inner;
320
+ while (wrapped.parent && unwrapExpression(wrapped.parent) === inner) wrapped = wrapped.parent;
321
+ return wrapped;
322
+ };
323
+ let parent = outermost(this.getRootStyleObject(node)).parent;
324
+ if (parent?.type === "Property" && parent.parent.type === "ObjectExpression") parent = outermost(parent.parent).parent;
325
+ return parent?.type === "VariableDeclarator";
326
+ }
247
327
  /** Include helper calls nested inside a real sub-element, but not variant names. */
248
328
  isInsideSubElement(node) {
249
329
  let current = node.parent;
@@ -317,11 +397,12 @@ var TastyContext = class {
317
397
  if (current.type !== "VariableDeclarator" || current.id.type !== "Identifier") return false;
318
398
  let init = current.init;
319
399
  while (init?.type === "TSAsExpression" || init?.type === "TSSatisfiesExpression" || init?.type === "TSTypeAssertion" || init?.type === "TSNonNullExpression") init = init.expression;
400
+ const annotated = this.stylesTypeAnnotation(current);
401
+ if (annotated === "map") return init?.type === "ObjectExpression" && init.properties.some((prop) => prop.type === "Property" && unwrapExpression(prop.value) === targetNode);
320
402
  if (init !== targetNode) return false;
403
+ if (annotated !== null) return annotated === "styles";
321
404
  const name = current.id.name;
322
405
  if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;
323
- const annotated = this.stylesTypeAnnotation(current);
324
- if (annotated !== null) return annotated;
325
406
  if (name === "styles" || name.endsWith("Styles")) return !(targetNode.type === "ObjectExpression" && isNamedCssBlockMap(targetNode));
326
407
  return false;
327
408
  }
@@ -346,9 +427,9 @@ var TastyContext = class {
346
427
  return null;
347
428
  }
348
429
  /**
349
- * Verdict from the declared type: `true` tasty, `false` explicitly not, `null`
350
- * when there is no annotation to go on and the caller should fall back to the
351
- * name.
430
+ * Verdict from the declared type: `'styles'` the object is tasty, `'map'` its
431
+ * values are, `false` explicitly neither, `null` when there is no annotation to
432
+ * go on and the caller should fall back to the name.
352
433
  *
353
434
  * A `Styles` reference anywhere in the annotation counts, so wrappers like
354
435
  * `Styles | undefined` and `Record<string, Styles>` opt in — while
@@ -358,7 +439,7 @@ var TastyContext = class {
358
439
  stylesTypeAnnotation(node) {
359
440
  const annotation = node.id.typeAnnotation?.typeAnnotation;
360
441
  if (!annotation) return null;
361
- return referencesStylesType(annotation);
442
+ return stylesAnnotationShape(annotation) ?? false;
362
443
  }
363
444
  getTastyCallContext(call, targetNode) {
364
445
  const args = call.arguments;
@@ -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, 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';\nimport { finishJSXAnalysis, jsxPropKind } from './type-aware-jsx.js';\n\n/**\n * Every object is considered, then TastyContext gates it by its actual context.\n * Narrow ancestry selectors miss nested shared styles and wrapped JSX values.\n */\nexport const STYLE_OBJECT_SELECTORS = ['ObjectExpression'] as const;\n\n/** One listener per object; context recognition remains the caller's gate. */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n return { ObjectExpression: handler };\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 let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const container = wrapped.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 isComponentJSXProp(node: TSESTree.JSXAttribute): boolean {\n return jsxPropKind(this.context, node) === 'component';\n }\n\n finishJSXAnalysis(): void {\n finishJSXAnalysis(this.context);\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 /** Local factory results, const aliases, and configured component imports. */\n isTastyJSXComponent(node: TSESTree.Node, name: string): boolean {\n const visited = new Set<TSESTree.VariableDeclarator>();\n const resolve = (reference: TSESTree.Node, localName: string): boolean => {\n let scope = this.context.sourceCode.getScope(reference);\n while (scope) {\n const variable = scope.set.get(localName);\n if (variable) {\n return variable.defs.some((def) => {\n if (def.type === 'ImportBinding') {\n return (\n def.parent.type === 'ImportDeclaration' &&\n def.parent.importKind !== 'type' &&\n this.importSources.has(def.parent.source.value) &&\n (def.node.type !== 'ImportSpecifier' ||\n def.node.importKind !== 'type')\n );\n }\n if (def.type !== 'Variable') return false;\n const declaration = def.node;\n if (\n declaration.id.type !== 'Identifier' ||\n declaration.parent.kind !== 'const' ||\n !declaration.init ||\n visited.has(declaration)\n )\n return false;\n visited.add(declaration);\n const init = unwrapExpression(declaration.init);\n if (init.type === 'Identifier') return resolve(init, init.name);\n if (init.type === 'MemberExpression' && !init.computed) {\n let member: TSESTree.Node = init;\n while (member.type === 'MemberExpression') {\n if (member.computed) return false;\n member = unwrapExpression(member.object);\n }\n const root = baseComponentName(init);\n return root !== null && resolve(init, root);\n }\n if (init.type !== 'CallExpression') return false;\n const imp = this.isTastyCall(init);\n return (\n imp?.importedName === 'tasty' ||\n (imp !== undefined &&\n this.config.styleFunctions?.[imp.importedName]?.kind ===\n 'options')\n );\n });\n }\n if (!scope.upper) break;\n scope = scope.upper;\n }\n return false;\n };\n return resolve(node, 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 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 let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const parent = wrapped.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 attribute.name.name.endsWith('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';\nimport { finishJSXAnalysis, jsxPropKind } from './type-aware-jsx.js';\nimport { isModuleTastyComponent } from './module-components.js';\n\n/**\n * Every object is considered, then TastyContext gates it by its actual context.\n * Narrow ancestry selectors miss nested shared styles and wrapped JSX values.\n */\nexport const STYLE_OBJECT_SELECTORS = ['ObjectExpression'] as const;\n\n/** One listener per object; context recognition remains the caller's gate. */\nexport function styleObjectListeners(\n handler: (node: TSESTree.ObjectExpression) => void,\n): Record<string, (node: TSESTree.ObjectExpression) => void> {\n return { ObjectExpression: handler };\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`, `Array<Styles>`) is recognised while\n * `CSSProperties` in the same positions is not. Maps of styles are told apart\n * first by `stylesAnnotationShape`. 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/** Utility types whose result has the same shape as their argument. */\nconst SHAPE_PRESERVING_TYPES = new Set([\n 'Readonly',\n 'Partial',\n 'Required',\n 'NonNullable',\n]);\n\n/**\n * What a `Styles`-bearing annotation declares: `'styles'` for a style object,\n * `'map'` for an object whose *values* are style objects (`Record<K, Styles>`,\n * `{ [k: string]: Styles }`, `{ root: Styles }`), `null` when `Styles` does not\n * appear at all.\n *\n * The distinction matters for a map: its keys are variant or block names, so\n * checking the map itself as a style object reports every one of them as an\n * unknown property, while the style objects it holds go unchecked.\n */\nfunction stylesAnnotationShape(\n node: TSESTree.TypeNode,\n): 'styles' | 'map' | null {\n if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {\n const shapes = node.types.map(stylesAnnotationShape);\n if (shapes.includes('styles')) return 'styles';\n return shapes.includes('map') ? 'map' : null;\n }\n\n if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {\n const name = node.typeName.name;\n const args = node.typeArguments?.params ?? [];\n\n if (/^Styles$/i.test(name)) return 'styles';\n if (name === 'Record' && args.length === 2) {\n if (stylesAnnotationShape(args[1]) === 'styles') return 'map';\n }\n if (SHAPE_PRESERVING_TYPES.has(name) && args.length === 1) {\n return stylesAnnotationShape(args[0]);\n }\n }\n\n if (node.type === 'TSTypeLiteral') {\n const holdsStyles = node.members.some(\n (member) =>\n (member.type === 'TSIndexSignature' ||\n member.type === 'TSPropertySignature') &&\n member.typeAnnotation !== undefined &&\n stylesAnnotationShape(member.typeAnnotation.typeAnnotation) ===\n 'styles',\n );\n if (holdsStyles) return 'map';\n }\n\n return referencesStylesType(node) ? 'styles' : null;\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 let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const container = wrapped.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 private relativeComponents = new Map<string, boolean>();\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 isComponentJSXProp(node: TSESTree.JSXAttribute): boolean {\n return jsxPropKind(this.context, node) === 'component';\n }\n\n finishJSXAnalysis(): void {\n finishJSXAnalysis(this.context);\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 /** Local factory results, const aliases, and configured component imports. */\n isTastyJSXComponent(node: TSESTree.Node, name: string): boolean {\n const visited = new Set<TSESTree.VariableDeclarator>();\n const resolve = (reference: TSESTree.Node, localName: string): boolean => {\n let scope = this.context.sourceCode.getScope(reference);\n while (scope) {\n const variable = scope.set.get(localName);\n if (variable) {\n return variable.defs.some((def) => {\n if (def.type === 'ImportBinding') {\n if (\n def.parent.type !== 'ImportDeclaration' ||\n def.parent.importKind === 'type' ||\n (def.node.type === 'ImportSpecifier' &&\n def.node.importKind === 'type')\n ) {\n return false;\n }\n const source = def.parent.source.value;\n if (this.importSources.has(source)) return true;\n return this.isRelativeTastyImport(source, def.node);\n }\n if (def.type !== 'Variable') return false;\n const declaration = def.node;\n if (\n declaration.id.type !== 'Identifier' ||\n declaration.parent.kind !== 'const' ||\n !declaration.init ||\n visited.has(declaration)\n )\n return false;\n visited.add(declaration);\n const init = unwrapExpression(declaration.init);\n if (init.type === 'Identifier') return resolve(init, init.name);\n if (init.type === 'MemberExpression' && !init.computed) {\n let member: TSESTree.Node = init;\n while (member.type === 'MemberExpression') {\n if (member.computed) return false;\n member = unwrapExpression(member.object);\n }\n const root = baseComponentName(init);\n return root !== null && resolve(init, root);\n }\n if (init.type !== 'CallExpression') return false;\n const imp = this.isTastyCall(init);\n return (\n imp?.importedName === 'tasty' ||\n (imp !== undefined &&\n this.config.styleFunctions?.[imp.importedName]?.kind ===\n 'options')\n );\n });\n }\n if (!scope.upper) break;\n scope = scope.upper;\n }\n return false;\n };\n return resolve(node, name);\n }\n\n /**\n * Whether a binding imported from a relative module names a Tasty component\n * declared there — `import { Panel } from './styled'`. A namespace import has\n * no single export to follow.\n */\n private isRelativeTastyImport(\n source: string,\n specifier: TSESTree.Node,\n ): boolean {\n if (\n specifier.type !== 'ImportSpecifier' &&\n specifier.type !== 'ImportDefaultSpecifier'\n ) {\n return false;\n }\n const exportName =\n specifier.type === 'ImportDefaultSpecifier'\n ? 'default'\n : specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : specifier.imported.value;\n const key = `${source}\\0${exportName}`;\n let result = this.relativeComponents.get(key);\n if (result === undefined) {\n result = isModuleTastyComponent(\n this.context.filename,\n source,\n exportName,\n {\n importSources: this.importSources,\n isComponentFactory: (name) =>\n name === 'tasty' ||\n this.config.styleFunctions?.[name]?.kind === 'options',\n },\n );\n this.relativeComponents.set(key, result);\n }\n return result;\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 /**\n * Whether the style object this node belongs to is declared on its own — a\n * `const CELL_STYLES: Styles` or a value of a `Record<K, Styles>` — rather\n * than handed to a call or prop. Such styles can be spread anywhere,\n * including into a sub-element, so their root is not necessarily the\n * component's root.\n */\n isSharedStyleObject(node: TSESTree.ObjectExpression): boolean {\n const outermost = (inner: TSESTree.Node): TSESTree.Node => {\n let wrapped = inner;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === inner) {\n wrapped = wrapped.parent;\n }\n return wrapped;\n };\n\n let parent = outermost(this.getRootStyleObject(node)).parent;\n if (\n parent?.type === 'Property' &&\n parent.parent.type === 'ObjectExpression'\n ) {\n parent = outermost(parent.parent).parent;\n }\n return parent?.type === 'VariableDeclarator';\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 // An explicit type annotation is authoritative, in both directions, and is\n // read before anything about the name. The name heuristics below are a\n // guess; a declared type is the author stating what the object is, so it\n // wins — `const CARD_STYLES: Styles` is as much a style object as\n // `const cardStyles: Styles`.\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 // A map of style objects: its values are checked, never the map itself.\n if (annotated === 'map') {\n return (\n init?.type === 'ObjectExpression' &&\n init.properties.some(\n (prop) =>\n prop.type === 'Property' &&\n unwrapExpression(prop.value) === targetNode,\n )\n );\n }\n\n if (init !== targetNode) return false;\n if (annotated !== null) return annotated === 'styles';\n\n const name = current.id.name;\n\n // Unannotated constants are shared values, not style objects by name alone.\n if (/^[A-Z][A-Z0-9_]*$/.test(name)) return false;\n\n // Only `styles` and `*Styles` count. The singular `style` is conventionally\n // a DOM inline-style object (`el.style`, `CSSProperties`, `setStyle(el, …)`)\n // holding raw CSS longhands, not tasty syntax — matching it reported those\n // longhands as tasty violations.\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: `'styles'` the object is tasty, `'map'` its\n * values are, `false` explicitly neither, `null` when there is no annotation to\n * go on and the caller should fall back to the 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 ): 'styles' | 'map' | false | null {\n const annotation = node.id.typeAnnotation?.typeAnnotation;\n\n if (!annotation) return null;\n\n return stylesAnnotationShape(annotation) ?? false;\n }\n\n private getTastyCallContext(\n call: TSESTree.CallExpression,\n targetNode: TSESTree.Node,\n ) {\n const args = call.arguments;\n\n // tasty({ styles: { ... } }) or tasty(Component, { styles: { ... } })\n const 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 let wrapped: TSESTree.Node = node;\n while (wrapped.parent && unwrapExpression(wrapped.parent) === node) {\n wrapped = wrapped.parent;\n }\n const parent = wrapped.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 attribute.name.name.endsWith('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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"}
@@ -0,0 +1,173 @@
1
+ import { createRequire } from "node:module";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { dirname, extname, join, resolve } from "node:path";
4
+
5
+ //#region src/module-components.ts
6
+ const require = createRequire(import.meta.url);
7
+ let typescript;
8
+ const modules = /* @__PURE__ */ new Map();
9
+ const EXTENSIONS = [
10
+ ".ts",
11
+ ".tsx",
12
+ ".mts",
13
+ ".cts",
14
+ ".js",
15
+ ".jsx",
16
+ ".mjs",
17
+ ".cjs"
18
+ ];
19
+ const SOURCE_EXTENSIONS = {
20
+ ".js": [".ts", ".tsx"],
21
+ ".jsx": [".tsx"],
22
+ ".mjs": [".mts"],
23
+ ".cjs": [".cts"]
24
+ };
25
+ function getTypeScript() {
26
+ if (typescript === void 0) try {
27
+ const module = require("typescript");
28
+ typescript = typeof module.createSourceFile === "function" ? module : null;
29
+ } catch {
30
+ typescript = null;
31
+ }
32
+ return typescript;
33
+ }
34
+ function version(filename) {
35
+ try {
36
+ const stat = statSync(filename);
37
+ return stat.isFile() ? `${stat.mtimeMs}:${stat.size}` : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ /** Resolve a relative specifier the way a bundler would, without `paths`. */
43
+ function resolveRelativeModule(fromFile, specifier) {
44
+ const base = resolve(dirname(fromFile), specifier);
45
+ const extension = extname(base);
46
+ return [
47
+ ...EXTENSIONS.includes(extension) ? [base] : [],
48
+ ...(SOURCE_EXTENSIONS[extension] ?? []).map((replacement) => base.slice(0, -extension.length) + replacement),
49
+ ...EXTENSIONS.map((ext) => base + ext),
50
+ ...EXTENSIONS.map((ext) => join(base, "index" + ext))
51
+ ].find((candidate) => version(candidate) !== null) ?? null;
52
+ }
53
+ function hasModifier(node, kind) {
54
+ const T = getTypeScript();
55
+ return T.canHaveModifiers(node) && (T.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false);
56
+ }
57
+ function readModule(filename) {
58
+ const T = getTypeScript();
59
+ const current = version(filename);
60
+ if (!T || current === null) return null;
61
+ const cached = modules.get(filename);
62
+ if (cached?.version === current) return cached.info;
63
+ const source = T.createSourceFile(filename, readFileSync(filename, "utf8"), T.ScriptTarget.Latest);
64
+ const info = {
65
+ imports: /* @__PURE__ */ new Map(),
66
+ constants: /* @__PURE__ */ new Map(),
67
+ exports: /* @__PURE__ */ new Map(),
68
+ reexports: /* @__PURE__ */ new Map(),
69
+ starSources: []
70
+ };
71
+ for (const statement of source.statements) if (T.isImportDeclaration(statement)) {
72
+ const clause = statement.importClause;
73
+ if (!clause || clause.isTypeOnly) continue;
74
+ if (!T.isStringLiteral(statement.moduleSpecifier)) continue;
75
+ const from = statement.moduleSpecifier.text;
76
+ if (clause.name) info.imports.set(clause.name.text, {
77
+ source: from,
78
+ imported: "default"
79
+ });
80
+ const bindings = clause.namedBindings;
81
+ if (bindings && T.isNamespaceImport(bindings)) info.imports.set(bindings.name.text, {
82
+ source: from,
83
+ imported: "*"
84
+ });
85
+ else if (bindings) for (const element of bindings.elements) {
86
+ if (element.isTypeOnly) continue;
87
+ info.imports.set(element.name.text, {
88
+ source: from,
89
+ imported: (element.propertyName ?? element.name).text
90
+ });
91
+ }
92
+ } else if (T.isVariableStatement(statement)) {
93
+ if (!(statement.declarationList.flags & T.NodeFlags.Const)) continue;
94
+ const exported = hasModifier(statement, T.SyntaxKind.ExportKeyword);
95
+ for (const declaration of statement.declarationList.declarations) {
96
+ if (!T.isIdentifier(declaration.name) || !declaration.initializer) continue;
97
+ const name = declaration.name.text;
98
+ info.constants.set(name, declaration.initializer);
99
+ if (exported) info.exports.set(name, name);
100
+ }
101
+ } else if (T.isExportDeclaration(statement)) {
102
+ if (statement.isTypeOnly) continue;
103
+ const from = statement.moduleSpecifier && T.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : null;
104
+ const clause = statement.exportClause;
105
+ if (!clause) {
106
+ if (from !== null) info.starSources.push(from);
107
+ } else if (T.isNamedExports(clause)) for (const element of clause.elements) {
108
+ if (element.isTypeOnly) continue;
109
+ const local = (element.propertyName ?? element.name).text;
110
+ if (from === null) info.exports.set(element.name.text, local);
111
+ else info.reexports.set(element.name.text, {
112
+ source: from,
113
+ imported: local
114
+ });
115
+ }
116
+ } else if (T.isExportAssignment(statement) && !statement.isExportEquals) info.defaultExpression = statement.expression;
117
+ modules.set(filename, {
118
+ version: current,
119
+ info
120
+ });
121
+ return info;
122
+ }
123
+ /** Whether `exportName` of the module `specifier` (relative to `fromFile`) is a Tasty component. */
124
+ function isModuleTastyComponent(fromFile, specifier, exportName, options, visited = /* @__PURE__ */ new Set()) {
125
+ if (!specifier.startsWith(".")) return false;
126
+ const filename = resolveRelativeModule(fromFile, specifier);
127
+ if (filename === null) return false;
128
+ const key = `${filename}\0${exportName}`;
129
+ if (visited.has(key)) return false;
130
+ visited.add(key);
131
+ const info = readModule(filename);
132
+ if (!info) return false;
133
+ const T = getTypeScript();
134
+ const fromImport = (binding) => options.importSources.has(binding.source) || binding.imported !== "*" && isModuleTastyComponent(filename, binding.source, binding.imported, options, visited);
135
+ const seenLocals = /* @__PURE__ */ new Set();
136
+ const fromLocal = (name) => {
137
+ if (seenLocals.has(name)) return false;
138
+ seenLocals.add(name);
139
+ const init = info.constants.get(name);
140
+ if (init) return fromExpression(init);
141
+ const binding = info.imports.get(name);
142
+ return binding !== void 0 && fromImport(binding);
143
+ };
144
+ const fromExpression = (node) => {
145
+ while (T.isParenthesizedExpression(node) || T.isAsExpression(node) || T.isSatisfiesExpression(node) || T.isNonNullExpression(node) || T.isTypeAssertionExpression(node)) node = node.expression;
146
+ if (T.isIdentifier(node)) return fromLocal(node.text);
147
+ if (T.isPropertyAccessExpression(node)) {
148
+ if (T.isIdentifier(node.expression)) {
149
+ const binding = info.imports.get(node.expression.text);
150
+ if (binding?.imported === "*" && !options.importSources.has(binding.source)) return isModuleTastyComponent(filename, binding.source, node.name.text, options, visited);
151
+ }
152
+ let root = node;
153
+ while (T.isPropertyAccessExpression(root)) root = root.expression;
154
+ return T.isIdentifier(root) && fromLocal(root.text);
155
+ }
156
+ if (T.isCallExpression(node) && T.isIdentifier(node.expression)) {
157
+ const binding = info.imports.get(node.expression.text);
158
+ return binding !== void 0 && options.importSources.has(binding.source) && options.isComponentFactory(binding.imported);
159
+ }
160
+ return false;
161
+ };
162
+ const local = info.exports.get(exportName);
163
+ if (local !== void 0) return fromLocal(local);
164
+ if (exportName === "default" && info.defaultExpression) return fromExpression(info.defaultExpression);
165
+ const reexport = info.reexports.get(exportName);
166
+ if (reexport) return fromImport(reexport);
167
+ if (exportName === "default") return false;
168
+ return info.starSources.some((source) => isModuleTastyComponent(filename, source, exportName, options, visited));
169
+ }
170
+
171
+ //#endregion
172
+ export { isModuleTastyComponent };
173
+ //# sourceMappingURL=module-components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-components.js","names":[],"sources":["../src/module-components.ts"],"sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, extname, join, resolve } from 'node:path';\nimport type ts from 'typescript';\n\n/**\n * Whether an export of a relative module is a Tasty component.\n *\n * Elements of a design-system component usually live next to it in `styled.ts`\n * or `elements.ts`, so a rule that only recognises `tasty()` results declared in\n * the file being linted misses most of them. This reads the imported module the\n * same syntactic way: an export is a component when it is a `tasty()` call (or a\n * configured options-kind factory), a `const` alias of one, an import from a\n * configured source, or a re-export that leads to one of those.\n *\n * Parsing goes through TypeScript, an optional peer dependency. Without it, an\n * imported component is simply not recognised, which is how it was before.\n */\nexport interface ModuleComponentOptions {\n importSources: ReadonlySet<string>;\n /** Imported names whose call result is a Tasty component. */\n isComponentFactory(importedName: string): boolean;\n}\n\ntype TypeScript = typeof ts;\n\ninterface ImportBinding {\n source: string;\n /** `'default'`, `'*'` for a namespace, or the exported name. */\n imported: string;\n}\n\ninterface ModuleInfo {\n imports: Map<string, ImportBinding>;\n constants: Map<string, ts.Expression>;\n /** Exported name -> local name. */\n exports: Map<string, string>;\n reexports: Map<string, ImportBinding>;\n starSources: string[];\n defaultExpression?: ts.Expression;\n}\n\nconst require = createRequire(import.meta.url);\nlet typescript: TypeScript | null | undefined;\nconst modules = new Map<string, { version: string; info: ModuleInfo }>();\n\nconst EXTENSIONS = [\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n];\n// A TypeScript ESM import names the emitted file: `./styled.js` is `styled.ts`.\nconst SOURCE_EXTENSIONS: Record<string, string[]> = {\n '.js': ['.ts', '.tsx'],\n '.jsx': ['.tsx'],\n '.mjs': ['.mts'],\n '.cjs': ['.cts'],\n};\n\nfunction getTypeScript(): TypeScript | null {\n if (typescript === undefined) {\n try {\n const module = require('typescript') as Partial<TypeScript>;\n // TypeScript 7 (the native port) exports only its version.\n typescript =\n typeof module.createSourceFile === 'function'\n ? (module as TypeScript)\n : null;\n } catch {\n typescript = null;\n }\n }\n return typescript;\n}\n\nfunction version(filename: string): string | null {\n try {\n const stat = statSync(filename);\n return stat.isFile() ? `${stat.mtimeMs}:${stat.size}` : null;\n } catch {\n return null;\n }\n}\n\n/** Resolve a relative specifier the way a bundler would, without `paths`. */\nexport function resolveRelativeModule(\n fromFile: string,\n specifier: string,\n): string | null {\n const base = resolve(dirname(fromFile), specifier);\n const extension = extname(base);\n const candidates = [\n ...(EXTENSIONS.includes(extension) ? [base] : []),\n ...(SOURCE_EXTENSIONS[extension] ?? []).map(\n (replacement) => base.slice(0, -extension.length) + replacement,\n ),\n ...EXTENSIONS.map((ext) => base + ext),\n ...EXTENSIONS.map((ext) => join(base, 'index' + ext)),\n ];\n\n return candidates.find((candidate) => version(candidate) !== null) ?? null;\n}\n\nfunction hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {\n const T = getTypeScript()!;\n return (\n T.canHaveModifiers(node) &&\n (T.getModifiers(node)?.some((modifier) => modifier.kind === kind) ?? false)\n );\n}\n\nfunction readModule(filename: string): ModuleInfo | null {\n const T = getTypeScript();\n const current = version(filename);\n if (!T || current === null) return null;\n\n const cached = modules.get(filename);\n if (cached?.version === current) return cached.info;\n\n const source = T.createSourceFile(\n filename,\n readFileSync(filename, 'utf8'),\n T.ScriptTarget.Latest,\n );\n const info: ModuleInfo = {\n imports: new Map(),\n constants: new Map(),\n exports: new Map(),\n reexports: new Map(),\n starSources: [],\n };\n\n for (const statement of source.statements) {\n if (T.isImportDeclaration(statement)) {\n const clause = statement.importClause;\n if (!clause || clause.isTypeOnly) continue;\n if (!T.isStringLiteral(statement.moduleSpecifier)) continue;\n const from = statement.moduleSpecifier.text;\n if (clause.name) {\n info.imports.set(clause.name.text, {\n source: from,\n imported: 'default',\n });\n }\n const bindings = clause.namedBindings;\n if (bindings && T.isNamespaceImport(bindings)) {\n info.imports.set(bindings.name.text, { source: from, imported: '*' });\n } else if (bindings) {\n for (const element of bindings.elements) {\n if (element.isTypeOnly) continue;\n info.imports.set(element.name.text, {\n source: from,\n imported: (element.propertyName ?? element.name).text,\n });\n }\n }\n } else if (T.isVariableStatement(statement)) {\n if (!(statement.declarationList.flags & T.NodeFlags.Const)) continue;\n const exported = hasModifier(statement, T.SyntaxKind.ExportKeyword);\n for (const declaration of statement.declarationList.declarations) {\n if (!T.isIdentifier(declaration.name) || !declaration.initializer) {\n continue;\n }\n const name = declaration.name.text;\n info.constants.set(name, declaration.initializer);\n if (exported) info.exports.set(name, name);\n }\n } else if (T.isExportDeclaration(statement)) {\n if (statement.isTypeOnly) continue;\n const from =\n statement.moduleSpecifier &&\n T.isStringLiteral(statement.moduleSpecifier)\n ? statement.moduleSpecifier.text\n : null;\n const clause = statement.exportClause;\n if (!clause) {\n if (from !== null) info.starSources.push(from);\n } else if (T.isNamedExports(clause)) {\n for (const element of clause.elements) {\n if (element.isTypeOnly) continue;\n const local = (element.propertyName ?? element.name).text;\n if (from === null) info.exports.set(element.name.text, local);\n else\n info.reexports.set(element.name.text, {\n source: from,\n imported: local,\n });\n }\n }\n } else if (T.isExportAssignment(statement) && !statement.isExportEquals) {\n info.defaultExpression = statement.expression;\n }\n }\n\n modules.set(filename, { version: current, info });\n return info;\n}\n\n/** Whether `exportName` of the module `specifier` (relative to `fromFile`) is a Tasty component. */\nexport function isModuleTastyComponent(\n fromFile: string,\n specifier: string,\n exportName: string,\n options: ModuleComponentOptions,\n visited = new Set<string>(),\n): boolean {\n if (!specifier.startsWith('.')) return false;\n const filename = resolveRelativeModule(fromFile, specifier);\n if (filename === null) return false;\n\n const key = `${filename}\\0${exportName}`;\n if (visited.has(key)) return false;\n visited.add(key);\n\n const info = readModule(filename);\n if (!info) return false;\n const T = getTypeScript()!;\n\n const fromImport = (binding: ImportBinding): boolean =>\n options.importSources.has(binding.source) ||\n (binding.imported !== '*' &&\n isModuleTastyComponent(\n filename,\n binding.source,\n binding.imported,\n options,\n visited,\n ));\n\n const seenLocals = new Set<string>();\n const fromLocal = (name: string): boolean => {\n if (seenLocals.has(name)) return false;\n seenLocals.add(name);\n const init = info.constants.get(name);\n if (init) return fromExpression(init);\n const binding = info.imports.get(name);\n return binding !== undefined && fromImport(binding);\n };\n\n const fromExpression = (node: ts.Expression): boolean => {\n while (\n T.isParenthesizedExpression(node) ||\n T.isAsExpression(node) ||\n T.isSatisfiesExpression(node) ||\n T.isNonNullExpression(node) ||\n T.isTypeAssertionExpression(node)\n ) {\n node = node.expression;\n }\n if (T.isIdentifier(node)) return fromLocal(node.text);\n if (T.isPropertyAccessExpression(node)) {\n // `UI.Panel` from `import * as UI from './ui'` is that module's `Panel`.\n if (T.isIdentifier(node.expression)) {\n const binding = info.imports.get(node.expression.text);\n if (\n binding?.imported === '*' &&\n !options.importSources.has(binding.source)\n ) {\n return isModuleTastyComponent(\n filename,\n binding.source,\n node.name.text,\n options,\n visited,\n );\n }\n }\n let root: ts.Expression = node;\n while (T.isPropertyAccessExpression(root)) root = root.expression;\n return T.isIdentifier(root) && fromLocal(root.text);\n }\n if (T.isCallExpression(node) && T.isIdentifier(node.expression)) {\n const binding = info.imports.get(node.expression.text);\n return (\n binding !== undefined &&\n options.importSources.has(binding.source) &&\n options.isComponentFactory(binding.imported)\n );\n }\n return false;\n };\n\n const local = info.exports.get(exportName);\n if (local !== undefined) return fromLocal(local);\n if (exportName === 'default' && info.defaultExpression) {\n return fromExpression(info.defaultExpression);\n }\n const reexport = info.reexports.get(exportName);\n if (reexport) return fromImport(reexport);\n if (exportName === 'default') return false;\n\n return info.starSources.some((source) =>\n isModuleTastyComponent(filename, source, exportName, options, visited),\n );\n}\n"],"mappings":"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"}
@@ -485,6 +485,32 @@ function getStateNegationEdits(stateKey) {
485
485
  });
486
486
  }
487
487
  /**
488
+ * Unwrap every `@own(...)` term of a key, for a scope where `@own` is the
489
+ * element itself. A term with operators inside keeps them grouped, so
490
+ * `@own(a | b) & c` becomes `(a | b) & c`, not `a | b & c`. Returns no edits
491
+ * when any term cannot be unwrapped safely, so a key is never half-rewritten.
492
+ */
493
+ function getOwnUnwrapEdits(stateKey) {
494
+ if (!stateKey.includes("@own(")) return [];
495
+ if (checkBracketBalance(stateKey)) return [];
496
+ if (parseStateKey(stateKey).errors.length > 0) return [];
497
+ const { tokens } = tokenize(stateKey);
498
+ const edits = [];
499
+ for (const token of tokens) {
500
+ if (token.type !== "STATE" || !token.value.startsWith("@own(")) continue;
501
+ const inner = token.value.slice(5, -1).trim();
502
+ if (!inner || findTopLevelComma(inner) !== -1) return [];
503
+ const innerTokens = tokenize(inner).tokens;
504
+ const single = tokens.length === 1 || innerTokens.length === 1 && innerTokens[0].value === inner;
505
+ edits.push({
506
+ start: token.offset,
507
+ end: token.offset + token.length,
508
+ replacement: single ? inner : `(${inner})`
509
+ });
510
+ }
511
+ return edits;
512
+ }
513
+ /**
488
514
  * The state atoms of a key, without its operators and grouping. The contents
489
515
  * of `@own(...)` are listed atom by atom; `@root()`, `@parent()`, and at-rule
490
516
  * conditions stay single atoms, since they describe other things.
@@ -554,5 +580,5 @@ function validateStateDefinition(value, opts = {}) {
554
580
  }
555
581
 
556
582
  //#endregion
557
- export { getStateAtoms, getStateNegationEdits, parseStateKey, validateStateDefinition };
583
+ export { getOwnUnwrapEdits, getStateAtoms, getStateNegationEdits, parseStateKey, validateStateDefinition };
558
584
  //# sourceMappingURL=state-key-parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-key-parser.js","names":[],"sources":["../../src/parsers/state-key-parser.ts"],"sourcesContent":["import { KNOWN_PSEUDO_CLASSES } from '../constants.js';\nimport type { StringEdit } from '../fix-utils.js';\nimport { checkBracketBalance } from './utils.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StateKeyError {\n message: string;\n offset: number;\n length: number;\n}\n\nexport interface StateKeyResult {\n errors: StateKeyError[];\n hasOwn: boolean;\n referencedAliases: string[];\n}\n\nexport interface StateKeyParserOptions {\n knownAliases?: string[];\n}\n\n// ============================================================================\n// Tokenizer\n// ============================================================================\n\ntype TokenType = 'AND' | 'OR' | 'NOT' | 'XOR' | 'LPAREN' | 'RPAREN' | 'STATE';\n\ninterface Token {\n type: TokenType;\n value: string;\n offset: number;\n length: number;\n}\n\n/**\n * Pattern for tokenizing state notation.\n * Matches operators, parentheses, @-prefixed states, value mods, boolean mods,\n * pseudo-classes with functions (including :is/:has/:not/:where with nesting),\n * class selectors, and attribute selectors.\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]*\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@parent\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@own\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\nfunction tokenize(stateKey: string): {\n tokens: Token[];\n errors: StateKeyError[];\n} {\n const tokens: Token[] = [];\n const errors: StateKeyError[] = [];\n\n // Replace commas with | outside of parentheses\n const normalized = replaceCommasOutsideParens(stateKey);\n\n const covered = new Set<number>();\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {\n const fullMatch = match[0];\n const offset = match.index;\n\n for (let i = offset; i < offset + fullMatch.length; i++) {\n covered.add(i);\n }\n\n if (match[1]) {\n switch (fullMatch) {\n case '&':\n tokens.push({ type: 'AND', value: '&', offset, length: 1 });\n break;\n case '|':\n tokens.push({ type: 'OR', value: '|', offset, length: 1 });\n break;\n case '!':\n tokens.push({ type: 'NOT', value: '!', offset, length: 1 });\n break;\n case '^':\n tokens.push({ type: 'XOR', value: '^', offset, length: 1 });\n break;\n }\n } else if (match[2]) {\n if (fullMatch === '(') {\n tokens.push({ type: 'LPAREN', value: '(', offset, length: 1 });\n } else {\n tokens.push({ type: 'RPAREN', value: ')', offset, length: 1 });\n }\n } else {\n tokens.push({\n type: 'STATE',\n value: fullMatch,\n offset,\n length: fullMatch.length,\n });\n }\n }\n\n // Check for uncovered characters (unrecognized tokens)\n const uncovered: { ch: string; pos: number }[] = [];\n for (let i = 0; i < stateKey.length; i++) {\n const ch = stateKey[i];\n if (ch === ' ' || ch === '\\t' || ch === ',') continue;\n if (!covered.has(i)) {\n uncovered.push({ ch, pos: i });\n }\n }\n\n if (uncovered.length > 0) {\n const chars = [...new Set(uncovered.map((u) => u.ch))].join('');\n errors.push({\n message: `Unrecognized characters '${chars}' in state key '${stateKey}'.`,\n offset: uncovered[0].pos,\n length: 1,\n });\n }\n\n return { tokens, errors };\n}\n\nfunction replaceCommasOutsideParens(str: string): string {\n let result = '';\n let depth = 0;\n\n for (const char of str) {\n if (char === '(') {\n depth++;\n result += char;\n } else if (char === ')') {\n depth--;\n result += char;\n } else if (char === ',' && depth === 0) {\n result += '|';\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Validator\n// ============================================================================\n\nconst MAX_XOR_CHAIN_LENGTH = 4;\n\nconst DIMENSION_SHORTHANDS = new Set(['w', 'h', 'is', 'bs']);\nconst DIMENSION_FULL = new Set([\n 'width',\n 'height',\n 'inline-size',\n 'block-size',\n]);\n\nclass StateKeyValidator {\n private errors: StateKeyError[] = [];\n private hasOwn = false;\n private referencedAliases: string[] = [];\n private tokens: Token[];\n private pos = 0;\n private opts: StateKeyParserOptions;\n private insideOwn = false;\n\n constructor(\n tokens: Token[],\n tokenErrors: StateKeyError[],\n opts: StateKeyParserOptions,\n ) {\n this.tokens = tokens;\n this.errors = [...tokenErrors];\n this.opts = opts;\n }\n\n validate(): StateKeyResult {\n if (this.tokens.length > 0) {\n this.parseExpression();\n }\n\n return {\n errors: this.errors,\n hasOwn: this.hasOwn,\n referencedAliases: this.referencedAliases,\n };\n }\n\n private current(): Token | undefined {\n return this.tokens[this.pos];\n }\n\n private advance(): Token | undefined {\n return this.tokens[this.pos++];\n }\n\n private match(type: TokenType): boolean {\n if (this.current()?.type === type) {\n this.advance();\n return true;\n }\n return false;\n }\n\n private parseExpression(): void {\n this.parseAnd();\n }\n\n private parseAnd(): void {\n this.parseOr();\n while (this.current()?.type === 'AND') {\n this.advance();\n if (!this.current() || this.current()?.type === 'AND') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '&' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseOr();\n }\n }\n\n private parseOr(): void {\n this.parseXor();\n while (this.current()?.type === 'OR') {\n this.advance();\n if (!this.current() || this.current()?.type === 'OR') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '|' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseXor();\n }\n }\n\n private parseXor(): void {\n this.parseUnary();\n let operandCount = 1;\n\n while (this.current()?.type === 'XOR') {\n this.advance();\n operandCount++;\n if (operandCount > MAX_XOR_CHAIN_LENGTH) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. Consider breaking into smaller expressions.`,\n offset: prev.offset,\n length: prev.length,\n });\n }\n this.parseUnary();\n }\n }\n\n private parseUnary(): void {\n if (this.match('NOT')) {\n if (\n !this.current() ||\n this.current()?.type === 'AND' ||\n this.current()?.type === 'OR' ||\n this.current()?.type === 'XOR'\n ) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '!' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseUnary();\n return;\n }\n this.parsePrimary();\n }\n\n private parsePrimary(): void {\n if (this.match('LPAREN')) {\n this.parseExpression();\n if (!this.match('RPAREN')) {\n this.errors.push({\n message: \"Missing closing ')' in state expression.\",\n offset: this.tokens[this.pos - 1]?.offset ?? 0,\n length: 1,\n });\n }\n return;\n }\n\n const token = this.current();\n if (token?.type === 'STATE') {\n this.advance();\n this.validateStateToken(token);\n return;\n }\n\n // Unexpected token or end\n if (token) {\n this.errors.push({\n message: `Unexpected token '${token.value}'.`,\n offset: token.offset,\n length: token.length,\n });\n this.advance();\n }\n }\n\n private validateStateToken(token: Token): void {\n const value = token.value;\n\n // @starting\n if (value === '@starting') return;\n\n // @media:type\n if (value.startsWith('@media:')) {\n const mediaType = value.slice(7);\n const validTypes = new Set(['print', 'screen', 'all', 'speech']);\n if (!validTypes.has(mediaType)) {\n this.errors.push({\n message: `Unknown media type '${mediaType}'. Valid: print, screen, all, speech.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // @media(...)\n if (value.startsWith('@media(')) {\n this.validateMediaQuery(value, token);\n return;\n }\n\n // @supports(...)\n if (value.startsWith('@supports(')) {\n this.validateSupportsQuery(value, token);\n return;\n }\n\n // @root(...)\n if (value.startsWith('@root(')) {\n this.validateInnerStateExpression(value, 6, token);\n return;\n }\n\n // @parent(...)\n if (value.startsWith('@parent(')) {\n this.validateParentState(value, token);\n return;\n }\n\n // @own(...)\n if (value.startsWith('@own(')) {\n this.hasOwn = true;\n if (this.insideOwn) {\n this.errors.push({\n message: 'Nested @own() is not allowed.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n const prevInsideOwn = this.insideOwn;\n this.insideOwn = true;\n this.validateInnerStateExpression(value, 5, token);\n this.insideOwn = prevInsideOwn;\n return;\n }\n\n // @(...) container query\n if (value.startsWith('@(')) {\n this.validateContainerQuery(value, token);\n return;\n }\n\n // @alias predefined state\n if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {\n this.referencedAliases.push(value);\n return;\n }\n\n // Pseudo-class/pseudo-element\n if (value.startsWith(':')) {\n this.validatePseudoClass(value, token);\n return;\n }\n\n // Class selector\n if (value.startsWith('.')) return;\n\n // Attribute selector\n if (value.startsWith('[')) {\n this.validateAttributeSelector(value, token);\n return;\n }\n\n // Value modifier (e.g., theme=danger)\n if (value.includes('=')) {\n this.validateValueModifier(value, token);\n return;\n }\n\n // Boolean modifier (e.g., hovered, disabled)\n if (/^[a-z][a-z0-9-]+$/i.test(value)) return;\n\n this.errors.push({\n message: `Unrecognized state token '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n\n private validateMediaQuery(raw: string, token: Token): void {\n const content = raw.slice(7, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @media() query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Expand dimension shorthands for validation\n const expanded = expandDimensionShorthands(content.trim());\n\n // Feature query (contains ':' but no comparison operators)\n if (\n expanded.includes(':') &&\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Boolean feature query (no comparison operators)\n if (\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Dimension query — validate dimension names\n this.validateDimensionCondition(expanded, token);\n }\n\n private validateDimensionCondition(condition: string, token: Token): void {\n // Range syntax: 600px <= width < 1200px\n const rangeMatch = condition.match(\n /^(.+?)\\s*(<=|<)\\s*(\\S+)\\s*(<=|<)\\s*(.+)$/,\n );\n if (rangeMatch) {\n const dim = rangeMatch[3];\n if (!DIMENSION_FULL.has(dim) && !DIMENSION_SHORTHANDS.has(dim)) {\n this.errors.push({\n message: `Unknown dimension '${dim}' in media/container query. Valid: width, height, inline-size, block-size (or w, h, is, bs).`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple comparison: width < 768px\n const simpleMatch = condition.match(/^(\\S+)\\s*(<=|>=|<|>|=)\\s*(.+)$/);\n if (simpleMatch) {\n const dim = simpleMatch[1];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n\n // Reversed: 768px > width\n const reversedMatch = condition.match(/^(.+?)\\s*(<=|>=|<|>|=)\\s*(\\S+)$/);\n if (reversedMatch) {\n const dim = reversedMatch[3];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n }\n\n private validateSupportsQuery(raw: string, token: Token): void {\n const content = raw.slice(10, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @supports() query.',\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateInnerStateExpression(\n raw: string,\n prefixLen: number,\n _token: Token,\n ): void {\n const content = raw.slice(prefixLen, -1);\n if (!content.trim()) return;\n\n const innerResult = parseStateKey(content, this.opts);\n this.errors.push(...innerResult.errors);\n if (innerResult.hasOwn) this.hasOwn = true;\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateParentState(raw: string, token: Token): void {\n const content = raw.slice(8, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @parent() state.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n let condition = content.trim();\n\n // Check for direct parent combinator: @parent(hovered, >)\n const lastCommaIdx = condition.lastIndexOf(',');\n if (lastCommaIdx !== -1) {\n const afterComma = condition.slice(lastCommaIdx + 1).trim();\n if (afterComma === '>') {\n condition = condition.slice(0, lastCommaIdx).trim();\n }\n }\n\n const innerResult = parseStateKey(condition, this.opts);\n this.errors.push(...innerResult.errors);\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateContainerQuery(raw: string, token: Token): void {\n const content = raw.slice(2, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty container query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Named container: @(layout, w < 600px)\n const commaIdx = findTopLevelComma(content);\n let condition: string;\n\n if (commaIdx !== -1) {\n condition = content.slice(commaIdx + 1).trim();\n } else {\n condition = content.trim();\n }\n\n // Style query: @($variant=primary) — skip\n if (condition.startsWith('$')) return;\n\n // Function-like: scroll-state(...) — skip\n if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) return;\n\n // Dimension query\n const expanded = expandDimensionShorthands(condition);\n if (\n expanded.includes('<') ||\n expanded.includes('>') ||\n expanded.includes('=')\n ) {\n this.validateDimensionCondition(expanded, token);\n }\n }\n\n private validatePseudoClass(value: string, token: Token): void {\n // :is(), :has(), :not(), :where() — structural pseudo-classes\n const enhancedMatch = /^:(is|has|not|where)\\(/.exec(value);\n if (enhancedMatch) return;\n\n // Functional pseudo-classes like :nth-child(2n+1)\n const funcMatch = /^(:[a-z-]+)\\(/.exec(value);\n if (funcMatch) {\n const baseName = funcMatch[1];\n if (!KNOWN_PSEUDO_CLASSES.has(baseName)) {\n this.errors.push({\n message: `Unknown pseudo-class '${baseName}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple pseudo-class: :hover, :focus, etc.\n if (!KNOWN_PSEUDO_CLASSES.has(value)) {\n this.errors.push({\n message: `Unknown pseudo-class '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateAttributeSelector(value: string, token: Token): void {\n if (!value.startsWith('[') || !value.endsWith(']')) {\n this.errors.push({\n message: `Malformed attribute selector '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateValueModifier(value: string, token: Token): void {\n const opMatch = value.match(/^([a-z][a-z0-9-]*)(\\^=|\\$=|\\*=|=)(.+)$/i);\n if (!opMatch) {\n this.errors.push({\n message: `Invalid value modifier syntax '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction expandDimensionShorthands(condition: string): string {\n return condition\n .replace(/\\bw\\b/g, 'width')\n .replace(/\\bh\\b/g, 'height')\n .replace(/\\bis\\b/g, 'inline-size')\n .replace(/\\bbs\\b/g, 'block-size');\n}\n\nfunction findTopLevelComma(str: string): number {\n let depth = 0;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === '(') depth++;\n else if (ch === ')') depth = Math.max(0, depth - 1);\n else if (ch === ',' && depth === 0) return i;\n }\n return -1;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Rewrite :not() state atoms, leaving raw CSS inside other functions alone.\n * A selector list/compound must stay inside :is(): negating individual parts\n * would change which elements match. Only a single pseudo can be unwrapped.\n */\nexport function getStateNegationEdits(stateKey: string): StringEdit[] {\n if (!stateKey.includes(':not(')) return [];\n if (checkBracketBalance(stateKey)) return [];\n if (parseStateKey(stateKey).errors.length > 0) return [];\n\n const { tokens } = tokenize(stateKey);\n return tokens\n .filter(\n (token) => token.type === 'STATE' && token.value.startsWith(':not('),\n )\n .flatMap((token) => {\n if (checkBracketBalance(token.value)) return [];\n const inner = token.value.slice(5, -1).trim();\n if (!inner) return [];\n\n const { tokens: innerTokens, errors } = tokenize(inner);\n const singlePseudo =\n errors.length === 0 &&\n innerTokens.length === 1 &&\n innerTokens[0].type === 'STATE' &&\n innerTokens[0].value === inner &&\n inner.startsWith(':') &&\n parseStateKey(inner).errors.length === 0 &&\n !inner.startsWith(':not(') &&\n // Keep forgiving selector-list wrappers, even for invalid branches.\n !inner.startsWith(':is(') &&\n !inner.startsWith(':where(') &&\n // Promoting :has() to a state atom enables Tasty's trailing-combinator\n // completion. Preserve the original raw CSS instead in that case.\n !(inner.startsWith(':has(') && /[>+~]\\s*\\)$/.test(inner));\n\n return [\n {\n start: token.offset,\n end: token.offset + token.length,\n replacement: singlePseudo ? `!${inner}` : `!:is(${inner})`,\n },\n ];\n });\n}\n\nexport interface StateAtom {\n /** The atom as written: `hovered`, `:hover`, `[open]`, `@root(...)`. */\n value: string;\n /** Whether the atom sits inside `@own(...)`. */\n own: boolean;\n}\n\n/**\n * The state atoms of a key, without its operators and grouping. The contents\n * of `@own(...)` are listed atom by atom; `@root()`, `@parent()`, and at-rule\n * conditions stay single atoms, since they describe other things.\n */\nexport function getStateAtoms(stateKey: string): StateAtom[] {\n const atoms: StateAtom[] = [];\n for (const token of tokenize(stateKey.trim()).tokens) {\n if (token.type !== 'STATE') continue;\n if (!token.value.startsWith('@own(')) {\n atoms.push({ value: token.value, own: false });\n continue;\n }\n for (const inner of tokenize(token.value.slice(5, -1)).tokens) {\n if (inner.type === 'STATE') atoms.push({ value: inner.value, own: true });\n }\n }\n return atoms;\n}\n\n/**\n * Validate a state key string and return detailed errors.\n */\nexport function parseStateKey(\n stateKey: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n if (!stateKey || !stateKey.trim()) {\n return { errors: [], hasOwn: false, referencedAliases: [] };\n }\n\n const trimmed = stateKey.trim();\n\n // The standalone `_` key is the map-wide fallback floor. It always applies\n // and must be used on its own — it carries no state logic to validate.\n if (trimmed === '_') {\n return { errors: [], hasOwn: false, referencedAliases: [] };\n }\n\n // A `_` combined with state logic (`_ & hovered`, `_ | x`) is a misuse: the\n // fallback floor can only be used on its own. Tasty ignores such keys at\n // runtime and warns; flag them here so the mistake surfaces in the editor.\n if (isMisusedFallbackKey(trimmed)) {\n return {\n errors: [\n {\n message:\n 'The fallback floor \"_\" cannot be combined with other state ' +\n 'logic. Use \"_\" on its own as a map-wide fallback floor.',\n offset: 0,\n length: stateKey.length,\n },\n ],\n hasOwn: false,\n referencedAliases: [],\n };\n }\n\n const { tokens, errors: tokenErrors } = tokenize(trimmed);\n const validator = new StateKeyValidator(tokens, tokenErrors, opts);\n return validator.validate();\n}\n\n/**\n * Detect a misused standalone `_` fallback key: a key that is not exactly\n * `_` but contains `_` as a standalone atom among state-logic operators\n * (`&`, `|`, `^`, `,`) or grouping parens. A `_` embedded in a longer token\n * (e.g. a modifier named `_private`) is not a misuse.\n */\nfunction isMisusedFallbackKey(stateKey: string): boolean {\n if (stateKey === '_' || !stateKey.includes('_')) return false;\n return /(?:^|[\\s&|^,(])_(?:[\\s&|^,)]|$)/.test(stateKey);\n}\n\n/**\n * Validate a state definition value (the RHS of a state alias).\n * State definitions should be valid state expressions like\n * '@media(w < 768px)', '@root(theme=dark)', etc.\n */\nexport function validateStateDefinition(\n value: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n return parseStateKey(value, opts);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"state-key-parser.js","names":[],"sources":["../../src/parsers/state-key-parser.ts"],"sourcesContent":["import { KNOWN_PSEUDO_CLASSES } from '../constants.js';\nimport type { StringEdit } from '../fix-utils.js';\nimport { checkBracketBalance } from './utils.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StateKeyError {\n message: string;\n offset: number;\n length: number;\n}\n\nexport interface StateKeyResult {\n errors: StateKeyError[];\n hasOwn: boolean;\n referencedAliases: string[];\n}\n\nexport interface StateKeyParserOptions {\n knownAliases?: string[];\n}\n\n// ============================================================================\n// Tokenizer\n// ============================================================================\n\ntype TokenType = 'AND' | 'OR' | 'NOT' | 'XOR' | 'LPAREN' | 'RPAREN' | 'STATE';\n\ninterface Token {\n type: TokenType;\n value: string;\n offset: number;\n length: number;\n}\n\n/**\n * Pattern for tokenizing state notation.\n * Matches operators, parentheses, @-prefixed states, value mods, boolean mods,\n * pseudo-classes with functions (including :is/:has/:not/:where with nesting),\n * class selectors, and attribute selectors.\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]*\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@parent\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@own\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\nfunction tokenize(stateKey: string): {\n tokens: Token[];\n errors: StateKeyError[];\n} {\n const tokens: Token[] = [];\n const errors: StateKeyError[] = [];\n\n // Replace commas with | outside of parentheses\n const normalized = replaceCommasOutsideParens(stateKey);\n\n const covered = new Set<number>();\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {\n const fullMatch = match[0];\n const offset = match.index;\n\n for (let i = offset; i < offset + fullMatch.length; i++) {\n covered.add(i);\n }\n\n if (match[1]) {\n switch (fullMatch) {\n case '&':\n tokens.push({ type: 'AND', value: '&', offset, length: 1 });\n break;\n case '|':\n tokens.push({ type: 'OR', value: '|', offset, length: 1 });\n break;\n case '!':\n tokens.push({ type: 'NOT', value: '!', offset, length: 1 });\n break;\n case '^':\n tokens.push({ type: 'XOR', value: '^', offset, length: 1 });\n break;\n }\n } else if (match[2]) {\n if (fullMatch === '(') {\n tokens.push({ type: 'LPAREN', value: '(', offset, length: 1 });\n } else {\n tokens.push({ type: 'RPAREN', value: ')', offset, length: 1 });\n }\n } else {\n tokens.push({\n type: 'STATE',\n value: fullMatch,\n offset,\n length: fullMatch.length,\n });\n }\n }\n\n // Check for uncovered characters (unrecognized tokens)\n const uncovered: { ch: string; pos: number }[] = [];\n for (let i = 0; i < stateKey.length; i++) {\n const ch = stateKey[i];\n if (ch === ' ' || ch === '\\t' || ch === ',') continue;\n if (!covered.has(i)) {\n uncovered.push({ ch, pos: i });\n }\n }\n\n if (uncovered.length > 0) {\n const chars = [...new Set(uncovered.map((u) => u.ch))].join('');\n errors.push({\n message: `Unrecognized characters '${chars}' in state key '${stateKey}'.`,\n offset: uncovered[0].pos,\n length: 1,\n });\n }\n\n return { tokens, errors };\n}\n\nfunction replaceCommasOutsideParens(str: string): string {\n let result = '';\n let depth = 0;\n\n for (const char of str) {\n if (char === '(') {\n depth++;\n result += char;\n } else if (char === ')') {\n depth--;\n result += char;\n } else if (char === ',' && depth === 0) {\n result += '|';\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Validator\n// ============================================================================\n\nconst MAX_XOR_CHAIN_LENGTH = 4;\n\nconst DIMENSION_SHORTHANDS = new Set(['w', 'h', 'is', 'bs']);\nconst DIMENSION_FULL = new Set([\n 'width',\n 'height',\n 'inline-size',\n 'block-size',\n]);\n\nclass StateKeyValidator {\n private errors: StateKeyError[] = [];\n private hasOwn = false;\n private referencedAliases: string[] = [];\n private tokens: Token[];\n private pos = 0;\n private opts: StateKeyParserOptions;\n private insideOwn = false;\n\n constructor(\n tokens: Token[],\n tokenErrors: StateKeyError[],\n opts: StateKeyParserOptions,\n ) {\n this.tokens = tokens;\n this.errors = [...tokenErrors];\n this.opts = opts;\n }\n\n validate(): StateKeyResult {\n if (this.tokens.length > 0) {\n this.parseExpression();\n }\n\n return {\n errors: this.errors,\n hasOwn: this.hasOwn,\n referencedAliases: this.referencedAliases,\n };\n }\n\n private current(): Token | undefined {\n return this.tokens[this.pos];\n }\n\n private advance(): Token | undefined {\n return this.tokens[this.pos++];\n }\n\n private match(type: TokenType): boolean {\n if (this.current()?.type === type) {\n this.advance();\n return true;\n }\n return false;\n }\n\n private parseExpression(): void {\n this.parseAnd();\n }\n\n private parseAnd(): void {\n this.parseOr();\n while (this.current()?.type === 'AND') {\n this.advance();\n if (!this.current() || this.current()?.type === 'AND') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '&' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseOr();\n }\n }\n\n private parseOr(): void {\n this.parseXor();\n while (this.current()?.type === 'OR') {\n this.advance();\n if (!this.current() || this.current()?.type === 'OR') {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '|' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseXor();\n }\n }\n\n private parseXor(): void {\n this.parseUnary();\n let operandCount = 1;\n\n while (this.current()?.type === 'XOR') {\n this.advance();\n operandCount++;\n if (operandCount > MAX_XOR_CHAIN_LENGTH) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. Consider breaking into smaller expressions.`,\n offset: prev.offset,\n length: prev.length,\n });\n }\n this.parseUnary();\n }\n }\n\n private parseUnary(): void {\n if (this.match('NOT')) {\n if (\n !this.current() ||\n this.current()?.type === 'AND' ||\n this.current()?.type === 'OR' ||\n this.current()?.type === 'XOR'\n ) {\n const prev = this.tokens[this.pos - 1];\n this.errors.push({\n message: \"Expected expression after '!' operator.\",\n offset: prev.offset,\n length: prev.length,\n });\n return;\n }\n this.parseUnary();\n return;\n }\n this.parsePrimary();\n }\n\n private parsePrimary(): void {\n if (this.match('LPAREN')) {\n this.parseExpression();\n if (!this.match('RPAREN')) {\n this.errors.push({\n message: \"Missing closing ')' in state expression.\",\n offset: this.tokens[this.pos - 1]?.offset ?? 0,\n length: 1,\n });\n }\n return;\n }\n\n const token = this.current();\n if (token?.type === 'STATE') {\n this.advance();\n this.validateStateToken(token);\n return;\n }\n\n // Unexpected token or end\n if (token) {\n this.errors.push({\n message: `Unexpected token '${token.value}'.`,\n offset: token.offset,\n length: token.length,\n });\n this.advance();\n }\n }\n\n private validateStateToken(token: Token): void {\n const value = token.value;\n\n // @starting\n if (value === '@starting') return;\n\n // @media:type\n if (value.startsWith('@media:')) {\n const mediaType = value.slice(7);\n const validTypes = new Set(['print', 'screen', 'all', 'speech']);\n if (!validTypes.has(mediaType)) {\n this.errors.push({\n message: `Unknown media type '${mediaType}'. Valid: print, screen, all, speech.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // @media(...)\n if (value.startsWith('@media(')) {\n this.validateMediaQuery(value, token);\n return;\n }\n\n // @supports(...)\n if (value.startsWith('@supports(')) {\n this.validateSupportsQuery(value, token);\n return;\n }\n\n // @root(...)\n if (value.startsWith('@root(')) {\n this.validateInnerStateExpression(value, 6, token);\n return;\n }\n\n // @parent(...)\n if (value.startsWith('@parent(')) {\n this.validateParentState(value, token);\n return;\n }\n\n // @own(...)\n if (value.startsWith('@own(')) {\n this.hasOwn = true;\n if (this.insideOwn) {\n this.errors.push({\n message: 'Nested @own() is not allowed.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n const prevInsideOwn = this.insideOwn;\n this.insideOwn = true;\n this.validateInnerStateExpression(value, 5, token);\n this.insideOwn = prevInsideOwn;\n return;\n }\n\n // @(...) container query\n if (value.startsWith('@(')) {\n this.validateContainerQuery(value, token);\n return;\n }\n\n // @alias predefined state\n if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {\n this.referencedAliases.push(value);\n return;\n }\n\n // Pseudo-class/pseudo-element\n if (value.startsWith(':')) {\n this.validatePseudoClass(value, token);\n return;\n }\n\n // Class selector\n if (value.startsWith('.')) return;\n\n // Attribute selector\n if (value.startsWith('[')) {\n this.validateAttributeSelector(value, token);\n return;\n }\n\n // Value modifier (e.g., theme=danger)\n if (value.includes('=')) {\n this.validateValueModifier(value, token);\n return;\n }\n\n // Boolean modifier (e.g., hovered, disabled)\n if (/^[a-z][a-z0-9-]+$/i.test(value)) return;\n\n this.errors.push({\n message: `Unrecognized state token '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n\n private validateMediaQuery(raw: string, token: Token): void {\n const content = raw.slice(7, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @media() query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Expand dimension shorthands for validation\n const expanded = expandDimensionShorthands(content.trim());\n\n // Feature query (contains ':' but no comparison operators)\n if (\n expanded.includes(':') &&\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Boolean feature query (no comparison operators)\n if (\n !expanded.includes('<') &&\n !expanded.includes('>') &&\n !expanded.includes('=')\n ) {\n return;\n }\n\n // Dimension query — validate dimension names\n this.validateDimensionCondition(expanded, token);\n }\n\n private validateDimensionCondition(condition: string, token: Token): void {\n // Range syntax: 600px <= width < 1200px\n const rangeMatch = condition.match(\n /^(.+?)\\s*(<=|<)\\s*(\\S+)\\s*(<=|<)\\s*(.+)$/,\n );\n if (rangeMatch) {\n const dim = rangeMatch[3];\n if (!DIMENSION_FULL.has(dim) && !DIMENSION_SHORTHANDS.has(dim)) {\n this.errors.push({\n message: `Unknown dimension '${dim}' in media/container query. Valid: width, height, inline-size, block-size (or w, h, is, bs).`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple comparison: width < 768px\n const simpleMatch = condition.match(/^(\\S+)\\s*(<=|>=|<|>|=)\\s*(.+)$/);\n if (simpleMatch) {\n const dim = simpleMatch[1];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n\n // Reversed: 768px > width\n const reversedMatch = condition.match(/^(.+?)\\s*(<=|>=|<|>|=)\\s*(\\S+)$/);\n if (reversedMatch) {\n const dim = reversedMatch[3];\n if (DIMENSION_FULL.has(dim) || DIMENSION_SHORTHANDS.has(dim)) {\n return;\n }\n }\n }\n\n private validateSupportsQuery(raw: string, token: Token): void {\n const content = raw.slice(10, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @supports() query.',\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateInnerStateExpression(\n raw: string,\n prefixLen: number,\n _token: Token,\n ): void {\n const content = raw.slice(prefixLen, -1);\n if (!content.trim()) return;\n\n const innerResult = parseStateKey(content, this.opts);\n this.errors.push(...innerResult.errors);\n if (innerResult.hasOwn) this.hasOwn = true;\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateParentState(raw: string, token: Token): void {\n const content = raw.slice(8, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty @parent() state.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n let condition = content.trim();\n\n // Check for direct parent combinator: @parent(hovered, >)\n const lastCommaIdx = condition.lastIndexOf(',');\n if (lastCommaIdx !== -1) {\n const afterComma = condition.slice(lastCommaIdx + 1).trim();\n if (afterComma === '>') {\n condition = condition.slice(0, lastCommaIdx).trim();\n }\n }\n\n const innerResult = parseStateKey(condition, this.opts);\n this.errors.push(...innerResult.errors);\n this.referencedAliases.push(...innerResult.referencedAliases);\n }\n\n private validateContainerQuery(raw: string, token: Token): void {\n const content = raw.slice(2, -1);\n if (!content.trim()) {\n this.errors.push({\n message: 'Empty container query.',\n offset: token.offset,\n length: token.length,\n });\n return;\n }\n\n // Named container: @(layout, w < 600px)\n const commaIdx = findTopLevelComma(content);\n let condition: string;\n\n if (commaIdx !== -1) {\n condition = content.slice(commaIdx + 1).trim();\n } else {\n condition = content.trim();\n }\n\n // Style query: @($variant=primary) — skip\n if (condition.startsWith('$')) return;\n\n // Function-like: scroll-state(...) — skip\n if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) return;\n\n // Dimension query\n const expanded = expandDimensionShorthands(condition);\n if (\n expanded.includes('<') ||\n expanded.includes('>') ||\n expanded.includes('=')\n ) {\n this.validateDimensionCondition(expanded, token);\n }\n }\n\n private validatePseudoClass(value: string, token: Token): void {\n // :is(), :has(), :not(), :where() — structural pseudo-classes\n const enhancedMatch = /^:(is|has|not|where)\\(/.exec(value);\n if (enhancedMatch) return;\n\n // Functional pseudo-classes like :nth-child(2n+1)\n const funcMatch = /^(:[a-z-]+)\\(/.exec(value);\n if (funcMatch) {\n const baseName = funcMatch[1];\n if (!KNOWN_PSEUDO_CLASSES.has(baseName)) {\n this.errors.push({\n message: `Unknown pseudo-class '${baseName}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n return;\n }\n\n // Simple pseudo-class: :hover, :focus, etc.\n if (!KNOWN_PSEUDO_CLASSES.has(value)) {\n this.errors.push({\n message: `Unknown pseudo-class '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateAttributeSelector(value: string, token: Token): void {\n if (!value.startsWith('[') || !value.endsWith(']')) {\n this.errors.push({\n message: `Malformed attribute selector '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n\n private validateValueModifier(value: string, token: Token): void {\n const opMatch = value.match(/^([a-z][a-z0-9-]*)(\\^=|\\$=|\\*=|=)(.+)$/i);\n if (!opMatch) {\n this.errors.push({\n message: `Invalid value modifier syntax '${value}'.`,\n offset: token.offset,\n length: token.length,\n });\n }\n }\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction expandDimensionShorthands(condition: string): string {\n return condition\n .replace(/\\bw\\b/g, 'width')\n .replace(/\\bh\\b/g, 'height')\n .replace(/\\bis\\b/g, 'inline-size')\n .replace(/\\bbs\\b/g, 'block-size');\n}\n\nfunction findTopLevelComma(str: string): number {\n let depth = 0;\n for (let i = 0; i < str.length; i++) {\n const ch = str[i];\n if (ch === '(') depth++;\n else if (ch === ')') depth = Math.max(0, depth - 1);\n else if (ch === ',' && depth === 0) return i;\n }\n return -1;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Rewrite :not() state atoms, leaving raw CSS inside other functions alone.\n * A selector list/compound must stay inside :is(): negating individual parts\n * would change which elements match. Only a single pseudo can be unwrapped.\n */\nexport function getStateNegationEdits(stateKey: string): StringEdit[] {\n if (!stateKey.includes(':not(')) return [];\n if (checkBracketBalance(stateKey)) return [];\n if (parseStateKey(stateKey).errors.length > 0) return [];\n\n const { tokens } = tokenize(stateKey);\n return tokens\n .filter(\n (token) => token.type === 'STATE' && token.value.startsWith(':not('),\n )\n .flatMap((token) => {\n if (checkBracketBalance(token.value)) return [];\n const inner = token.value.slice(5, -1).trim();\n if (!inner) return [];\n\n const { tokens: innerTokens, errors } = tokenize(inner);\n const singlePseudo =\n errors.length === 0 &&\n innerTokens.length === 1 &&\n innerTokens[0].type === 'STATE' &&\n innerTokens[0].value === inner &&\n inner.startsWith(':') &&\n parseStateKey(inner).errors.length === 0 &&\n !inner.startsWith(':not(') &&\n // Keep forgiving selector-list wrappers, even for invalid branches.\n !inner.startsWith(':is(') &&\n !inner.startsWith(':where(') &&\n // Promoting :has() to a state atom enables Tasty's trailing-combinator\n // completion. Preserve the original raw CSS instead in that case.\n !(inner.startsWith(':has(') && /[>+~]\\s*\\)$/.test(inner));\n\n return [\n {\n start: token.offset,\n end: token.offset + token.length,\n replacement: singlePseudo ? `!${inner}` : `!:is(${inner})`,\n },\n ];\n });\n}\n\n/**\n * Unwrap every `@own(...)` term of a key, for a scope where `@own` is the\n * element itself. A term with operators inside keeps them grouped, so\n * `@own(a | b) & c` becomes `(a | b) & c`, not `a | b & c`. Returns no edits\n * when any term cannot be unwrapped safely, so a key is never half-rewritten.\n */\nexport function getOwnUnwrapEdits(stateKey: string): StringEdit[] {\n if (!stateKey.includes('@own(')) return [];\n if (checkBracketBalance(stateKey)) return [];\n if (parseStateKey(stateKey).errors.length > 0) return [];\n\n const { tokens } = tokenize(stateKey);\n const edits: StringEdit[] = [];\n for (const token of tokens) {\n if (token.type !== 'STATE' || !token.value.startsWith('@own(')) continue;\n const inner = token.value.slice(5, -1).trim();\n // A comma is an OR only outside parentheses; it would not survive grouping.\n if (!inner || findTopLevelComma(inner) !== -1) return [];\n\n const innerTokens = tokenize(inner).tokens;\n const single =\n tokens.length === 1 ||\n (innerTokens.length === 1 && innerTokens[0].value === inner);\n edits.push({\n start: token.offset,\n end: token.offset + token.length,\n replacement: single ? inner : `(${inner})`,\n });\n }\n return edits;\n}\n\nexport interface StateAtom {\n /** The atom as written: `hovered`, `:hover`, `[open]`, `@root(...)`. */\n value: string;\n /** Whether the atom sits inside `@own(...)`. */\n own: boolean;\n}\n\n/**\n * The state atoms of a key, without its operators and grouping. The contents\n * of `@own(...)` are listed atom by atom; `@root()`, `@parent()`, and at-rule\n * conditions stay single atoms, since they describe other things.\n */\nexport function getStateAtoms(stateKey: string): StateAtom[] {\n const atoms: StateAtom[] = [];\n for (const token of tokenize(stateKey.trim()).tokens) {\n if (token.type !== 'STATE') continue;\n if (!token.value.startsWith('@own(')) {\n atoms.push({ value: token.value, own: false });\n continue;\n }\n for (const inner of tokenize(token.value.slice(5, -1)).tokens) {\n if (inner.type === 'STATE') atoms.push({ value: inner.value, own: true });\n }\n }\n return atoms;\n}\n\n/**\n * Validate a state key string and return detailed errors.\n */\nexport function parseStateKey(\n stateKey: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n if (!stateKey || !stateKey.trim()) {\n return { errors: [], hasOwn: false, referencedAliases: [] };\n }\n\n const trimmed = stateKey.trim();\n\n // The standalone `_` key is the map-wide fallback floor. It always applies\n // and must be used on its own — it carries no state logic to validate.\n if (trimmed === '_') {\n return { errors: [], hasOwn: false, referencedAliases: [] };\n }\n\n // A `_` combined with state logic (`_ & hovered`, `_ | x`) is a misuse: the\n // fallback floor can only be used on its own. Tasty ignores such keys at\n // runtime and warns; flag them here so the mistake surfaces in the editor.\n if (isMisusedFallbackKey(trimmed)) {\n return {\n errors: [\n {\n message:\n 'The fallback floor \"_\" cannot be combined with other state ' +\n 'logic. Use \"_\" on its own as a map-wide fallback floor.',\n offset: 0,\n length: stateKey.length,\n },\n ],\n hasOwn: false,\n referencedAliases: [],\n };\n }\n\n const { tokens, errors: tokenErrors } = tokenize(trimmed);\n const validator = new StateKeyValidator(tokens, tokenErrors, opts);\n return validator.validate();\n}\n\n/**\n * Detect a misused standalone `_` fallback key: a key that is not exactly\n * `_` but contains `_` as a standalone atom among state-logic operators\n * (`&`, `|`, `^`, `,`) or grouping parens. A `_` embedded in a longer token\n * (e.g. a modifier named `_private`) is not a misuse.\n */\nfunction isMisusedFallbackKey(stateKey: string): boolean {\n if (stateKey === '_' || !stateKey.includes('_')) return false;\n return /(?:^|[\\s&|^,(])_(?:[\\s&|^,)]|$)/.test(stateKey);\n}\n\n/**\n * Validate a state definition value (the RHS of a state alias).\n * State definitions should be valid state expressions like\n * '@media(w < 768px)', '@root(theme=dark)', etc.\n */\nexport function validateStateDefinition(\n value: string,\n opts: StateKeyParserOptions = {},\n): StateKeyResult {\n return parseStateKey(value, opts);\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { createRule } from "../create-rule.js";
2
2
  import { getKeyName, getStringValue } from "../utils.js";
3
3
  import { TastyContext, styleObjectListeners } from "../context.js";
4
- import { parseStateKey } from "../parsers/state-key-parser.js";
5
- import { replaceStringValue } from "../fix-utils.js";
4
+ import { getOwnUnwrapEdits, parseStateKey } from "../parsers/state-key-parser.js";
5
+ import { replaceInStringValue } from "../fix-utils.js";
6
6
 
7
7
  //#region src/rules/no-own-at-root.ts
8
8
  var no_own_at_root_default = createRule({
@@ -20,6 +20,7 @@ var no_own_at_root_default = createRule({
20
20
  function handleStyleObject(node) {
21
21
  if (!ctx.isStyleObject(node)) return;
22
22
  if (ctx.isInsideSubElement(node)) return;
23
+ if (ctx.isSharedStyleObject(node)) return;
23
24
  for (const prop of node.properties) {
24
25
  if (prop.type !== "Property" || prop.computed) continue;
25
26
  const key = getKeyName(prop.key);
@@ -34,10 +35,9 @@ var no_own_at_root_default = createRule({
34
35
  node: stateProp.key,
35
36
  messageId: "ownAtRoot",
36
37
  fix(fixer) {
37
- if (!stateKey.startsWith("@own(") || !stateKey.endsWith(")")) return null;
38
- const inner = stateKey.slice(5, -1);
39
- if (!inner) return null;
40
- return replaceStringValue(fixer, stateProp.key, inner);
38
+ const edits = getOwnUnwrapEdits(stateKey);
39
+ if (edits.length === 0) return null;
40
+ return replaceInStringValue(fixer, stateProp.key, edits, context.sourceCode);
41
41
  }
42
42
  });
43
43
  }
@@ -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 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"}
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 {\n getOwnUnwrapEdits,\n parseStateKey,\n} from '../parsers/state-key-parser.js';\nimport { replaceInStringValue } 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 // Shared styles may be spread into a sub-element, where `@own` matters.\n if (ctx.isSharedStyleObject(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 const edits = getOwnUnwrapEdits(stateKey);\n if (edits.length === 0) return null;\n return replaceInStringValue(\n fixer,\n stateProp.key,\n edits,\n context.sourceCode,\n );\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":";;;;;;;AAYA,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,OAAI,IAAI,oBAAoB,KAAK,CAAE;AAEnC,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;OACT,MAAM,QAAQ,kBAAkB,SAAS;AACzC,WAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,cAAO,qBACL,OACA,UAAU,KACV,OACA,QAAQ,WACT;;MAEJ,CAAC;;;;AAMV,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAGvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
@@ -24,6 +24,7 @@ var require_default_state_default = createRule({
24
24
  if (key === null) continue;
25
25
  if (/^[A-Z@&$#]/.test(key)) continue;
26
26
  if (prop.value.type !== "ObjectExpression") continue;
27
+ if (prop.value.properties.some((p) => p.type === "SpreadElement")) continue;
27
28
  if (!prop.value.properties.some((p) => {
28
29
  if (p.type !== "Property" || p.computed) return false;
29
30
  const stateKey = getKeyName(p.key);
@@ -1 +1 @@
1
- {"version":3,"file":"require-default-state.js","names":[],"sources":["../../src/rules/require-default-state.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\n\ntype MessageIds = 'missingDefaultState';\n\nexport default createRule<[], MessageIds>({\n name: 'require-default-state',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a state mapping object doesn't have a default ('') key\",\n },\n messages: {\n missingDefaultState:\n \"State mapping for '{{property}}' has no default ('') or fallback floor ('_') value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n const styleCtx = ctx.getStyleContext(node);\n if (!styleCtx) return;\n\n // Skip if extending (omitting '' is intentional)\n if (styleCtx.isExtending) 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 // Skip sub-elements and special keys\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n // A '' default or a '_' fallback floor both provide a value in the\n // normal state, so either one satisfies the requirement.\n const hasDefault = prop.value.properties.some((p) => {\n if (p.type !== 'Property' || p.computed) return false;\n const stateKey = getKeyName(p.key);\n return stateKey === '' || stateKey === '_';\n });\n\n if (!hasDefault) {\n context.report({\n node: prop.value,\n messageId: 'missingDefaultState',\n data: { property: key },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,oCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU,EACR,qBACE,uFACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;GAC1D,MAAM,WAAW,IAAI,gBAAgB,KAAK;AAC1C,OAAI,CAAC,SAAU;AAGf,OAAI,SAAS,YAAa;AAE1B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAGlB,QAAI,aAAa,KAAK,IAAI,CAAE;AAE5B,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAU5C,QAAI,CANe,KAAK,MAAM,WAAW,MAAM,MAAM;AACnD,SAAI,EAAE,SAAS,cAAc,EAAE,SAAU,QAAO;KAChD,MAAM,WAAW,WAAW,EAAE,IAAI;AAClC,YAAO,aAAa,MAAM,aAAa;MACvC,CAGA,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,UAAU,KAAK;KACxB,CAAC;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
1
+ {"version":3,"file":"require-default-state.js","names":[],"sources":["../../src/rules/require-default-state.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName } from '../utils.js';\n\ntype MessageIds = 'missingDefaultState';\n\nexport default createRule<[], MessageIds>({\n name: 'require-default-state',\n meta: {\n type: 'suggestion',\n docs: {\n description:\n \"Warn when a state mapping object doesn't have a default ('') key\",\n },\n messages: {\n missingDefaultState:\n \"State mapping for '{{property}}' has no default ('') or fallback floor ('_') value.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n const styleCtx = ctx.getStyleContext(node);\n if (!styleCtx) return;\n\n // Skip if extending (omitting '' is intentional)\n if (styleCtx.isExtending) 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 // Skip sub-elements and special keys\n if (/^[A-Z@&$#]/.test(key)) continue;\n\n if (prop.value.type !== 'ObjectExpression') continue;\n\n // A spread may bring the default with it (`...BASE_STYLES.fill`), and\n // its source is often a cast member expression that cannot be read.\n if (prop.value.properties.some((p) => p.type === 'SpreadElement'))\n continue;\n\n // A '' default or a '_' fallback floor both provide a value in the\n // normal state, so either one satisfies the requirement.\n const hasDefault = prop.value.properties.some((p) => {\n if (p.type !== 'Property' || p.computed) return false;\n const stateKey = getKeyName(p.key);\n return stateKey === '' || stateKey === '_';\n });\n\n if (!hasDefault) {\n context.report({\n node: prop.value,\n messageId: 'missingDefaultState',\n data: { property: key },\n });\n }\n }\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":";;;;;AAOA,oCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,oEACH;EACD,UAAU,EACR,qBACE,uFACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EAErC,SAAS,kBAAkB,MAAiC;GAC1D,MAAM,WAAW,IAAI,gBAAgB,KAAK;AAC1C,OAAI,CAAC,SAAU;AAGf,OAAI,SAAS,YAAa;AAE1B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAE/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,KAAM;AAGlB,QAAI,aAAa,KAAK,IAAI,CAAE;AAE5B,QAAI,KAAK,MAAM,SAAS,mBAAoB;AAI5C,QAAI,KAAK,MAAM,WAAW,MAAM,MAAM,EAAE,SAAS,gBAAgB,CAC/D;AAUF,QAAI,CANe,KAAK,MAAM,WAAW,MAAM,MAAM;AACnD,SAAI,EAAE,SAAS,cAAc,EAAE,SAAU,QAAO;KAChD,MAAM,WAAW,WAAW,EAAE,IAAI;AAClC,YAAO,aAAa,MAAM,aAAa;MACvC,CAGA,SAAQ,OAAO;KACb,MAAM,KAAK;KACX,WAAW;KACX,MAAM,EAAE,UAAU,KAAK;KACxB,CAAC;;;AAKR,SAAO;GACL,kBAAkB,MAAM;AACtB,QAAI,YAAY,KAAK;;GAEvB,GAAG,qBAAqB,kBAAkB;GAC3C;;CAEJ,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { createRule } from "../create-rule.js";
2
2
  import { BUILT_IN_CUSTOM_PROPERTIES } from "../constants.js";
3
- import { getKeyName } from "../utils.js";
3
+ import { getKeyName, getStringValue, unwrapExpression } from "../utils.js";
4
4
  import { TastyContext } from "../context.js";
5
5
  import { stringStyleValueListeners } from "../style-values.js";
6
6
  import { scanValueWords } from "../value-words.js";
@@ -13,7 +13,7 @@ var valid_custom_property_default = createRule({
13
13
  docs: { description: "Validate $name custom property references" },
14
14
  messages: {
15
15
  invalidSyntax: "Invalid custom property '{{token}}'. Use '$name' with letters, digits, or hyphens (e.g. $accent-color).",
16
- unknownProperty: "Unknown custom property '{{token}}'. Declare it as a '$name' key in this styles object, or add it to your tasty config."
16
+ unknownProperty: "Unknown custom property '{{token}}'. Declare it as a '$name' key in a styles object or 'tokens' prop in this file, or add it to your tasty config."
17
17
  },
18
18
  schema: []
19
19
  },
@@ -29,6 +29,20 @@ var valid_custom_property_default = createRule({
29
29
  if (key && key.startsWith("$") && !key.startsWith("$$")) fileCustomProperties.add(key);
30
30
  }
31
31
  }
32
+ /**
33
+ * Tokens declare custom properties too: `tokens={{ $progress: … }}` on an
34
+ * element, a `tokens` option of a component definition, and a raw
35
+ * `'--progress'` entry of an inline `style`, which sets the same property.
36
+ */
37
+ function collectDeclaredTokens(node, prefix) {
38
+ for (const prop of node.properties) {
39
+ if (prop.type !== "Property") continue;
40
+ const key = prop.computed ? getStringValue(unwrapExpression(prop.key)) : getKeyName(prop.key);
41
+ if (key === null) continue;
42
+ const name = prefix === "$" ? key : key.startsWith("--") ? "$" + key.slice(2) : "";
43
+ if (/^\$[^$]/.test(name)) fileCustomProperties.add(name);
44
+ }
45
+ }
32
46
  function checkValue(value, node) {
33
47
  if (ctx.config.tokens === false) return;
34
48
  for (const word of scanValueWords(value)) {
@@ -44,7 +58,7 @@ var valid_custom_property_default = createRule({
44
58
  });
45
59
  }
46
60
  }
47
- return stringStyleValueListeners(ctx, checkValue, {
61
+ const listeners = stringStyleValueListeners(ctx, checkValue, {
48
62
  onStyleObject: collectLocalProperties,
49
63
  onProgramExit() {
50
64
  if (!Array.isArray(ctx.config.tokens) || ctx.config.tokens.length === 0) return;
@@ -59,6 +73,27 @@ var valid_custom_property_default = createRule({
59
73
  }
60
74
  }
61
75
  });
76
+ return {
77
+ ...listeners,
78
+ JSXAttribute(node) {
79
+ listeners.JSXAttribute(node);
80
+ if (node.name.type !== "JSXIdentifier") return;
81
+ const name = node.name.name;
82
+ if (name !== "tokens" && name !== "style") return;
83
+ if (node.value?.type !== "JSXExpressionContainer") return;
84
+ const value = unwrapExpression(node.value.expression);
85
+ if (value.type !== "ObjectExpression") return;
86
+ collectDeclaredTokens(value, name === "tokens" ? "$" : "--");
87
+ },
88
+ Property(node) {
89
+ if (node.computed || getKeyName(node.key) !== "tokens") return;
90
+ const value = unwrapExpression(node.value);
91
+ const call = node.parent.parent;
92
+ if (value.type !== "ObjectExpression" || call?.type !== "CallExpression") return;
93
+ const imp = ctx.isTastyCall(call);
94
+ if (imp?.importedName === "tasty" || imp && ctx.config.styleFunctions?.[imp.importedName]?.kind === "options") collectDeclaredTokens(value, "$");
95
+ }
96
+ };
62
97
  }
63
98
  });
64
99
 
@@ -1 +1 @@
1
- {"version":3,"file":"valid-custom-property.js","names":[],"sources":["../../src/rules/valid-custom-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { BUILT_IN_CUSTOM_PROPERTIES } from '../constants.js';\nimport { getKeyName } from '../utils.js';\nimport { scanValueWords } from '../value-words.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownProperty';\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-property',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Validate $name custom property references',\n },\n messages: {\n invalidSyntax:\n \"Invalid custom property '{{token}}'. Use '$name' with letters, digits, or hyphens (e.g. $accent-color).\",\n unknownProperty:\n \"Unknown custom property '{{token}}'. Declare it as a '$name' key in this styles object, or add it to your tasty config.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const fileCustomProperties = new Set<string>();\n const pendingChecks: PendingExistenceCheck[] = [];\n\n function collectLocalProperties(node: TSESTree.ObjectExpression): void {\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key && key.startsWith('$') && !key.startsWith('$$')) {\n fileCustomProperties.add(key);\n }\n }\n }\n\n function checkValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.tokens === false) return;\n\n for (const word of scanValueWords(value)) {\n // A dynamic color opacity is another custom-property reference.\n const token =\n word.value.startsWith('#') && word.value.includes('.$')\n ? word.value.slice(word.value.indexOf('.$') + 1)\n : word.value;\n if (!token.startsWith('$')) continue;\n if (\n token.startsWith('$$') &&\n word.isFunction &&\n value[word.offset + word.value.length] === '('\n )\n continue;\n const baseName = token.startsWith('$$') ? '$' + token.slice(2) : token;\n\n if (BUILT_IN_CUSTOM_PROPERTIES.has(baseName)) continue;\n\n pendingChecks.push({ token, baseName, node });\n }\n }\n\n return stringStyleValueListeners(ctx, checkValue, {\n onStyleObject: collectLocalProperties,\n onProgramExit() {\n if (\n !Array.isArray(ctx.config.tokens) ||\n ctx.config.tokens.length === 0\n ) {\n return;\n }\n\n for (const { token, baseName, node } of pendingChecks) {\n if (fileCustomProperties.has(baseName)) continue;\n if (ctx.config.tokens.includes(baseName)) continue;\n\n context.report({\n node,\n messageId: 'unknownProperty',\n data: { token },\n });\n }\n },\n });\n },\n});\n"],"mappings":";;;;;;;;AAgBA,oCAAe,WAA2B;CACxC,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aAAa,6CACd;EACD,UAAU;GACR,eACE;GACF,iBACE;GACH;EACD,QAAQ,EAAE;EACX;CACD,gBAAgB,EAAE;CAClB,OAAO,SAAS;EACd,MAAM,MAAM,IAAI,aAAa,QAAQ;EACrC,MAAM,uCAAuB,IAAI,KAAa;EAC9C,MAAM,gBAAyC,EAAE;EAEjD,SAAS,uBAAuB,MAAuC;AACrE,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,OAAO,IAAI,WAAW,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,CACrD,sBAAqB,IAAI,IAAI;;;EAKnC,SAAS,WAAW,OAAe,MAA2B;AAC5D,OAAI,IAAI,OAAO,WAAW,MAAO;AAEjC,QAAK,MAAM,QAAQ,eAAe,MAAM,EAAE;IAExC,MAAM,QACJ,KAAK,MAAM,WAAW,IAAI,IAAI,KAAK,MAAM,SAAS,KAAK,GACnD,KAAK,MAAM,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG,EAAE,GAC9C,KAAK;AACX,QAAI,CAAC,MAAM,WAAW,IAAI,CAAE;AAC5B,QACE,MAAM,WAAW,KAAK,IACtB,KAAK,cACL,MAAM,KAAK,SAAS,KAAK,MAAM,YAAY,IAE3C;IACF,MAAM,WAAW,MAAM,WAAW,KAAK,GAAG,MAAM,MAAM,MAAM,EAAE,GAAG;AAEjE,QAAI,2BAA2B,IAAI,SAAS,CAAE;AAE9C,kBAAc,KAAK;KAAE;KAAO;KAAU;KAAM,CAAC;;;AAIjD,SAAO,0BAA0B,KAAK,YAAY;GAChD,eAAe;GACf,gBAAgB;AACd,QACE,CAAC,MAAM,QAAQ,IAAI,OAAO,OAAO,IACjC,IAAI,OAAO,OAAO,WAAW,EAE7B;AAGF,SAAK,MAAM,EAAE,OAAO,UAAU,UAAU,eAAe;AACrD,SAAI,qBAAqB,IAAI,SAAS,CAAE;AACxC,SAAI,IAAI,OAAO,OAAO,SAAS,SAAS,CAAE;AAE1C,aAAQ,OAAO;MACb;MACA,WAAW;MACX,MAAM,EAAE,OAAO;MAChB,CAAC;;;GAGP,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"valid-custom-property.js","names":[],"sources":["../../src/rules/valid-custom-property.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext } from '../context.js';\nimport { stringStyleValueListeners } from '../style-values.js';\nimport { BUILT_IN_CUSTOM_PROPERTIES } from '../constants.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\nimport { scanValueWords } from '../value-words.js';\n\ntype MessageIds = 'invalidSyntax' | 'unknownProperty';\n\ninterface PendingExistenceCheck {\n token: string;\n baseName: string;\n node: TSESTree.Node;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-custom-property',\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Validate $name custom property references',\n },\n messages: {\n invalidSyntax:\n \"Invalid custom property '{{token}}'. Use '$name' with letters, digits, or hyphens (e.g. $accent-color).\",\n unknownProperty:\n \"Unknown custom property '{{token}}'. Declare it as a '$name' key in a styles object or 'tokens' prop in this file, or add it to your tasty config.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const fileCustomProperties = new Set<string>();\n const pendingChecks: PendingExistenceCheck[] = [];\n\n function collectLocalProperties(node: TSESTree.ObjectExpression): void {\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key && key.startsWith('$') && !key.startsWith('$$')) {\n fileCustomProperties.add(key);\n }\n }\n }\n\n /**\n * Tokens declare custom properties too: `tokens={{ $progress: … }}` on an\n * element, a `tokens` option of a component definition, and a raw\n * `'--progress'` entry of an inline `style`, which sets the same property.\n */\n function collectDeclaredTokens(\n node: TSESTree.ObjectExpression,\n prefix: '$' | '--',\n ): void {\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key === null) continue;\n const name =\n prefix === '$' ? key : key.startsWith('--') ? '$' + key.slice(2) : '';\n if (/^\\$[^$]/.test(name)) fileCustomProperties.add(name);\n }\n }\n\n function checkValue(value: string, node: TSESTree.Node): void {\n if (ctx.config.tokens === false) return;\n\n for (const word of scanValueWords(value)) {\n // A dynamic color opacity is another custom-property reference.\n const token =\n word.value.startsWith('#') && word.value.includes('.$')\n ? word.value.slice(word.value.indexOf('.$') + 1)\n : word.value;\n if (!token.startsWith('$')) continue;\n if (\n token.startsWith('$$') &&\n word.isFunction &&\n value[word.offset + word.value.length] === '('\n )\n continue;\n const baseName = token.startsWith('$$') ? '$' + token.slice(2) : token;\n\n if (BUILT_IN_CUSTOM_PROPERTIES.has(baseName)) continue;\n\n pendingChecks.push({ token, baseName, node });\n }\n }\n\n const listeners = stringStyleValueListeners(ctx, checkValue, {\n onStyleObject: collectLocalProperties,\n onProgramExit() {\n if (\n !Array.isArray(ctx.config.tokens) ||\n ctx.config.tokens.length === 0\n ) {\n return;\n }\n\n for (const { token, baseName, node } of pendingChecks) {\n if (fileCustomProperties.has(baseName)) continue;\n if (ctx.config.tokens.includes(baseName)) continue;\n\n context.report({\n node,\n messageId: 'unknownProperty',\n data: { token },\n });\n }\n },\n });\n\n return {\n ...listeners,\n JSXAttribute(node: TSESTree.JSXAttribute) {\n listeners.JSXAttribute(node);\n if (node.name.type !== 'JSXIdentifier') return;\n const name = node.name.name;\n if (name !== 'tokens' && name !== 'style') return;\n if (node.value?.type !== 'JSXExpressionContainer') return;\n const value = unwrapExpression(node.value.expression);\n if (value.type !== 'ObjectExpression') return;\n collectDeclaredTokens(value, name === 'tokens' ? '$' : '--');\n },\n Property(node: TSESTree.Property) {\n if (node.computed || getKeyName(node.key) !== 'tokens') return;\n const value = unwrapExpression(node.value);\n const call = node.parent.parent;\n if (\n value.type !== 'ObjectExpression' ||\n call?.type !== 'CallExpression'\n )\n return;\n const imp = ctx.isTastyCall(call);\n if (\n imp?.importedName === 'tasty' ||\n (imp &&\n ctx.config.styleFunctions?.[imp.importedName]?.kind === 'options')\n ) {\n collectDeclaredTokens(value, '$');\n }\n },\n };\n },\n});\n"],"mappings":"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"}
@@ -4,6 +4,17 @@ import { TastyContext, styleObjectListeners } from "../context.js";
4
4
  import selectorParser from "postcss-selector-parser";
5
5
 
6
6
  //#region src/rules/valid-sub-element.ts
7
+ /** Expressions whose result is visibly not an object. */
8
+ const NON_OBJECT_EXPRESSIONS = new Set([
9
+ "TemplateLiteral",
10
+ "ArrayExpression",
11
+ "ArrowFunctionExpression",
12
+ "FunctionExpression",
13
+ "ClassExpression",
14
+ "UnaryExpression",
15
+ "BinaryExpression",
16
+ "UpdateExpression"
17
+ ]);
7
18
  const LEGACY_PSEUDO_ELEMENTS = new Set([
8
19
  ":before",
9
20
  ":after",
@@ -83,6 +94,28 @@ var valid_sub_element_default = createRule({
83
94
  });
84
95
  }
85
96
  }
97
+ /**
98
+ * The type to name in the report when a value can never be a style object,
99
+ * or `null` when it is `false` (disables the sub-element) or might be one.
100
+ * A `const` binding is followed to what it names; any other binding, member
101
+ * or call is opaque, and Tasty accepts a reference to a style object there.
102
+ */
103
+ function nonObjectType(node, seen) {
104
+ node = unwrapExpression(node);
105
+ if (node.type === "Literal") return node.value === false ? null : typeof node.value;
106
+ if (node.type === "Identifier") {
107
+ if (seen.has(node)) return null;
108
+ seen.add(node);
109
+ let scope = context.sourceCode.getScope(node);
110
+ while (scope && !scope.set.has(node.name)) scope = scope.upper;
111
+ const variable = scope?.set.get(node.name);
112
+ const def = variable?.defs.length === 1 ? variable.defs[0] : void 0;
113
+ if (def?.type !== "Variable" || def.parent.kind !== "const" || !def.node.init) return null;
114
+ const init = unwrapExpression(def.node.init);
115
+ return init.type === "ObjectExpression" ? null : nonObjectType(init, seen);
116
+ }
117
+ return NON_OBJECT_EXPRESSIONS.has(node.type) ? node.type : null;
118
+ }
86
119
  function checkStyleObject(node) {
87
120
  if (visited.has(node)) return;
88
121
  visited.add(node);
@@ -95,8 +128,8 @@ var valid_sub_element_default = createRule({
95
128
  checkAffix(key, value);
96
129
  checkStyleObject(value);
97
130
  } else {
98
- if (value.type === "Literal" && value.value === false) continue;
99
- const valueType = value.type === "Literal" ? typeof value.value : value.type;
131
+ const valueType = nonObjectType(value, /* @__PURE__ */ new Set());
132
+ if (valueType === null) continue;
100
133
  context.report({
101
134
  node: prop.value,
102
135
  messageId: "subElementNotObject",
@@ -1 +1 @@
1
- {"version":3,"file":"valid-sub-element.js","names":[],"sources":["../../src/rules/valid-sub-element.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\ntype MessageIds = 'subElementNotObject' | 'subElementTargetsRoot';\n\nconst LEGACY_PSEUDO_ELEMENTS = new Set([\n ':before',\n ':after',\n ':first-line',\n ':first-letter',\n]);\n\nfunction hasDescendantTarget(nodes: selectorParser.Node[]): boolean {\n let lastCharacter = '';\n for (const node of nodes.slice(1)) {\n if (node.type === 'combinator') {\n if (node.value.trim()) return true;\n // The affix renderer skips authored whitespace. Tags insert their own\n // separator; pseudos attach directly, and classes/attributes attach\n // after a tag, class, attribute or placeholder.\n continue;\n }\n if (node.type === 'tag' || node.type === 'universal') {\n if (lastCharacter) return true;\n } else if (node.type === 'class' || node.type === 'attribute') {\n if (lastCharacter && !/[\\]@a-zA-Z0-9-]/.test(lastCharacter)) return true;\n }\n lastCharacter = node.toString().trim().slice(-1);\n }\n return false;\n}\n\nfunction firstRootSelector(selector: string, key: string): string | null {\n try {\n const root = selectorParser().astSync(selector);\n for (const branch of root.nodes) {\n const nodes = branch.nodes.filter((node) => node.type !== 'comment');\n if (nodes[0]?.type !== 'nesting' || nodes.length < 2) continue;\n // An explicit placeholder creates a descendant selector even with an\n // ampersand prefix. A different trailing uppercase name injects the key\n // as its descendant; the sub-element's own name does not.\n if (\n nodes.some((node) => node.type === 'tag' && node.value.startsWith('@'))\n )\n continue;\n const trailingElement = branch\n .toString()\n .trim()\n .slice(1)\n .trim()\n .match(/(?:^|[\\s>+~\\]:])([A-Z][a-zA-Z0-9]*)$/);\n if (trailingElement && trailingElement[1] !== key) continue;\n // Follow Tasty's affix normalization, rather than CSS whitespace alone.\n // Nested :is() arguments do not change the outer selector's target.\n if (hasDescendantTarget(nodes)) continue;\n const hasPseudoElement = nodes.some((node) => {\n if (node.type !== 'pseudo') return false;\n let name = node.value.toLowerCase();\n if (name.startsWith('::')) return true;\n if (name.includes('\\\\')) {\n const identifier = selectorParser().astSync(name.slice(1)).first\n ?.first;\n if (identifier?.type === 'tag')\n name = ':' + identifier.value.toLowerCase();\n }\n return LEGACY_PSEUDO_ELEMENTS.has(name);\n });\n if (!hasPseudoElement) return branch.toString().trim();\n }\n } catch {\n // Invalid/incomplete selector text belongs to syntax validation.\n }\n return null;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-sub-element',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate sub-element values and prevent root-targeting affixes',\n },\n messages: {\n subElementNotObject:\n \"Sub-element '{{name}}' value must be a style object, not a {{type}}.\",\n subElementTargetsRoot:\n \"Sub-element '{{name}}' selector '{{selector}}' styles the containing element instead of a sub-element. Use property state maps in that containing scope, or a descendant '$' selector such as 'h1'. Pseudo-elements such as '&::before' are allowed.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n\n function checkAffix(name: string, node: TSESTree.ObjectExpression) {\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key !== '$') continue;\n const selector = getStringValue(unwrapExpression(prop.value));\n if (selector === null) continue;\n const rootSelector = firstRootSelector(selector, name);\n if (rootSelector === null) continue;\n context.report({\n node: prop.value,\n messageId: 'subElementTargetsRoot',\n data: { name, selector: rootSelector },\n });\n }\n }\n\n // Recurse only through owned sub-elements. Some shared/JSX listeners match\n // just the root, and descriptor objects and state maps are not styles.\n function checkStyleObject(node: TSESTree.ObjectExpression) {\n if (visited.has(node)) return;\n visited.add(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key === null || !/^[A-Z]/.test(key)) continue;\n\n const value = unwrapExpression(prop.value);\n if (value.type === 'ObjectExpression') {\n checkAffix(key, value);\n checkStyleObject(value);\n } else {\n if (value.type === 'Literal' && value.value === false) {\n continue;\n }\n\n const valueType =\n value.type === 'Literal' ? typeof value.value : value.type;\n\n context.report({\n node: prop.value,\n messageId: 'subElementNotObject',\n data: { name: key, type: valueType },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (ctx.isStyleObject(node)) checkStyleObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"valid-sub-element.js","names":[],"sources":["../../src/rules/valid-sub-element.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport selectorParser from 'postcss-selector-parser';\nimport { createRule } from '../create-rule.js';\nimport { TastyContext, styleObjectListeners } from '../context.js';\nimport { getKeyName, getStringValue, unwrapExpression } from '../utils.js';\n\ntype MessageIds = 'subElementNotObject' | 'subElementTargetsRoot';\n\n/** Expressions whose result is visibly not an object. */\nconst NON_OBJECT_EXPRESSIONS = new Set<string>([\n 'TemplateLiteral',\n 'ArrayExpression',\n 'ArrowFunctionExpression',\n 'FunctionExpression',\n 'ClassExpression',\n 'UnaryExpression',\n 'BinaryExpression',\n 'UpdateExpression',\n]);\n\nconst LEGACY_PSEUDO_ELEMENTS = new Set([\n ':before',\n ':after',\n ':first-line',\n ':first-letter',\n]);\n\nfunction hasDescendantTarget(nodes: selectorParser.Node[]): boolean {\n let lastCharacter = '';\n for (const node of nodes.slice(1)) {\n if (node.type === 'combinator') {\n if (node.value.trim()) return true;\n // The affix renderer skips authored whitespace. Tags insert their own\n // separator; pseudos attach directly, and classes/attributes attach\n // after a tag, class, attribute or placeholder.\n continue;\n }\n if (node.type === 'tag' || node.type === 'universal') {\n if (lastCharacter) return true;\n } else if (node.type === 'class' || node.type === 'attribute') {\n if (lastCharacter && !/[\\]@a-zA-Z0-9-]/.test(lastCharacter)) return true;\n }\n lastCharacter = node.toString().trim().slice(-1);\n }\n return false;\n}\n\nfunction firstRootSelector(selector: string, key: string): string | null {\n try {\n const root = selectorParser().astSync(selector);\n for (const branch of root.nodes) {\n const nodes = branch.nodes.filter((node) => node.type !== 'comment');\n if (nodes[0]?.type !== 'nesting' || nodes.length < 2) continue;\n // An explicit placeholder creates a descendant selector even with an\n // ampersand prefix. A different trailing uppercase name injects the key\n // as its descendant; the sub-element's own name does not.\n if (\n nodes.some((node) => node.type === 'tag' && node.value.startsWith('@'))\n )\n continue;\n const trailingElement = branch\n .toString()\n .trim()\n .slice(1)\n .trim()\n .match(/(?:^|[\\s>+~\\]:])([A-Z][a-zA-Z0-9]*)$/);\n if (trailingElement && trailingElement[1] !== key) continue;\n // Follow Tasty's affix normalization, rather than CSS whitespace alone.\n // Nested :is() arguments do not change the outer selector's target.\n if (hasDescendantTarget(nodes)) continue;\n const hasPseudoElement = nodes.some((node) => {\n if (node.type !== 'pseudo') return false;\n let name = node.value.toLowerCase();\n if (name.startsWith('::')) return true;\n if (name.includes('\\\\')) {\n const identifier = selectorParser().astSync(name.slice(1)).first\n ?.first;\n if (identifier?.type === 'tag')\n name = ':' + identifier.value.toLowerCase();\n }\n return LEGACY_PSEUDO_ELEMENTS.has(name);\n });\n if (!hasPseudoElement) return branch.toString().trim();\n }\n } catch {\n // Invalid/incomplete selector text belongs to syntax validation.\n }\n return null;\n}\n\nexport default createRule<[], MessageIds>({\n name: 'valid-sub-element',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Validate sub-element values and prevent root-targeting affixes',\n },\n messages: {\n subElementNotObject:\n \"Sub-element '{{name}}' value must be a style object, not a {{type}}.\",\n subElementTargetsRoot:\n \"Sub-element '{{name}}' selector '{{selector}}' styles the containing element instead of a sub-element. Use property state maps in that containing scope, or a descendant '$' selector such as 'h1'. Pseudo-elements such as '&::before' are allowed.\",\n },\n schema: [],\n },\n defaultOptions: [],\n create(context) {\n const ctx = new TastyContext(context);\n const visited = new WeakSet<TSESTree.ObjectExpression>();\n\n function checkAffix(name: string, node: TSESTree.ObjectExpression) {\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key !== '$') continue;\n const selector = getStringValue(unwrapExpression(prop.value));\n if (selector === null) continue;\n const rootSelector = firstRootSelector(selector, name);\n if (rootSelector === null) continue;\n context.report({\n node: prop.value,\n messageId: 'subElementTargetsRoot',\n data: { name, selector: rootSelector },\n });\n }\n }\n\n /**\n * The type to name in the report when a value can never be a style object,\n * or `null` when it is `false` (disables the sub-element) or might be one.\n * A `const` binding is followed to what it names; any other binding, member\n * or call is opaque, and Tasty accepts a reference to a style object there.\n */\n function nonObjectType(\n node: TSESTree.Node,\n seen: Set<TSESTree.Node>,\n ): string | null {\n node = unwrapExpression(node);\n if (node.type === 'Literal') {\n return node.value === false ? null : typeof node.value;\n }\n if (node.type === 'Identifier') {\n if (seen.has(node)) return null;\n seen.add(node);\n let scope: ReturnType<typeof context.sourceCode.getScope> | null =\n context.sourceCode.getScope(node);\n while (scope && !scope.set.has(node.name)) scope = scope.upper;\n const variable = scope?.set.get(node.name);\n const def = variable?.defs.length === 1 ? variable.defs[0] : undefined;\n if (\n def?.type !== 'Variable' ||\n def.parent.kind !== 'const' ||\n !def.node.init\n ) {\n return null;\n }\n const init = unwrapExpression(def.node.init);\n return init.type === 'ObjectExpression'\n ? null\n : nonObjectType(init, seen);\n }\n return NON_OBJECT_EXPRESSIONS.has(node.type) ? node.type : null;\n }\n\n // Recurse only through owned sub-elements. Some shared/JSX listeners match\n // just the root, and descriptor objects and state maps are not styles.\n function checkStyleObject(node: TSESTree.ObjectExpression) {\n if (visited.has(node)) return;\n visited.add(node);\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property') continue;\n\n const key = prop.computed\n ? getStringValue(unwrapExpression(prop.key))\n : getKeyName(prop.key);\n if (key === null || !/^[A-Z]/.test(key)) continue;\n\n const value = unwrapExpression(prop.value);\n if (value.type === 'ObjectExpression') {\n checkAffix(key, value);\n checkStyleObject(value);\n } else {\n const valueType = nonObjectType(value, new Set());\n if (valueType === null) continue;\n\n context.report({\n node: prop.value,\n messageId: 'subElementNotObject',\n data: { name: key, type: valueType },\n });\n }\n }\n }\n\n function handleStyleObject(node: TSESTree.ObjectExpression) {\n if (ctx.isStyleObject(node)) checkStyleObject(node);\n }\n\n return {\n ImportDeclaration(node) {\n ctx.trackImport(node);\n },\n ...styleObjectListeners(handleStyleObject),\n };\n },\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/eslint-plugin-tasty",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "ESLint plugin for validating tasty() and tastyStatic() style objects",
5
5
  "type": "module",
6
6
  "exports": {