@tenphi/eslint-plugin-tasty 1.13.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -268,6 +268,7 @@ not matched.
268
268
  | `tasty/prefer-state-negation` | warn | Top-level `:not(...)` in state keys instead of the `!` prefix (autofixable; nested CSS selectors stay intact) |
269
269
  | `tasty/prefer-custom-property-syntax` | warn | `var(--prop)` / `$x-color` / `transparent` / `currentColor` instead of `$prop` / `#color` / `#clear` / `#current` |
270
270
  | `tasty/prefer-hide` | warn | `display: 'none'` instead of `hide: true` |
271
+ | `tasty/prefer-fill-mix` | warn | Deprecated two-color `fill`, space-separated (`'#surface #dark.04'`) or comma-mixed (`'#dark 4%, #surface'`), instead of a compound color (`'#surface+#dark.04'`) or `color-mix()`, autofixed when the result is the same color; mix parts weighted by their opacity (`'#dark.04, #surface'`). Needs `@tenphi/tasty` 3.15, and 3.16 for compound colors ([details](docs/rules/prefer-fill-mix.md)) |
271
272
  | `tasty/prefer-directional-shorthand` | warn | 4-value `margin`/`padding`/`inset`/`radius`/`fade`/`border` with placeholder positions instead of directional form |
272
273
  | `tasty/prefer-longhand-property` | error | Lossy `flex` shorthand instead of `flexGrow` / `flexShrink` / `flexBasis` |
273
274
 
package/dist/config.js CHANGED
@@ -377,6 +377,37 @@ function mtimesMatch(cached, current) {
377
377
  for (const [path, mtime] of cached) if (current.get(path) !== mtime) return false;
378
378
  return true;
379
379
  }
