@eslint-config-snapshot/api 0.9.0 → 0.14.1
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/CHANGELOG.md +45 -0
- package/dist/index.cjs +434 -91
- package/dist/index.js +434 -91
- package/package.json +2 -1
- package/src/config.ts +32 -8
- package/src/diff.ts +51 -17
- package/src/extract.ts +19 -0
- package/src/index.ts +1 -1
- package/src/sampling.ts +351 -40
- package/src/snapshot.ts +41 -44
- package/test/config.test.ts +8 -4
- package/test/diff.test.ts +49 -1
- package/test/sampling.test.ts +50 -11
- package/test/snapshot.test.ts +31 -4
package/dist/index.js
CHANGED
|
@@ -128,9 +128,11 @@ function lowestCommonAncestor(paths) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// src/sampling.ts
|
|
131
|
+
import createDebug from "debug";
|
|
131
132
|
import fg from "fast-glob";
|
|
132
|
-
|
|
133
|
+
var debugSampling = createDebug("eslint-config-snapshot:sampling");
|
|
133
134
|
async function sampleWorkspaceFiles(workspaceAbs, config) {
|
|
135
|
+
const startedAt = Date.now();
|
|
134
136
|
const all = await fg(config.includeGlobs, {
|
|
135
137
|
cwd: workspaceAbs,
|
|
136
138
|
ignore: config.excludeGlobs,
|
|
@@ -139,45 +141,46 @@ async function sampleWorkspaceFiles(workspaceAbs, config) {
|
|
|
139
141
|
unique: true
|
|
140
142
|
});
|
|
141
143
|
const normalized = sortUnique(all.map((entry) => normalizePath(entry)));
|
|
144
|
+
debugSampling("workspace=%s candidates=%d", workspaceAbs, normalized.length);
|
|
142
145
|
if (normalized.length === 0) {
|
|
143
146
|
return [];
|
|
144
147
|
}
|
|
145
148
|
if (normalized.length <= config.maxFilesPerWorkspace) {
|
|
149
|
+
debugSampling("workspace=%s using all files=%d elapsedMs=%d", workspaceAbs, normalized.length, Date.now() - startedAt);
|
|
146
150
|
return normalized;
|
|
147
151
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
const selected = selectDistributed(normalized, config.maxFilesPerWorkspace, config.tokenHints);
|
|
153
|
+
debugSampling(
|
|
154
|
+
"workspace=%s selected=%d mode=token-distributed elapsedMs=%d files=%o",
|
|
155
|
+
workspaceAbs,
|
|
156
|
+
selected.length,
|
|
157
|
+
Date.now() - startedAt,
|
|
158
|
+
selected
|
|
159
|
+
);
|
|
160
|
+
return selected;
|
|
161
|
+
}
|
|
162
|
+
function selectDistributed(files, count, tokenHints) {
|
|
156
163
|
if (files.length <= count) {
|
|
157
164
|
return files;
|
|
158
165
|
}
|
|
166
|
+
const tokenPriorityMap = createTokenPriorityMap(tokenHints);
|
|
159
167
|
const selected = [];
|
|
160
168
|
const selectedSet = /* @__PURE__ */ new Set();
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
const token = getPrimaryToken(file);
|
|
167
|
-
if (!token || tokenSeen.has(token)) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
tokenSeen.add(token);
|
|
171
|
-
selected.push(file);
|
|
172
|
-
selectedSet.add(file);
|
|
173
|
-
}
|
|
169
|
+
const preferred = files.filter((file) => isPreferredForLintSampling(file));
|
|
170
|
+
const nonPreferred = files.filter((file) => !isPreferredForLintSampling(file));
|
|
171
|
+
appendTokenRepresentatives(preferred, tokenPriorityMap, selected, selectedSet, count);
|
|
172
|
+
appendTokenRepresentatives(nonPreferred, tokenPriorityMap, selected, selectedSet, count);
|
|
174
173
|
if (selected.length >= count) {
|
|
175
174
|
return sortUnique(selected).slice(0, count);
|
|
176
175
|
}
|
|
177
176
|
const remaining = files.filter((file) => !selectedSet.has(file));
|
|
178
177
|
const needed = count - selected.length;
|
|
179
|
-
const
|
|
180
|
-
|
|
178
|
+
const preferredRemaining = remaining.filter((file) => isPreferredForLintSampling(file));
|
|
179
|
+
const nonPreferredRemaining = remaining.filter((file) => !isPreferredForLintSampling(file));
|
|
180
|
+
const preferredPicked = pickUniformly(preferredRemaining, needed);
|
|
181
|
+
const afterPreferredNeed = needed - preferredPicked.length;
|
|
182
|
+
const fallbackPicked = afterPreferredNeed > 0 ? pickUniformly(nonPreferredRemaining, afterPreferredNeed) : [];
|
|
183
|
+
return sortUnique([...selected, ...preferredPicked, ...fallbackPicked]).slice(0, count);
|
|
181
184
|
}
|
|
182
185
|
function pickUniformly(files, count) {
|
|
183
186
|
if (count <= 0 || files.length === 0) {
|
|
@@ -191,14 +194,60 @@ function pickUniformly(files, count) {
|
|
|
191
194
|
}
|
|
192
195
|
const picked = [];
|
|
193
196
|
const usedIndices = /* @__PURE__ */ new Set();
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
+
if (count >= 3) {
|
|
198
|
+
const anchorIndices = [0, Math.floor((files.length - 1) / 2), files.length - 1];
|
|
199
|
+
for (const anchorIndex of anchorIndices) {
|
|
200
|
+
if (picked.length >= count || usedIndices.has(anchorIndex)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
usedIndices.add(anchorIndex);
|
|
204
|
+
const anchored = files[anchorIndex];
|
|
205
|
+
if (anchored !== void 0) {
|
|
206
|
+
picked.push(anchored);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const candidate of buildDistributedCandidates(files.length, count)) {
|
|
211
|
+
if (picked.length >= count) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
const safeIndex = nextFreeIndex(candidate, usedIndices, files.length);
|
|
215
|
+
if (usedIndices.has(safeIndex)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
197
218
|
usedIndices.add(safeIndex);
|
|
198
|
-
|
|
219
|
+
const selected = files[safeIndex];
|
|
220
|
+
if (selected !== void 0) {
|
|
221
|
+
picked.push(selected);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (picked.length < count) {
|
|
225
|
+
for (let index = 0; index < files.length && picked.length < count; index += 1) {
|
|
226
|
+
if (usedIndices.has(index)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
usedIndices.add(index);
|
|
230
|
+
const fallback = files[index];
|
|
231
|
+
if (fallback !== void 0) {
|
|
232
|
+
picked.push(fallback);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
199
235
|
}
|
|
200
236
|
return picked;
|
|
201
237
|
}
|
|
238
|
+
function buildDistributedCandidates(length, count) {
|
|
239
|
+
if (length <= 0 || count <= 0) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
if (count === 1) {
|
|
243
|
+
return [0];
|
|
244
|
+
}
|
|
245
|
+
const candidates = [];
|
|
246
|
+
for (let index = 0; index < count; index += 1) {
|
|
247
|
+
candidates.push(Math.round(index * (length - 1) / (count - 1)));
|
|
248
|
+
}
|
|
249
|
+
return candidates;
|
|
250
|
+
}
|
|
202
251
|
function nextFreeIndex(candidate, used, max) {
|
|
203
252
|
if (!used.has(candidate)) {
|
|
204
253
|
return candidate;
|
|
@@ -215,25 +264,257 @@ function nextFreeIndex(candidate, used, max) {
|
|
|
215
264
|
}
|
|
216
265
|
return candidate;
|
|
217
266
|
}
|
|
218
|
-
function getPrimaryToken(file) {
|
|
219
|
-
const parts = file.split("/");
|
|
220
|
-
|
|
221
|
-
|
|
267
|
+
function getPrimaryToken(file, tokenPriorityMap) {
|
|
268
|
+
const parts = file.split("/").filter((entry) => entry.length > 0);
|
|
269
|
+
if (parts.length === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const basename = parts[parts.length - 1];
|
|
273
|
+
if (basename === void 0) {
|
|
222
274
|
return null;
|
|
223
275
|
}
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
276
|
+
const basenameTokens = tokenizePathPart(basename, true);
|
|
277
|
+
const directoryTokensForward = parts.slice(0, -1).flatMap((entry) => tokenizePathPart(entry, false));
|
|
278
|
+
const directoryTokens = [];
|
|
279
|
+
for (let index = directoryTokensForward.length - 1; index >= 0; index -= 1) {
|
|
280
|
+
const token = directoryTokensForward[index];
|
|
281
|
+
if (token !== void 0) {
|
|
282
|
+
directoryTokens.push(token);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const allTokens = [...basenameTokens, ...directoryTokens].filter((entry) => entry.length > 1);
|
|
286
|
+
const bestKnownToken = pickBestKnownToken(allTokens, tokenPriorityMap);
|
|
287
|
+
if (bestKnownToken !== null) {
|
|
288
|
+
return bestKnownToken;
|
|
289
|
+
}
|
|
290
|
+
const fallback = allTokens.find((entry) => !GENERIC_TOKENS.has(entry));
|
|
291
|
+
return fallback ?? null;
|
|
292
|
+
}
|
|
293
|
+
function tokenizePathPart(part, stripExtension) {
|
|
294
|
+
const normalized = stripExtension ? part.replace(/\.[^.]+$/u, "") : part;
|
|
295
|
+
const expanded = normalized.replaceAll(/([a-z])([A-Z])/gu, "$1 $2").replaceAll(/[_\-.]+/gu, " ").toLowerCase();
|
|
296
|
+
return expanded.split(/\s+/u).filter((entry) => entry.length > 0);
|
|
297
|
+
}
|
|
298
|
+
function pickBestKnownToken(tokens, tokenPriorityMap) {
|
|
299
|
+
let bestToken = null;
|
|
300
|
+
let bestGroupPriority = Number.POSITIVE_INFINITY;
|
|
301
|
+
for (const token of tokens) {
|
|
302
|
+
const normalizedToken = normalizeToken(token);
|
|
303
|
+
const groupPriority = tokenPriorityMap.get(normalizedToken);
|
|
304
|
+
if (groupPriority === void 0) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (groupPriority < bestGroupPriority) {
|
|
308
|
+
bestGroupPriority = groupPriority;
|
|
309
|
+
bestToken = normalizedToken;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return bestToken;
|
|
313
|
+
}
|
|
314
|
+
function normalizeToken(token) {
|
|
315
|
+
if (token.endsWith("ies") && token.length > 3) {
|
|
316
|
+
return `${token.slice(0, -3)}y`;
|
|
317
|
+
}
|
|
318
|
+
if (token.endsWith("s") && token.length > 3) {
|
|
319
|
+
return token.slice(0, -1);
|
|
320
|
+
}
|
|
321
|
+
return token;
|
|
322
|
+
}
|
|
323
|
+
function isPreferredForLintSampling(file) {
|
|
324
|
+
return CODE_PREFERRED_EXTENSIONS.has(getExtension(file));
|
|
325
|
+
}
|
|
326
|
+
function getExtension(file) {
|
|
327
|
+
const lastDot = file.lastIndexOf(".");
|
|
328
|
+
if (lastDot === -1 || lastDot === file.length - 1) {
|
|
329
|
+
return "";
|
|
330
|
+
}
|
|
331
|
+
return file.slice(lastDot + 1).toLowerCase();
|
|
228
332
|
}
|
|
229
333
|
var GENERIC_TOKENS = /* @__PURE__ */ new Set(["src", "index", "main", "test", "spec", "package", "packages", "lib", "dist"]);
|
|
334
|
+
var CODE_PREFERRED_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "cjs", "mjs"]);
|
|
335
|
+
var DEFAULT_TOKEN_HINT_GROUPS = [
|
|
336
|
+
[
|
|
337
|
+
"chunk",
|
|
338
|
+
"conf",
|
|
339
|
+
"config",
|
|
340
|
+
"container",
|
|
341
|
+
"controller",
|
|
342
|
+
"helpers",
|
|
343
|
+
"mock",
|
|
344
|
+
"mocks",
|
|
345
|
+
"presentation",
|
|
346
|
+
"repository",
|
|
347
|
+
"route",
|
|
348
|
+
"routes",
|
|
349
|
+
"schema",
|
|
350
|
+
"setup",
|
|
351
|
+
"spec",
|
|
352
|
+
"stories",
|
|
353
|
+
"style",
|
|
354
|
+
"styles",
|
|
355
|
+
"test",
|
|
356
|
+
"type",
|
|
357
|
+
"types",
|
|
358
|
+
"utils",
|
|
359
|
+
"view",
|
|
360
|
+
"views"
|
|
361
|
+
],
|
|
362
|
+
[
|
|
363
|
+
"adapter",
|
|
364
|
+
"api",
|
|
365
|
+
"apis",
|
|
366
|
+
"builder",
|
|
367
|
+
"client",
|
|
368
|
+
"component",
|
|
369
|
+
"components",
|
|
370
|
+
"constants",
|
|
371
|
+
"context",
|
|
372
|
+
"core",
|
|
373
|
+
"dto",
|
|
374
|
+
"entity",
|
|
375
|
+
"entry",
|
|
376
|
+
"env",
|
|
377
|
+
"factory",
|
|
378
|
+
"fetcher",
|
|
379
|
+
"handler",
|
|
380
|
+
"hook",
|
|
381
|
+
"hooks",
|
|
382
|
+
"init",
|
|
383
|
+
"integration",
|
|
384
|
+
"interceptor",
|
|
385
|
+
"interface",
|
|
386
|
+
"layout",
|
|
387
|
+
"layouts",
|
|
388
|
+
"listener",
|
|
389
|
+
"logger",
|
|
390
|
+
"manager",
|
|
391
|
+
"mapper",
|
|
392
|
+
"meta",
|
|
393
|
+
"middleware",
|
|
394
|
+
"model",
|
|
395
|
+
"module",
|
|
396
|
+
"normalizer",
|
|
397
|
+
"options",
|
|
398
|
+
"page",
|
|
399
|
+
"pages",
|
|
400
|
+
"parser",
|
|
401
|
+
"plugin",
|
|
402
|
+
"provider",
|
|
403
|
+
"registry",
|
|
404
|
+
"resolver",
|
|
405
|
+
"router",
|
|
406
|
+
"runtime",
|
|
407
|
+
"serializer",
|
|
408
|
+
"server",
|
|
409
|
+
"service",
|
|
410
|
+
"settings",
|
|
411
|
+
"shared",
|
|
412
|
+
"slice",
|
|
413
|
+
"state",
|
|
414
|
+
"store",
|
|
415
|
+
"subscriber",
|
|
416
|
+
"theme",
|
|
417
|
+
"tracker",
|
|
418
|
+
"transform",
|
|
419
|
+
"unit",
|
|
420
|
+
"validator"
|
|
421
|
+
],
|
|
422
|
+
[
|
|
423
|
+
"base",
|
|
424
|
+
"bundle",
|
|
425
|
+
"common",
|
|
426
|
+
"compiler",
|
|
427
|
+
"contract",
|
|
428
|
+
"definition",
|
|
429
|
+
"definitions",
|
|
430
|
+
"deserializer",
|
|
431
|
+
"event",
|
|
432
|
+
"events",
|
|
433
|
+
"fixture",
|
|
434
|
+
"fixtures",
|
|
435
|
+
"guard",
|
|
436
|
+
"internal",
|
|
437
|
+
"loader",
|
|
438
|
+
"publisher",
|
|
439
|
+
"reducer",
|
|
440
|
+
"stub",
|
|
441
|
+
"stubs",
|
|
442
|
+
"tests",
|
|
443
|
+
"util"
|
|
444
|
+
]
|
|
445
|
+
];
|
|
446
|
+
function createTokenPriorityMap(input) {
|
|
447
|
+
const groups = normalizeTokenHintGroups(input);
|
|
448
|
+
const entries = [];
|
|
449
|
+
for (const [index, group] of groups.entries()) {
|
|
450
|
+
entries.push(...toPriorityEntries(group, index + 1));
|
|
451
|
+
}
|
|
452
|
+
return new Map(entries);
|
|
453
|
+
}
|
|
454
|
+
function normalizeTokenHintGroups(input) {
|
|
455
|
+
if (!input || input.length === 0) {
|
|
456
|
+
return DEFAULT_TOKEN_HINT_GROUPS.map((group) => [...group]);
|
|
457
|
+
}
|
|
458
|
+
if (Array.isArray(input[0])) {
|
|
459
|
+
const nested = input;
|
|
460
|
+
return nested.map((group) => group.filter((token) => token.trim().length > 0));
|
|
461
|
+
}
|
|
462
|
+
const flat = input;
|
|
463
|
+
return [flat.filter((token) => token.trim().length > 0)];
|
|
464
|
+
}
|
|
465
|
+
function toPriorityEntries(tokens, priority) {
|
|
466
|
+
return tokens.map((token) => [normalizeToken(token), priority]);
|
|
467
|
+
}
|
|
468
|
+
function appendTokenRepresentatives(files, tokenPriorityMap, selected, selectedSet, count) {
|
|
469
|
+
if (selected.length >= count || files.length === 0) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const tokenToFiles = /* @__PURE__ */ new Map();
|
|
473
|
+
const tokenFirstIndex = /* @__PURE__ */ new Map();
|
|
474
|
+
for (const [index, file] of files.entries()) {
|
|
475
|
+
const token = getPrimaryToken(file, tokenPriorityMap);
|
|
476
|
+
if (!token) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
tokenFirstIndex.set(token, Math.min(tokenFirstIndex.get(token) ?? Number.POSITIVE_INFINITY, index));
|
|
480
|
+
const current = tokenToFiles.get(token) ?? [];
|
|
481
|
+
current.push(file);
|
|
482
|
+
tokenToFiles.set(token, current);
|
|
483
|
+
}
|
|
484
|
+
const orderedTokens = [...tokenToFiles.keys()].sort((left, right) => {
|
|
485
|
+
const leftPriority = tokenPriorityMap.get(left) ?? Number.POSITIVE_INFINITY;
|
|
486
|
+
const rightPriority = tokenPriorityMap.get(right) ?? Number.POSITIVE_INFINITY;
|
|
487
|
+
if (leftPriority !== rightPriority) {
|
|
488
|
+
return leftPriority - rightPriority;
|
|
489
|
+
}
|
|
490
|
+
const leftIndex = tokenFirstIndex.get(left) ?? Number.POSITIVE_INFINITY;
|
|
491
|
+
const rightIndex = tokenFirstIndex.get(right) ?? Number.POSITIVE_INFINITY;
|
|
492
|
+
if (leftIndex !== rightIndex) {
|
|
493
|
+
return leftIndex - rightIndex;
|
|
494
|
+
}
|
|
495
|
+
return left.localeCompare(right);
|
|
496
|
+
});
|
|
497
|
+
for (const token of orderedTokens) {
|
|
498
|
+
if (selected.length >= count) {
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
const firstFile = tokenToFiles.get(token)?.[0];
|
|
502
|
+
if (!firstFile || selectedSet.has(firstFile)) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
selected.push(firstFile);
|
|
506
|
+
selectedSet.add(firstFile);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
230
509
|
|
|
231
510
|
// src/extract.ts
|
|
511
|
+
import createDebug2 from "debug";
|
|
232
512
|
import { spawnSync } from "child_process";
|
|
233
513
|
import { existsSync, readFileSync } from "fs";
|
|
234
514
|
import { createRequire } from "module";
|
|
235
515
|
import path2 from "path";
|
|
236
516
|
import { pathToFileURL } from "url";
|
|
517
|
+
var debugExtract = createDebug2("eslint-config-snapshot:extract");
|
|
237
518
|
function resolveEslintBinForWorkspace(workspaceAbs) {
|
|
238
519
|
const anchor = path2.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
239
520
|
const req = createRequire(anchor);
|
|
@@ -280,11 +561,16 @@ function findPackageRoot(entryAbs) {
|
|
|
280
561
|
}
|
|
281
562
|
function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
|
|
282
563
|
const eslintBin = resolveEslintBinForWorkspace(workspaceAbs);
|
|
564
|
+
const commandArgs = [eslintBin, "--print-config", fileAbs];
|
|
565
|
+
const startedAt = Date.now();
|
|
566
|
+
debugExtract("spawn: cwd=%s cmd=%s %o", workspaceAbs, process.execPath, commandArgs);
|
|
283
567
|
const proc = spawnSync(process.execPath, [eslintBin, "--print-config", fileAbs], {
|
|
284
568
|
cwd: workspaceAbs,
|
|
285
569
|
encoding: "utf8"
|
|
286
570
|
});
|
|
571
|
+
debugExtract("spawn: done status=%s elapsedMs=%d", String(proc.status), Date.now() - startedAt);
|
|
287
572
|
if (proc.status !== 0) {
|
|
573
|
+
debugExtract("spawn: stderr=%s", proc.stderr.trim());
|
|
288
574
|
throw new Error(`Failed to run eslint --print-config for ${fileAbs}`);
|
|
289
575
|
}
|
|
290
576
|
const stdout = proc.stdout.trim();
|
|
@@ -314,6 +600,7 @@ function resolveEslintVersionForWorkspace(workspaceAbs) {
|
|
|
314
600
|
return "unknown";
|
|
315
601
|
}
|
|
316
602
|
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
603
|
+
debugExtract("workspace=%s sampleCount=%d", workspaceAbs, fileAbsList.length);
|
|
317
604
|
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
318
605
|
const results = [];
|
|
319
606
|
for (const fileAbs of fileAbsList) {
|
|
@@ -322,9 +609,16 @@ async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
|
322
609
|
results.push({ fileAbs, rules });
|
|
323
610
|
} catch (error) {
|
|
324
611
|
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
612
|
+
debugExtract("extract failed: workspace=%s file=%s error=%s", workspaceAbs, fileAbs, normalizedError.message);
|
|
325
613
|
results.push({ fileAbs, error: normalizedError });
|
|
326
614
|
}
|
|
327
615
|
}
|
|
616
|
+
debugExtract(
|
|
617
|
+
"workspace=%s extracted=%d failed=%d",
|
|
618
|
+
workspaceAbs,
|
|
619
|
+
results.filter((entry) => entry.rules !== void 0).length,
|
|
620
|
+
results.filter((entry) => entry.error !== void 0).length
|
|
621
|
+
);
|
|
328
622
|
return results;
|
|
329
623
|
}
|
|
330
624
|
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
@@ -335,6 +629,7 @@ async function createWorkspaceEvaluator(workspaceAbs) {
|
|
|
335
629
|
const eslintModule = await import(pathToFileURL(eslintModuleEntry).href);
|
|
336
630
|
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
337
631
|
if (ESLintClass) {
|
|
632
|
+
debugExtract("workspace=%s evaluator=eslint-api", workspaceAbs);
|
|
338
633
|
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
339
634
|
return async (fileAbs) => {
|
|
340
635
|
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
@@ -346,6 +641,7 @@ async function createWorkspaceEvaluator(workspaceAbs) {
|
|
|
346
641
|
}
|
|
347
642
|
} catch {
|
|
348
643
|
}
|
|
644
|
+
debugExtract("workspace=%s evaluator=spawn-print-config", workspaceAbs);
|
|
349
645
|
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
350
646
|
}
|
|
351
647
|
function normalizeRules(rules) {
|
|
@@ -380,45 +676,27 @@ function aggregateRules(ruleMaps) {
|
|
|
380
676
|
const aggregated = /* @__PURE__ */ new Map();
|
|
381
677
|
for (const rules of ruleMaps) {
|
|
382
678
|
for (const [ruleName, nextEntry] of rules.entries()) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const severityCmp = compareSeverity(nextEntry[0], currentEntry[0]);
|
|
389
|
-
if (severityCmp > 0) {
|
|
390
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
if (severityCmp < 0) {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const currentOptions = currentEntry.length > 1 ? canonicalizeJson(currentEntry[1]) : void 0;
|
|
397
|
-
const nextOptions = nextEntry.length > 1 ? canonicalizeJson(nextEntry[1]) : void 0;
|
|
398
|
-
if (currentOptions === void 0 && nextOptions !== void 0) {
|
|
399
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (currentOptions !== void 0 && nextOptions === void 0) {
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
if (currentOptions === void 0 && nextOptions === void 0) {
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
const currentJson = JSON.stringify(currentOptions);
|
|
409
|
-
const nextJson = JSON.stringify(nextOptions);
|
|
410
|
-
if (nextJson < currentJson) {
|
|
411
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
412
|
-
}
|
|
679
|
+
const normalizedEntry = canonicalizeJson(nextEntry);
|
|
680
|
+
const variantKey = toVariantKey(normalizedEntry);
|
|
681
|
+
const variants = aggregated.get(ruleName) ?? /* @__PURE__ */ new Map();
|
|
682
|
+
variants.set(variantKey, normalizedEntry);
|
|
683
|
+
aggregated.set(ruleName, variants);
|
|
413
684
|
}
|
|
414
685
|
}
|
|
415
|
-
|
|
686
|
+
const entries = [...aggregated.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ruleName, variants]) => {
|
|
687
|
+
const sortedVariants = [...variants.values()].sort(compareVariants);
|
|
688
|
+
if (sortedVariants.length === 1) {
|
|
689
|
+
return [ruleName, sortedVariants[0]];
|
|
690
|
+
}
|
|
691
|
+
return [ruleName, sortedVariants];
|
|
692
|
+
});
|
|
693
|
+
return new Map(entries);
|
|
416
694
|
}
|
|
417
695
|
function buildSnapshot(groupId, workspaces, rules) {
|
|
418
696
|
const sortedRules = [...rules.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
419
697
|
const rulesObject = {};
|
|
420
698
|
for (const [name, config] of sortedRules) {
|
|
421
|
-
rulesObject[name] = canonicalizeJson(config);
|
|
699
|
+
rulesObject[name] = isSingleRuleEntry(config) ? canonicalizeJson(config) : config.map((variant) => canonicalizeJson(variant)).sort(compareVariants);
|
|
422
700
|
}
|
|
423
701
|
return {
|
|
424
702
|
formatVersion: 1,
|
|
@@ -440,6 +718,17 @@ async function readSnapshotFile(fileAbs) {
|
|
|
440
718
|
const raw = await readFile(fileAbs, "utf8");
|
|
441
719
|
return JSON.parse(raw);
|
|
442
720
|
}
|
|
721
|
+
function toVariantKey(entry) {
|
|
722
|
+
return JSON.stringify(canonicalizeJson(entry));
|
|
723
|
+
}
|
|
724
|
+
function compareVariants(a, b) {
|
|
725
|
+
const aJson = JSON.stringify(canonicalizeJson(a));
|
|
726
|
+
const bJson = JSON.stringify(canonicalizeJson(b));
|
|
727
|
+
return aJson.localeCompare(bJson);
|
|
728
|
+
}
|
|
729
|
+
function isSingleRuleEntry(entry) {
|
|
730
|
+
return !Array.isArray(entry[0]);
|
|
731
|
+
}
|
|
443
732
|
|
|
444
733
|
// src/diff.ts
|
|
445
734
|
function diffSnapshots(before, after) {
|
|
@@ -454,32 +743,51 @@ function diffSnapshots(before, after) {
|
|
|
454
743
|
for (const name of beforeNames.filter((entry) => afterNames.includes(entry))) {
|
|
455
744
|
const oldEntry = beforeRules[name];
|
|
456
745
|
const newEntry = afterRules[name];
|
|
457
|
-
|
|
746
|
+
const oldVariants = toVariants(oldEntry);
|
|
747
|
+
const newVariants = toVariants(newEntry);
|
|
748
|
+
const oldSeverity = summarizeSeveritySet(oldVariants);
|
|
749
|
+
const newSeverity = summarizeSeveritySet(newVariants);
|
|
750
|
+
if (oldSeverity !== newSeverity) {
|
|
458
751
|
severityChanges.push({
|
|
459
752
|
rule: name,
|
|
460
|
-
before:
|
|
461
|
-
after:
|
|
753
|
+
before: oldSeverity,
|
|
754
|
+
after: newSeverity
|
|
462
755
|
});
|
|
463
756
|
}
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
|
|
757
|
+
const oldSerialized = JSON.stringify(oldVariants);
|
|
758
|
+
const newSerialized = JSON.stringify(newVariants);
|
|
759
|
+
if (oldSerialized === newSerialized) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const oldIsOnlyOff = oldVariants.every((entry) => entry[0] === "off");
|
|
763
|
+
const newIsOnlyOff = newVariants.every((entry) => entry[0] === "off");
|
|
764
|
+
if (oldIsOnlyOff || newIsOnlyOff) {
|
|
765
|
+
if (oldIsOnlyOff && newIsOnlyOff) {
|
|
766
|
+
const oldHasOptions = oldVariants.some((variant) => variant.length > 1);
|
|
767
|
+
const newHasOptions = newVariants.some((variant) => variant.length > 1);
|
|
768
|
+
if (oldHasOptions && !newHasOptions) {
|
|
469
769
|
removedRules.push(name);
|
|
470
|
-
} else if (
|
|
770
|
+
} else if (!oldHasOptions && newHasOptions) {
|
|
471
771
|
introducedRules.push(name);
|
|
772
|
+
} else if (oldVariants.length > newVariants.length) {
|
|
773
|
+
removedRules.push(name);
|
|
774
|
+
} else if (oldVariants.length < newVariants.length) {
|
|
775
|
+
introducedRules.push(name);
|
|
776
|
+
} else {
|
|
777
|
+
optionChanges.push({
|
|
778
|
+
rule: name,
|
|
779
|
+
before: oldVariants,
|
|
780
|
+
after: newVariants
|
|
781
|
+
});
|
|
472
782
|
}
|
|
473
783
|
}
|
|
474
784
|
continue;
|
|
475
785
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
482
|
-
}
|
|
786
|
+
optionChanges.push({
|
|
787
|
+
rule: name,
|
|
788
|
+
before: oldVariants,
|
|
789
|
+
after: newVariants
|
|
790
|
+
});
|
|
483
791
|
}
|
|
484
792
|
const beforeWorkspaces = sortUnique(before.workspaces);
|
|
485
793
|
const afterWorkspaces = sortUnique(after.workspaces);
|
|
@@ -494,6 +802,17 @@ function diffSnapshots(before, after) {
|
|
|
494
802
|
}
|
|
495
803
|
};
|
|
496
804
|
}
|
|
805
|
+
function toVariants(entry) {
|
|
806
|
+
if (!Array.isArray(entry[0])) {
|
|
807
|
+
return [canonicalizeJson(entry)];
|
|
808
|
+
}
|
|
809
|
+
return entry.map((variant) => canonicalizeJson(variant));
|
|
810
|
+
}
|
|
811
|
+
function summarizeSeveritySet(variants) {
|
|
812
|
+
const severityOrder = ["error", "warn", "off"];
|
|
813
|
+
const severities = new Set(variants.map((variant) => variant[0]));
|
|
814
|
+
return severityOrder.filter((severity) => severities.has(severity)).join("|");
|
|
815
|
+
}
|
|
497
816
|
function hasDiff(diff) {
|
|
498
817
|
return diff.introducedRules.length > 0 || diff.removedRules.length > 0 || diff.severityChanges.length > 0 || diff.optionChanges.length > 0 || diff.workspaceMembershipChanges.added.length > 0 || diff.workspaceMembershipChanges.removed.length > 0;
|
|
499
818
|
}
|
|
@@ -508,10 +827,9 @@ var DEFAULT_CONFIG = {
|
|
|
508
827
|
groups: [{ name: "default", match: ["**/*"] }]
|
|
509
828
|
},
|
|
510
829
|
sampling: {
|
|
511
|
-
maxFilesPerWorkspace:
|
|
512
|
-
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs}"],
|
|
513
|
-
excludeGlobs: ["**/node_modules/**", "**/dist/**"]
|
|
514
|
-
hintGlobs: []
|
|
830
|
+
maxFilesPerWorkspace: 10,
|
|
831
|
+
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}"],
|
|
832
|
+
excludeGlobs: ["**/node_modules/**", "**/dist/**"]
|
|
515
833
|
}
|
|
516
834
|
};
|
|
517
835
|
var SPEC_SEARCH_PLACES = [
|
|
@@ -586,10 +904,35 @@ function getConfigScaffold(preset = "minimal") {
|
|
|
586
904
|
groups: [{ name: 'default', match: ['**/*'] }]
|
|
587
905
|
},
|
|
588
906
|
sampling: {
|
|
589
|
-
maxFilesPerWorkspace:
|
|
590
|
-
includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
|
|
907
|
+
maxFilesPerWorkspace: 10,
|
|
908
|
+
includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}'],
|
|
591
909
|
excludeGlobs: ['**/node_modules/**', '**/dist/**'],
|
|
592
|
-
|
|
910
|
+
tokenHints: [
|
|
911
|
+
'chunk',
|
|
912
|
+
'conf',
|
|
913
|
+
'config',
|
|
914
|
+
'container',
|
|
915
|
+
'controller',
|
|
916
|
+
'helpers',
|
|
917
|
+
'mock',
|
|
918
|
+
'mocks',
|
|
919
|
+
'presentation',
|
|
920
|
+
'repository',
|
|
921
|
+
'route',
|
|
922
|
+
'routes',
|
|
923
|
+
'schema',
|
|
924
|
+
'setup',
|
|
925
|
+
'spec',
|
|
926
|
+
'stories',
|
|
927
|
+
'style',
|
|
928
|
+
'styles',
|
|
929
|
+
'test',
|
|
930
|
+
'type',
|
|
931
|
+
'types',
|
|
932
|
+
'utils',
|
|
933
|
+
'view',
|
|
934
|
+
'views'
|
|
935
|
+
]
|
|
593
936
|
}
|
|
594
937
|
}
|
|
595
938
|
`;
|