@tenphi/eslint-plugin-tasty 1.11.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"style-values.js","names":[],"sources":["../src/style-values.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { TastyContext } from './context.js';\nimport { KNOWN_CSS_PROPERTIES, KNOWN_TASTY_PROPERTIES } from './constants.js';\nimport { getKeyName, getStringValue, unwrapExpression } from './utils.js';\n\ninterface ValueListenerOptions {\n /** Collect declarations before checking their values (token existence rules). */\n onStyleObject?: (node: TSESTree.ObjectExpression) => void;\n /** Rewrites that depend on Tasty semantics need evidence beyond the prop name. */\n requireTastyJSX?: boolean;\n /** Deferred checks run after all declarations have been collected. */\n onProgramExit?: () => void;\n}\n\n/** Visit visible values without evaluating bindings, calls, or interpolations. */\nexport function styleValueListeners(\n ctx: TastyContext,\n check: (property: string, node: TSESTree.Node) => void,\n options: ValueListenerOptions = {},\n) {\n function visit(\n property: string,\n node: TSESTree.Node,\n attribute?: TSESTree.JSXAttribute,\n ): void {\n node = unwrapExpression(node);\n if (node.type === 'ObjectExpression') {\n // A property value object is a state map, never a sub-element here.\n for (const prop of node.properties) {\n if (prop.type === 'Property' && !prop.computed) {\n const value = unwrapExpression(prop.value);\n if (value.type !== 'ObjectExpression')\n visit(property, value, attribute);\n }\n }\n } else if (node.type === 'ConditionalExpression') {\n visit(property, node.consequent, attribute);\n visit(property, node.alternate, attribute);\n } else if (node.type === 'LogicalExpression') {\n const left = unwrapExpression(node.left);\n const str = getStringValue(left);\n const known = left.type === 'Literal' || str !== null;\n const value = left.type === 'Literal' ? left.value : str;\n if (node.operator === '&&') {\n // The left operand can only be the result when it is falsy.\n if (!known || value) visit(property, node.right, attribute);\n } else if (known) {\n visit(\n property,\n node.operator === '??'\n ? value == null\n ? node.right\n : left\n : value\n ? left\n : node.right,\n attribute,\n );\n } else {\n visit(property, node.left, attribute);\n visit(property, node.right, attribute);\n }\n } else {\n const visible =\n node.type === 'Literal' ||\n node.type === 'JSXAttribute' ||\n getStringValue(node) !== null ||\n (node.type === 'UnaryExpression' && node.argument.type === 'Literal');\n // Opaque values cannot produce a value diagnostic; avoid a compiler pass.\n if (attribute && visible && ctx.isComponentJSXProp(attribute)) return;\n check(property, node);\n }\n }\n\n return {\n 'Program:exit'() {\n ctx.finishJSXAnalysis();\n options.onProgramExit?.();\n },\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n ctx.trackImport(node);\n },\n ObjectExpression(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n options.onStyleObject?.(node);\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key === null || /^[A-Z@&]/.test(key) || key === '$') continue;\n visit(key, prop.value);\n }\n },\n JSXAttribute(node: TSESTree.JSXAttribute) {\n if (node.name.type !== 'JSXIdentifier') return;\n const key = node.name.name;\n if (\n !KNOWN_TASTY_PROPERTIES.has(key) &&\n !KNOWN_CSS_PROPERTIES.has(key) &&\n !ctx.config.styles.includes(key)\n )\n return;\n\n const opening = node.parent;\n if (opening?.type !== 'JSXOpeningElement') return;\n let tag = opening.name;\n while (tag.type === 'JSXMemberExpression') tag = tag.object;\n // Native HTML/SVG attributes contain CSS, not Tasty's value language.\n if (tag.type !== 'JSXIdentifier' || !/^[A-Z]/.test(tag.name)) return;\n if (options.requireTastyJSX && !ctx.isTastyJSXComponent(node, tag.name))\n return;\n\n const value =\n node.value?.type === 'JSXExpressionContainer'\n ? node.value.expression\n : node.value;\n if (value) visit(key, value, node);\n else visit(key, node, node); // A bare JSX attribute means boolean true.\n },\n };\n}\n\n/** Adapt a node listener to the string-only rules. */\nexport function stringStyleValueListeners(\n ctx: TastyContext,\n check: (value: string, node: TSESTree.Node, property: string) => void,\n options: ValueListenerOptions = {},\n) {\n return styleValueListeners(\n ctx,\n (property, node) => {\n const value = getStringValue(node);\n if (value !== null) check(value, node, property);\n },\n options,\n );\n}\n"],"mappings":";;;;;AAeA,SAAgB,oBACd,KACA,OACA,UAAgC,EAAE,EAClC;CACA,SAAS,MACP,UACA,MACA,WACM;AACN,SAAO,iBAAiB,KAAK;AAC7B,MAAI,KAAK,SAAS,oBAEhB;QAAK,MAAM,QAAQ,KAAK,WACtB,KAAI,KAAK,SAAS,cAAc,CAAC,KAAK,UAAU;IAC9C,MAAM,QAAQ,iBAAiB,KAAK,MAAM;AAC1C,QAAI,MAAM,SAAS,mBACjB,OAAM,UAAU,OAAO,UAAU;;aAG9B,KAAK,SAAS,yBAAyB;AAChD,SAAM,UAAU,KAAK,YAAY,UAAU;AAC3C,SAAM,UAAU,KAAK,WAAW,UAAU;aACjC,KAAK,SAAS,qBAAqB;GAC5C,MAAM,OAAO,iBAAiB,KAAK,KAAK;GACxC,MAAM,MAAM,eAAe,KAAK;GAChC,MAAM,QAAQ,KAAK,SAAS,aAAa,QAAQ;GACjD,MAAM,QAAQ,KAAK,SAAS,YAAY,KAAK,QAAQ;AACrD,OAAI,KAAK,aAAa,MAEpB;QAAI,CAAC,SAAS,MAAO,OAAM,UAAU,KAAK,OAAO,UAAU;cAClD,MACT,OACE,UACA,KAAK,aAAa,OACd,SAAS,OACP,KAAK,QACL,OACF,QACE,OACA,KAAK,OACX,UACD;QACI;AACL,UAAM,UAAU,KAAK,MAAM,UAAU;AACrC,UAAM,UAAU,KAAK,OAAO,UAAU;;SAEnC;GACL,MAAM,UACJ,KAAK,SAAS,aACd,KAAK,SAAS,kBACd,eAAe,KAAK,KAAK,QACxB,KAAK,SAAS,qBAAqB,KAAK,SAAS,SAAS;AAE7D,OAAI,aAAa,WAAW,IAAI,mBAAmB,UAAU,CAAE;AAC/D,SAAM,UAAU,KAAK;;;AAIzB,QAAO;EACL,iBAAiB;AACf,OAAI,mBAAmB;AACvB,WAAQ,iBAAiB;;EAE3B,kBAAkB,MAAkC;AAClD,OAAI,YAAY,KAAK;;EAEvB,iBAAiB,MAAiC;AAChD,OAAI,CAAC,IAAI,cAAc,KAAK,CAAE;AAC9B,WAAQ,gBAAgB,KAAK;AAC7B,QAAK,MAAM,QAAQ,KAAK,YAAY;AAClC,QAAI,KAAK,SAAS,cAAc,KAAK,SAAU;IAC/C,MAAM,MAAM,WAAW,KAAK,IAAI;AAChC,QAAI,QAAQ,QAAQ,WAAW,KAAK,IAAI,IAAI,QAAQ,IAAK;AACzD,UAAM,KAAK,KAAK,MAAM;;;EAG1B,aAAa,MAA6B;AACxC,OAAI,KAAK,KAAK,SAAS,gBAAiB;GACxC,MAAM,MAAM,KAAK,KAAK;AACtB,OACE,CAAC,uBAAuB,IAAI,IAAI,IAChC,CAAC,qBAAqB,IAAI,IAAI,IAC9B,CAAC,IAAI,OAAO,OAAO,SAAS,IAAI,CAEhC;GAEF,MAAM,UAAU,KAAK;AACrB,OAAI,SAAS,SAAS,oBAAqB;GAC3C,IAAI,MAAM,QAAQ;AAClB,UAAO,IAAI,SAAS,sBAAuB,OAAM,IAAI;AAErD,OAAI,IAAI,SAAS,mBAAmB,CAAC,SAAS,KAAK,IAAI,KAAK,CAAE;AAC9D,OAAI,QAAQ,mBAAmB,CAAC,IAAI,oBAAoB,MAAM,IAAI,KAAK,CACrE;GAEF,MAAM,QACJ,KAAK,OAAO,SAAS,2BACjB,KAAK,MAAM,aACX,KAAK;AACX,OAAI,MAAO,OAAM,KAAK,OAAO,KAAK;OAC7B,OAAM,KAAK,MAAM,KAAK;;EAE9B;;;AAIH,SAAgB,0BACd,KACA,OACA,UAAgC,EAAE,EAClC;AACA,QAAO,oBACL,MACC,UAAU,SAAS;EAClB,MAAM,QAAQ,eAAe,KAAK;AAClC,MAAI,UAAU,KAAM,OAAM,OAAO,MAAM,SAAS;IAElD,QACD"}
1
+ {"version":3,"file":"style-values.js","names":[],"sources":["../src/style-values.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { TastyContext } from './context.js';\nimport { KNOWN_CSS_PROPERTIES, KNOWN_TASTY_PROPERTIES } from './constants.js';\nimport { getKeyName, getStringValue, unwrapExpression } from './utils.js';\n\ninterface ValueListenerOptions {\n /** Collect declarations before checking their values (token existence rules). */\n onStyleObject?: (node: TSESTree.ObjectExpression) => void;\n /** Rewrites that depend on Tasty semantics need evidence beyond the prop name. */\n requireTastyJSX?: boolean;\n /** Deferred checks run after all declarations have been collected. */\n onProgramExit?: () => void;\n}\n\n/** Visit visible values without evaluating bindings, calls, or interpolations. */\nexport function styleValueListeners(\n ctx: TastyContext,\n check: (property: string, node: TSESTree.Node) => void,\n options: ValueListenerOptions = {},\n) {\n function visit(\n property: string,\n node: TSESTree.Node,\n attribute?: TSESTree.JSXAttribute,\n ): void {\n node = unwrapExpression(node);\n if (node.type === 'ObjectExpression') {\n // A property value object is a state map, never a sub-element here.\n for (const prop of node.properties) {\n if (prop.type === 'Property' && !prop.computed) {\n const value = unwrapExpression(prop.value);\n if (value.type !== 'ObjectExpression')\n visit(property, value, attribute);\n }\n }\n } else if (node.type === 'ConditionalExpression') {\n visit(property, node.consequent, attribute);\n visit(property, node.alternate, attribute);\n } else if (node.type === 'LogicalExpression') {\n const left = unwrapExpression(node.left);\n const str = getStringValue(left);\n const known = left.type === 'Literal' || str !== null;\n const value = left.type === 'Literal' ? left.value : str;\n if (node.operator === '&&') {\n // The left operand can only be the result when it is falsy.\n if (!known || value) visit(property, node.right, attribute);\n } else if (known) {\n visit(\n property,\n node.operator === '??'\n ? value == null\n ? node.right\n : left\n : value\n ? left\n : node.right,\n attribute,\n );\n } else {\n visit(property, node.left, attribute);\n visit(property, node.right, attribute);\n }\n } else {\n const visible =\n node.type === 'Literal' ||\n node.type === 'JSXAttribute' ||\n getStringValue(node) !== null ||\n (node.type === 'UnaryExpression' && node.argument.type === 'Literal');\n // Opaque values cannot produce a value diagnostic; avoid a compiler pass.\n if (attribute && visible) {\n const kind = ctx.jsxPropKind(attribute);\n if (kind === 'component') return;\n // Where types do not decide, a third-party component owns its props.\n if (kind === 'unknown' && ctx.isExternalJSXElement(attribute.parent))\n return;\n }\n check(property, node);\n }\n }\n\n return {\n 'Program:exit'() {\n ctx.finishJSXAnalysis();\n options.onProgramExit?.();\n },\n ImportDeclaration(node: TSESTree.ImportDeclaration) {\n ctx.trackImport(node);\n },\n ObjectExpression(node: TSESTree.ObjectExpression) {\n if (!ctx.isStyleObject(node)) return;\n options.onStyleObject?.(node);\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n const key = getKeyName(prop.key);\n if (key === null || /^[A-Z@&]/.test(key) || key === '$') continue;\n visit(key, prop.value);\n }\n },\n JSXAttribute(node: TSESTree.JSXAttribute) {\n if (node.name.type !== 'JSXIdentifier') return;\n const key = node.name.name;\n if (\n !KNOWN_TASTY_PROPERTIES.has(key) &&\n !KNOWN_CSS_PROPERTIES.has(key) &&\n !ctx.config.styles.includes(key)\n )\n return;\n\n const opening = node.parent;\n if (opening?.type !== 'JSXOpeningElement') return;\n let tag = opening.name;\n while (tag.type === 'JSXMemberExpression') tag = tag.object;\n // Native HTML/SVG attributes contain CSS, not Tasty's value language.\n if (tag.type !== 'JSXIdentifier' || !/^[A-Z]/.test(tag.name)) return;\n // Without prop types, a third-party component is taken to own its props.\n if (!ctx.typeAwareJSX && ctx.isExternalJSXElement(opening)) return;\n if (options.requireTastyJSX && !ctx.isTastyJSXComponent(node, tag.name))\n return;\n\n const value =\n node.value?.type === 'JSXExpressionContainer'\n ? node.value.expression\n : node.value;\n if (value) visit(key, value, node);\n else visit(key, node, node); // A bare JSX attribute means boolean true.\n },\n };\n}\n\n/** Adapt a node listener to the string-only rules. */\nexport function stringStyleValueListeners(\n ctx: TastyContext,\n check: (value: string, node: TSESTree.Node, property: string) => void,\n options: ValueListenerOptions = {},\n) {\n return styleValueListeners(\n ctx,\n (property, node) => {\n const value = getStringValue(node);\n if (value !== null) check(value, node, property);\n },\n options,\n );\n}\n"],"mappings":"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"}
@@ -241,9 +241,17 @@ function makeClassifier(T, program, filename, text) {
241
241
  return kind;
242
242
  };
