@tenphi/eslint-plugin-tasty 1.12.0 → 1.13.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
@@ -104,7 +104,8 @@ When a rule's advice is "change the component you are extending", it stays quiet
104
104
  over a base imported from someone else's package. Components declared in the same
105
105
  file and relative, absolute, `~`, `#` or `@/` imports count as yours already —
106
106
  list a scope here only for a design system you publish from your own monorepo
107
- (`*` matches any run of characters).
107
+ (`*` matches any run of characters). An entry also covers its package's subpaths,
108
+ so `@my-org/ui` owns `@my-org/ui/button`.
108
109
 
109
110
  A design system can also list the components consumers should customize through
110
111
  their props instead of restyling, in `protectedComponents`; consumers inherit the
@@ -113,6 +114,33 @@ list through `extends`. See [`tasty/no-protected-restyle`](docs/rules/no-protect
113
114
  > `functions` was called `funcs` before v1. The old spelling is still read as a
114
115
  > deprecated alias.
115
116
 
117
+ ### Versioning a shared config
118
+
119
+ A config that relies on newer plugin features can say so with `requires`, a
120
+ semver range of `@tenphi/eslint-plugin-tasty` versions:
121
+
122
+ ```ts
123
+ // tasty.config.ts of a design system that consumers extend
124
+ export default {
125
+ requires: '>=1.13.0',
126
+ protectedComponents: [{ source: '@my-org/ui', exclude: ['Button.Group'] }],
127
+ };
128
+ ```
129
+
130
+ Every config in the `extends` chain may declare one, and linting fails when the
131
+ installed plugin does not satisfy them all, naming the config, the range and the
132
+ installed version. A consumer that lags behind its design system finds out on
133
+ the first lint run instead of getting silently different results.
134
+
135
+ The plugin also warns, on stderr and once per config file, about keys it does not
136
+ read (a typo, or a key from a newer plugin), about known keys of the wrong shape
137
+ (`states: '@mobile'` instead of a list), and about an `extends` that names
138
+ nothing usable: a non-string value, a package that cannot be resolved, a package
139
+ without a `tasty.config.*`, or a chain that leads back to itself. A package
140
+ `extends` can also name a file inside the package, such as
141
+ `@my-org/ui/lint/tasty.config`. `presetDescriptions` and `stateDescriptions`,
142
+ which the Tasty VS Code extension reads from the same file, are accepted.
143
+
116
144
  ### Custom style functions
117
145
 
118
146
  Design systems can wrap Tasty with their own component factories and style
@@ -445,6 +473,15 @@ rule suppression. Native HTML/SVG attributes and React's singular `style` prop
445
473
  are excluded. Bindings, calls, spreads, and interpolated templates are not evaluated.
446
474
  By default, these checks do not require TypeScript type services.
447
475
 
476
+ A component imported from another package is skipped: a bare specifier that is
477
+ not listed in `importSources` or `ownedSources`, where an entry also covers the
478
+ paths below it (`@my/ds` covers `@my/ds/button`). Its props are its own, so
479
+ `<FocusScope contain />` from `react-aria` is not read as a style. Components
480
+ declared in the file, `tasty()` wrappers, and relative or in-repo imports (`./`,
481
+ `~/`, `#`, `@/`) are still checked, since they may be Tasty components. With
482
+ `typeAwareJSX` on, prop types decide, and the package rule applies only where
483
+ they cannot (`any`, unresolved types).
484
+
448
485
  ### Experimental type-aware JSX props
449
486
 
450
487
  Opt into type analysis to distinguish semantic component props from style props.
@@ -471,7 +508,7 @@ The resolver uses JSX's contextual prop type and follows property declarations:
471
508
  - Props inherited or picked from Tasty's `Styles` remain validated, including
472
509
  indexed aliases such as `Styles['gap']` and explicit `StyleValue` types.
473
510
  - Declared component props such as `position: 'before' | 'after'` or
474
- `prefix: ReactNode` are excluded from JSX value checks.
511
+ `marker: ReactNode` are excluded from JSX value checks.
475
512
  - Missing components, unresolved types, `any`, generics, index signatures,
476
513
  excluded files, and broad primitive types retain the existing name heuristic.
477
514
  If a union combines a style declaration and a semantic declaration, the style
package/dist/config.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { DEFAULT_IMPORT_SOURCES } from "./constants.js";
2
+ import { version } from "./version.js";
2
3
  import { existsSync, readFileSync, statSync } from "fs";
3
4
  import { dirname, join, resolve } from "path";
4
5
  import { createJiti } from "jiti";
6
+ import semver from "semver";
5
7
 
6
8
  //#region src/config.ts
7
9
  const CONFIG_FILENAMES = [
@@ -11,6 +13,97 @@ const CONFIG_FILENAMES = [
11
13
  "tasty.config.json"
12
14
  ];
13
15
  const configCache = /* @__PURE__ */ new Map();
16
+ /**
17
+ * Every key a config may carry; anything else is a typo or from a newer plugin.
18
+ * Typed against the config interface so a new key cannot be missed here.
19
+ */
20
+ const KNOWN_KEYS = new Set(Object.keys({
21
+ extends: true,
22
+ requires: true,
23
+ tokens: true,
24
+ units: true,
25
+ functions: true,
26
+ funcs: true,
27
+ states: true,
28
+ presets: true,
29
+ recipes: true,
30
+ styles: true,
31
+ importSources: true,
32
+ ownedSources: true,
33
+ styleFunctions: true,
34
+ protectedComponents: true,
35
+ presetDescriptions: true,
36
+ stateDescriptions: true
37
+ }));
38
+ const warnings = /* @__PURE__ */ new Set();
39
+ /** A config problem worth a message but not a failed run, said once per process. */
40
+ function warnOnce(message) {
41
+ if (warnings.has(message)) return;
42
+ warnings.add(message);
43
+ console.warn(`[eslint-plugin-tasty] ${message}`);
44
+ }
45
+ /** Fails the run when the installed plugin is outside the config's `requires`. */
46
+ function checkRequires(config, configPath) {
47
+ const range = config.requires;
48
+ if (range === void 0) return;
49
+ if (typeof range !== "string" || range.trim() === "" || semver.validRange(range) === null) throw new Error(`tasty: ${configPath}: 'requires' must be a semver range of @tenphi/eslint-plugin-tasty versions, such as '>=1.13.0'.`);
50
+ if (!semver.satisfies(version, range, { includePrerelease: true })) throw new Error(`tasty: ${configPath} requires @tenphi/eslint-plugin-tasty ${range}, but ${version} is installed. Upgrade the plugin.`);
51
+ }
52
+ /** List keys, and whether `false` (turning the check off) is allowed. */
53
+ const LIST_KEYS = {
54
+ tokens: true,
55
+ units: true,
56
+ functions: true,
57
+ funcs: true,
58
+ states: false,
59
+ presets: false,
60
+ recipes: false,
61
+ styles: false,
62
+ importSources: false,
63
+ ownedSources: false
64
+ };
65
+ /** Keys Tasty's runtime config (`configure()`) holds as objects keyed by name. */
66
+ const RUNTIME_OBJECT_KEYS = [
67
+ "tokens",
68
+ "units",
69
+ "functions",
70
+ "states",
71
+ "presets",
72
+ "recipes"
73
+ ];
74
+ function isPlainObject(value) {
75
+ return typeof value === "object" && value !== null && !Array.isArray(value);
76
+ }
77
+ function describe(value) {
78
+ if (value === null) return "null";
79
+ if (Array.isArray(value)) return value.every((item) => typeof item === "string") ? "a list" : "a list with a non-string entry";
80
+ return isPlainObject(value) ? "an object" : `a ${typeof value}`;
81
+ }
82
+ /**
83
+ * Warns about keys the plugin does not read and known keys of the wrong shape,
84
+ * and returns the config without the latter, so `states: '@mobile'` is not
85
+ * read character by character.
86
+ */
87
+ function checkKeys(config, configPath) {
88
+ const runtime = RUNTIME_OBJECT_KEYS.some((key) => isPlainObject(config[key])) ? " It looks like Tasty's runtime config, which the plugin does not read." : "";
89
+ const unknown = Object.keys(config).filter((key) => !KNOWN_KEYS.has(key) && key !== "__esModule");
90
+ if (unknown.length > 0) {
91
+ const keys = unknown.map((key) => `'${key}'`).join(", ");
92
+ warnOnce(`${configPath}: ignoring unknown ${unknown.length === 1 ? "key" : "keys"} ${keys}.${runtime || ` Check the spelling, or upgrade @tenphi/eslint-plugin-tasty (${version} is installed) if the config was written for a newer version.`}`);
93
+ }
94
+ const checked = { ...config };
95
+ const ignore = (key, expected) => {
96
+ warnOnce(`${configPath}: ignoring '${key}', which must be ${expected}, not ${describe(checked[key])}.${runtime}`);
97
+ delete checked[key];
98
+ };
99
+ for (const [key, allowsFalse] of Object.entries(LIST_KEYS)) {
100
+ const value = checked[key];
101
+ if (value === void 0 || value === false && allowsFalse || Array.isArray(value) && value.every((item) => typeof item === "string")) continue;
102
+ ignore(key, `${allowsFalse ? "false or " : ""}a list of names`);
103
+ }
104
+ if (checked.styleFunctions !== void 0 && !isPlainObject(checked.styleFunctions)) ignore("styleFunctions", "an object keyed by function name");
105
+ return checked;
106
+ }
14
107
  function findProjectRoot(startDir) {
15
108
  let dir = startDir;
16
109
  while (dir !== dirname(dir)) {
@@ -155,19 +248,8 @@ function loadRawConfig(configPath) {
155
248
  }
156
249
  function mergeConfigs(parent, child) {
157
250
  const result = { ...parent };
158
- for (const key of [
159
- "tokens",
160
- "units",
161
- "functions",
162
- "funcs",
163
- "states",
164
- "presets",
165
- "recipes",
166
- "styles",
167
- "importSources",
168
- "ownedSources",
169
- "protectedComponents"
170
- ]) {
251
+ const arrayKeys = [...Object.keys(LIST_KEYS), "protectedComponents"];
252
+ for (const key of arrayKeys) {
171
253
  const childVal = child[key];
172
254
  if (childVal === void 0) continue;
173
255
  if (childVal === false) {
@@ -184,40 +266,74 @@ function mergeConfigs(parent, child) {
184
266
  };
185
267
  return result;
186
268
  }
187
- function resolveConfigChain(configPath, visited = /* @__PURE__ */ new Set()) {
269
+ /**
270
+ * The config file `extends` points to, or null when there is none. A relative
271
+ * or absolute path that is not a file fails the run, as reading it always has; a
272
+ * value that names nothing usable is skipped with a warning.
273
+ */
274
+ function resolveExtends(config, configPath, touched) {
275
+ const target = config.extends;
276
+ if (target === void 0) return null;
277
+ const configDir = dirname(configPath);
278
+ if (typeof target !== "string" || target === "") {
279
+ warnOnce(`${configPath}: 'extends' must be a config path or a package name, not ${JSON.stringify(target)}. It is ignored.`);
280
+ return null;
281
+ }
282
+ if (target.startsWith(".") || target.startsWith("/")) {
283
+ const parentPath = resolve(configDir, target);
284
+ touched.push(parentPath);
285
+ if (!isFile(parentPath)) throw new Error(`tasty: ${configPath}: 'extends' points to ${parentPath}, which is not a file.`);
286
+ return parentPath;
287
+ }
288
+ const packageName = target.split("/").slice(0, target.startsWith("@") ? 2 : 1).join("/");
289
+ const subpath = target.slice(packageName.length + 1);
290
+ const pkgDir = resolvePackageDir(packageName, configDir);
291
+ if (!pkgDir) {
292
+ warnOnce(`${configPath}: 'extends' names '${packageName}', which cannot be resolved from ${configDir}. It is ignored.`);
293
+ return null;
294
+ }
295
+ const pkgConfig = subpath ? findConfigFile(join(pkgDir, subpath)) : findConfigInDir(pkgDir);
296
+ if (!pkgConfig) warnOnce(subpath ? `${configPath}: 'extends' names '${target}', but ${join(pkgDir, subpath)} is not a config file. It is ignored.` : `${configPath}: 'extends' names '${target}', which has no tasty.config.* at ${pkgDir}. It is ignored.`);
297
+ return pkgConfig;
298
+ }
299
+ function isFile(path) {
300
+ return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
301
+ }
302
+ /** A config file at `path`, with or without its extension, or in that directory. */
303
+ function findConfigFile(path) {
304
+ for (const ext of [
305
+ "",
306
+ ".ts",
307
+ ".js",
308
+ ".mjs",
309
+ ".json"
310
+ ]) if (isFile(path + ext)) return path + ext;
311
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory() ? findConfigInDir(path) : null;
312
+ }
313
+ /**
314
+ * Loads a config and the chain it extends, parents merged first. Every file
315
+ * read (or expected) is pushed to `touched`, whose mtimes decide when the
316
+ * result, or a failure, is stale.
317
+ */
318
+ function resolveConfigChain(configPath, touched, visited = /* @__PURE__ */ new Set()) {
188
319
  const absPath = resolve(configPath);
189
- if (visited.has(absPath)) return {
190
- config: {},
191
- chainPaths: []
192
- };
320
+ if (visited.has(absPath)) {
321
+ warnOnce(`${absPath} is extended again by its own extends chain. The cycle is ignored.`);
322
+ return {};
323
+ }
193
324
  visited.add(absPath);
194
- const config = loadRawConfig(absPath);
195
- const chainPaths = [absPath, ...extractImportPaths(absPath)];
196
- if (!config.extends) return {
197
- config,
198
- chainPaths
199
- };
200
- let parentPath;
201
- if (config.extends.startsWith(".") || config.extends.startsWith("/")) parentPath = resolve(dirname(absPath), config.extends);
202
- else {
203
- const pkgDir = resolvePackageDir(config.extends, dirname(absPath));
204
- if (pkgDir) {
205
- const pkgConfig = findConfigInDir(pkgDir);
206
- if (pkgConfig) parentPath = pkgConfig;
207
- else return {
208
- config,
209
- chainPaths
210
- };
211
- } else return {
212
- config,
213
- chainPaths
214
- };
325
+ touched.push(absPath);
326
+ let config = loadRawConfig(absPath);
327
+ touched.push(...extractImportPaths(absPath));
328
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
329
+ warnOnce(`${absPath}: the config must be an object. It is ignored.`);
330
+ config = {};
215
331
  }
216
- const parentResult = resolveConfigChain(parentPath, visited);
217
- return {
218
- config: mergeConfigs(parentResult.config, config),
219
- chainPaths: [...parentResult.chainPaths, ...chainPaths]
220
- };
332
+ checkRequires(config, absPath);
333
+ config = checkKeys(config, absPath);
334
+ const parentPath = resolveExtends(config, absPath, touched);
335
+ if (!parentPath) return config;
336
+ return mergeConfigs(resolveConfigChain(parentPath, touched, visited), config);
221
337
  }
222
338
  function toResolved(config) {
223
339
  return {
@@ -271,29 +387,25 @@ function loadConfig(filePath) {
271
387
  if (configFiles.length === 0 && !pkgConfigFile) return DEFAULT_CONFIG;
272
388
  const cacheKey = [pkgConfigFile ?? "", ...configFiles].join("\0");
273
389
  const cached = configCache.get(cacheKey);
274
- if (cached) {
275
- const currentMtimes = getMtimes([...cached.fileMtimes.keys()]);
276
- if (mtimesMatch(cached.fileMtimes, currentMtimes)) return cached.config;
390
+ if (cached && mtimesMatch(cached.fileMtimes, getMtimes([...cached.fileMtimes.keys()]))) {
391
+ if (cached.result instanceof Error) throw cached.result;
392
+ return cached.result;
277
393
  }
278
- let merged = {};
279
- const allChainPaths = [];
280
- if (pkgConfigFile) {
281
- const result = resolveConfigChain(pkgConfigFile);
282
- merged = result.config;
283
- allChainPaths.push(...result.chainPaths);
284
- }
285
- for (let i = configFiles.length - 1; i >= 0; i--) {
286
- const { config, chainPaths } = resolveConfigChain(configFiles[i]);
287
- merged = mergeConfigs(merged, config);
288
- allChainPaths.push(...chainPaths);
394
+ const touched = [];
395
+ let result;
396
+ try {
397
+ let merged = pkgConfigFile ? resolveConfigChain(pkgConfigFile, touched) : {};
398
+ for (let i = configFiles.length - 1; i >= 0; i--) merged = mergeConfigs(merged, resolveConfigChain(configFiles[i], touched));
399
+ result = toResolved(merged);
400
+ } catch (error) {
401
+ result = error instanceof Error ? error : new Error(String(error));
289
402
  }
290
- const resolved = toResolved(merged);
291
- const fileMtimes = getMtimes(allChainPaths);
292
403
  configCache.set(cacheKey, {
293
- config: resolved,
294
- fileMtimes
404
+ result,
405
+ fileMtimes: getMtimes(touched)
295
406
  });
296
- return resolved;
407
+ if (result instanceof Error) throw result;
408
+ return result;
297
409
  }
298
410
 
299
411
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nimport { createJiti } from 'jiti';\n\nimport type { ResolvedConfig, TastyValidationConfig } from './types.js';\nimport { DEFAULT_IMPORT_SOURCES } from './constants.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 config: ResolvedConfig;\n fileMtimes: Map<string, number>;\n}\n\nconst configCache = new Map<string, CachedConfig>();\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 'tokens',\n 'units',\n 'functions',\n 'funcs',\n 'states',\n 'presets',\n 'recipes',\n 'styles',\n 'importSources',\n 'ownedSources',\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\ninterface ConfigChainResult {\n config: TastyValidationConfig;\n chainPaths: string[];\n}\n\nfunction resolveConfigChain(\n configPath: string,\n visited = new Set<string>(),\n): ConfigChainResult {\n const absPath = resolve(configPath);\n if (visited.has(absPath)) return { config: {}, chainPaths: [] };\n visited.add(absPath);\n\n const config = loadRawConfig(absPath);\n const importPaths = extractImportPaths(absPath);\n const chainPaths = [absPath, ...importPaths];\n\n if (!config.extends) return { config, chainPaths };\n\n let parentPath: string;\n if (config.extends.startsWith('.') || config.extends.startsWith('/')) {\n parentPath = resolve(dirname(absPath), config.extends);\n } else {\n const pkgDir = resolvePackageDir(config.extends, dirname(absPath));\n if (pkgDir) {\n const pkgConfig = findConfigInDir(pkgDir);\n if (pkgConfig) {\n parentPath = pkgConfig;\n } else {\n return { config, chainPaths };\n }\n } else {\n return { config, chainPaths };\n }\n }\n\n const parentResult = resolveConfigChain(parentPath, visited);\n return {\n config: mergeConfigs(parentResult.config, config),\n chainPaths: [...parentResult.chainPaths, ...chainPaths],\n };\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 (cached) {\n const currentMtimes = getMtimes([...cached.fileMtimes.keys()]);\n if (mtimesMatch(cached.fileMtimes, currentMtimes)) {\n return cached.config;\n }\n }\n\n let merged: TastyValidationConfig = {};\n const allChainPaths: string[] = [];\n\n if (pkgConfigFile) {\n const result = resolveConfigChain(pkgConfigFile);\n merged = result.config;\n allChainPaths.push(...result.chainPaths);\n }\n\n for (let i = configFiles.length - 1; i >= 0; i--) {\n const { config, chainPaths } = resolveConfigChain(configFiles[i]);\n merged = mergeConfigs(merged, config);\n allChainPaths.push(...chainPaths);\n }\n\n const resolved = toResolved(merged);\n const fileMtimes = getMtimes(allChainPaths);\n\n configCache.set(cacheKey, { config: resolved, fileMtimes });\n\n return resolved;\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\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/constants.js CHANGED
@@ -216,17 +216,18 @@ const RENAMED_SPECIAL_STYLE_KEYS = {
216
216
  * positive from `known-property` on valid CSS — which is why the list is derived
217
217
  * from data instead of maintained by hand.
218
218
  *
219
- * The dataset also carries at-rule descriptors (`src`, `syntax`, `symbols`, `size`),
220
- * which are kept, as stylelint keeps them. They are only reachable as top-level
221
- * style keys, where the cost is a declaration browsers drop rather than a report —
222
- * and separating them would mean hand-maintaining the exclusion list this change
223
- * exists to delete. Inside an at-rule block nothing validates them: `known-property`
224
- * exempts every `@`-prefixed key and does not descend.
219
+ * The dataset also carries at-rule descriptors (`src`, `prefix`, `syntax`,
220
+ * `symbols`), which stylelint keeps. They are left out here: a style rule cannot
221
+ * declare them, so as style keys they render declarations browsers drop, and as
222
+ * JSX props (`<NumberInput prefix="$" />`) they would be read as style values.
223
+ * `@webref/css` says which names are descriptors only; one that is also a
224
+ * property (`fontWeight`, `speakAs`, `size`) stays. Inside an at-rule block
225
+ * nothing validates them: `known-property` exempts every `@`-prefixed key and
226
+ * does not descend.
225
227
  */
226
228
  const DATASET_CSS_PROPERTIES = [
227
229
  "accelerator",
228
230
  "accentColor",
229
- "additiveSymbols",
230
231
  "alignContent",
231
232
  "alignItems",
232
233
  "alignSelf",
@@ -260,7 +261,6 @@ const DATASET_CSS_PROPERTIES = [
260
261
  "animationTriggerTimeline",
261
262
  "appRegion",
262
263
  "appearance",
263
- "ascentOverride",
264
264
  "aspectRatio",
265
265
  "audioLevel",
266
266
  "azimuth",
@@ -280,7 +280,6 @@ const DATASET_CSS_PROPERTIES = [
280
280
  "backgroundRepeatX",
281
281
  "backgroundRepeatY",
282
282
  "backgroundSize",
283
- "basePalette",
284
283
  "baselineShift",
285
284
  "baselineSource",
286
285
  "behavior",
@@ -419,7 +418,6 @@ const DATASET_CSS_PROPERTIES = [
419
418
  "cx",
420
419
  "cy",
421
420
  "d",
422
- "descentOverride",
423
421
  "direction",
424
422
  "display",
425
423
  "displayAlign",
@@ -438,7 +436,6 @@ const DATASET_CSS_PROPERTIES = [
438
436
  "epubTextTransform",
439
437
  "epubWordBreak",
440
438
  "epubWritingMode",
441
- "fallback",
442
439
  "fieldSizing",
443
440
  "fill",
444
441
  "fillBreak",
@@ -468,7 +465,6 @@ const DATASET_CSS_PROPERTIES = [
468
465
  "flowFrom",
469
466
  "flowInto",
470
467
  "font",
471
- "fontDisplay",
472
468
  "fontFamily",
473
469
  "fontFeatureSettings",
474
470
  "fontKerning",
@@ -531,11 +527,9 @@ const DATASET_CSS_PROPERTIES = [
531
527
  "imageRendering",
532
528
  "imageResolution",
533
529
  "imeMode",
534
- "inherits",
535
530
  "initialLetter",
536
531
  "initialLetterAlign",
537
532
  "initialLetterWrap",
538
- "initialValue",
539
533
  "inlineSize",
540
534
  "inlineSizing",
541
535
  "inputFormat",
@@ -574,7 +568,6 @@ const DATASET_CSS_PROPERTIES = [
574
568
  "lineBreak",
575
569
  "lineClamp",
576
570
  "lineFitEdge",
577
- "lineGapOverride",
578
571
  "lineGrid",
579
572
  "lineHeight",
580
573
  "lineHeightStep",
@@ -608,7 +601,6 @@ const DATASET_CSS_PROPERTIES = [
608
601
  "markerSegment",
609
602
  "markerSide",
610
603
  "markerStart",
611
- "marks",
612
604
  "mask",
613
605
  "maskBorder",
614
606
  "maskBorderMode",
@@ -654,8 +646,6 @@ const DATASET_CSS_PROPERTIES = [
654
646
  "navLeft",
655
647
  "navRight",
656
648
  "navUp",
657
- "navigation",
658
- "negative",
659
649
  "objectFit",
660
650
  "objectPosition",
661
651
  "objectViewBox",
@@ -672,7 +662,6 @@ const DATASET_CSS_PROPERTIES = [
672
662
  "offsetRotation",
673
663
  "opacity",
674
664
  "order",
675
- "orientation",
676
665
  "orphans",
677
666
  "outline",
678
667
  "outlineColor",
@@ -698,13 +687,11 @@ const DATASET_CSS_PROPERTIES = [
698
687
  "overflowX",
699
688
  "overflowY",
700
689
  "overlay",
701
- "overrideColors",
702
690
  "overscrollBehavior",
703
691
  "overscrollBehaviorBlock",
704
692
  "overscrollBehaviorInline",
705
693
  "overscrollBehaviorX",
706
694
  "overscrollBehaviorY",
707
- "pad",
708
695
  "padding",
709
696
  "paddingBlock",
710
697
  "paddingBlockEnd",
@@ -720,7 +707,6 @@ const DATASET_CSS_PROPERTIES = [
720
707
  "pageBreakAfter",
721
708
  "pageBreakBefore",
722
709
  "pageBreakInside",
723
- "pageOrientation",
724
710
  "paintOrder",
725
711
  "pause",
726
712
  "pauseAfter",
@@ -745,12 +731,10 @@ const DATASET_CSS_PROPERTIES = [
745
731
  "positionTryOptions",
746
732
  "positionTryOrder",
747
733
  "positionVisibility",
748
- "prefix",
749
734
  "printColorAdjust",
750
735
  "propertyName",
751
736
  "quotes",
752
737
  "r",
753
- "range",
754
738
  "readingFlow",
755
739
  "readingOrder",
756
740
  "regionFragment",
@@ -846,7 +830,6 @@ const DATASET_CSS_PROPERTIES = [
846
830
  "shapeOutside",
847
831
  "shapeRendering",
848
832
  "size",
849
- "sizeAdjust",
850
833
  "sliderOrientation",
851
834
  "snapHeight",
852
835
  "solidColor",
@@ -860,7 +843,6 @@ const DATASET_CSS_PROPERTIES = [
860
843
  "speakNumeral",
861
844
  "speakPunctuation",
862
845
  "speechRate",
863
- "src",
864
846
  "stopColor",
865
847
  "stopOpacity",
866
848
  "stress",
@@ -886,11 +868,7 @@ const DATASET_CSS_PROPERTIES = [
886
868
  "strokeRepeat",
887
869
  "strokeSize",
888
870
  "strokeWidth",
889
- "suffix",
890
871
  "supportedColorSchemes",
891
- "symbols",
892
- "syntax",
893
- "system",
894
872
  "tabSize",
895
873
  "tableLayout",
896
874
  "textAlign",
@@ -976,11 +954,9 @@ const DATASET_CSS_PROPERTIES = [
976
954
  "transitionProperty",
977
955
  "transitionTimingFunction",
978
956
  "translate",
979
- "types",
980
957
  "ucAltSkin",
981
958
  "ucSkin",
982
959
  "unicodeBidi",
983
- "unicodeRange",
984
960
  "userSelect",
985
961
  "userZoom",
986
962
  "vectorEffect",