@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.cjs
CHANGED
|
@@ -185,9 +185,11 @@ function lowestCommonAncestor(paths) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// src/sampling.ts
|
|
188
|
+
var import_debug = __toESM(require("debug"), 1);
|
|
188
189
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
189
|
-
var
|
|
190
|
+
var debugSampling = (0, import_debug.default)("eslint-config-snapshot:sampling");
|
|
190
191
|
async function sampleWorkspaceFiles(workspaceAbs, config) {
|
|
192
|
+
const startedAt = Date.now();
|
|
191
193
|
const all = await (0, import_fast_glob.default)(config.includeGlobs, {
|
|
192
194
|
cwd: workspaceAbs,
|
|
193
195
|
ignore: config.excludeGlobs,
|
|
@@ -196,45 +198,46 @@ async function sampleWorkspaceFiles(workspaceAbs, config) {
|
|
|
196
198
|
unique: true
|
|
197
199
|
});
|
|
198
200
|
const normalized = sortUnique(all.map((entry) => normalizePath(entry)));
|
|
201
|
+
debugSampling("workspace=%s candidates=%d", workspaceAbs, normalized.length);
|
|
199
202
|
if (normalized.length === 0) {
|
|
200
203
|
return [];
|
|
201
204
|
}
|
|
202
205
|
if (normalized.length <= config.maxFilesPerWorkspace) {
|
|
206
|
+
debugSampling("workspace=%s using all files=%d elapsedMs=%d", workspaceAbs, normalized.length, Date.now() - startedAt);
|
|
203
207
|
return normalized;
|
|
204
208
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
const selected = selectDistributed(normalized, config.maxFilesPerWorkspace, config.tokenHints);
|
|
210
|
+
debugSampling(
|
|
211
|
+
"workspace=%s selected=%d mode=token-distributed elapsedMs=%d files=%o",
|
|
212
|
+
workspaceAbs,
|
|
213
|
+
selected.length,
|
|
214
|
+
Date.now() - startedAt,
|
|
215
|
+
selected
|
|
216
|
+
);
|
|
217
|
+
return selected;
|
|
218
|
+
}
|
|
219
|
+
function selectDistributed(files, count, tokenHints) {
|
|
213
220
|
if (files.length <= count) {
|
|
214
221
|
return files;
|
|
215
222
|
}
|
|
223
|
+
const tokenPriorityMap = createTokenPriorityMap(tokenHints);
|
|
216
224
|
const selected = [];
|
|
217
225
|
const selectedSet = /* @__PURE__ */ new Set();
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
const token = getPrimaryToken(file);
|
|
224
|
-
if (!token || tokenSeen.has(token)) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
tokenSeen.add(token);
|
|
228
|
-
selected.push(file);
|
|
229
|
-
selectedSet.add(file);
|
|
230
|
-
}
|
|
226
|
+
const preferred = files.filter((file) => isPreferredForLintSampling(file));
|
|
227
|
+
const nonPreferred = files.filter((file) => !isPreferredForLintSampling(file));
|
|
228
|
+
appendTokenRepresentatives(preferred, tokenPriorityMap, selected, selectedSet, count);
|
|
229
|
+
appendTokenRepresentatives(nonPreferred, tokenPriorityMap, selected, selectedSet, count);
|
|
231
230
|
if (selected.length >= count) {
|
|
232
231
|
return sortUnique(selected).slice(0, count);
|
|
233
232
|
}
|
|
234
233
|
const remaining = files.filter((file) => !selectedSet.has(file));
|
|
235
234
|
const needed = count - selected.length;
|
|
236
|
-
const
|
|
237
|
-
|
|
235
|
+
const preferredRemaining = remaining.filter((file) => isPreferredForLintSampling(file));
|
|
236
|
+
const nonPreferredRemaining = remaining.filter((file) => !isPreferredForLintSampling(file));
|
|
237
|
+
const preferredPicked = pickUniformly(preferredRemaining, needed);
|
|
238
|
+
const afterPreferredNeed = needed - preferredPicked.length;
|
|
239
|
+
const fallbackPicked = afterPreferredNeed > 0 ? pickUniformly(nonPreferredRemaining, afterPreferredNeed) : [];
|
|
240
|
+
return sortUnique([...selected, ...preferredPicked, ...fallbackPicked]).slice(0, count);
|
|
238
241
|
}
|
|
239
242
|
function pickUniformly(files, count) {
|
|
240
243
|
if (count <= 0 || files.length === 0) {
|
|
@@ -248,14 +251,60 @@ function pickUniformly(files, count) {
|
|
|
248
251
|
}
|
|
249
252
|
const picked = [];
|
|
250
253
|
const usedIndices = /* @__PURE__ */ new Set();
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
+
if (count >= 3) {
|
|
255
|
+
const anchorIndices = [0, Math.floor((files.length - 1) / 2), files.length - 1];
|
|
256
|
+
for (const anchorIndex of anchorIndices) {
|
|
257
|
+
if (picked.length >= count || usedIndices.has(anchorIndex)) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
usedIndices.add(anchorIndex);
|
|
261
|
+
const anchored = files[anchorIndex];
|
|
262
|
+
if (anchored !== void 0) {
|
|
263
|
+
picked.push(anchored);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const candidate of buildDistributedCandidates(files.length, count)) {
|
|
268
|
+
if (picked.length >= count) {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
const safeIndex = nextFreeIndex(candidate, usedIndices, files.length);
|
|
272
|
+
if (usedIndices.has(safeIndex)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
254
275
|
usedIndices.add(safeIndex);
|
|
255
|
-
|
|
276
|
+
const selected = files[safeIndex];
|
|
277
|
+
if (selected !== void 0) {
|
|
278
|
+
picked.push(selected);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (picked.length < count) {
|
|
282
|
+
for (let index = 0; index < files.length && picked.length < count; index += 1) {
|
|
283
|
+
if (usedIndices.has(index)) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
usedIndices.add(index);
|
|
287
|
+
const fallback = files[index];
|
|
288
|
+
if (fallback !== void 0) {
|
|
289
|
+
picked.push(fallback);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
256
292
|
}
|
|
257
293
|
return picked;
|
|
258
294
|
}
|
|
295
|
+
function buildDistributedCandidates(length, count) {
|
|
296
|
+
if (length <= 0 || count <= 0) {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
if (count === 1) {
|
|
300
|
+
return [0];
|
|
301
|
+
}
|
|
302
|
+
const candidates = [];
|
|
303
|
+
for (let index = 0; index < count; index += 1) {
|
|
304
|
+
candidates.push(Math.round(index * (length - 1) / (count - 1)));
|
|
305
|
+
}
|
|
306
|
+
return candidates;
|
|
307
|
+
}
|
|
259
308
|
function nextFreeIndex(candidate, used, max) {
|
|
260
309
|
if (!used.has(candidate)) {
|
|
261
310
|
return candidate;
|
|
@@ -272,25 +321,257 @@ function nextFreeIndex(candidate, used, max) {
|
|
|
272
321
|
}
|
|
273
322
|
return candidate;
|
|
274
323
|
}
|
|
275
|
-
function getPrimaryToken(file) {
|
|
276
|
-
const parts = file.split("/");
|
|
277
|
-
|
|
278
|
-
|
|
324
|
+
function getPrimaryToken(file, tokenPriorityMap) {
|
|
325
|
+
const parts = file.split("/").filter((entry) => entry.length > 0);
|
|
326
|
+
if (parts.length === 0) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const basename = parts[parts.length - 1];
|
|
330
|
+
if (basename === void 0) {
|
|
279
331
|
return null;
|
|
280
332
|
}
|
|
281
|
-
const
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
333
|
+
const basenameTokens = tokenizePathPart(basename, true);
|
|
334
|
+
const directoryTokensForward = parts.slice(0, -1).flatMap((entry) => tokenizePathPart(entry, false));
|
|
335
|
+
const directoryTokens = [];
|
|
336
|
+
for (let index = directoryTokensForward.length - 1; index >= 0; index -= 1) {
|
|
337
|
+
const token = directoryTokensForward[index];
|
|
338
|
+
if (token !== void 0) {
|
|
339
|
+
directoryTokens.push(token);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const allTokens = [...basenameTokens, ...directoryTokens].filter((entry) => entry.length > 1);
|
|
343
|
+
const bestKnownToken = pickBestKnownToken(allTokens, tokenPriorityMap);
|
|
344
|
+
if (bestKnownToken !== null) {
|
|
345
|
+
return bestKnownToken;
|
|
346
|
+
}
|
|
347
|
+
const fallback = allTokens.find((entry) => !GENERIC_TOKENS.has(entry));
|
|
348
|
+
return fallback ?? null;
|
|
349
|
+
}
|
|
350
|
+
function tokenizePathPart(part, stripExtension) {
|
|
351
|
+
const normalized = stripExtension ? part.replace(/\.[^.]+$/u, "") : part;
|
|
352
|
+
const expanded = normalized.replaceAll(/([a-z])([A-Z])/gu, "$1 $2").replaceAll(/[_\-.]+/gu, " ").toLowerCase();
|
|
353
|
+
return expanded.split(/\s+/u).filter((entry) => entry.length > 0);
|
|
354
|
+
}
|
|
355
|
+
function pickBestKnownToken(tokens, tokenPriorityMap) {
|
|
356
|
+
let bestToken = null;
|
|
357
|
+
let bestGroupPriority = Number.POSITIVE_INFINITY;
|
|
358
|
+
for (const token of tokens) {
|
|
359
|
+
const normalizedToken = normalizeToken(token);
|
|
360
|
+
const groupPriority = tokenPriorityMap.get(normalizedToken);
|
|
361
|
+
if (groupPriority === void 0) {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (groupPriority < bestGroupPriority) {
|
|
365
|
+
bestGroupPriority = groupPriority;
|
|
366
|
+
bestToken = normalizedToken;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return bestToken;
|
|
370
|
+
}
|
|
371
|
+
function normalizeToken(token) {
|
|
372
|
+
if (token.endsWith("ies") && token.length > 3) {
|
|
373
|
+
return `${token.slice(0, -3)}y`;
|
|
374
|
+
}
|
|
375
|
+
if (token.endsWith("s") && token.length > 3) {
|
|
376
|
+
return token.slice(0, -1);
|
|
377
|
+
}
|
|
378
|
+
return token;
|
|
379
|
+
}
|
|
380
|
+
function isPreferredForLintSampling(file) {
|
|
381
|
+
return CODE_PREFERRED_EXTENSIONS.has(getExtension(file));
|
|
382
|
+
}
|
|
383
|
+
function getExtension(file) {
|
|
384
|
+
const lastDot = file.lastIndexOf(".");
|
|
385
|
+
if (lastDot === -1 || lastDot === file.length - 1) {
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
|
+
return file.slice(lastDot + 1).toLowerCase();
|
|
285
389
|
}
|
|
286
390
|
var GENERIC_TOKENS = /* @__PURE__ */ new Set(["src", "index", "main", "test", "spec", "package", "packages", "lib", "dist"]);
|
|
391
|
+
var CODE_PREFERRED_EXTENSIONS = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx", "cjs", "mjs"]);
|
|
392
|
+
var DEFAULT_TOKEN_HINT_GROUPS = [
|
|
393
|
+
[
|
|
394
|
+
"chunk",
|
|
395
|
+
"conf",
|
|
396
|
+
"config",
|
|
397
|
+
"container",
|
|
398
|
+
"controller",
|
|
399
|
+
"helpers",
|
|
400
|
+
"mock",
|
|
401
|
+
"mocks",
|
|
402
|
+
"presentation",
|
|
403
|
+
"repository",
|
|
404
|
+
"route",
|
|
405
|
+
"routes",
|
|
406
|
+
"schema",
|
|
407
|
+
"setup",
|
|
408
|
+
"spec",
|
|
409
|
+
"stories",
|
|
410
|
+
"style",
|
|
411
|
+
"styles",
|
|
412
|
+
"test",
|
|
413
|
+
"type",
|
|
414
|
+
"types",
|
|
415
|
+
"utils",
|
|
416
|
+
"view",
|
|
417
|
+
"views"
|
|
418
|
+
],
|
|
419
|
+
[
|
|
420
|
+
"adapter",
|
|
421
|
+
"api",
|
|
422
|
+
"apis",
|
|
423
|
+
"builder",
|
|
424
|
+
"client",
|
|
425
|
+
"component",
|
|
426
|
+
"components",
|
|
427
|
+
"constants",
|
|
428
|
+
"context",
|
|
429
|
+
"core",
|
|
430
|
+
"dto",
|
|
431
|
+
"entity",
|
|
432
|
+
"entry",
|
|
433
|
+
"env",
|
|
434
|
+
"factory",
|
|
435
|
+
"fetcher",
|
|
436
|
+
"handler",
|
|
437
|
+
"hook",
|
|
438
|
+
"hooks",
|
|
439
|
+
"init",
|
|
440
|
+
"integration",
|
|
441
|
+
"interceptor",
|
|
442
|
+
"interface",
|
|
443
|
+
"layout",
|
|
444
|
+
"layouts",
|
|
445
|
+
"listener",
|
|
446
|
+
"logger",
|
|
447
|
+
"manager",
|
|
448
|
+
"mapper",
|
|
449
|
+
"meta",
|
|
450
|
+
"middleware",
|
|
451
|
+
"model",
|
|
452
|
+
"module",
|
|
453
|
+
"normalizer",
|
|
454
|
+
"options",
|
|
455
|
+
"page",
|
|
456
|
+
"pages",
|
|
457
|
+
"parser",
|
|
458
|
+
"plugin",
|
|
459
|
+
"provider",
|
|
460
|
+
"registry",
|
|
461
|
+
"resolver",
|
|
462
|
+
"router",
|
|
463
|
+
"runtime",
|
|
464
|
+
"serializer",
|
|
465
|
+
"server",
|
|
466
|
+
"service",
|
|
467
|
+
"settings",
|
|
468
|
+
"shared",
|
|
469
|
+
"slice",
|
|
470
|
+
"state",
|
|
471
|
+
"store",
|
|
472
|
+
"subscriber",
|
|
473
|
+
"theme",
|
|
474
|
+
"tracker",
|
|
475
|
+
"transform",
|
|
476
|
+
"unit",
|
|
477
|
+
"validator"
|
|
478
|
+
],
|
|
479
|
+
[
|
|
480
|
+
"base",
|
|
481
|
+
"bundle",
|
|
482
|
+
"common",
|
|
483
|
+
"compiler",
|
|
484
|
+
"contract",
|
|
485
|
+
"definition",
|
|
486
|
+
"definitions",
|
|
487
|
+
"deserializer",
|
|
488
|
+
"event",
|
|
489
|
+
"events",
|
|
490
|
+
"fixture",
|
|
491
|
+
"fixtures",
|
|
492
|
+
"guard",
|
|
493
|
+
"internal",
|
|
494
|
+
"loader",
|
|
495
|
+
"publisher",
|
|
496
|
+
"reducer",
|
|
497
|
+
"stub",
|
|
498
|
+
"stubs",
|
|
499
|
+
"tests",
|
|
500
|
+
"util"
|
|
501
|
+
]
|
|
502
|
+
];
|
|
503
|
+
function createTokenPriorityMap(input) {
|
|
504
|
+
const groups = normalizeTokenHintGroups(input);
|
|
505
|
+
const entries = [];
|
|
506
|
+
for (const [index, group] of groups.entries()) {
|
|
507
|
+
entries.push(...toPriorityEntries(group, index + 1));
|
|
508
|
+
}
|
|
509
|
+
return new Map(entries);
|
|
510
|
+
}
|
|
511
|
+
function normalizeTokenHintGroups(input) {
|
|
512
|
+
if (!input || input.length === 0) {
|
|
513
|
+
return DEFAULT_TOKEN_HINT_GROUPS.map((group) => [...group]);
|
|
514
|
+
}
|
|
515
|
+
if (Array.isArray(input[0])) {
|
|
516
|
+
const nested = input;
|
|
517
|
+
return nested.map((group) => group.filter((token) => token.trim().length > 0));
|
|
518
|
+
}
|
|
519
|
+
const flat = input;
|
|
520
|
+
return [flat.filter((token) => token.trim().length > 0)];
|
|
521
|
+
}
|
|
522
|
+
function toPriorityEntries(tokens, priority) {
|
|
523
|
+
return tokens.map((token) => [normalizeToken(token), priority]);
|
|
524
|
+
}
|
|
525
|
+
function appendTokenRepresentatives(files, tokenPriorityMap, selected, selectedSet, count) {
|
|
526
|
+
if (selected.length >= count || files.length === 0) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const tokenToFiles = /* @__PURE__ */ new Map();
|
|
530
|
+
const tokenFirstIndex = /* @__PURE__ */ new Map();
|
|
531
|
+
for (const [index, file] of files.entries()) {
|
|
532
|
+
const token = getPrimaryToken(file, tokenPriorityMap);
|
|
533
|
+
if (!token) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
tokenFirstIndex.set(token, Math.min(tokenFirstIndex.get(token) ?? Number.POSITIVE_INFINITY, index));
|
|
537
|
+
const current = tokenToFiles.get(token) ?? [];
|
|
538
|
+
current.push(file);
|
|
539
|
+
tokenToFiles.set(token, current);
|
|
540
|
+
}
|
|
541
|
+
const orderedTokens = [...tokenToFiles.keys()].sort((left, right) => {
|
|
542
|
+
const leftPriority = tokenPriorityMap.get(left) ?? Number.POSITIVE_INFINITY;
|
|
543
|
+
const rightPriority = tokenPriorityMap.get(right) ?? Number.POSITIVE_INFINITY;
|
|
544
|
+
if (leftPriority !== rightPriority) {
|
|
545
|
+
return leftPriority - rightPriority;
|
|
546
|
+
}
|
|
547
|
+
const leftIndex = tokenFirstIndex.get(left) ?? Number.POSITIVE_INFINITY;
|
|
548
|
+
const rightIndex = tokenFirstIndex.get(right) ?? Number.POSITIVE_INFINITY;
|
|
549
|
+
if (leftIndex !== rightIndex) {
|
|
550
|
+
return leftIndex - rightIndex;
|
|
551
|
+
}
|
|
552
|
+
return left.localeCompare(right);
|
|
553
|
+
});
|
|
554
|
+
for (const token of orderedTokens) {
|
|
555
|
+
if (selected.length >= count) {
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
const firstFile = tokenToFiles.get(token)?.[0];
|
|
559
|
+
if (!firstFile || selectedSet.has(firstFile)) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
selected.push(firstFile);
|
|
563
|
+
selectedSet.add(firstFile);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
287
566
|
|
|
288
567
|
// src/extract.ts
|
|
568
|
+
var import_debug2 = __toESM(require("debug"), 1);
|
|
289
569
|
var import_node_child_process = require("child_process");
|
|
290
570
|
var import_node_fs = require("fs");
|
|
291
571
|
var import_node_module = require("module");
|
|
292
572
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
293
573
|
var import_node_url = require("url");
|
|
574
|
+
var debugExtract = (0, import_debug2.default)("eslint-config-snapshot:extract");
|
|
294
575
|
function resolveEslintBinForWorkspace(workspaceAbs) {
|
|
295
576
|
const anchor = import_node_path2.default.join(workspaceAbs, "__snapshot_anchor__.cjs");
|
|
296
577
|
const req = (0, import_node_module.createRequire)(anchor);
|
|
@@ -337,11 +618,16 @@ function findPackageRoot(entryAbs) {
|
|
|
337
618
|
}
|
|
338
619
|
function extractRulesFromPrintConfig(workspaceAbs, fileAbs) {
|
|
339
620
|
const eslintBin = resolveEslintBinForWorkspace(workspaceAbs);
|
|
621
|
+
const commandArgs = [eslintBin, "--print-config", fileAbs];
|
|
622
|
+
const startedAt = Date.now();
|
|
623
|
+
debugExtract("spawn: cwd=%s cmd=%s %o", workspaceAbs, process.execPath, commandArgs);
|
|
340
624
|
const proc = (0, import_node_child_process.spawnSync)(process.execPath, [eslintBin, "--print-config", fileAbs], {
|
|
341
625
|
cwd: workspaceAbs,
|
|
342
626
|
encoding: "utf8"
|
|
343
627
|
});
|
|
628
|
+
debugExtract("spawn: done status=%s elapsedMs=%d", String(proc.status), Date.now() - startedAt);
|
|
344
629
|
if (proc.status !== 0) {
|
|
630
|
+
debugExtract("spawn: stderr=%s", proc.stderr.trim());
|
|
345
631
|
throw new Error(`Failed to run eslint --print-config for ${fileAbs}`);
|
|
346
632
|
}
|
|
347
633
|
const stdout = proc.stdout.trim();
|
|
@@ -371,6 +657,7 @@ function resolveEslintVersionForWorkspace(workspaceAbs) {
|
|
|
371
657
|
return "unknown";
|
|
372
658
|
}
|
|
373
659
|
async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
660
|
+
debugExtract("workspace=%s sampleCount=%d", workspaceAbs, fileAbsList.length);
|
|
374
661
|
const evaluate = await createWorkspaceEvaluator(workspaceAbs);
|
|
375
662
|
const results = [];
|
|
376
663
|
for (const fileAbs of fileAbsList) {
|
|
@@ -379,9 +666,16 @@ async function extractRulesForWorkspaceSamples(workspaceAbs, fileAbsList) {
|
|
|
379
666
|
results.push({ fileAbs, rules });
|
|
380
667
|
} catch (error) {
|
|
381
668
|
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
669
|
+
debugExtract("extract failed: workspace=%s file=%s error=%s", workspaceAbs, fileAbs, normalizedError.message);
|
|
382
670
|
results.push({ fileAbs, error: normalizedError });
|
|
383
671
|
}
|
|
384
672
|
}
|
|
673
|
+
debugExtract(
|
|
674
|
+
"workspace=%s extracted=%d failed=%d",
|
|
675
|
+
workspaceAbs,
|
|
676
|
+
results.filter((entry) => entry.rules !== void 0).length,
|
|
677
|
+
results.filter((entry) => entry.error !== void 0).length
|
|
678
|
+
);
|
|
385
679
|
return results;
|
|
386
680
|
}
|
|
387
681
|
async function createWorkspaceEvaluator(workspaceAbs) {
|
|
@@ -392,6 +686,7 @@ async function createWorkspaceEvaluator(workspaceAbs) {
|
|
|
392
686
|
const eslintModule = await import((0, import_node_url.pathToFileURL)(eslintModuleEntry).href);
|
|
393
687
|
const ESLintClass = eslintModule.ESLint ?? eslintModule.default?.ESLint;
|
|
394
688
|
if (ESLintClass) {
|
|
689
|
+
debugExtract("workspace=%s evaluator=eslint-api", workspaceAbs);
|
|
395
690
|
const eslint = new ESLintClass({ cwd: workspaceAbs });
|
|
396
691
|
return async (fileAbs) => {
|
|
397
692
|
const config = await eslint.calculateConfigForFile(fileAbs);
|
|
@@ -403,6 +698,7 @@ async function createWorkspaceEvaluator(workspaceAbs) {
|
|
|
403
698
|
}
|
|
404
699
|
} catch {
|
|
405
700
|
}
|
|
701
|
+
debugExtract("workspace=%s evaluator=spawn-print-config", workspaceAbs);
|
|
406
702
|
return (fileAbs) => Promise.resolve(extractRulesFromPrintConfig(workspaceAbs, fileAbs));
|
|
407
703
|
}
|
|
408
704
|
function normalizeRules(rules) {
|
|
@@ -437,45 +733,27 @@ function aggregateRules(ruleMaps) {
|
|
|
437
733
|
const aggregated = /* @__PURE__ */ new Map();
|
|
438
734
|
for (const rules of ruleMaps) {
|
|
439
735
|
for (const [ruleName, nextEntry] of rules.entries()) {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const severityCmp = compareSeverity(nextEntry[0], currentEntry[0]);
|
|
446
|
-
if (severityCmp > 0) {
|
|
447
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (severityCmp < 0) {
|
|
451
|
-
continue;
|
|
452
|
-
}
|
|
453
|
-
const currentOptions = currentEntry.length > 1 ? canonicalizeJson(currentEntry[1]) : void 0;
|
|
454
|
-
const nextOptions = nextEntry.length > 1 ? canonicalizeJson(nextEntry[1]) : void 0;
|
|
455
|
-
if (currentOptions === void 0 && nextOptions !== void 0) {
|
|
456
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
if (currentOptions !== void 0 && nextOptions === void 0) {
|
|
460
|
-
continue;
|
|
461
|
-
}
|
|
462
|
-
if (currentOptions === void 0 && nextOptions === void 0) {
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
const currentJson = JSON.stringify(currentOptions);
|
|
466
|
-
const nextJson = JSON.stringify(nextOptions);
|
|
467
|
-
if (nextJson < currentJson) {
|
|
468
|
-
aggregated.set(ruleName, canonicalizeJson(nextEntry));
|
|
469
|
-
}
|
|
736
|
+
const normalizedEntry = canonicalizeJson(nextEntry);
|
|
737
|
+
const variantKey = toVariantKey(normalizedEntry);
|
|
738
|
+
const variants = aggregated.get(ruleName) ?? /* @__PURE__ */ new Map();
|
|
739
|
+
variants.set(variantKey, normalizedEntry);
|
|
740
|
+
aggregated.set(ruleName, variants);
|
|
470
741
|
}
|
|
471
742
|
}
|
|
472
|
-
|
|
743
|
+
const entries = [...aggregated.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([ruleName, variants]) => {
|
|
744
|
+
const sortedVariants = [...variants.values()].sort(compareVariants);
|
|
745
|
+
if (sortedVariants.length === 1) {
|
|
746
|
+
return [ruleName, sortedVariants[0]];
|
|
747
|
+
}
|
|
748
|
+
return [ruleName, sortedVariants];
|
|
749
|
+
});
|
|
750
|
+
return new Map(entries);
|
|
473
751
|
}
|
|
474
752
|
function buildSnapshot(groupId, workspaces, rules) {
|
|
475
753
|
const sortedRules = [...rules.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
476
754
|
const rulesObject = {};
|
|
477
755
|
for (const [name, config] of sortedRules) {
|
|
478
|
-
rulesObject[name] = canonicalizeJson(config);
|
|
756
|
+
rulesObject[name] = isSingleRuleEntry(config) ? canonicalizeJson(config) : config.map((variant) => canonicalizeJson(variant)).sort(compareVariants);
|
|
479
757
|
}
|
|
480
758
|
return {
|
|
481
759
|
formatVersion: 1,
|
|
@@ -497,6 +775,17 @@ async function readSnapshotFile(fileAbs) {
|
|
|
497
775
|
const raw = await (0, import_promises.readFile)(fileAbs, "utf8");
|
|
498
776
|
return JSON.parse(raw);
|
|
499
777
|
}
|
|
778
|
+
function toVariantKey(entry) {
|
|
779
|
+
return JSON.stringify(canonicalizeJson(entry));
|
|
780
|
+
}
|
|
781
|
+
function compareVariants(a, b) {
|
|
782
|
+
const aJson = JSON.stringify(canonicalizeJson(a));
|
|
783
|
+
const bJson = JSON.stringify(canonicalizeJson(b));
|
|
784
|
+
return aJson.localeCompare(bJson);
|
|
785
|
+
}
|
|
786
|
+
function isSingleRuleEntry(entry) {
|
|
787
|
+
return !Array.isArray(entry[0]);
|
|
788
|
+
}
|
|
500
789
|
|
|
501
790
|
// src/diff.ts
|
|
502
791
|
function diffSnapshots(before, after) {
|
|
@@ -511,32 +800,51 @@ function diffSnapshots(before, after) {
|
|
|
511
800
|
for (const name of beforeNames.filter((entry) => afterNames.includes(entry))) {
|
|
512
801
|
const oldEntry = beforeRules[name];
|
|
513
802
|
const newEntry = afterRules[name];
|
|
514
|
-
|
|
803
|
+
const oldVariants = toVariants(oldEntry);
|
|
804
|
+
const newVariants = toVariants(newEntry);
|
|
805
|
+
const oldSeverity = summarizeSeveritySet(oldVariants);
|
|
806
|
+
const newSeverity = summarizeSeveritySet(newVariants);
|
|
807
|
+
if (oldSeverity !== newSeverity) {
|
|
515
808
|
severityChanges.push({
|
|
516
809
|
rule: name,
|
|
517
|
-
before:
|
|
518
|
-
after:
|
|
810
|
+
before: oldSeverity,
|
|
811
|
+
after: newSeverity
|
|
519
812
|
});
|
|
520
813
|
}
|
|
521
|
-
const
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
814
|
+
const oldSerialized = JSON.stringify(oldVariants);
|
|
815
|
+
const newSerialized = JSON.stringify(newVariants);
|
|
816
|
+
if (oldSerialized === newSerialized) {
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
const oldIsOnlyOff = oldVariants.every((entry) => entry[0] === "off");
|
|
820
|
+
const newIsOnlyOff = newVariants.every((entry) => entry[0] === "off");
|
|
821
|
+
if (oldIsOnlyOff || newIsOnlyOff) {
|
|
822
|
+
if (oldIsOnlyOff && newIsOnlyOff) {
|
|
823
|
+
const oldHasOptions = oldVariants.some((variant) => variant.length > 1);
|
|
824
|
+
const newHasOptions = newVariants.some((variant) => variant.length > 1);
|
|
825
|
+
if (oldHasOptions && !newHasOptions) {
|
|
526
826
|
removedRules.push(name);
|
|
527
|
-
} else if (
|
|
827
|
+
} else if (!oldHasOptions && newHasOptions) {
|
|
528
828
|
introducedRules.push(name);
|
|
829
|
+
} else if (oldVariants.length > newVariants.length) {
|
|
830
|
+
removedRules.push(name);
|
|
831
|
+
} else if (oldVariants.length < newVariants.length) {
|
|
832
|
+
introducedRules.push(name);
|
|
833
|
+
} else {
|
|
834
|
+
optionChanges.push({
|
|
835
|
+
rule: name,
|
|
836
|
+
before: oldVariants,
|
|
837
|
+
after: newVariants
|
|
838
|
+
});
|
|
529
839
|
}
|
|
530
840
|
}
|
|
531
841
|
continue;
|
|
532
842
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
});
|
|
539
|
-
}
|
|
843
|
+
optionChanges.push({
|
|
844
|
+
rule: name,
|
|
845
|
+
before: oldVariants,
|
|
846
|
+
after: newVariants
|
|
847
|
+
});
|
|
540
848
|
}
|
|
541
849
|
const beforeWorkspaces = sortUnique(before.workspaces);
|
|
542
850
|
const afterWorkspaces = sortUnique(after.workspaces);
|
|
@@ -551,6 +859,17 @@ function diffSnapshots(before, after) {
|
|
|
551
859
|
}
|
|
552
860
|
};
|
|
553
861
|
}
|
|
862
|
+
function toVariants(entry) {
|
|
863
|
+
if (!Array.isArray(entry[0])) {
|
|
864
|
+
return [canonicalizeJson(entry)];
|
|
865
|
+
}
|
|
866
|
+
return entry.map((variant) => canonicalizeJson(variant));
|
|
867
|
+
}
|
|
868
|
+
function summarizeSeveritySet(variants) {
|
|
869
|
+
const severityOrder = ["error", "warn", "off"];
|
|
870
|
+
const severities = new Set(variants.map((variant) => variant[0]));
|
|
871
|
+
return severityOrder.filter((severity) => severities.has(severity)).join("|");
|
|
872
|
+
}
|
|
554
873
|
function hasDiff(diff) {
|
|
555
874
|
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;
|
|
556
875
|
}
|
|
@@ -565,10 +884,9 @@ var DEFAULT_CONFIG = {
|
|
|
565
884
|
groups: [{ name: "default", match: ["**/*"] }]
|
|
566
885
|
},
|
|
567
886
|
sampling: {
|
|
568
|
-
maxFilesPerWorkspace:
|
|
569
|
-
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs}"],
|
|
570
|
-
excludeGlobs: ["**/node_modules/**", "**/dist/**"]
|
|
571
|
-
hintGlobs: []
|
|
887
|
+
maxFilesPerWorkspace: 10,
|
|
888
|
+
includeGlobs: ["**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}"],
|
|
889
|
+
excludeGlobs: ["**/node_modules/**", "**/dist/**"]
|
|
572
890
|
}
|
|
573
891
|
};
|
|
574
892
|
var SPEC_SEARCH_PLACES = [
|
|
@@ -643,10 +961,35 @@ function getConfigScaffold(preset = "minimal") {
|
|
|
643
961
|
groups: [{ name: 'default', match: ['**/*'] }]
|
|
644
962
|
},
|
|
645
963
|
sampling: {
|
|
646
|
-
maxFilesPerWorkspace:
|
|
647
|
-
includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
|
|
964
|
+
maxFilesPerWorkspace: 10,
|
|
965
|
+
includeGlobs: ['**/*.{js,jsx,ts,tsx,cjs,mjs,md,mdx,json,css}'],
|
|
648
966
|
excludeGlobs: ['**/node_modules/**', '**/dist/**'],
|
|
649
|
-
|
|
967
|
+
tokenHints: [
|
|
968
|
+
'chunk',
|
|
969
|
+
'conf',
|
|
970
|
+
'config',
|
|
971
|
+
'container',
|
|
972
|
+
'controller',
|
|
973
|
+
'helpers',
|
|
974
|
+
'mock',
|
|
975
|
+
'mocks',
|
|
976
|
+
'presentation',
|
|
977
|
+
'repository',
|
|
978
|
+
'route',
|
|
979
|
+
'routes',
|
|
980
|
+
'schema',
|
|
981
|
+
'setup',
|
|
982
|
+
'spec',
|
|
983
|
+
'stories',
|
|
984
|
+
'style',
|
|
985
|
+
'styles',
|
|
986
|
+
'test',
|
|
987
|
+
'type',
|
|
988
|
+
'types',
|
|
989
|
+
'utils',
|
|
990
|
+
'view',
|
|
991
|
+
'views'
|
|
992
|
+
]
|
|
650
993
|
}
|
|
651
994
|
}
|
|
652
995
|
`;
|