@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 +39 -2
- package/dist/config.js +175 -63
- package/dist/config.js.map +1 -1
- package/dist/constants.js +8 -32
- package/dist/constants.js.map +1 -1
- package/dist/context.js +92 -42
- package/dist/context.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/parsers/value-parser.js +6 -0
- package/dist/parsers/value-parser.js.map +1 -1
- package/dist/property-expectations.js +7 -2
- package/dist/property-expectations.js.map +1 -1
- package/dist/protected-components.js +4 -3
- package/dist/protected-components.js.map +1 -1
- package/dist/rules/valid-directional-modifier.js +1 -0
- package/dist/rules/valid-directional-modifier.js.map +1 -1
- package/dist/rules/valid-value.js +1 -1
- package/dist/rules/valid-value.js.map +1 -1
- package/dist/style-values.js +6 -1
- package/dist/style-values.js.map +1 -1
- package/dist/type-aware-jsx.js +10 -2
- package/dist/type-aware-jsx.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/utils.js +10 -1
- package/dist/utils.js.map +1 -1
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -2
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
|
-
`
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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))
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
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
|
-
|
|
294
|
-
fileMtimes
|
|
404
|
+
result,
|
|
405
|
+
fileMtimes: getMtimes(touched)
|
|
295
406
|
});
|
|
296
|
-
|
|
407
|
+
if (result instanceof Error) throw result;
|
|
408
|
+
return result;
|
|
297
409
|
}
|
|
298
410
|
|
|
299
411
|
//#endregion
|
package/dist/config.js.map
CHANGED
|
@@ -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`, `
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
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",
|