380
+ const tastyVersionCache = /* @__PURE__ */ new Map();
381
+ /**
382
+ * The version of `@tenphi/tasty` installed for the project `filePath` belongs
383
+ * to, or `null` when it cannot be found. Rules whose fixes need a newer Tasty
384
+ * check it, so `--fix` never writes syntax the installed Tasty cannot render.
385
+ */
386
+ function getInstalledTastyVersion(filePath) {
387
+ const projectRoot = findProjectRoot(dirname(resolve(filePath)));
388
+ if (!projectRoot) return null;
389
+ const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);
390
+ if (!pkgDir) return null;
391
+ const pkgJson = join(pkgDir, "package.json");
392
+ let mtime;
393
+ try {
394
+ mtime = statSync(pkgJson).mtimeMs;
395
+ } catch {
396
+ return null;
397
+ }
398
+ const cached = tastyVersionCache.get(pkgJson);
399
+ if (cached?.mtime === mtime) return cached.version;
400
+ let version = null;
401
+ try {
402
+ const parsed = JSON.parse(readFileSync(pkgJson, "utf-8"));
403
+ if (typeof parsed.version === "string") version = parsed.version;
404
+ } catch {}
405
+ tastyVersionCache.set(pkgJson, {
406
+ mtime,
407
+ version
408
+ });
409
+ return version;
410
+ }
380
411
  function loadConfig(filePath) {
381
412
  const fileDir = dirname(resolve(filePath));
382
413
  const projectRoot = findProjectRoot(fileDir);
@@ -409,5 +440,5 @@ function loadConfig(filePath) {
409
440
  }
410
441
 
411
442
  //#endregion
412
- export { loadConfig };
443
+ export { getInstalledTastyVersion, loadConfig };
413
444
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":["pluginVersion"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport { createJiti } from 'jiti';\nimport semver from 'semver';\n\nimport type { ResolvedConfig, TastyValidationConfig } from './types.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.js';\nimport { version as pluginVersion } from './version.js';\n\nconst CONFIG_FILENAMES = [\n 'tasty.config.ts',\n 'tasty.config.js',\n 'tasty.config.mjs',\n 'tasty.config.json',\n];\n\ninterface CachedConfig {\n /** The resolved config, or the error that loading it failed with. */\n result: ResolvedConfig | Error;\n fileMtimes: Map<string, number>;\n}\n\nconst configCache = new Map<string, CachedConfig>();\n\n/**\n * Every key a config may carry; anything else is a typo or from a newer plugin.\n * Typed against the config interface so a new key cannot be missed here.\n */\nconst KNOWN_KEYS = new Set(\n Object.keys({\n extends: true,\n requires: true,\n tokens: true,\n units: true,\n functions: true,\n funcs: true,\n states: true,\n presets: true,\n recipes: true,\n styles: true,\n importSources: true,\n ownedSources: true,\n styleFunctions: true,\n protectedComponents: true,\n // The Tasty VS Code extension reads these from the same file.\n presetDescriptions: true,\n stateDescriptions: true,\n } satisfies Record<keyof TastyValidationConfig, true>),\n);\n\nconst warnings = new Set<string>();\n\n/** A config problem worth a message but not a failed run, said once per process. */\nfunction warnOnce(message: string): void {\n if (warnings.has(message)) return;\n warnings.add(message);\n console.warn(`[eslint-plugin-tasty] ${message}`);\n}\n\n/** Fails the run when the installed plugin is outside the config's `requires`. */\nfunction checkRequires(config: TastyValidationConfig, configPath: string) {\n const range: unknown = config.requires;\n if (range === undefined) return;\n if (\n typeof range !== 'string' ||\n range.trim() === '' ||\n semver.validRange(range) === null\n ) {\n throw new Error(\n `tasty: ${configPath}: 'requires' must be a semver range of @tenphi/eslint-plugin-tasty versions, such as '>=1.13.0'.`,\n );\n }\n // A snapshot of the next release still satisfies `>=` the current one.\n if (!semver.satisfies(pluginVersion, range, { includePrerelease: true })) {\n throw new Error(\n `tasty: ${configPath} requires @tenphi/eslint-plugin-tasty ${range}, but ${pluginVersion} is installed. Upgrade the plugin.`,\n );\n }\n}\n\n/** List keys, and whether `false` (turning the check off) is allowed. */\nconst LIST_KEYS = {\n tokens: true,\n units: true,\n functions: true,\n funcs: true,\n states: false,\n presets: false,\n recipes: false,\n styles: false,\n importSources: false,\n ownedSources: false,\n} as const;\n\n/** Keys Tasty's runtime config (`configure()`) holds as objects keyed by name. */\nconst RUNTIME_OBJECT_KEYS = [\n 'tokens',\n 'units',\n 'functions',\n 'states',\n 'presets',\n 'recipes',\n];\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) {\n return value.every((item) => typeof item === 'string')\n ? 'a list'\n : 'a list with a non-string entry';\n }\n return isPlainObject(value) ? 'an object' : `a ${typeof value}`;\n}\n\n/**\n * Warns about keys the plugin does not read and known keys of the wrong shape,\n * and returns the config without the latter, so `states: '@mobile'` is not\n * read character by character.\n */\nfunction checkKeys(\n config: TastyValidationConfig,\n configPath: string,\n): TastyValidationConfig {\n // Tasty's SSR guide keeps the runtime config in a `tasty.config.ts` too.\n const runtime = RUNTIME_OBJECT_KEYS.some((key) =>\n isPlainObject((config as Record<string, unknown>)[key]),\n )\n ? \" It looks like Tasty's runtime config, which the plugin does not read.\"\n : '';\n\n const unknown = Object.keys(config).filter(\n (key) => !KNOWN_KEYS.has(key) && key !== '__esModule',\n );\n if (unknown.length > 0) {\n const keys = unknown.map((key) => `'${key}'`).join(', ');\n warnOnce(\n `${configPath}: ignoring unknown ${unknown.length === 1 ? 'key' : 'keys'} ${keys}.${\n runtime ||\n ` Check the spelling, or upgrade @tenphi/eslint-plugin-tasty (${pluginVersion} is installed) if the config was written for a newer version.`\n }`,\n );\n }\n\n const checked: Record<string, unknown> = { ...config };\n const ignore = (key: string, expected: string) => {\n warnOnce(\n `${configPath}: ignoring '${key}', which must be ${expected}, not ${describe(checked[key])}.${runtime}`,\n );\n delete checked[key];\n };\n for (const [key, allowsFalse] of Object.entries(LIST_KEYS)) {\n const value = checked[key];\n if (\n value === undefined ||\n (value === false && allowsFalse) ||\n (Array.isArray(value) && value.every((item) => typeof item === 'string'))\n ) {\n continue;\n }\n ignore(key, `${allowsFalse ? 'false or ' : ''}a list of names`);\n }\n if (\n checked.styleFunctions !== undefined &&\n !isPlainObject(checked.styleFunctions)\n ) {\n ignore('styleFunctions', 'an object keyed by function name');\n }\n return checked as TastyValidationConfig;\n}\n\nfunction findProjectRoot(startDir: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(\n packageName: string,\n startDir: string,\n): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n const candidate = join(dir, 'node_modules', ...packageName.split('/'));\n if (existsSync(candidate) && existsSync(join(candidate, 'package.json'))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction findConfigInDir(dir: string): string | null {\n for (const name of CONFIG_FILENAMES) {\n const path = join(dir, name);\n if (existsSync(path)) {\n return path;\n }\n }\n return null;\n}\n\nfunction collectConfigFiles(startDir: string, projectRoot: string): string[] {\n const configs: string[] = [];\n let dir = startDir;\n const root = resolve(projectRoot);\n\n while (true) {\n const found = findConfigInDir(dir);\n if (found) configs.push(found);\n if (resolve(dir) === root) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return configs;\n}\n\nconst TASTY_PACKAGE_NAME = '@tenphi/tasty';\n\nfunction stripComments(source: string): string {\n let result = '';\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n result += ch;\n i++;\n while (i < source.length && source[i] !== ch) {\n if (source[i] === '\\\\') {\n result += source[i++];\n }\n if (i < source.length) {\n result += source[i++];\n }\n }\n if (i < source.length) {\n result += source[i++];\n }\n continue;\n }\n if (ch === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i += 2;\n continue;\n }\n if (ch === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') {\n i++;\n }\n continue;\n }\n result += source[i++];\n }\n return result;\n}\n\nfunction stripImports(source: string): string {\n return source.replace(/^\\s*import\\s+.*?;\\s*$/gm, '');\n}\n\nfunction extractBalancedBraces(content: string, start: number): string | null {\n if (content[start] !== '{') return null;\n let depth = 0;\n for (let i = start; i < content.length; i++) {\n const ch = content[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n i++;\n while (i < content.length && content[i] !== ch) {\n if (content[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n if (depth === 0) return content.slice(start, i + 1);\n }\n return null;\n}\n\nfunction extractImportPaths(configPath: string): string[] {\n const source = readFileSync(configPath, 'utf-8');\n const configDir = dirname(configPath);\n const paths: string[] = [];\n const re = /^\\s*import\\s+.*?\\s+from\\s+['\"]([^'\"]+)['\"]/gm;\n let m;\n\n while ((m = re.exec(source)) !== null) {\n const specifier = m[1];\n\n if (!specifier.startsWith('.')) continue;\n\n const abs = resolve(configDir, specifier);\n\n for (const ext of ['', '.ts', '.js', '.mjs', '.tsx', '.jsx']) {\n const candidate = abs + ext;\n\n if (existsSync(candidate)) {\n paths.push(candidate);\n break;\n }\n }\n }\n\n return paths;\n}\n\nfunction stripTypeScriptSyntax(source: string): string {\n return source\n .replace(/\\bas\\s+const\\b/g, '')\n .replace(/\\bsatisfies\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '')\n .replace(/\\bas\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '');\n}\n\nfunction loadRawConfig(configPath: string): TastyValidationConfig {\n if (configPath.endsWith('.json')) {\n return JSON.parse(\n readFileSync(configPath, 'utf-8'),\n ) as TastyValidationConfig;\n }\n\n // Try jiti first — it resolves imports and TypeScript natively.\n try {\n const jiti = createJiti(dirname(configPath), { moduleCache: false });\n const mod = jiti(configPath) as Record<string, unknown>;\n const config = (mod.default ?? mod) as TastyValidationConfig;\n\n if (config && typeof config === 'object') {\n return config;\n }\n } catch {\n // Fall through to text-based parsing.\n }\n\n // Fallback: text-based extraction (no import support).\n const content = readFileSync(configPath, 'utf-8');\n const stripped = stripImports(stripComments(content));\n const match = stripped.match(/export\\s+default\\s+/);\n\n if (match && match.index != null) {\n const braceStart = match.index + match[0].length;\n const objectStr = extractBalancedBraces(stripped, braceStart);\n\n if (objectStr) {\n const cleaned = stripTypeScriptSyntax(objectStr);\n\n try {\n const fn = new Function(`return (${cleaned})`);\n return fn() as TastyValidationConfig;\n } catch (err) {\n console.warn(\n `[eslint-plugin-tasty] Failed to parse config file ${configPath}: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n }\n\n return {};\n}\n\nfunction mergeConfigs(\n parent: TastyValidationConfig,\n child: TastyValidationConfig,\n): TastyValidationConfig {\n const result: TastyValidationConfig = { ...parent };\n\n const arrayKeys = [\n ...(Object.keys(LIST_KEYS) as (keyof typeof LIST_KEYS)[]),\n 'protectedComponents',\n ] as const;\n\n for (const key of arrayKeys) {\n const childVal = child[key];\n if (childVal === undefined) continue;\n\n if (childVal === false) {\n (result as Record<string, unknown>)[key] = false;\n continue;\n }\n\n const parentVal = parent[key];\n if (Array.isArray(parentVal) && Array.isArray(childVal)) {\n (result as Record<string, unknown>)[key] = [\n ...new Set([...parentVal, ...childVal]),\n ];\n } else {\n (result as Record<string, unknown>)[key] = childVal;\n }\n }\n\n if (child.styleFunctions !== undefined) {\n result.styleFunctions = {\n ...parent.styleFunctions,\n ...child.styleFunctions,\n };\n }\n\n return result;\n}\n\n/**\n * The config file `extends` points to, or null when there is none. A relative\n * or absolute path that is not a file fails the run, as reading it always has; a\n * value that names nothing usable is skipped with a warning.\n */\nfunction resolveExtends(\n config: TastyValidationConfig,\n configPath: string,\n touched: string[],\n): string | null {\n const target: unknown = config.extends;\n if (target === undefined) return null;\n const configDir = dirname(configPath);\n if (typeof target !== 'string' || target === '') {\n warnOnce(\n `${configPath}: 'extends' must be a config path or a package name, not ${JSON.stringify(target)}. It is ignored.`,\n );\n return null;\n }\n\n if (target.startsWith('.') || target.startsWith('/')) {\n const parentPath = resolve(configDir, target);\n // Watched, so creating the file clears a cached failure.\n touched.push(parentPath);\n if (!isFile(parentPath)) {\n throw new Error(\n `tasty: ${configPath}: 'extends' points to ${parentPath}, which is not a file.`,\n );\n }\n return parentPath;\n }\n\n // `@scope/pkg/path/to/config` names a file inside the package.\n const parts = target.split('/');\n const packageName = parts.slice(0, target.startsWith('@') ? 2 : 1).join('/');\n const subpath = target.slice(packageName.length + 1);\n const pkgDir = resolvePackageDir(packageName, configDir);\n if (!pkgDir) {\n warnOnce(\n `${configPath}: 'extends' names '${packageName}', which cannot be resolved from ${configDir}. It is ignored.`,\n );\n return null;\n }\n const pkgConfig = subpath\n ? findConfigFile(join(pkgDir, subpath))\n : findConfigInDir(pkgDir);\n if (!pkgConfig) {\n warnOnce(\n subpath\n ? `${configPath}: 'extends' names '${target}', but ${join(pkgDir, subpath)} is not a config file. It is ignored.`\n : `${configPath}: 'extends' names '${target}', which has no tasty.config.* at ${pkgDir}. It is ignored.`,\n );\n }\n return pkgConfig;\n}\n\nfunction isFile(path: string): boolean {\n return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;\n}\n\n/** A config file at `path`, with or without its extension, or in that directory. */\nfunction findConfigFile(path: string): string | null {\n for (const ext of ['', '.ts', '.js', '.mjs', '.json']) {\n if (isFile(path + ext)) return path + ext;\n }\n return statSync(path, { throwIfNoEntry: false })?.isDirectory()\n ? findConfigInDir(path)\n : null;\n}\n\n/**\n * Loads a config and the chain it extends, parents merged first. Every file\n * read (or expected) is pushed to `touched`, whose mtimes decide when the\n * result, or a failure, is stale.\n */\nfunction resolveConfigChain(\n configPath: string,\n touched: string[],\n visited = new Set<string>(),\n): TastyValidationConfig {\n const absPath = resolve(configPath);\n if (visited.has(absPath)) {\n warnOnce(\n `${absPath} is extended again by its own extends chain. The cycle is ignored.`,\n );\n return {};\n }\n visited.add(absPath);\n touched.push(absPath);\n\n let config = loadRawConfig(absPath);\n touched.push(...extractImportPaths(absPath));\n\n if (!config || typeof config !== 'object' || Array.isArray(config)) {\n warnOnce(`${absPath}: the config must be an object. It is ignored.`);\n config = {};\n }\n checkRequires(config, absPath);\n config = checkKeys(config, absPath);\n\n const parentPath = resolveExtends(config, absPath, touched);\n if (!parentPath) return config;\n\n return mergeConfigs(resolveConfigChain(parentPath, touched, visited), config);\n}\n\nfunction toResolved(config: TastyValidationConfig): ResolvedConfig {\n return {\n tokens: config.tokens ?? [],\n units: config.units ?? [],\n // `funcs` is the v2 spelling; `functions` wins when both are present.\n functions: config.functions ?? config.funcs ?? [],\n states: config.states ?? [],\n presets: config.presets ?? [],\n recipes: config.recipes ?? [],\n styles: config.styles ?? [],\n importSources: config.importSources ?? DEFAULT_IMPORT_SOURCES,\n ownedSources: config.ownedSources ?? [],\n styleFunctions: config.styleFunctions ?? {},\n protectedComponents: config.protectedComponents || [],\n };\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n tokens: [],\n units: [],\n functions: [],\n states: [],\n presets: [],\n recipes: [],\n styles: [],\n importSources: DEFAULT_IMPORT_SOURCES,\n ownedSources: [],\n styleFunctions: {},\n protectedComponents: [],\n};\n\nfunction getMtimes(paths: string[]): Map<string, number> {\n const mtimes = new Map<string, number>();\n for (const p of paths) {\n try {\n mtimes.set(p, statSync(p).mtimeMs);\n } catch {\n mtimes.set(p, -1);\n }\n }\n return mtimes;\n}\n\nfunction mtimesMatch(\n cached: Map<string, number>,\n current: Map<string, number>,\n): boolean {\n if (cached.size !== current.size) return false;\n for (const [path, mtime] of cached) {\n if (current.get(path) !== mtime) return false;\n }\n return true;\n}\n\nexport function loadConfig(filePath: string): ResolvedConfig {\n const fileDir = dirname(resolve(filePath));\n const projectRoot = findProjectRoot(fileDir);\n if (!projectRoot) return DEFAULT_CONFIG;\n\n const configFiles = collectConfigFiles(fileDir, projectRoot);\n\n const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);\n const pkgConfigFile = pkgDir ? findConfigInDir(pkgDir) : null;\n\n if (configFiles.length === 0 && !pkgConfigFile) return DEFAULT_CONFIG;\n\n const cacheKey = [pkgConfigFile ?? '', ...configFiles].join('\\0');\n\n const cached = configCache.get(cacheKey);\n if (\n cached &&\n mtimesMatch(cached.fileMtimes, getMtimes([...cached.fileMtimes.keys()]))\n ) {\n if (cached.result instanceof Error) throw cached.result;\n return cached.result;\n }\n\n // A failure is cached too: oxlint keeps linting after a plugin error, and\n // every rule on every file would otherwise reload the chain to fail again.\n const touched: string[] = [];\n let result: ResolvedConfig | Error;\n try {\n let merged: TastyValidationConfig = pkgConfigFile\n ? resolveConfigChain(pkgConfigFile, touched)\n : {};\n for (let i = configFiles.length - 1; i >= 0; i--) {\n merged = mergeConfigs(\n merged,\n resolveConfigChain(configFiles[i], touched),\n );\n }\n result = toResolved(merged);\n } catch (error) {\n result = error instanceof Error ? error : new Error(String(error));\n }\n\n configCache.set(cacheKey, { result, fileMtimes: getMtimes(touched) });\n if (result instanceof Error) throw result;\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config.js","names":["pluginVersion"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport { createJiti } from 'jiti';\nimport semver from 'semver';\n\nimport type { ResolvedConfig, TastyValidationConfig } from './types.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.js';\nimport { version as pluginVersion } from './version.js';\n\nconst CONFIG_FILENAMES = [\n 'tasty.config.ts',\n 'tasty.config.js',\n 'tasty.config.mjs',\n 'tasty.config.json',\n];\n\ninterface CachedConfig {\n /** The resolved config, or the error that loading it failed with. */\n result: ResolvedConfig | Error;\n fileMtimes: Map<string, number>;\n}\n\nconst configCache = new Map<string, CachedConfig>();\n\n/**\n * Every key a config may carry; anything else is a typo or from a newer plugin.\n * Typed against the config interface so a new key cannot be missed here.\n */\nconst KNOWN_KEYS = new Set(\n Object.keys({\n extends: true,\n requires: true,\n tokens: true,\n units: true,\n functions: true,\n funcs: true,\n states: true,\n presets: true,\n recipes: true,\n styles: true,\n importSources: true,\n ownedSources: true,\n styleFunctions: true,\n protectedComponents: true,\n // The Tasty VS Code extension reads these from the same file.\n presetDescriptions: true,\n stateDescriptions: true,\n } satisfies Record<keyof TastyValidationConfig, true>),\n);\n\nconst warnings = new Set<string>();\n\n/** A config problem worth a message but not a failed run, said once per process. */\nfunction warnOnce(message: string): void {\n if (warnings.has(message)) return;\n warnings.add(message);\n console.warn(`[eslint-plugin-tasty] ${message}`);\n}\n\n/** Fails the run when the installed plugin is outside the config's `requires`. */\nfunction checkRequires(config: TastyValidationConfig, configPath: string) {\n const range: unknown = config.requires;\n if (range === undefined) return;\n if (\n typeof range !== 'string' ||\n range.trim() === '' ||\n semver.validRange(range) === null\n ) {\n throw new Error(\n `tasty: ${configPath}: 'requires' must be a semver range of @tenphi/eslint-plugin-tasty versions, such as '>=1.13.0'.`,\n );\n }\n // A snapshot of the next release still satisfies `>=` the current one.\n if (!semver.satisfies(pluginVersion, range, { includePrerelease: true })) {\n throw new Error(\n `tasty: ${configPath} requires @tenphi/eslint-plugin-tasty ${range}, but ${pluginVersion} is installed. Upgrade the plugin.`,\n );\n }\n}\n\n/** List keys, and whether `false` (turning the check off) is allowed. */\nconst LIST_KEYS = {\n tokens: true,\n units: true,\n functions: true,\n funcs: true,\n states: false,\n presets: false,\n recipes: false,\n styles: false,\n importSources: false,\n ownedSources: false,\n} as const;\n\n/** Keys Tasty's runtime config (`configure()`) holds as objects keyed by name. */\nconst RUNTIME_OBJECT_KEYS = [\n 'tokens',\n 'units',\n 'functions',\n 'states',\n 'presets',\n 'recipes',\n];\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) {\n return value.every((item) => typeof item === 'string')\n ? 'a list'\n : 'a list with a non-string entry';\n }\n return isPlainObject(value) ? 'an object' : `a ${typeof value}`;\n}\n\n/**\n * Warns about keys the plugin does not read and known keys of the wrong shape,\n * and returns the config without the latter, so `states: '@mobile'` is not\n * read character by character.\n */\nfunction checkKeys(\n config: TastyValidationConfig,\n configPath: string,\n): TastyValidationConfig {\n // Tasty's SSR guide keeps the runtime config in a `tasty.config.ts` too.\n const runtime = RUNTIME_OBJECT_KEYS.some((key) =>\n isPlainObject((config as Record<string, unknown>)[key]),\n )\n ? \" It looks like Tasty's runtime config, which the plugin does not read.\"\n : '';\n\n const unknown = Object.keys(config).filter(\n (key) => !KNOWN_KEYS.has(key) && key !== '__esModule',\n );\n if (unknown.length > 0) {\n const keys = unknown.map((key) => `'${key}'`).join(', ');\n warnOnce(\n `${configPath}: ignoring unknown ${unknown.length === 1 ? 'key' : 'keys'} ${keys}.${\n runtime ||\n ` Check the spelling, or upgrade @tenphi/eslint-plugin-tasty (${pluginVersion} is installed) if the config was written for a newer version.`\n }`,\n );\n }\n\n const checked: Record<string, unknown> = { ...config };\n const ignore = (key: string, expected: string) => {\n warnOnce(\n `${configPath}: ignoring '${key}', which must be ${expected}, not ${describe(checked[key])}.${runtime}`,\n );\n delete checked[key];\n };\n for (const [key, allowsFalse] of Object.entries(LIST_KEYS)) {\n const value = checked[key];\n if (\n value === undefined ||\n (value === false && allowsFalse) ||\n (Array.isArray(value) && value.every((item) => typeof item === 'string'))\n ) {\n continue;\n }\n ignore(key, `${allowsFalse ? 'false or ' : ''}a list of names`);\n }\n if (\n checked.styleFunctions !== undefined &&\n !isPlainObject(checked.styleFunctions)\n ) {\n ignore('styleFunctions', 'an object keyed by function name');\n }\n return checked as TastyValidationConfig;\n}\n\nfunction findProjectRoot(startDir: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, 'package.json'))) {\n return dir;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction resolvePackageDir(\n packageName: string,\n startDir: string,\n): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n const candidate = join(dir, 'node_modules', ...packageName.split('/'));\n if (existsSync(candidate) && existsSync(join(candidate, 'package.json'))) {\n return candidate;\n }\n dir = dirname(dir);\n }\n return null;\n}\n\nfunction findConfigInDir(dir: string): string | null {\n for (const name of CONFIG_FILENAMES) {\n const path = join(dir, name);\n if (existsSync(path)) {\n return path;\n }\n }\n return null;\n}\n\nfunction collectConfigFiles(startDir: string, projectRoot: string): string[] {\n const configs: string[] = [];\n let dir = startDir;\n const root = resolve(projectRoot);\n\n while (true) {\n const found = findConfigInDir(dir);\n if (found) configs.push(found);\n if (resolve(dir) === root) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n return configs;\n}\n\nconst TASTY_PACKAGE_NAME = '@tenphi/tasty';\n\nfunction stripComments(source: string): string {\n let result = '';\n let i = 0;\n while (i < source.length) {\n const ch = source[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n result += ch;\n i++;\n while (i < source.length && source[i] !== ch) {\n if (source[i] === '\\\\') {\n result += source[i++];\n }\n if (i < source.length) {\n result += source[i++];\n }\n }\n if (i < source.length) {\n result += source[i++];\n }\n continue;\n }\n if (ch === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i += 2;\n continue;\n }\n if (ch === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') {\n i++;\n }\n continue;\n }\n result += source[i++];\n }\n return result;\n}\n\nfunction stripImports(source: string): string {\n return source.replace(/^\\s*import\\s+.*?;\\s*$/gm, '');\n}\n\nfunction extractBalancedBraces(content: string, start: number): string | null {\n if (content[start] !== '{') return null;\n let depth = 0;\n for (let i = start; i < content.length; i++) {\n const ch = content[i];\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n i++;\n while (i < content.length && content[i] !== ch) {\n if (content[i] === '\\\\') i++;\n i++;\n }\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n if (depth === 0) return content.slice(start, i + 1);\n }\n return null;\n}\n\nfunction extractImportPaths(configPath: string): string[] {\n const source = readFileSync(configPath, 'utf-8');\n const configDir = dirname(configPath);\n const paths: string[] = [];\n const re = /^\\s*import\\s+.*?\\s+from\\s+['\"]([^'\"]+)['\"]/gm;\n let m;\n\n while ((m = re.exec(source)) !== null) {\n const specifier = m[1];\n\n if (!specifier.startsWith('.')) continue;\n\n const abs = resolve(configDir, specifier);\n\n for (const ext of ['', '.ts', '.js', '.mjs', '.tsx', '.jsx']) {\n const candidate = abs + ext;\n\n if (existsSync(candidate)) {\n paths.push(candidate);\n break;\n }\n }\n }\n\n return paths;\n}\n\nfunction stripTypeScriptSyntax(source: string): string {\n return source\n .replace(/\\bas\\s+const\\b/g, '')\n .replace(/\\bsatisfies\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '')\n .replace(/\\bas\\s+[A-Z]\\w*(?:<[^>]*>)?/g, '');\n}\n\nfunction loadRawConfig(configPath: string): TastyValidationConfig {\n if (configPath.endsWith('.json')) {\n return JSON.parse(\n readFileSync(configPath, 'utf-8'),\n ) as TastyValidationConfig;\n }\n\n // Try jiti first — it resolves imports and TypeScript natively.\n try {\n const jiti = createJiti(dirname(configPath), { moduleCache: false });\n const mod = jiti(configPath) as Record<string, unknown>;\n const config = (mod.default ?? mod) as TastyValidationConfig;\n\n if (config && typeof config === 'object') {\n return config;\n }\n } catch {\n // Fall through to text-based parsing.\n }\n\n // Fallback: text-based extraction (no import support).\n const content = readFileSync(configPath, 'utf-8');\n const stripped = stripImports(stripComments(content));\n const match = stripped.match(/export\\s+default\\s+/);\n\n if (match && match.index != null) {\n const braceStart = match.index + match[0].length;\n const objectStr = extractBalancedBraces(stripped, braceStart);\n\n if (objectStr) {\n const cleaned = stripTypeScriptSyntax(objectStr);\n\n try {\n const fn = new Function(`return (${cleaned})`);\n return fn() as TastyValidationConfig;\n } catch (err) {\n console.warn(\n `[eslint-plugin-tasty] Failed to parse config file ${configPath}: ${err instanceof Error ? err.message : err}`,\n );\n }\n }\n }\n\n return {};\n}\n\nfunction mergeConfigs(\n parent: TastyValidationConfig,\n child: TastyValidationConfig,\n): TastyValidationConfig {\n const result: TastyValidationConfig = { ...parent };\n\n const arrayKeys = [\n ...(Object.keys(LIST_KEYS) as (keyof typeof LIST_KEYS)[]),\n 'protectedComponents',\n ] as const;\n\n for (const key of arrayKeys) {\n const childVal = child[key];\n if (childVal === undefined) continue;\n\n if (childVal === false) {\n (result as Record<string, unknown>)[key] = false;\n continue;\n }\n\n const parentVal = parent[key];\n if (Array.isArray(parentVal) && Array.isArray(childVal)) {\n (result as Record<string, unknown>)[key] = [\n ...new Set([...parentVal, ...childVal]),\n ];\n } else {\n (result as Record<string, unknown>)[key] = childVal;\n }\n }\n\n if (child.styleFunctions !== undefined) {\n result.styleFunctions = {\n ...parent.styleFunctions,\n ...child.styleFunctions,\n };\n }\n\n return result;\n}\n\n/**\n * The config file `extends` points to, or null when there is none. A relative\n * or absolute path that is not a file fails the run, as reading it always has; a\n * value that names nothing usable is skipped with a warning.\n */\nfunction resolveExtends(\n config: TastyValidationConfig,\n configPath: string,\n touched: string[],\n): string | null {\n const target: unknown = config.extends;\n if (target === undefined) return null;\n const configDir = dirname(configPath);\n if (typeof target !== 'string' || target === '') {\n warnOnce(\n `${configPath}: 'extends' must be a config path or a package name, not ${JSON.stringify(target)}. It is ignored.`,\n );\n return null;\n }\n\n if (target.startsWith('.') || target.startsWith('/')) {\n const parentPath = resolve(configDir, target);\n // Watched, so creating the file clears a cached failure.\n touched.push(parentPath);\n if (!isFile(parentPath)) {\n throw new Error(\n `tasty: ${configPath}: 'extends' points to ${parentPath}, which is not a file.`,\n );\n }\n return parentPath;\n }\n\n // `@scope/pkg/path/to/config` names a file inside the package.\n const parts = target.split('/');\n const packageName = parts.slice(0, target.startsWith('@') ? 2 : 1).join('/');\n const subpath = target.slice(packageName.length + 1);\n const pkgDir = resolvePackageDir(packageName, configDir);\n if (!pkgDir) {\n warnOnce(\n `${configPath}: 'extends' names '${packageName}', which cannot be resolved from ${configDir}. It is ignored.`,\n );\n return null;\n }\n const pkgConfig = subpath\n ? findConfigFile(join(pkgDir, subpath))\n : findConfigInDir(pkgDir);\n if (!pkgConfig) {\n warnOnce(\n subpath\n ? `${configPath}: 'extends' names '${target}', but ${join(pkgDir, subpath)} is not a config file. It is ignored.`\n : `${configPath}: 'extends' names '${target}', which has no tasty.config.* at ${pkgDir}. It is ignored.`,\n );\n }\n return pkgConfig;\n}\n\nfunction isFile(path: string): boolean {\n return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;\n}\n\n/** A config file at `path`, with or without its extension, or in that directory. */\nfunction findConfigFile(path: string): string | null {\n for (const ext of ['', '.ts', '.js', '.mjs', '.json']) {\n if (isFile(path + ext)) return path + ext;\n }\n return statSync(path, { throwIfNoEntry: false })?.isDirectory()\n ? findConfigInDir(path)\n : null;\n}\n\n/**\n * Loads a config and the chain it extends, parents merged first. Every file\n * read (or expected) is pushed to `touched`, whose mtimes decide when the\n * result, or a failure, is stale.\n */\nfunction resolveConfigChain(\n configPath: string,\n touched: string[],\n visited = new Set<string>(),\n): TastyValidationConfig {\n const absPath = resolve(configPath);\n if (visited.has(absPath)) {\n warnOnce(\n `${absPath} is extended again by its own extends chain. The cycle is ignored.`,\n );\n return {};\n }\n visited.add(absPath);\n touched.push(absPath);\n\n let config = loadRawConfig(absPath);\n touched.push(...extractImportPaths(absPath));\n\n if (!config || typeof config !== 'object' || Array.isArray(config)) {\n warnOnce(`${absPath}: the config must be an object. It is ignored.`);\n config = {};\n }\n checkRequires(config, absPath);\n config = checkKeys(config, absPath);\n\n const parentPath = resolveExtends(config, absPath, touched);\n if (!parentPath) return config;\n\n return mergeConfigs(resolveConfigChain(parentPath, touched, visited), config);\n}\n\nfunction toResolved(config: TastyValidationConfig): ResolvedConfig {\n return {\n tokens: config.tokens ?? [],\n units: config.units ?? [],\n // `funcs` is the v2 spelling; `functions` wins when both are present.\n functions: config.functions ?? config.funcs ?? [],\n states: config.states ?? [],\n presets: config.presets ?? [],\n recipes: config.recipes ?? [],\n styles: config.styles ?? [],\n importSources: config.importSources ?? DEFAULT_IMPORT_SOURCES,\n ownedSources: config.ownedSources ?? [],\n styleFunctions: config.styleFunctions ?? {},\n protectedComponents: config.protectedComponents || [],\n };\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n tokens: [],\n units: [],\n functions: [],\n states: [],\n presets: [],\n recipes: [],\n styles: [],\n importSources: DEFAULT_IMPORT_SOURCES,\n ownedSources: [],\n styleFunctions: {},\n protectedComponents: [],\n};\n\nfunction getMtimes(paths: string[]): Map<string, number> {\n const mtimes = new Map<string, number>();\n for (const p of paths) {\n try {\n mtimes.set(p, statSync(p).mtimeMs);\n } catch {\n mtimes.set(p, -1);\n }\n }\n return mtimes;\n}\n\nfunction mtimesMatch(\n cached: Map<string, number>,\n current: Map<string, number>,\n): boolean {\n if (cached.size !== current.size) return false;\n for (const [path, mtime] of cached) {\n if (current.get(path) !== mtime) return false;\n }\n return true;\n}\n\nconst tastyVersionCache = new Map<\n string,\n { mtime: number; version: string | null }\n>();\n\n/**\n * The version of `@tenphi/tasty` installed for the project `filePath` belongs\n * to, or `null` when it cannot be found. Rules whose fixes need a newer Tasty\n * check it, so `--fix` never writes syntax the installed Tasty cannot render.\n */\nexport function getInstalledTastyVersion(filePath: string): string | null {\n const projectRoot = findProjectRoot(dirname(resolve(filePath)));\n if (!projectRoot) return null;\n\n const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);\n if (!pkgDir) return null;\n\n const pkgJson = join(pkgDir, 'package.json');\n let mtime: number;\n try {\n mtime = statSync(pkgJson).mtimeMs;\n } catch {\n return null;\n }\n\n const cached = tastyVersionCache.get(pkgJson);\n if (cached?.mtime === mtime) return cached.version;\n\n let version: string | null = null;\n try {\n const parsed = JSON.parse(readFileSync(pkgJson, 'utf-8')) as {\n version?: unknown;\n };\n if (typeof parsed.version === 'string') version = parsed.version;\n } catch {\n // An unreadable manifest is the same as no Tasty at all.\n }\n\n tastyVersionCache.set(pkgJson, { mtime, version });\n return version;\n}\n\nexport function loadConfig(filePath: string): ResolvedConfig {\n const fileDir = dirname(resolve(filePath));\n const projectRoot = findProjectRoot(fileDir);\n if (!projectRoot) return DEFAULT_CONFIG;\n\n const configFiles = collectConfigFiles(fileDir, projectRoot);\n\n const pkgDir = resolvePackageDir(TASTY_PACKAGE_NAME, projectRoot);\n const pkgConfigFile = pkgDir ? findConfigInDir(pkgDir) : null;\n\n if (configFiles.length === 0 && !pkgConfigFile) return DEFAULT_CONFIG;\n\n const cacheKey = [pkgConfigFile ?? '', ...configFiles].join('\\0');\n\n const cached = configCache.get(cacheKey);\n if (\n cached &&\n mtimesMatch(cached.fileMtimes, getMtimes([...cached.fileMtimes.keys()]))\n ) {\n if (cached.result instanceof Error) throw cached.result;\n return cached.result;\n }\n\n // A failure is cached too: oxlint keeps linting after a plugin error, and\n // every rule on every file would otherwise reload the chain to fail again.\n const touched: string[] = [];\n let result: ResolvedConfig | Error;\n try {\n let merged: TastyValidationConfig = pkgConfigFile\n ? resolveConfigChain(pkgConfigFile, touched)\n : {};\n for (let i = configFiles.length - 1; i >= 0; i--) {\n merged = mergeConfigs(\n merged,\n resolveConfigChain(configFiles[i], touched),\n );\n }\n result = toResolved(merged);\n } catch (error) {\n result = error instanceof Error ? error : new Error(String(error));\n }\n\n configCache.set(cacheKey, { result, fileMtimes: getMtimes(touched) });\n if (result instanceof Error) throw result;\n return result;\n}\n"],"mappings":"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"}
package/dist/configs.js CHANGED
@@ -38,6 +38,7 @@ const recommended = {
38
38
  "tasty/prefer-state-negation": "warn",
39
39
  "tasty/prefer-custom-property-syntax": "warn",
40
40
  "tasty/prefer-hide": "warn",
41
+ "tasty/prefer-fill-mix": "warn",
41
42
  "tasty/prefer-directional-shorthand": "warn",
42
43
  "tasty/prefer-longhand-property": "error"
43
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"configs.js","names":[],"sources":["../src/configs.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\n\nexport const recommended: TSESLint.SharedConfig.RulesRecord = {\n 'tasty/known-property': 'warn',\n 'tasty/valid-value': 'error',\n 'tasty/valid-color-token': 'error',\n 'tasty/valid-custom-unit': 'error',\n 'tasty/valid-boolean-property': 'error',\n 'tasty/valid-state-key': 'error',\n 'tasty/valid-styles-structure': 'error',\n 'tasty/no-nested-state-map': 'error',\n 'tasty/no-important': 'error',\n 'tasty/valid-sub-element': 'error',\n 'tasty/valid-directional-modifier': 'error',\n 'tasty/valid-radius-shape': 'error',\n 'tasty/no-nested-selector': 'error',\n 'tasty/no-state-in-selector': 'warn',\n 'tasty/prefer-element-selector': 'warn',\n 'tasty/static-no-dynamic-values': 'error',\n 'tasty/static-valid-selector': 'error',\n 'tasty/valid-preset': 'error',\n 'tasty/valid-recipe': 'error',\n 'tasty/valid-transition': 'warn',\n 'tasty/require-default-state': 'error',\n 'tasty/no-own-at-root': 'warn',\n 'tasty/no-ignored-pseudo-element-state': 'error',\n 'tasty/valid-default-state-order': 'warn',\n 'tasty/prefer-shorthand-property': 'warn',\n 'tasty/no-raw-color-values': 'warn',\n 'tasty/no-raw-transition-duration': 'warn',\n 'tasty/no-runtime-styles-mutation': 'warn',\n 'tasty/no-style-spread': 'warn',\n 'tasty/no-style-prop': 'warn',\n 'tasty/no-classname-prop': 'warn',\n 'tasty/no-styles-prop': 'warn',\n 'tasty/no-protected-restyle': 'warn',\n 'tasty/consistent-token-usage': 'warn',\n 'tasty/prefer-auto-calc': 'warn',\n 'tasty/prefer-state-negation': 'warn',\n 'tasty/prefer-custom-property-syntax': 'warn',\n 'tasty/prefer-hide': 'warn',\n 'tasty/prefer-directional-shorthand': 'warn',\n 'tasty/prefer-longhand-property': 'error',\n};\n\nexport const strict: TSESLint.SharedConfig.RulesRecord = {\n ...recommended,\n 'tasty/valid-custom-property': 'warn',\n 'tasty/no-unknown-state-alias': 'warn',\n 'tasty/valid-state-definition': 'warn',\n};\n"],"mappings":";AAEA,MAAa,cAAiD;CAC5D,wBAAwB;CACxB,qBAAqB;CACrB,2BAA2B;CAC3B,2BAA2B;CAC3B,gCAAgC;CAChC,yBAAyB;CACzB,gCAAgC;CAChC,6BAA6B;CAC7B,sBAAsB;CACtB,2BAA2B;CAC3B,oCAAoC;CACpC,4BAA4B;CAC5B,4BAA4B;CAC5B,8BAA8B;CAC9B,iCAAiC;CACjC,kCAAkC;CAClC,+BAA+B;CAC/B,sBAAsB;CACtB,sBAAsB;CACtB,0BAA0B;CAC1B,+BAA+B;CAC/B,wBAAwB;CACxB,yCAAyC;CACzC,mCAAmC;CACnC,mCAAmC;CACnC,6BAA6B;CAC7B,oCAAoC;CACpC,oCAAoC;CACpC,yBAAyB;CACzB,uBAAuB;CACvB,2BAA2B;CAC3B,wBAAwB;CACxB,8BAA8B;CAC9B,gCAAgC;CAChC,0BAA0B;CAC1B,+BAA+B;CAC/B,uCAAuC;CACvC,qBAAqB;CACrB,sCAAsC;CACtC,kCAAkC;CACnC;AAED,MAAa,SAA4C;CACvD,GAAG;CACH,+BAA+B;CAC/B,gCAAgC;CAChC,gCAAgC;CACjC"}
1
+ {"version":3,"file":"configs.js","names":[],"sources":["../src/configs.ts"],"sourcesContent":["import type { TSESLint } from '@typescript-eslint/utils';\n\nexport const recommended: TSESLint.SharedConfig.RulesRecord = {\n 'tasty/known-property': 'warn',\n 'tasty/valid-value': 'error',\n 'tasty/valid-color-token': 'error',\n 'tasty/valid-custom-unit': 'error',\n 'tasty/valid-boolean-property': 'error',\n 'tasty/valid-state-key': 'error',\n 'tasty/valid-styles-structure': 'error',\n 'tasty/no-nested-state-map': 'error',\n 'tasty/no-important': 'error',\n 'tasty/valid-sub-element': 'error',\n 'tasty/valid-directional-modifier': 'error',\n 'tasty/valid-radius-shape': 'error',\n 'tasty/no-nested-selector': 'error',\n 'tasty/no-state-in-selector': 'warn',\n 'tasty/prefer-element-selector': 'warn',\n 'tasty/static-no-dynamic-values': 'error',\n 'tasty/static-valid-selector': 'error',\n 'tasty/valid-preset': 'error',\n 'tasty/valid-recipe': 'error',\n 'tasty/valid-transition': 'warn',\n 'tasty/require-default-state': 'error',\n 'tasty/no-own-at-root': 'warn',\n 'tasty/no-ignored-pseudo-element-state': 'error',\n 'tasty/valid-default-state-order': 'warn',\n 'tasty/prefer-shorthand-property': 'warn',\n 'tasty/no-raw-color-values': 'warn',\n 'tasty/no-raw-transition-duration': 'warn',\n 'tasty/no-runtime-styles-mutation': 'warn',\n 'tasty/no-style-spread': 'warn',\n 'tasty/no-style-prop': 'warn',\n 'tasty/no-classname-prop': 'warn',\n 'tasty/no-styles-prop': 'warn',\n 'tasty/no-protected-restyle': 'warn',\n 'tasty/consistent-token-usage': 'warn',\n 'tasty/prefer-auto-calc': 'warn',\n 'tasty/prefer-state-negation': 'warn',\n 'tasty/prefer-custom-property-syntax': 'warn',\n 'tasty/prefer-hide': 'warn',\n 'tasty/prefer-fill-mix': 'warn',\n 'tasty/prefer-directional-shorthand': 'warn',\n 'tasty/prefer-longhand-property': 'error',\n};\n\nexport const strict: TSESLint.SharedConfig.RulesRecord = {\n ...recommended,\n 'tasty/valid-custom-property': 'warn',\n 'tasty/no-unknown-state-alias': 'warn',\n 'tasty/valid-state-definition': 'warn',\n};\n"],"mappings":";AAEA,MAAa,cAAiD;CAC5D,wBAAwB;CACxB,qBAAqB;CACrB,2BAA2B;CAC3B,2BAA2B;CAC3B,gCAAgC;CAChC,yBAAyB;CACzB,gCAAgC;CAChC,6BAA6B;CAC7B,sBAAsB;CACtB,2BAA2B;CAC3B,oCAAoC;CACpC,4BAA4B;CAC5B,4BAA4B;CAC5B,8BAA8B;CAC9B,iCAAiC;CACjC,kCAAkC;CAClC,+BAA+B;CAC/B,sBAAsB;CACtB,sBAAsB;CACtB,0BAA0B;CAC1B,+BAA+B;CAC/B,wBAAwB;CACxB,yCAAyC;CACzC,mCAAmC;CACnC,mCAAmC;CACnC,6BAA6B;CAC7B,oCAAoC;CACpC,oCAAoC;CACpC,yBAAyB;CACzB,uBAAuB;CACvB,2BAA2B;CAC3B,wBAAwB;CACxB,8BAA8B;CAC9B,gCAAgC;CAChC,0BAA0B;CAC1B,+BAA+B;CAC/B,uCAAuC;CACvC,qBAAqB;CACrB,yBAAyB;CACzB,sCAAsC;CACtC,kCAAkC;CACnC;AAED,MAAa,SAA4C;CACvD,GAAG;CACH,+BAA+B;CAC/B,gCAAgC;CAChC,gCAAgC;CACjC"}
package/dist/constants.js CHANGED
@@ -1110,14 +1110,10 @@ const CSS_UNITS = new Set([
1110
1110
  /**
1111
1111
  * Properties that accept `true` as a value (means "use default").
1112
1112
  *
1113
- * `fill` is documented as accepting `true` but is deliberately absent: as of
1114
- * tasty 3.8 the handler passes the boolean straight through, so `fill: true`
1115
- * emits `background-color: true` — a declaration the browser drops, leaving no
1116
- * background at all. Reporting it is the useful answer while that is what runs.
1117
- * `constants.round-trip.test.ts` pins both halves of that: no property in this
1118
- * set may swallow `true`, and `fill` still does — so whichever way tasty
1119
- * resolves it (docs or implementation), a test says so rather than the plugin
1120
- * quietly disagreeing with the runtime.
1113
+ * `fill` has no default color, so `fill: true` stays an error. Older Tasty
1114
+ * versions emitted invalid CSS; Tasty 3.14 ignores it instead. Neither gives
1115
+ * the element a background. The runtime guard in `constants.round-trip.test.ts`
1116
+ * checks that accepted properties resolve their defaults and fill stays absent.
1121
1117
  */
1122
1118
  const BOOLEAN_TRUE_PROPERTIES = new Set([
1123
1119
  "border",
@@ -2191,6 +2187,7 @@ const PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION = new Set([
2191
2187
  "font",
2192
2188
  "fontFamily",
2193
2189
  "fontSize",
2190
+ "fontStyle",
2194
2191
  "fontWeight",
2195
2192
  "justify",
2196
2193
  "justifyContent",