@tenphi/eslint-plugin-tasty 1.13.0 → 1.14.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 +1 -0
- package/dist/config.js +32 -1
- package/dist/config.js.map +1 -1
- package/dist/configs.js +1 -0
- package/dist/configs.js.map +1 -1
- package/dist/constants.js +7 -8
- package/dist/constants.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/value-parser.js +2 -1
- package/dist/parsers/value-parser.js.map +1 -1
- package/dist/property-expectations.js +10 -0
- package/dist/property-expectations.js.map +1 -1
- package/dist/rules/prefer-fill-mix.js +163 -0
- package/dist/rules/prefer-fill-mix.js.map +1 -0
- package/dist/rules/valid-value.js +24 -0
- package/dist/rules/valid-value.js.map +1 -1
- package/package.json +2 -2
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` (`'#surface #dark.04'`) instead of a comma mix (`'#dark 4%, #surface'`), autofixed when the overlay's opacity is written on it; mix parts weighted by their opacity (`'#dark.04, #surface'`). Needs `@tenphi/tasty` 3.15 ([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
|
package/dist/config.js.map
CHANGED
|
@@ -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
|
};
|
package/dist/configs.js.map
CHANGED
|
@@ -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
|
@@ -105,6 +105,7 @@ const KNOWN_TASTY_PROPERTIES = new Set([
|
|
|
105
105
|
"outline",
|
|
106
106
|
"color",
|
|
107
107
|
"fill",
|
|
108
|
+
"svgFill",
|
|
108
109
|
"fade",
|
|
109
110
|
"image",
|
|
110
111
|
"textTransform",
|
|
@@ -1109,14 +1110,10 @@ const CSS_UNITS = new Set([
|
|
|
1109
1110
|
/**
|
|
1110
1111
|
* Properties that accept `true` as a value (means "use default").
|
|
1111
1112
|
*
|
|
1112
|
-
* `fill`
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* `constants.round-trip.test.ts` pins both halves of that: no property in this
|
|
1117
|
-
* set may swallow `true`, and `fill` still does — so whichever way tasty
|
|
1118
|
-
* resolves it (docs or implementation), a test says so rather than the plugin
|
|
1119
|
-
* 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.
|
|
1120
1117
|
*/
|
|
1121
1118
|
const BOOLEAN_TRUE_PROPERTIES = new Set([
|
|
1122
1119
|
"border",
|
|
@@ -2107,6 +2104,7 @@ const SYSTEM_COLORS = new Set([
|
|
|
2107
2104
|
*/
|
|
2108
2105
|
const COLOR_BEARING_PROPERTIES = new Set([
|
|
2109
2106
|
"fill",
|
|
2107
|
+
"svgFill",
|
|
2110
2108
|
"color",
|
|
2111
2109
|
"caretColor",
|
|
2112
2110
|
"accentColor",
|
|
@@ -2189,6 +2187,7 @@ const PROPERTIES_WITHOUT_COLOR_TOKEN_EXPANSION = new Set([
|
|
|
2189
2187
|
"font",
|
|
2190
2188
|
"fontFamily",
|
|
2191
2189
|
"fontSize",
|
|
2190
|
+
"fontStyle",
|
|
2192
2191
|
"fontWeight",
|
|
2193
2192
|
"justify",
|
|
2194
2193
|
"justifyContent",
|