243
243
  }
244
+ function typeAwareJSXOption(context) {
245
+ return context.settings.tasty?.typeAwareJSX;
246
+ }
247
+ /** Whether `settings.tasty.typeAwareJSX` turns the classifier on. */
248
+ function isTypeAwareJSX(context) {
249
+ const option = typeAwareJSXOption(context);
250
+ return option !== void 0 && option !== false;
251
+ }
244
252
  /** No compiler is loaded, or program created, unless the setting is enabled. */
245
253
  function jsxPropKind(context, attribute) {
246
- const option = context.settings.tasty?.typeAwareJSX;
254
+ const option = typeAwareJSXOption(context);
247
255
  if (option === void 0 || option === false) return "unknown";
248
256
  if (option !== true && (!option || typeof option !== "object" || Array.isArray(option) || option.project !== void 0 && typeof option.project !== "string")) throw new Error("tasty: typeAwareJSX must be true, false, or { project: string }.");
249
257
  const filename = resolve(context.cwd, context.filename);
@@ -276,5 +284,5 @@ function finishJSXAnalysis(context) {
276
284
  }
277
285
 
278
286
  //#endregion
279
- export { finishJSXAnalysis, jsxPropKind };
287
+ export { finishJSXAnalysis, isTypeAwareJSX, jsxPropKind };
280
288
  //# sourceMappingURL=type-aware-jsx.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"type-aware-jsx.js","names":[],"sources":["../src/type-aware-jsx.ts"],"sourcesContent":["import { statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, resolve } from 'node:path';\nimport type ts from 'typescript';\nimport type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\n\n/** Experimental, independently managed TypeScript analysis for either linter. */\nexport interface TypeAwareJSXOptions {\n /** tsconfig path relative to the linter's cwd; otherwise find the nearest one. */\n project?: string;\n}\n\ntype PropKind = 'style' | 'component' | 'unknown';\ntype TypeScript = typeof ts;\ntype Classifier = (attribute: TSESTree.JSXAttribute) => PropKind;\n\ninterface Project {\n config: ts.ParsedCommandLine;\n configVersions: Map<string, string>;\n program?: ts.Program;\n versions: Map<string, string>;\n overlay?: { filename: string; text: string };\n sourceFiles: Map<\n string,\n {\n version: string;\n overlayText?: string;\n parseKey: string;\n source: ts.SourceFile;\n }\n >;\n excludedFiles: Map<string, string>;\n}\n\nconst require = createRequire(import.meta.url);\nlet typescript: TypeScript | undefined;\nconst projects = new Map<string, Project>();\n// All value rules share one classifier for a file, including in Oxlint.\nconst classifiers = new WeakMap<\n object,\n Map<\n string,\n {\n filename: string;\n text: string;\n classify: Classifier;\n }\n >\n>();\n\nfunction getTypeScript(): TypeScript {\n if (!typescript) {\n let module: Partial<TypeScript>;\n try {\n module = require('typescript') as Partial<TypeScript>;\n } catch {\n throw new Error(\n 'tasty: typeAwareJSX requires TypeScript. Install typescript as a dev dependency.',\n );\n }\n // TypeScript 7 (the native port) exports only its version from the main entry.\n if (typeof module.createProgram !== 'function') {\n throw new Error(\n `tasty: typeAwareJSX requires the TypeScript compiler API, which ${\n module.version\n ? `typescript ${module.version}`\n : 'the installed typescript'\n } does not provide. Install TypeScript 5.6–6.x as a dev dependency.`,\n );\n }\n typescript = module as TypeScript;\n }\n return typescript;\n}\n\nfunction version(filename: string): string {\n try {\n const stat = statSync(filename);\n return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;\n } catch {\n return 'missing';\n }\n}\n\nfunction changed(versions: Map<string, string>): boolean {\n for (const [filename, previous] of versions) {\n if (version(filename) !== previous) return true;\n }\n return false;\n}\n\nfunction readConfig(\n T: TypeScript,\n configPath: string,\n configVersions: Map<string, string>,\n): ts.ParsedCommandLine {\n const config = T.getParsedCommandLineOfConfigFile(\n configPath,\n {},\n {\n ...T.sys,\n readFile(filename) {\n configVersions.set(filename, version(filename));\n return T.sys.readFile(filename);\n },\n onUnRecoverableConfigFileDiagnostic(diagnostic) {\n throw new Error(\n `tasty: ${T.flattenDiagnosticMessageText(diagnostic.messageText, '\\n')}`,\n );\n },\n },\n );\n if (!config || config.errors.length) {\n throw new Error(\n `tasty: invalid typeAwareJSX project ${configPath}: ${config?.errors\n .map((d) => T.flattenDiagnosticMessageText(d.messageText, '\\n'))\n .join('\\n')}`,\n );\n }\n return config;\n}\n\nfunction readProject(T: TypeScript, configPath: string): Project {\n const configVersions = new Map<string, string>();\n return {\n config: readConfig(T, configPath, configVersions),\n configVersions,\n versions: new Map(),\n sourceFiles: new Map(),\n excludedFiles: new Map(),\n };\n}\n\nfunction getProgram(\n T: TypeScript,\n configPath: string,\n filename: string,\n text: string,\n): ts.Program {\n let project = projects.get(configPath);\n let rootsChanged = false;\n if (!project || changed(project.configVersions)) {\n project = readProject(T, configPath);\n } else if (\n project.program &&\n !project.program.getSourceFile(filename) &&\n project.excludedFiles.get(filename) !== version(filename)\n ) {\n const refreshed = readProject(T, configPath);\n const previousFiles = project.config.fileNames;\n rootsChanged =\n refreshed.config.fileNames.length !== previousFiles.length ||\n refreshed.config.fileNames.some(\n (file, index) => file !== previousFiles[index],\n );\n if (rootsChanged) project.config = refreshed.config;\n if (!project.config.fileNames.includes(filename))\n project.excludedFiles.set(filename, version(filename));\n else project.excludedFiles.delete(filename);\n }\n // Bound retained compiler graphs in long-lived editor processes.\n projects.delete(configPath);\n projects.set(configPath, project);\n if (projects.size > 3) projects.delete(projects.keys().next().value!);\n\n const sourcesChanged = changed(project.versions);\n const overlay =\n project.excludedFiles.has(filename) &&\n !project.program?.getSourceFile(filename) &&\n !rootsChanged &&\n !sourcesChanged\n ? undefined\n : T.sys.readFile(filename) === text\n ? undefined\n : { filename, text };\n const overlayChanged =\n project.overlay?.filename !== overlay?.filename ||\n project.overlay?.text !== overlay?.text;\n if (!project.program || rootsChanged || overlayChanged || sourcesChanged) {\n const host = T.createCompilerHost(project.config.options, true);\n host.getParsedCommandLine = (path) =>\n readConfig(T, path, project.configVersions);\n const readFile = host.readFile;\n host.readFile = (path) =>\n resolve(path) === overlay?.filename ? overlay.text : readFile(path);\n const getSourceFile = host.getSourceFile;\n host.getSourceFile = (path, options, onError, force) => {\n const key = resolve(path);\n const stamp = version(key);\n const overlayText = key === overlay?.filename ? overlay.text : undefined;\n const parseOptions =\n typeof options === 'number' ? { languageVersion: options } : options;\n const parseKey = [\n parseOptions.languageVersion,\n parseOptions.impliedNodeFormat,\n parseOptions.jsDocParsingMode,\n ].join(':');\n const cached = project.sourceFiles.get(key);\n if (\n !force &&\n cached?.version === stamp &&\n cached.overlayText === overlayText &&\n cached.parseKey === parseKey\n )\n return cached.source;\n const source = getSourceFile(path, options, onError, force);\n if (source)\n project.sourceFiles.set(key, {\n version: stamp,\n overlayText,\n parseKey,\n source,\n });\n else project.sourceFiles.delete(key);\n return source;\n };\n project.program = T.createProgram({\n rootNames: project.config.fileNames,\n options: project.config.options,\n projectReferences: project.config.projectReferences,\n host,\n oldProgram: project.program,\n });\n project.overlay = project.program.getSourceFile(filename)\n ? overlay\n : undefined;\n if (!project.program.getSourceFile(filename))\n project.excludedFiles.set(filename, version(filename));\n project.versions = new Map(\n project.program\n .getSourceFiles()\n .map((file) => [file.fileName, version(file.fileName)]),\n );\n const retained = new Set(\n project.program.getSourceFiles().map((file) => resolve(file.fileName)),\n );\n for (const path of project.sourceFiles.keys()) {\n if (!retained.has(path)) project.sourceFiles.delete(path);\n }\n }\n return project.program;\n}\n\nfunction makeClassifier(\n T: TypeScript,\n program: ts.Program,\n filename: string,\n text: string,\n): Classifier {\n const source = program.getSourceFile(filename);\n // Excluded files and virtual/transformed source keep the existing heuristic.\n if (!source || source.text !== text) return () => 'unknown';\n const checker = program.getTypeChecker();\n const modulePath = T.resolveModuleName(\n '@tenphi/tasty',\n filename,\n program.getCompilerOptions(),\n T.sys,\n ).resolvedModule?.resolvedFileName;\n const moduleFile = modulePath && program.getSourceFile(modulePath);\n const module = moduleFile && checker.getSymbolAtLocation(moduleFile);\n const exports = module ? checker.getExportsOfModule(module) : [];\n const styleTypeSymbols = new Set(\n exports\n ?.filter((s) =>\n ['Styles', 'StyleValue', 'StyleValueStateMap'].includes(s.name),\n )\n .map((s) =>\n s.flags & T.SymbolFlags.Alias ? checker.getAliasedSymbol(s) : s,\n ),\n );\n let symbol = exports?.find((s) => s.name === 'Styles');\n if (symbol && symbol.flags & T.SymbolFlags.Alias) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n const styles = symbol && checker.getDeclaredTypeOfSymbol(symbol);\n if (!styles || styles.flags & (T.TypeFlags.Any | T.TypeFlags.Unknown)) {\n return () => 'unknown';\n }\n const styleDeclarations = new Set(\n styles.getProperties().flatMap((property) => property.declarations ?? []),\n );\n // A dependency may resolve a different copy of either styling package.\n function isStyleDeclaration(declaration: ts.Declaration): boolean {\n return (\n styleDeclarations.has(declaration) ||\n /[/\\\\]node_modules[/\\\\](?:@tenphi[/\\\\]tasty|csstype)[/\\\\]/.test(\n declaration.getSourceFile().fileName,\n )\n );\n }\n const attributes = new Map<number, ts.JsxAttribute>();\n function collect(node: ts.Node): void {\n if (T.isJsxAttribute(node)) attributes.set(node.getStart(source), node);\n T.forEachChild(node, collect);\n }\n collect(source);\n\n function referencesStyles(\n node: ts.Node,\n visited = new Set<ts.Node>(),\n bindings = new Map<ts.Symbol, ts.TypeNode>(),\n ): boolean {\n if (visited.has(node)) return false;\n visited.add(node);\n try {\n if (\n T.isPropertySignature(node) ||\n T.isPropertyDeclaration(node) ||\n T.isParameter(node) ||\n T.isTypeAliasDeclaration(node) ||\n T.isMappedTypeNode(node) ||\n T.isParenthesizedTypeNode(node)\n )\n return node.type\n ? referencesStyles(node.type, visited, bindings)\n : false;\n if (T.isIndexedAccessTypeNode(node)) {\n const object = checker.getTypeFromTypeNode(node.objectType);\n const index = checker.getTypeFromTypeNode(node.indexType);\n const keys = index.isUnion() ? index.types : [index];\n // Trace the selected property, not unrelated style props on the object.\n return (\n object === styles ||\n keys.some((key) => {\n if (!key.isStringLiteral() && !key.isNumberLiteral()) return false;\n const property = checker.getPropertyOfType(\n object,\n String(key.value),\n );\n return property?.declarations?.some(\n (declaration) =>\n isStyleDeclaration(declaration) ||\n referencesStyles(declaration, visited, bindings),\n );\n })\n );\n }\n if (T.isTypeReferenceNode(node) || T.isImportTypeNode(node)) {\n const name = T.isTypeReferenceNode(node)\n ? node.typeName\n : node.qualifier;\n let reference = name && checker.getSymbolAtLocation(name);\n if (reference && reference.flags & T.SymbolFlags.Alias) {\n reference = checker.getAliasedSymbol(reference);\n }\n if (reference && styleTypeSymbols.has(reference)) return true;\n if (reference?.declarations?.some(isStyleDeclaration)) return true;\n const bound = reference && bindings.get(reference);\n if (bound) return referencesStyles(bound, visited, bindings);\n if (\n reference?.declarations?.some((declaration) => {\n if (!T.isTypeAliasDeclaration(declaration)) return false;\n const local = new Map(bindings);\n declaration.typeParameters?.forEach((parameter, index) => {\n const symbol = checker.getSymbolAtLocation(parameter.name);\n const argument = node.typeArguments?.[index] ?? parameter.default;\n if (symbol && argument) local.set(symbol, argument);\n });\n return referencesStyles(declaration.type, visited, local);\n })\n )\n return true;\n }\n if (T.isConditionalTypeNode(node)) {\n // Extract/Exclude-style filters preserve an input value or return never.\n const parameter = (branch: ts.TypeNode) => {\n if (!T.isTypeReferenceNode(branch)) return undefined;\n const symbol = checker.getSymbolAtLocation(branch.typeName);\n return symbol && symbol.flags & T.SymbolFlags.TypeParameter\n ? symbol\n : undefined;\n };\n const trueParameter = parameter(node.trueType);\n const falseParameter = parameter(node.falseType);\n const preservesValue =\n (trueParameter &&\n node.falseType.kind === T.SyntaxKind.NeverKeyword) ||\n (falseParameter &&\n node.trueType.kind === T.SyntaxKind.NeverKeyword) ||\n (trueParameter && trueParameter === falseParameter);\n return (\n Boolean(preservesValue) &&\n (referencesStyles(node.trueType, visited, bindings) ||\n referencesStyles(node.falseType, visited, bindings))\n );\n }\n // Nested fields, function arguments and conditional constraints are content,\n // not evidence that the prop's value itself originates in a style type.\n return (\n (T.isUnionTypeNode(node) || T.isIntersectionTypeNode(node)) &&\n node.types.some((part) => referencesStyles(part, visited, bindings))\n );\n } finally {\n visited.delete(node);\n }\n }\n\n function unresolved(type: ts.Type): boolean {\n if (\n type.flags &\n (T.TypeFlags.Any |\n T.TypeFlags.Unknown |\n T.TypeFlags.TypeParameter |\n T.TypeFlags.Conditional |\n T.TypeFlags.IndexedAccess |\n T.TypeFlags.Never)\n )\n return true;\n return type.isUnionOrIntersection() && type.types.some(unresolved);\n }\n\n function hasPrimitiveFlag(type: ts.Type, flags: number): boolean {\n return (\n Boolean(type.flags & flags) ||\n (type.isIntersection() &&\n type.types.some((part) => hasPrimitiveFlag(part, flags)))\n );\n }\n\n function unresolvedComponentProp(\n node: ts.JsxAttribute,\n name: string,\n ): boolean {\n const tag = node.parent.parent.tagName;\n const component = checker.getTypeAtLocation(tag);\n return [\n ...component.getCallSignatures(),\n ...component.getConstructSignatures(),\n ].some((signature) => {\n const parameter = signature.parameters[0];\n if (!parameter) return false;\n const props = checker.getTypeOfSymbolAtLocation(parameter, tag);\n const property = checker.getPropertyOfType(props, name);\n return property\n ? unresolved(checker.getTypeOfSymbolAtLocation(property, tag))\n : false;\n });\n }\n\n const results = new Map<number, PropKind>();\n return (attribute) => {\n const start = attribute.range[0];\n const cached = results.get(start);\n if (cached) return cached;\n const node = attributes.get(start);\n let kind: PropKind = 'unknown';\n if (node && node.end === attribute.range[1] && T.isIdentifier(node.name)) {\n const props = checker.getContextualType(node.parent);\n const property =\n props && checker.getPropertyOfType(props, node.name.text);\n const declarations = property?.declarations;\n if (property && declarations?.length) {\n const type = checker.getTypeOfSymbolAtLocation(property, node);\n if (\n declarations.some((d) => isStyleDeclaration(d) || referencesStyles(d))\n ) {\n kind = 'style';\n } else if (\n !unresolved(type) &&\n !(\n declarations.some((declaration) =>\n unresolved(checker.getTypeAtLocation(declaration)),\n ) && unresolvedComponentProp(node, node.name.text)\n )\n ) {\n // Broad primitives may be hand-written style props. Preserve checks.\n const nonNullable = checker.getNonNullableType(type);\n const parts = nonNullable.isUnion()\n ? nonNullable.types\n : [nonNullable];\n const primitive =\n T.TypeFlags.StringLike |\n T.TypeFlags.NumberLike |\n T.TypeFlags.BooleanLike |\n T.TypeFlags.BigIntLike;\n const broad =\n T.TypeFlags.String |\n T.TypeFlags.Number |\n T.TypeFlags.Boolean |\n T.TypeFlags.BigInt;\n const broadPrimitive =\n parts.every((part) => hasPrimitiveFlag(part, primitive)) &&\n (Boolean(nonNullable.flags & broad) ||\n parts.some((part) => hasPrimitiveFlag(part, broad)) ||\n parts.every((part) =>\n hasPrimitiveFlag(part, T.TypeFlags.BooleanLike),\n ));\n if (!broadPrimitive) kind = 'component';\n }\n }\n }\n results.set(start, kind);\n return kind;\n };\n}\n\n/** No compiler is loaded, or program created, unless the setting is enabled. */\nexport function jsxPropKind(\n context: RuleContext<string, unknown[]>,\n attribute: TSESTree.JSXAttribute,\n): PropKind {\n const settings = context.settings.tasty as\n | { typeAwareJSX?: boolean | TypeAwareJSXOptions }\n | undefined;\n const option = settings?.typeAwareJSX;\n if (option === undefined || option === false) return 'unknown';\n if (\n option !== true &&\n (!option ||\n typeof option !== 'object' ||\n Array.isArray(option) ||\n (option.project !== undefined && typeof option.project !== 'string'))\n )\n throw new Error(\n 'tasty: typeAwareJSX must be true, false, or { project: string }.',\n );\n\n const filename = resolve(context.cwd, context.filename);\n const explicit = option === true ? undefined : option.project;\n const sourceCode = context.sourceCode;\n const key = explicit ? resolve(context.cwd, explicit) : 'nearest';\n let byProject = classifiers.get(sourceCode);\n if (!byProject) {\n byProject = new Map();\n classifiers.set(sourceCode, byProject);\n }\n let cached = byProject.get(key);\n if (\n !cached ||\n cached.filename !== filename ||\n cached.text !== sourceCode.text\n ) {\n const T = getTypeScript();\n const configPath = explicit\n ? key\n : T.findConfigFile(dirname(filename), T.sys.fileExists);\n if (!configPath)\n throw new Error(\n `tasty: typeAwareJSX could not find a tsconfig for ${filename}.`,\n );\n const program = getProgram(T, configPath, filename, sourceCode.text);\n cached = {\n filename,\n text: sourceCode.text,\n classify: makeClassifier(T, program, filename, sourceCode.text),\n };\n byProject.set(key, cached);\n }\n return cached.classify(attribute);\n}\n\n/** A SourceCode object may be reused by ESLint after dependencies change. */\nexport function finishJSXAnalysis(\n context: RuleContext<string, unknown[]>,\n): void {\n classifiers.delete(context.sourceCode);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"type-aware-jsx.js","names":[],"sources":["../src/type-aware-jsx.ts"],"sourcesContent":["import { statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, resolve } from 'node:path';\nimport type ts from 'typescript';\nimport type { TSESTree } from '@typescript-eslint/utils';\nimport type { RuleContext } from '@typescript-eslint/utils/ts-eslint';\n\n/** Experimental, independently managed TypeScript analysis for either linter. */\nexport interface TypeAwareJSXOptions {\n /** tsconfig path relative to the linter's cwd; otherwise find the nearest one. */\n project?: string;\n}\n\nexport type PropKind = 'style' | 'component' | 'unknown';\ntype TypeScript = typeof ts;\ntype Classifier = (attribute: TSESTree.JSXAttribute) => PropKind;\n\ninterface Project {\n config: ts.ParsedCommandLine;\n configVersions: Map<string, string>;\n program?: ts.Program;\n versions: Map<string, string>;\n overlay?: { filename: string; text: string };\n sourceFiles: Map<\n string,\n {\n version: string;\n overlayText?: string;\n parseKey: string;\n source: ts.SourceFile;\n }\n >;\n excludedFiles: Map<string, string>;\n}\n\nconst require = createRequire(import.meta.url);\nlet typescript: TypeScript | undefined;\nconst projects = new Map<string, Project>();\n// All value rules share one classifier for a file, including in Oxlint.\nconst classifiers = new WeakMap<\n object,\n Map<\n string,\n {\n filename: string;\n text: string;\n classify: Classifier;\n }\n >\n>();\n\nfunction getTypeScript(): TypeScript {\n if (!typescript) {\n let module: Partial<TypeScript>;\n try {\n module = require('typescript') as Partial<TypeScript>;\n } catch {\n throw new Error(\n 'tasty: typeAwareJSX requires TypeScript. Install typescript as a dev dependency.',\n );\n }\n // TypeScript 7 (the native port) exports only its version from the main entry.\n if (typeof module.createProgram !== 'function') {\n throw new Error(\n `tasty: typeAwareJSX requires the TypeScript compiler API, which ${\n module.version\n ? `typescript ${module.version}`\n : 'the installed typescript'\n } does not provide. Install TypeScript 5.6–6.x as a dev dependency.`,\n );\n }\n typescript = module as TypeScript;\n }\n return typescript;\n}\n\nfunction version(filename: string): string {\n try {\n const stat = statSync(filename);\n return `${stat.mtimeMs}:${stat.ctimeMs}:${stat.size}`;\n } catch {\n return 'missing';\n }\n}\n\nfunction changed(versions: Map<string, string>): boolean {\n for (const [filename, previous] of versions) {\n if (version(filename) !== previous) return true;\n }\n return false;\n}\n\nfunction readConfig(\n T: TypeScript,\n configPath: string,\n configVersions: Map<string, string>,\n): ts.ParsedCommandLine {\n const config = T.getParsedCommandLineOfConfigFile(\n configPath,\n {},\n {\n ...T.sys,\n readFile(filename) {\n configVersions.set(filename, version(filename));\n return T.sys.readFile(filename);\n },\n onUnRecoverableConfigFileDiagnostic(diagnostic) {\n throw new Error(\n `tasty: ${T.flattenDiagnosticMessageText(diagnostic.messageText, '\\n')}`,\n );\n },\n },\n );\n if (!config || config.errors.length) {\n throw new Error(\n `tasty: invalid typeAwareJSX project ${configPath}: ${config?.errors\n .map((d) => T.flattenDiagnosticMessageText(d.messageText, '\\n'))\n .join('\\n')}`,\n );\n }\n return config;\n}\n\nfunction readProject(T: TypeScript, configPath: string): Project {\n const configVersions = new Map<string, string>();\n return {\n config: readConfig(T, configPath, configVersions),\n configVersions,\n versions: new Map(),\n sourceFiles: new Map(),\n excludedFiles: new Map(),\n };\n}\n\nfunction getProgram(\n T: TypeScript,\n configPath: string,\n filename: string,\n text: string,\n): ts.Program {\n let project = projects.get(configPath);\n let rootsChanged = false;\n if (!project || changed(project.configVersions)) {\n project = readProject(T, configPath);\n } else if (\n project.program &&\n !project.program.getSourceFile(filename) &&\n project.excludedFiles.get(filename) !== version(filename)\n ) {\n const refreshed = readProject(T, configPath);\n const previousFiles = project.config.fileNames;\n rootsChanged =\n refreshed.config.fileNames.length !== previousFiles.length ||\n refreshed.config.fileNames.some(\n (file, index) => file !== previousFiles[index],\n );\n if (rootsChanged) project.config = refreshed.config;\n if (!project.config.fileNames.includes(filename))\n project.excludedFiles.set(filename, version(filename));\n else project.excludedFiles.delete(filename);\n }\n // Bound retained compiler graphs in long-lived editor processes.\n projects.delete(configPath);\n projects.set(configPath, project);\n if (projects.size > 3) projects.delete(projects.keys().next().value!);\n\n const sourcesChanged = changed(project.versions);\n const overlay =\n project.excludedFiles.has(filename) &&\n !project.program?.getSourceFile(filename) &&\n !rootsChanged &&\n !sourcesChanged\n ? undefined\n : T.sys.readFile(filename) === text\n ? undefined\n : { filename, text };\n const overlayChanged =\n project.overlay?.filename !== overlay?.filename ||\n project.overlay?.text !== overlay?.text;\n if (!project.program || rootsChanged || overlayChanged || sourcesChanged) {\n const host = T.createCompilerHost(project.config.options, true);\n host.getParsedCommandLine = (path) =>\n readConfig(T, path, project.configVersions);\n const readFile = host.readFile;\n host.readFile = (path) =>\n resolve(path) === overlay?.filename ? overlay.text : readFile(path);\n const getSourceFile = host.getSourceFile;\n host.getSourceFile = (path, options, onError, force) => {\n const key = resolve(path);\n const stamp = version(key);\n const overlayText = key === overlay?.filename ? overlay.text : undefined;\n const parseOptions =\n typeof options === 'number' ? { languageVersion: options } : options;\n const parseKey = [\n parseOptions.languageVersion,\n parseOptions.impliedNodeFormat,\n parseOptions.jsDocParsingMode,\n ].join(':');\n const cached = project.sourceFiles.get(key);\n if (\n !force &&\n cached?.version === stamp &&\n cached.overlayText === overlayText &&\n cached.parseKey === parseKey\n )\n return cached.source;\n const source = getSourceFile(path, options, onError, force);\n if (source)\n project.sourceFiles.set(key, {\n version: stamp,\n overlayText,\n parseKey,\n source,\n });\n else project.sourceFiles.delete(key);\n return source;\n };\n project.program = T.createProgram({\n rootNames: project.config.fileNames,\n options: project.config.options,\n projectReferences: project.config.projectReferences,\n host,\n oldProgram: project.program,\n });\n project.overlay = project.program.getSourceFile(filename)\n ? overlay\n : undefined;\n if (!project.program.getSourceFile(filename))\n project.excludedFiles.set(filename, version(filename));\n project.versions = new Map(\n project.program\n .getSourceFiles()\n .map((file) => [file.fileName, version(file.fileName)]),\n );\n const retained = new Set(\n project.program.getSourceFiles().map((file) => resolve(file.fileName)),\n );\n for (const path of project.sourceFiles.keys()) {\n if (!retained.has(path)) project.sourceFiles.delete(path);\n }\n }\n return project.program;\n}\n\nfunction makeClassifier(\n T: TypeScript,\n program: ts.Program,\n filename: string,\n text: string,\n): Classifier {\n const source = program.getSourceFile(filename);\n // Excluded files and virtual/transformed source keep the existing heuristic.\n if (!source || source.text !== text) return () => 'unknown';\n const checker = program.getTypeChecker();\n const modulePath = T.resolveModuleName(\n '@tenphi/tasty',\n filename,\n program.getCompilerOptions(),\n T.sys,\n ).resolvedModule?.resolvedFileName;\n const moduleFile = modulePath && program.getSourceFile(modulePath);\n const module = moduleFile && checker.getSymbolAtLocation(moduleFile);\n const exports = module ? checker.getExportsOfModule(module) : [];\n const styleTypeSymbols = new Set(\n exports\n ?.filter((s) =>\n ['Styles', 'StyleValue', 'StyleValueStateMap'].includes(s.name),\n )\n .map((s) =>\n s.flags & T.SymbolFlags.Alias ? checker.getAliasedSymbol(s) : s,\n ),\n );\n let symbol = exports?.find((s) => s.name === 'Styles');\n if (symbol && symbol.flags & T.SymbolFlags.Alias) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n const styles = symbol && checker.getDeclaredTypeOfSymbol(symbol);\n if (!styles || styles.flags & (T.TypeFlags.Any | T.TypeFlags.Unknown)) {\n return () => 'unknown';\n }\n const styleDeclarations = new Set(\n styles.getProperties().flatMap((property) => property.declarations ?? []),\n );\n // A dependency may resolve a different copy of either styling package.\n function isStyleDeclaration(declaration: ts.Declaration): boolean {\n return (\n styleDeclarations.has(declaration) ||\n /[/\\\\]node_modules[/\\\\](?:@tenphi[/\\\\]tasty|csstype)[/\\\\]/.test(\n declaration.getSourceFile().fileName,\n )\n );\n }\n const attributes = new Map<number, ts.JsxAttribute>();\n function collect(node: ts.Node): void {\n if (T.isJsxAttribute(node)) attributes.set(node.getStart(source), node);\n T.forEachChild(node, collect);\n }\n collect(source);\n\n function referencesStyles(\n node: ts.Node,\n visited = new Set<ts.Node>(),\n bindings = new Map<ts.Symbol, ts.TypeNode>(),\n ): boolean {\n if (visited.has(node)) return false;\n visited.add(node);\n try {\n if (\n T.isPropertySignature(node) ||\n T.isPropertyDeclaration(node) ||\n T.isParameter(node) ||\n T.isTypeAliasDeclaration(node) ||\n T.isMappedTypeNode(node) ||\n T.isParenthesizedTypeNode(node)\n )\n return node.type\n ? referencesStyles(node.type, visited, bindings)\n : false;\n if (T.isIndexedAccessTypeNode(node)) {\n const object = checker.getTypeFromTypeNode(node.objectType);\n const index = checker.getTypeFromTypeNode(node.indexType);\n const keys = index.isUnion() ? index.types : [index];\n // Trace the selected property, not unrelated style props on the object.\n return (\n object === styles ||\n keys.some((key) => {\n if (!key.isStringLiteral() && !key.isNumberLiteral()) return false;\n const property = checker.getPropertyOfType(\n object,\n String(key.value),\n );\n return property?.declarations?.some(\n (declaration) =>\n isStyleDeclaration(declaration) ||\n referencesStyles(declaration, visited, bindings),\n );\n })\n );\n }\n if (T.isTypeReferenceNode(node) || T.isImportTypeNode(node)) {\n const name = T.isTypeReferenceNode(node)\n ? node.typeName\n : node.qualifier;\n let reference = name && checker.getSymbolAtLocation(name);\n if (reference && reference.flags & T.SymbolFlags.Alias) {\n reference = checker.getAliasedSymbol(reference);\n }\n if (reference && styleTypeSymbols.has(reference)) return true;\n if (reference?.declarations?.some(isStyleDeclaration)) return true;\n const bound = reference && bindings.get(reference);\n if (bound) return referencesStyles(bound, visited, bindings);\n if (\n reference?.declarations?.some((declaration) => {\n if (!T.isTypeAliasDeclaration(declaration)) return false;\n const local = new Map(bindings);\n declaration.typeParameters?.forEach((parameter, index) => {\n const symbol = checker.getSymbolAtLocation(parameter.name);\n const argument = node.typeArguments?.[index] ?? parameter.default;\n if (symbol && argument) local.set(symbol, argument);\n });\n return referencesStyles(declaration.type, visited, local);\n })\n )\n return true;\n }\n if (T.isConditionalTypeNode(node)) {\n // Extract/Exclude-style filters preserve an input value or return never.\n const parameter = (branch: ts.TypeNode) => {\n if (!T.isTypeReferenceNode(branch)) return undefined;\n const symbol = checker.getSymbolAtLocation(branch.typeName);\n return symbol && symbol.flags & T.SymbolFlags.TypeParameter\n ? symbol\n : undefined;\n };\n const trueParameter = parameter(node.trueType);\n const falseParameter = parameter(node.falseType);\n const preservesValue =\n (trueParameter &&\n node.falseType.kind === T.SyntaxKind.NeverKeyword) ||\n (falseParameter &&\n node.trueType.kind === T.SyntaxKind.NeverKeyword) ||\n (trueParameter && trueParameter === falseParameter);\n return (\n Boolean(preservesValue) &&\n (referencesStyles(node.trueType, visited, bindings) ||\n referencesStyles(node.falseType, visited, bindings))\n );\n }\n // Nested fields, function arguments and conditional constraints are content,\n // not evidence that the prop's value itself originates in a style type.\n return (\n (T.isUnionTypeNode(node) || T.isIntersectionTypeNode(node)) &&\n node.types.some((part) => referencesStyles(part, visited, bindings))\n );\n } finally {\n visited.delete(node);\n }\n }\n\n function unresolved(type: ts.Type): boolean {\n if (\n type.flags &\n (T.TypeFlags.Any |\n T.TypeFlags.Unknown |\n T.TypeFlags.TypeParameter |\n T.TypeFlags.Conditional |\n T.TypeFlags.IndexedAccess |\n T.TypeFlags.Never)\n )\n return true;\n return type.isUnionOrIntersection() && type.types.some(unresolved);\n }\n\n function hasPrimitiveFlag(type: ts.Type, flags: number): boolean {\n return (\n Boolean(type.flags & flags) ||\n (type.isIntersection() &&\n type.types.some((part) => hasPrimitiveFlag(part, flags)))\n );\n }\n\n function unresolvedComponentProp(\n node: ts.JsxAttribute,\n name: string,\n ): boolean {\n const tag = node.parent.parent.tagName;\n const component = checker.getTypeAtLocation(tag);\n return [\n ...component.getCallSignatures(),\n ...component.getConstructSignatures(),\n ].some((signature) => {\n const parameter = signature.parameters[0];\n if (!parameter) return false;\n const props = checker.getTypeOfSymbolAtLocation(parameter, tag);\n const property = checker.getPropertyOfType(props, name);\n return property\n ? unresolved(checker.getTypeOfSymbolAtLocation(property, tag))\n : false;\n });\n }\n\n const results = new Map<number, PropKind>();\n return (attribute) => {\n const start = attribute.range[0];\n const cached = results.get(start);\n if (cached) return cached;\n const node = attributes.get(start);\n let kind: PropKind = 'unknown';\n if (node && node.end === attribute.range[1] && T.isIdentifier(node.name)) {\n const props = checker.getContextualType(node.parent);\n const property =\n props && checker.getPropertyOfType(props, node.name.text);\n const declarations = property?.declarations;\n if (property && declarations?.length) {\n const type = checker.getTypeOfSymbolAtLocation(property, node);\n if (\n declarations.some((d) => isStyleDeclaration(d) || referencesStyles(d))\n ) {\n kind = 'style';\n } else if (\n !unresolved(type) &&\n !(\n declarations.some((declaration) =>\n unresolved(checker.getTypeAtLocation(declaration)),\n ) && unresolvedComponentProp(node, node.name.text)\n )\n ) {\n // Broad primitives may be hand-written style props. Preserve checks.\n const nonNullable = checker.getNonNullableType(type);\n const parts = nonNullable.isUnion()\n ? nonNullable.types\n : [nonNullable];\n const primitive =\n T.TypeFlags.StringLike |\n T.TypeFlags.NumberLike |\n T.TypeFlags.BooleanLike |\n T.TypeFlags.BigIntLike;\n const broad =\n T.TypeFlags.String |\n T.TypeFlags.Number |\n T.TypeFlags.Boolean |\n T.TypeFlags.BigInt;\n const broadPrimitive =\n parts.every((part) => hasPrimitiveFlag(part, primitive)) &&\n (Boolean(nonNullable.flags & broad) ||\n parts.some((part) => hasPrimitiveFlag(part, broad)) ||\n parts.every((part) =>\n hasPrimitiveFlag(part, T.TypeFlags.BooleanLike),\n ));\n if (!broadPrimitive) kind = 'component';\n }\n }\n }\n results.set(start, kind);\n return kind;\n };\n}\n\nfunction typeAwareJSXOption(\n context: RuleContext<string, unknown[]>,\n): boolean | TypeAwareJSXOptions | undefined {\n const settings = context.settings.tasty as\n | { typeAwareJSX?: boolean | TypeAwareJSXOptions }\n | undefined;\n return settings?.typeAwareJSX;\n}\n\n/** Whether `settings.tasty.typeAwareJSX` turns the classifier on. */\nexport function isTypeAwareJSX(\n context: RuleContext<string, unknown[]>,\n): boolean {\n const option = typeAwareJSXOption(context);\n return option !== undefined && option !== false;\n}\n\n/** No compiler is loaded, or program created, unless the setting is enabled. */\nexport function jsxPropKind(\n context: RuleContext<string, unknown[]>,\n attribute: TSESTree.JSXAttribute,\n): PropKind {\n const option = typeAwareJSXOption(context);\n if (option === undefined || option === false) return 'unknown';\n if (\n option !== true &&\n (!option ||\n typeof option !== 'object' ||\n Array.isArray(option) ||\n (option.project !== undefined && typeof option.project !== 'string'))\n )\n throw new Error(\n 'tasty: typeAwareJSX must be true, false, or { project: string }.',\n );\n\n const filename = resolve(context.cwd, context.filename);\n const explicit = option === true ? undefined : option.project;\n const sourceCode = context.sourceCode;\n const key = explicit ? resolve(context.cwd, explicit) : 'nearest';\n let byProject = classifiers.get(sourceCode);\n if (!byProject) {\n byProject = new Map();\n classifiers.set(sourceCode, byProject);\n }\n let cached = byProject.get(key);\n if (\n !cached ||\n cached.filename !== filename ||\n cached.text !== sourceCode.text\n ) {\n const T = getTypeScript();\n const configPath = explicit\n ? key\n : T.findConfigFile(dirname(filename), T.sys.fileExists);\n if (!configPath)\n throw new Error(\n `tasty: typeAwareJSX could not find a tsconfig for ${filename}.`,\n );\n const program = getProgram(T, configPath, filename, sourceCode.text);\n cached = {\n filename,\n text: sourceCode.text,\n classify: makeClassifier(T, program, filename, sourceCode.text),\n };\n byProject.set(key, cached);\n }\n return cached.classify(attribute);\n}\n\n/** A SourceCode object may be reused by ESLint after dependencies change. */\nexport function finishJSXAnalysis(\n context: RuleContext<string, unknown[]>,\n): void {\n classifiers.delete(context.sourceCode);\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -12,9 +12,16 @@ interface StyleFunctionConfig {
12
12
  interface ProtectedComponentConfig {
13
13
  /** Package the components are imported from. `*` matches any run of characters. */
14
14
  source: string;
15
- /** Exported names to protect, `*` matching any run of characters. Defaults to every export. */
15
+ /**
16
+ * Exported names to protect, `*` matching any run of characters. Defaults to
17
+ * every export. A name with a dot is a member path, such as `Button.Split` or
18
+ * `Menu.*`, and wins over an entry that matches only the export.
19
+ */
16
20
  include?: string[];
17
- /** Exported names to leave out, such as layout and typography primitives. */
21
+ /**
22
+ * Exported names or member paths to leave out, such as layout and typography
23
+ * primitives, or a separate component attached as `Button.Group`.
24
+ */
18
25
  exclude?: string[];
19
26
  /**
20
27
  * Style props that place or size the root and stay allowed: `'position'` and
package/dist/utils.js CHANGED
@@ -8,6 +8,15 @@ function unwrapExpression(node) {
8
8
  return current;
9
9
  }
10
10
  /** A whole-string pattern in which `*` matches any run of characters. */
11
+ /**
12
+ * A variable's one value definition. TypeScript declares a same-named type or
13
+ * interface (`const Box = tasty(…); type Box = …`) on the same variable, so
14
+ * type definitions are set aside; null for a redeclared or type-only name.
15
+ */
16
+ function valueDefinition(variable) {
17
+ const defs = variable.defs.filter((def) => def.type !== "Type");
18
+ return defs.length === 1 ? defs[0] : null;
19
+ }
11
20
  function wildcardPattern(pattern) {
12
21
  const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
22
  return new RegExp(`^${escaped.replace(/\\\*/g, ".*")}$`);
@@ -128,5 +137,5 @@ function collectLocalStateAliases(node) {
128
137
  }
129
138
 
130
139
  //#endregion
131
- export { collectLocalStateAliases, getKeyName, getStringValue, isKnownStateAlias, isRawHexColor, isStaticValue, isValidSelector, isValidUnit, unwrapExpression, validateColorTokenSyntax, wildcardPattern };
140
+ export { collectLocalStateAliases, getKeyName, getStringValue, isKnownStateAlias, isRawHexColor, isStaticValue, isValidSelector, isValidUnit, unwrapExpression, validateColorTokenSyntax, valueDefinition, wildcardPattern };
132
141
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport {\n BUILT_IN_UNITS,\n CSS_UNITS,\n BUILT_IN_STATE_PREFIXES,\n} from './constants.js';\nimport type { ResolvedConfig } from './types.js';\n\n/** Strip TypeScript wrappers that do not change an expression's runtime value. */\nexport function unwrapExpression(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === 'TSAsExpression' ||\n current.type === 'TSSatisfiesExpression' ||\n current.type === 'TSNonNullExpression' ||\n current.type === 'TSTypeAssertion' ||\n current.type === 'TSInstantiationExpression'\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\n/** A whole-string pattern in which `*` matches any run of characters. */\nexport function wildcardPattern(pattern: string): RegExp {\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, '.*')}$`);\n}\n\n/**\n * Gets the string value of a property key node.\n */\nexport function getKeyName(key: TSESTree.Node): string | null {\n if (key.type === 'Identifier') return key.name;\n if (key.type === 'Literal' && typeof key.value === 'string') return key.value;\n if (key.type === 'Literal' && typeof key.value === 'number')\n return String(key.value);\n return null;\n}\n\n/**\n * Gets the string value of a node if it is a string literal.\n */\nexport function getStringValue(node: TSESTree.Node): string | null {\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {\n return node.quasis[0].value.cooked ?? null;\n }\n return null;\n}\n\n/**\n * Checks if a value node is a static literal.\n */\nexport function isStaticValue(node: TSESTree.Node): boolean {\n node = unwrapExpression(node);\n if (node.type === 'Literal') return true;\n if (\n node.type === 'UnaryExpression' &&\n node.operator === '-' &&\n node.argument.type === 'Literal'\n ) {\n return true;\n }\n if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {\n return true;\n }\n if (node.type === 'ArrayExpression') {\n return node.elements.every((el) => el !== null && isStaticValue(el));\n }\n if (node.type === 'ObjectExpression') {\n return node.properties.every(\n (prop) =>\n prop.type === 'Property' && !prop.computed && isStaticValue(prop.value),\n );\n }\n return false;\n}\n\n/**\n * Validates color token syntax.\n * Returns null if valid, or an error message if invalid.\n */\nexport function validateColorTokenSyntax(token: string): string | null {\n // Strip leading # or ##\n let name = token;\n if (name.startsWith('##')) {\n name = name.slice(2);\n } else if (name.startsWith('#')) {\n name = name.slice(1);\n } else {\n return 'Color token must start with #';\n }\n\n if (name.length === 0) return 'Empty color token name';\n\n // Check for opacity suffix\n const dotIndex = name.indexOf('.');\n if (dotIndex !== -1) {\n const tokenName = name.slice(0, dotIndex);\n const opacitySuffix = name.slice(dotIndex + 1);\n\n if (tokenName.length === 0) return 'Empty color token name before opacity';\n\n if (opacitySuffix.startsWith('$')) {\n // Dynamic opacity from CSS custom property — always valid\n return null;\n }\n\n if (opacitySuffix.length === 0) return 'Trailing dot with no opacity value';\n\n // Tasty reads the digits as a decimal fraction (`.5` and `.50` are 50%,\n // `.150` is 15%), so any digit string is in range.\n if (!/^\\d+$/.test(opacitySuffix)) {\n return /^-\\d+$/.test(opacitySuffix)\n ? 'Opacity cannot be negative'\n : `Invalid opacity value '${opacitySuffix}'`;\n }\n }\n\n return null;\n}\n\n/**\n * Checks if a string looks like a raw hex color (not a token).\n * Hex colors: #fff, #ffff, #ffffff, #ffffffff (3, 4, 6, or 8 hex chars).\n */\nexport function isRawHexColor(value: string): boolean {\n if (!value.startsWith('#')) return false;\n const hex = value.slice(1).split('.')[0];\n if (![3, 4, 6, 8].includes(hex.length)) return false;\n return /^[0-9a-fA-F]+$/.test(hex);\n}\n\n/**\n * Extracts custom unit from a value token like \"2x\", \"1.5r\", \"3cols\".\n * Returns the unit name, or null if not a custom-unit value.\n */\nexport function extractCustomUnit(token: string): string | null {\n const match = token.match(/^-?[\\d.]+([a-zA-Z]+)$/);\n if (!match) return null;\n return match[1];\n}\n\n/**\n * Checks if a unit is valid (built-in, CSS, or in config).\n */\nexport function isValidUnit(unit: string, config: ResolvedConfig): boolean {\n if (config.units === false) return true;\n if (BUILT_IN_UNITS.has(unit)) return true;\n if (CSS_UNITS.has(unit)) return true;\n if (Array.isArray(config.units) && config.units.includes(unit)) return true;\n return false;\n}\n\n/**\n * Checks if a state alias key (starting with @) is known.\n */\nexport function isKnownStateAlias(\n key: string,\n config: ResolvedConfig,\n): boolean {\n // Built-in prefixes\n for (const prefix of BUILT_IN_STATE_PREFIXES) {\n if (key === prefix || key.startsWith(prefix + '(')) return true;\n }\n // Container query shorthand\n if (key.startsWith('@(')) return true;\n // Config aliases\n return config.states.includes(key);\n}\n\n/**\n * Checks if a CSS selector string is basically valid.\n */\nexport function isValidSelector(selector: string): string | null {\n if (selector.length === 0) return 'Selector cannot be empty';\n\n // Check balanced brackets\n let depth = 0;\n for (const char of selector) {\n if (char === '(' || char === '[') depth++;\n if (char === ')' || char === ']') depth--;\n if (depth < 0) return 'Unbalanced brackets in selector';\n }\n if (depth !== 0) return 'Unbalanced brackets in selector';\n\n return null;\n}\n\n/**\n * Finds a property by key name in an object expression.\n */\nexport function findProperty(\n obj: TSESTree.ObjectExpression,\n name: string,\n): TSESTree.Property | undefined {\n for (const prop of obj.properties) {\n if (prop.type === 'Property' && !prop.computed) {\n const keyName = getKeyName(prop.key);\n if (keyName === name) return prop;\n }\n }\n return undefined;\n}\n\n/**\n * Collects local predefined state alias names from a styles ObjectExpression.\n * Local states are top-level `@name` keys with string literal values,\n * mirroring the runtime's `extractLocalPredefinedStates()`.\n */\nexport function collectLocalStateAliases(\n node: TSESTree.ObjectExpression,\n): string[] {\n const aliases: string[] = [];\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(key)) continue;\n\n // Skip built-in prefixes — they are not local state definitions\n if (BUILT_IN_STATE_PREFIXES.has(key)) continue;\n\n // Must have a string value to be a state definition\n if (getStringValue(prop.value) !== null) {\n aliases.push(key);\n }\n }\n\n return aliases;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import type { TSESTree } from '@typescript-eslint/utils';\nimport type { Scope } from '@typescript-eslint/utils/ts-eslint';\nimport {\n BUILT_IN_UNITS,\n CSS_UNITS,\n BUILT_IN_STATE_PREFIXES,\n} from './constants.js';\nimport type { ResolvedConfig } from './types.js';\n\n/** Strip TypeScript wrappers that do not change an expression's runtime value. */\nexport function unwrapExpression(node: TSESTree.Node): TSESTree.Node {\n let current = node;\n\n while (\n current.type === 'TSAsExpression' ||\n current.type === 'TSSatisfiesExpression' ||\n current.type === 'TSNonNullExpression' ||\n current.type === 'TSTypeAssertion' ||\n current.type === 'TSInstantiationExpression'\n ) {\n current = current.expression;\n }\n\n return current;\n}\n\n/** A whole-string pattern in which `*` matches any run of characters. */\n/**\n * A variable's one value definition. TypeScript declares a same-named type or\n * interface (`const Box = tasty(…); type Box = …`) on the same variable, so\n * type definitions are set aside; null for a redeclared or type-only name.\n */\nexport function valueDefinition(\n variable: Scope.Variable,\n): Scope.Definition | null {\n const defs = variable.defs.filter((def) => def.type !== 'Type');\n return defs.length === 1 ? defs[0] : null;\n}\n\nexport function wildcardPattern(pattern: string): RegExp {\n const escaped = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, '.*')}$`);\n}\n\n/**\n * Gets the string value of a property key node.\n */\nexport function getKeyName(key: TSESTree.Node): string | null {\n if (key.type === 'Identifier') return key.name;\n if (key.type === 'Literal' && typeof key.value === 'string') return key.value;\n if (key.type === 'Literal' && typeof key.value === 'number')\n return String(key.value);\n return null;\n}\n\n/**\n * Gets the string value of a node if it is a string literal.\n */\nexport function getStringValue(node: TSESTree.Node): string | null {\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value;\n }\n if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {\n return node.quasis[0].value.cooked ?? null;\n }\n return null;\n}\n\n/**\n * Checks if a value node is a static literal.\n */\nexport function isStaticValue(node: TSESTree.Node): boolean {\n node = unwrapExpression(node);\n if (node.type === 'Literal') return true;\n if (\n node.type === 'UnaryExpression' &&\n node.operator === '-' &&\n node.argument.type === 'Literal'\n ) {\n return true;\n }\n if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {\n return true;\n }\n if (node.type === 'ArrayExpression') {\n return node.elements.every((el) => el !== null && isStaticValue(el));\n }\n if (node.type === 'ObjectExpression') {\n return node.properties.every(\n (prop) =>\n prop.type === 'Property' && !prop.computed && isStaticValue(prop.value),\n );\n }\n return false;\n}\n\n/**\n * Validates color token syntax.\n * Returns null if valid, or an error message if invalid.\n */\nexport function validateColorTokenSyntax(token: string): string | null {\n // Strip leading # or ##\n let name = token;\n if (name.startsWith('##')) {\n name = name.slice(2);\n } else if (name.startsWith('#')) {\n name = name.slice(1);\n } else {\n return 'Color token must start with #';\n }\n\n if (name.length === 0) return 'Empty color token name';\n\n // Check for opacity suffix\n const dotIndex = name.indexOf('.');\n if (dotIndex !== -1) {\n const tokenName = name.slice(0, dotIndex);\n const opacitySuffix = name.slice(dotIndex + 1);\n\n if (tokenName.length === 0) return 'Empty color token name before opacity';\n\n if (opacitySuffix.startsWith('$')) {\n // Dynamic opacity from CSS custom property — always valid\n return null;\n }\n\n if (opacitySuffix.length === 0) return 'Trailing dot with no opacity value';\n\n // Tasty reads the digits as a decimal fraction (`.5` and `.50` are 50%,\n // `.150` is 15%), so any digit string is in range.\n if (!/^\\d+$/.test(opacitySuffix)) {\n return /^-\\d+$/.test(opacitySuffix)\n ? 'Opacity cannot be negative'\n : `Invalid opacity value '${opacitySuffix}'`;\n }\n }\n\n return null;\n}\n\n/**\n * Checks if a string looks like a raw hex color (not a token).\n * Hex colors: #fff, #ffff, #ffffff, #ffffffff (3, 4, 6, or 8 hex chars).\n */\nexport function isRawHexColor(value: string): boolean {\n if (!value.startsWith('#')) return false;\n const hex = value.slice(1).split('.')[0];\n if (![3, 4, 6, 8].includes(hex.length)) return false;\n return /^[0-9a-fA-F]+$/.test(hex);\n}\n\n/**\n * Extracts custom unit from a value token like \"2x\", \"1.5r\", \"3cols\".\n * Returns the unit name, or null if not a custom-unit value.\n */\nexport function extractCustomUnit(token: string): string | null {\n const match = token.match(/^-?[\\d.]+([a-zA-Z]+)$/);\n if (!match) return null;\n return match[1];\n}\n\n/**\n * Checks if a unit is valid (built-in, CSS, or in config).\n */\nexport function isValidUnit(unit: string, config: ResolvedConfig): boolean {\n if (config.units === false) return true;\n if (BUILT_IN_UNITS.has(unit)) return true;\n if (CSS_UNITS.has(unit)) return true;\n if (Array.isArray(config.units) && config.units.includes(unit)) return true;\n return false;\n}\n\n/**\n * Checks if a state alias key (starting with @) is known.\n */\nexport function isKnownStateAlias(\n key: string,\n config: ResolvedConfig,\n): boolean {\n // Built-in prefixes\n for (const prefix of BUILT_IN_STATE_PREFIXES) {\n if (key === prefix || key.startsWith(prefix + '(')) return true;\n }\n // Container query shorthand\n if (key.startsWith('@(')) return true;\n // Config aliases\n return config.states.includes(key);\n}\n\n/**\n * Checks if a CSS selector string is basically valid.\n */\nexport function isValidSelector(selector: string): string | null {\n if (selector.length === 0) return 'Selector cannot be empty';\n\n // Check balanced brackets\n let depth = 0;\n for (const char of selector) {\n if (char === '(' || char === '[') depth++;\n if (char === ')' || char === ']') depth--;\n if (depth < 0) return 'Unbalanced brackets in selector';\n }\n if (depth !== 0) return 'Unbalanced brackets in selector';\n\n return null;\n}\n\n/**\n * Finds a property by key name in an object expression.\n */\nexport function findProperty(\n obj: TSESTree.ObjectExpression,\n name: string,\n): TSESTree.Property | undefined {\n for (const prop of obj.properties) {\n if (prop.type === 'Property' && !prop.computed) {\n const keyName = getKeyName(prop.key);\n if (keyName === name) return prop;\n }\n }\n return undefined;\n}\n\n/**\n * Collects local predefined state alias names from a styles ObjectExpression.\n * Local states are top-level `@name` keys with string literal values,\n * mirroring the runtime's `extractLocalPredefinedStates()`.\n */\nexport function collectLocalStateAliases(\n node: TSESTree.ObjectExpression,\n): string[] {\n const aliases: string[] = [];\n\n for (const prop of node.properties) {\n if (prop.type !== 'Property' || prop.computed) continue;\n\n const key = getKeyName(prop.key);\n if (key === null) continue;\n if (!/^@[A-Za-z][A-Za-z0-9-]*$/.test(key)) continue;\n\n // Skip built-in prefixes — they are not local state definitions\n if (BUILT_IN_STATE_PREFIXES.has(key)) continue;\n\n // Must have a string value to be a state definition\n if (getStringValue(prop.value) !== null) {\n aliases.push(key);\n }\n }\n\n return aliases;\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.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "ESLint plugin for validating tasty() and tastyStatic() style objects",
5
5
  "type": "module",
6
6
  "exports": {
@@ -56,6 +56,7 @@
56
56
  "@types/node": "^22.0.0",
57
57
  "@typescript-eslint/parser": "^8.56.1",
58
58
  "@typescript-eslint/rule-tester": "^8.56.1",
59
+ "@webref/css": "^8.7.8",
59
60
  "eslint": "^10.0.0",
60
61
  "eslint-config-prettier": "^10.1.8",
61
62
  "known-css-properties": "^0.37.0",