@isentinel/eslint-config 6.0.0-beta.2 → 6.0.0-beta.21
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 +322 -3
- package/bin/index.js +1 -1
- package/bin/lint.js +3 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +719 -0
- package/dist/formatter-agents.d.mts +22 -0
- package/dist/formatter-agents.mjs +69 -0
- package/dist/index.d.mts +19142 -11553
- package/dist/index.mjs +5394 -2590
- package/dist/lint-cli.d.mts +1 -0
- package/dist/lint-cli.mjs +4827 -0
- package/dist/lint-ignored.d.mts +1 -0
- package/dist/lint-ignored.mjs +280 -0
- package/dist/oxlint.d.mts +32373 -0
- package/dist/oxlint.mjs +4711 -0
- package/package.json +74 -41
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
//#region src/guards.ts
|
|
7
|
+
/**
|
|
8
|
+
* Internal runtime type guards. Prefer these over `as` assertions so values
|
|
9
|
+
* crossing untyped boundaries (`JSON.parse`, dynamic `import`, plugin objects)
|
|
10
|
+
* are validated at runtime rather than asserted away.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Whether a value is a non-null, non-array object usable as a string-keyed
|
|
14
|
+
* record. Narrows `unknown` without an assertion.
|
|
15
|
+
*
|
|
16
|
+
* @param value - The value to test.
|
|
17
|
+
* @returns Whether the value is a plain object.
|
|
18
|
+
*/
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether a value is an array whose every element is a string.
|
|
24
|
+
*
|
|
25
|
+
* @param value - The value to test.
|
|
26
|
+
* @returns Whether the value is a `string` array.
|
|
27
|
+
*/
|
|
28
|
+
function isStringArray(value) {
|
|
29
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/lint-cli/lib/exec/eslint-install.ts
|
|
33
|
+
/**
|
|
34
|
+
* Locating the ESLint installation a run's config is resolved against.
|
|
35
|
+
*
|
|
36
|
+
* The runner and its ignore helper both need it and must agree: the helper
|
|
37
|
+
* serializes the config's match patterns out of one ESLint's config loader, and
|
|
38
|
+
* the runner evaluates them with that same ESLint's matcher. Resolving them
|
|
39
|
+
* separately would let the two drift onto different installations, and a
|
|
40
|
+
* matcher that disagrees with the one that produced the patterns is exactly the
|
|
41
|
+
* failure this feature cannot have.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* A synthetic basename for `createRequire`, which resolves relative to a file
|
|
45
|
+
* rather than a directory. Spelled so it can never collide with a real consumer
|
|
46
|
+
* module.
|
|
47
|
+
*/
|
|
48
|
+
const RESOLVE_ANCHOR = "__isentinel-lint__.js";
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the ESLint the consumer's config will be linted with: their own,
|
|
51
|
+
* resolved from `cwd`, falling back to the one resolvable from this file (the
|
|
52
|
+
* hoisted peer dependency) when `cwd` has no `node_modules` of its own — the
|
|
53
|
+
* case in the fixture-based tests.
|
|
54
|
+
*
|
|
55
|
+
* @param cwd - The consumer project root.
|
|
56
|
+
* @returns The package root and a require anchored in it.
|
|
57
|
+
* @throws {Error} When ESLint cannot be resolved from either location.
|
|
58
|
+
*/
|
|
59
|
+
function resolveEslintInstall(cwd) {
|
|
60
|
+
let packageJson;
|
|
61
|
+
try {
|
|
62
|
+
packageJson = createRequire(path.join(cwd, RESOLVE_ANCHOR)).resolve("eslint/package.json");
|
|
63
|
+
} catch {
|
|
64
|
+
packageJson = createRequire(import.meta.url).resolve("eslint/package.json");
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
requireFrom: createRequire(packageJson),
|
|
68
|
+
root: path.dirname(packageJson)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/lint-cli/ignored-child.ts
|
|
73
|
+
/**
|
|
74
|
+
* Internal helper process for {@link file://./ignored.ts}. Loads the consumer's
|
|
75
|
+
* resolved ESLint config once and writes back what the runner needs to classify
|
|
76
|
+
* lint targets: the config's match patterns when they are serializable, and
|
|
77
|
+
* otherwise the ignored subset of a target list.
|
|
78
|
+
*
|
|
79
|
+
* Run as a child rather than in-process for two reasons: the config-loading
|
|
80
|
+
* API is async while `plan` is synchronous end to end, and loading a
|
|
81
|
+
* consumer's flat config pulls their whole plugin tree (and jiti) into memory
|
|
82
|
+
* — 6-10s and several hundred MB in a large project, neither of which should
|
|
83
|
+
* outlive the one query the runner needs.
|
|
84
|
+
*
|
|
85
|
+
* Invoked as `node <this file> <cwd> <outFile>` with the JSON target array on
|
|
86
|
+
* stdin.
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* Whether a required module exposes the `ESLint` constructor this helper uses.
|
|
90
|
+
*
|
|
91
|
+
* @param value - The imported module's namespace.
|
|
92
|
+
* @returns Whether the namespace carries an `ESLint` constructor.
|
|
93
|
+
*/
|
|
94
|
+
function isEslintModule(value) {
|
|
95
|
+
return isRecord(value) && typeof value["ESLint"] === "function";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether a required module exposes the `ConfigLoader` constructor this helper
|
|
99
|
+
* uses.
|
|
100
|
+
*
|
|
101
|
+
* @param value - The required module's exports.
|
|
102
|
+
* @returns Whether the exports carry a `ConfigLoader` constructor.
|
|
103
|
+
*/
|
|
104
|
+
function isConfigLoaderModule(value) {
|
|
105
|
+
return isRecord(value) && typeof value["ConfigLoader"] === "function";
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The config keys that leave a `files`-less config able to say something about
|
|
109
|
+
* a path, mirroring `META_FIELDS` in `@eslint/config-array`: a config carrying
|
|
110
|
+
* `ignores` and nothing else outside this set is a global ignore, and one with
|
|
111
|
+
* any further key only excludes files from itself.
|
|
112
|
+
*/
|
|
113
|
+
const META_KEYS = /* @__PURE__ */ new Set(["basePath", "name"]);
|
|
114
|
+
/**
|
|
115
|
+
* Load the consumer's resolved config array.
|
|
116
|
+
*
|
|
117
|
+
* Reaching it means reaching past the `eslint` package's exports map: no public
|
|
118
|
+
* API returns it, and `calculateConfigForFile` strips exactly the
|
|
119
|
+
* `files`/`ignores` keys this needs. Only that coupling is caught here — a
|
|
120
|
+
* config that throws on load is a broken project, not a missing capability, and
|
|
121
|
+
* is left to fail the helper outright rather than be retried through a second,
|
|
122
|
+
* equally doomed config load.
|
|
123
|
+
*
|
|
124
|
+
* @param cwd - The consumer project root.
|
|
125
|
+
* @returns The config array, or `undefined` when the loader is unreachable.
|
|
126
|
+
* @rejects {Error} When the consumer's config fails to load.
|
|
127
|
+
*/
|
|
128
|
+
async function loadConfigArray(cwd) {
|
|
129
|
+
let loader;
|
|
130
|
+
try {
|
|
131
|
+
const { requireFrom, root } = resolveEslintInstall(cwd);
|
|
132
|
+
const configLoaderModule = requireFrom(path.join(root, "lib", "config", "config-loader.js"));
|
|
133
|
+
if (!isConfigLoaderModule(configLoaderModule)) throw new Error("eslint config-loader did not export a ConfigLoader constructor");
|
|
134
|
+
const { ConfigLoader } = configLoaderModule;
|
|
135
|
+
loader = new ConfigLoader({
|
|
136
|
+
configFile: void 0,
|
|
137
|
+
cwd,
|
|
138
|
+
ignoreEnabled: true
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
return loader.loadConfigArrayForDirectory(path.join(cwd, "__placeholder__"));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether a config ignores paths for the whole run rather than just for itself.
|
|
147
|
+
*
|
|
148
|
+
* @param config - One entry of the resolved config array.
|
|
149
|
+
* @returns True when `ignores` is the config's only substantive key.
|
|
150
|
+
*/
|
|
151
|
+
function isGlobalIgnore(config) {
|
|
152
|
+
return config["ignores"] !== void 0 && Object.keys(config).filter((key) => !META_KEYS.has(key)).length === 1;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether a `files`/`ignores` value is made purely of glob strings. Flat config
|
|
156
|
+
* also permits function matchers, and `files` may nest one level for AND
|
|
157
|
+
* matching; a function anywhere leaves the config with no form as data.
|
|
158
|
+
*
|
|
159
|
+
* @param value - The `files` or `ignores` value to test.
|
|
160
|
+
* @returns True when every matcher is a string.
|
|
161
|
+
*/
|
|
162
|
+
function isGlobList(value) {
|
|
163
|
+
return Array.isArray(value) && value.flat(1).every((matcher) => typeof matcher === "string");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reduce one config to its match keys.
|
|
167
|
+
*
|
|
168
|
+
* @param config - One entry of the resolved config array.
|
|
169
|
+
* @returns The entry, or `undefined` when a matcher is a function.
|
|
170
|
+
*/
|
|
171
|
+
function serializeEntry({ basePath, files, ignores }) {
|
|
172
|
+
const entry = {};
|
|
173
|
+
if (files !== void 0) {
|
|
174
|
+
if (!isGlobList(files)) return;
|
|
175
|
+
entry.files = files;
|
|
176
|
+
}
|
|
177
|
+
if (ignores !== void 0) {
|
|
178
|
+
if (!isGlobList(ignores)) return;
|
|
179
|
+
entry.ignores = ignores;
|
|
180
|
+
}
|
|
181
|
+
if (typeof basePath === "string") entry.basePath = basePath;
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Reduce the resolved config array to the entries that decide whether ESLint
|
|
186
|
+
* lints a path at all, or `undefined` when a matcher is a function rather than
|
|
187
|
+
* a glob and so cannot cross a process boundary.
|
|
188
|
+
*
|
|
189
|
+
* Only two kinds of entry can make that decision: one with `files`, which can
|
|
190
|
+
* match a path, and a bare global ignore, which can veto it. A `files`-less
|
|
191
|
+
* config with other keys alongside its `ignores` merely narrows which configs
|
|
192
|
+
* merge into the one ESLint lints with, which nothing here reads — so it is
|
|
193
|
+
* dropped rather than carried, and with it the risk of a stripped `rules` key
|
|
194
|
+
* silently promoting it into a global ignore.
|
|
195
|
+
*
|
|
196
|
+
* @param configArray - The resolved config array.
|
|
197
|
+
* @returns The serializable entries, or `undefined` when one is a function.
|
|
198
|
+
*/
|
|
199
|
+
function serializeEntries(configArray) {
|
|
200
|
+
const entries = [];
|
|
201
|
+
for (const config of configArray) {
|
|
202
|
+
if (config["files"] === void 0 && !isGlobalIgnore(config)) continue;
|
|
203
|
+
const entry = serializeEntry(config);
|
|
204
|
+
if (entry === void 0) return;
|
|
205
|
+
entries.push(entry);
|
|
206
|
+
}
|
|
207
|
+
return entries;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The last-resort classification: load ESLint itself and ask it per target.
|
|
211
|
+
* Only reached when no config array could be built, so nothing cheaper is
|
|
212
|
+
* already in hand.
|
|
213
|
+
*
|
|
214
|
+
* @param cwd - The consumer project root.
|
|
215
|
+
* @param targets - The target files to classify.
|
|
216
|
+
* @returns The ignored subset of `targets`.
|
|
217
|
+
* @rejects {Error} When ESLint cannot be resolved or its config fails to load.
|
|
218
|
+
*/
|
|
219
|
+
async function queryEslint(cwd, targets) {
|
|
220
|
+
const { requireFrom } = resolveEslintInstall(cwd);
|
|
221
|
+
const eslintModule = await import(pathToFileURL(requireFrom.resolve("eslint")).href);
|
|
222
|
+
if (!isEslintModule(eslintModule)) throw new Error("eslint did not export an ESLint constructor");
|
|
223
|
+
const { ESLint } = eslintModule;
|
|
224
|
+
const eslint = new ESLint({ cwd });
|
|
225
|
+
const ignored = [];
|
|
226
|
+
for (const target of targets) if (await eslint.isPathIgnored(target)) ignored.push(target);
|
|
227
|
+
return ignored;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The target list, read only by the paths that classify it — the predicate
|
|
231
|
+
* never looks at it, and it is the larger of the two inputs.
|
|
232
|
+
*
|
|
233
|
+
* @returns The target files, absolute.
|
|
234
|
+
*/
|
|
235
|
+
function readTargets() {
|
|
236
|
+
const parsed = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
237
|
+
if (!isStringArray(parsed)) throw new Error("expected a JSON array of target paths on stdin");
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The best answer this helper can give: the config's patterns when they are
|
|
242
|
+
* data, the ignored subset of the target list when they are not.
|
|
243
|
+
*
|
|
244
|
+
* Both fallbacks reuse whatever the step before them already paid for. A config
|
|
245
|
+
* that holds a function matcher still classifies targets here, off the array
|
|
246
|
+
* already loaded, rather than loading the config a second time through `ESLint`
|
|
247
|
+
* — which only the case of no reachable config loader at all has to fall back
|
|
248
|
+
* to.
|
|
249
|
+
*
|
|
250
|
+
* @param cwd - The consumer project root.
|
|
251
|
+
* @returns The payload to write back.
|
|
252
|
+
* @rejects {Error} When the consumer's config fails to load.
|
|
253
|
+
*/
|
|
254
|
+
async function resolvePayload(cwd) {
|
|
255
|
+
const configArray = await loadConfigArray(cwd);
|
|
256
|
+
if (configArray === void 0) return {
|
|
257
|
+
ignored: await queryEslint(cwd, readTargets()),
|
|
258
|
+
mode: "answers"
|
|
259
|
+
};
|
|
260
|
+
const entries = serializeEntries(configArray);
|
|
261
|
+
if (entries === void 0) return {
|
|
262
|
+
ignored: readTargets().filter((target) => configArray.getConfigStatus(target) !== "matched"),
|
|
263
|
+
mode: "answers"
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
basePath: configArray.basePath,
|
|
267
|
+
entries,
|
|
268
|
+
mode: "predicate"
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async function main() {
|
|
272
|
+
const [cwd, outFile] = process.argv.slice(2);
|
|
273
|
+
if (cwd === void 0 || outFile === void 0) throw new Error("usage: node ignored-child <cwd> <outFile> (targets on stdin)");
|
|
274
|
+
fs.writeFileSync(outFile, JSON.stringify(await resolvePayload(cwd)));
|
|
275
|
+
}
|
|
276
|
+
main().catch(() => {
|
|
277
|
+
process.exitCode = 1;
|
|
278
|
+
});
|
|
279
|
+
//#endregion
|
|
280
|
+
export {};
|