@alint-js/core 0.0.28 → 0.0.30

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/dist/index.mjs CHANGED
@@ -1,16 +1,15 @@
1
- import { r as withAgentRetry } from "./retry-C4sGjSE5.mjs";
1
+ import { o as withAgentRetry, t as combineAbortSignals } from "./agent-weXkvGkc.mjs";
2
+ import { a as sliceRange, i as sliceLines, n as createSourceFile, r as createSourceRuntime, t as extractJsSourceTargets } from "./extract-CIWssxNK.mjs";
2
3
  import { minimatch } from "minimatch";
3
4
  import { isAbsolute, relative, resolve } from "pathe";
4
5
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
5
6
  import { dirname, extname, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
6
- import { clamp } from "es-toolkit";
7
- import { parseSync } from "oxc-parser";
8
7
  import { AsyncLocalStorage } from "node:async_hooks";
9
8
  import process, { cwd } from "node:process";
10
- import { errorCauseFrom, errorMessageFrom } from "@moeru/std/error";
11
9
  import { createHash, randomUUID } from "node:crypto";
12
10
  import { statSync } from "node:fs";
13
11
  import { array, check, description, is, literal, number, object, optional, pipe, record, string, union, unknown } from "valibot";
12
+ import { errorMessageFrom } from "@moeru/std/error";
14
13
  //#region src/config/config-array.ts
15
14
  const effectiveBasePathSymbol = Symbol("effectiveBasePath");
16
15
  const inheritedMatcherScopesSymbol = Symbol("inheritedMatcherScopes");
@@ -233,359 +232,6 @@ function withExpansionMetadata(item, inheritedMatcherScopes, effectiveBasePath)
233
232
  return item;
234
233
  }
235
234
  //#endregion
236
- //#region src/core/source/runtime.ts
237
- function createSourceFile(path, text) {
238
- return {
239
- language: inferLanguage(path),
240
- lines: text.split(/\r?\n/),
241
- path,
242
- text
243
- };
244
- }
245
- function createSourceRuntime() {
246
- return {
247
- getText,
248
- readFile: async (filePath) => createSourceFile(filePath, await readFile(filePath, "utf8")),
249
- sliceLines,
250
- sliceRange
251
- };
252
- }
253
- function getText(target) {
254
- return target.text;
255
- }
256
- function sliceLines(file, range) {
257
- const startLine = clampLine(range.startLine, file.lines.length);
258
- const endLine = clampLine(range.endLine, file.lines.length);
259
- const orderedStartLine = Math.min(startLine, endLine);
260
- const orderedEndLine = Math.max(startLine, endLine);
261
- const text = file.lines.slice(orderedStartLine - 1, orderedEndLine).join("\n");
262
- return {
263
- filePath: file.path,
264
- loc: {
265
- end: {
266
- column: file.lines[orderedEndLine - 1]?.length ?? 0,
267
- line: orderedEndLine
268
- },
269
- start: {
270
- column: 0,
271
- line: orderedStartLine
272
- }
273
- },
274
- text
275
- };
276
- }
277
- function sliceRange(file, range) {
278
- const start = clampOffset(range.start, file.text.length);
279
- const end = clampOffset(range.end, file.text.length);
280
- const orderedStart = Math.min(start, end);
281
- const orderedEnd = Math.max(start, end);
282
- return {
283
- filePath: file.path,
284
- loc: {
285
- end: getPosition(file.text, orderedEnd),
286
- start: getPosition(file.text, orderedStart)
287
- },
288
- text: file.text.slice(orderedStart, orderedEnd)
289
- };
290
- }
291
- function clampLine(line, lineCount) {
292
- if (lineCount === 0) return 1;
293
- return clamp(Math.trunc(line), 1, lineCount);
294
- }
295
- function clampOffset(offset, textLength) {
296
- return clamp(Math.trunc(offset), 0, textLength);
297
- }
298
- function getPosition(text, offset) {
299
- let line = 1;
300
- let column = 0;
301
- let index = 0;
302
- while (index < offset) {
303
- const character = text[index];
304
- if (character === "\r") {
305
- if (text[index + 1] === "\n" && index + 1 < offset) index += 1;
306
- line += 1;
307
- column = 0;
308
- } else if (character === "\n") {
309
- line += 1;
310
- column = 0;
311
- } else column += 1;
312
- index += 1;
313
- }
314
- return {
315
- column,
316
- line
317
- };
318
- }
319
- function inferLanguage(path) {
320
- switch (extname(path)) {
321
- case ".cjs":
322
- case ".js":
323
- case ".jsx":
324
- case ".mjs": return "javascript";
325
- case ".cts":
326
- case ".mts":
327
- case ".ts":
328
- case ".tsx": return "typescript";
329
- default: return "unknown";
330
- }
331
- }
332
- //#endregion
333
- //#region src/core/languages/js/parser.ts
334
- function parseSync$1(filename, sourceText, options) {
335
- return parseSync(filename, sourceText, options);
336
- }
337
- //#endregion
338
- //#region src/core/languages/js/extract.ts
339
- function extractJsSourceTargets(file) {
340
- const result = parseSync$1(file.path, file.text, { sourceType: "module" });
341
- const state = {
342
- bindingNodes: /* @__PURE__ */ new Map(),
343
- exportedNodes: /* @__PURE__ */ new Set(),
344
- file,
345
- inferredNames: /* @__PURE__ */ new Map(),
346
- seenClasses: /* @__PURE__ */ new Set(),
347
- seenFunctions: /* @__PURE__ */ new Set(),
348
- targets: [],
349
- visited: /* @__PURE__ */ new Set()
350
- };
351
- collectModuleBindings(result.program, state);
352
- collectExportedBindings(result.program, state);
353
- visit(result.program, state);
354
- const sortedTargets = [...state.targets].sort((left, right) => (left.range?.start ?? 0) - (right.range?.start ?? 0));
355
- return [createFileTarget(file), ...withStableIdentities(sortedTargets)];
356
- }
357
- function addClassTarget(node, state) {
358
- if (state.seenClasses.has(node)) return;
359
- const target = createClassTarget(node, state);
360
- if (!target) return;
361
- state.seenClasses.add(node);
362
- state.targets.push(target);
363
- }
364
- function addFunctionTarget(node, state) {
365
- if (state.seenFunctions.has(node)) return;
366
- const target = createFunctionTarget(node, state);
367
- if (!target) return;
368
- state.seenFunctions.add(node);
369
- state.targets.push(target);
370
- }
371
- function asAstNode(value) {
372
- return isAstNode(value) ? value : void 0;
373
- }
374
- function collectDeclarationBindings(node, state) {
375
- const name = getNodeName(node);
376
- if (name && (isClassNode(node) || isFunctionNode(node))) {
377
- state.bindingNodes.set(name, node);
378
- return;
379
- }
380
- if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
381
- for (const declarator of node.declarations) {
382
- if (!isAstNode(declarator)) continue;
383
- const id = asAstNode(declarator.id);
384
- const initializer = asAstNode(declarator.init);
385
- if (typeof id?.name === "string" && initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.bindingNodes.set(id.name, initializer);
386
- }
387
- }
388
- function collectExportedBindings(node, state) {
389
- if (!node) return;
390
- if (Array.isArray(node)) {
391
- for (const child of node) collectExportedBindings(child, state);
392
- return;
393
- }
394
- if (node.type === "ExportNamedDeclaration" && !node.source && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
395
- if (!isAstNode(specifier) || specifier.type !== "ExportSpecifier") continue;
396
- const local = asAstNode(specifier.local);
397
- if (typeof local?.name === "string") markExportedBinding(local.name, state);
398
- }
399
- else if (node.type === "ExportDefaultDeclaration") {
400
- const declaration = asAstNode(node.declaration);
401
- if (typeof declaration?.name === "string") markExportedBinding(declaration.name, state);
402
- }
403
- for (const value of Object.values(node)) if (Array.isArray(value)) collectExportedBindings(value.filter(isAstNode), state);
404
- else if (isAstNode(value)) collectExportedBindings(value, state);
405
- }
406
- function collectModuleBindings(program, state) {
407
- if (!Array.isArray(program.body)) return;
408
- for (const statement of program.body) {
409
- if (!isAstNode(statement)) continue;
410
- const declaration = isExportDeclaration(statement) ? asAstNode(statement.declaration) : statement;
411
- if (declaration) collectDeclarationBindings(declaration, state);
412
- }
413
- }
414
- function createClassTarget(node, state) {
415
- const range = getRange(node);
416
- if (!range) return;
417
- const source = sliceRange(state.file, range);
418
- const name = getNodeName(node) ?? state.inferredNames.get(node);
419
- return {
420
- file: state.file,
421
- identity: createRangeIdentity("class", name, range),
422
- kind: "class",
423
- language: state.file.language,
424
- loc: source.loc,
425
- metadata: { exported: isExportedTarget(node, state) },
426
- name,
427
- origin: {
428
- physicalPath: state.file.path,
429
- range
430
- },
431
- range,
432
- text: source.text
433
- };
434
- }
435
- function createFileTarget(file) {
436
- return {
437
- file,
438
- identity: "file",
439
- kind: "file",
440
- language: file.language,
441
- origin: { physicalPath: file.path },
442
- text: file.text
443
- };
444
- }
445
- function createFunctionTarget(node, state) {
446
- const range = getRange(node);
447
- if (!range) return;
448
- const source = sliceRange(state.file, range);
449
- const functionNode = node.type === "MethodDefinition" ? asAstNode(node.value) : node;
450
- const name = getNodeName(node) ?? getNodeName(functionNode) ?? state.inferredNames.get(node);
451
- return {
452
- file: state.file,
453
- identity: createRangeIdentity("function", name, range),
454
- kind: "function",
455
- language: state.file.language,
456
- loc: source.loc,
457
- metadata: {
458
- async: functionNode?.async === true,
459
- exported: isExportedTarget(node, state)
460
- },
461
- name,
462
- origin: {
463
- physicalPath: state.file.path,
464
- range
465
- },
466
- range,
467
- text: source.text
468
- };
469
- }
470
- function createRangeIdentity(kind, name, range) {
471
- return `${kind}:${name ?? "anonymous"}:${range.start}:${range.end}`;
472
- }
473
- function createSemanticIdentity(target) {
474
- if (target.kind !== "class" && target.kind !== "function" || !target.name) return;
475
- return `${target.kind}:${target.name}`;
476
- }
477
- function getNodeName(node) {
478
- if (!node) return;
479
- if (typeof node.name === "string") return node.name;
480
- const id = asAstNode(node.id);
481
- if (typeof id?.name === "string") return id.name;
482
- const key = asAstNode(node.key);
483
- if (typeof key?.name === "string") return key.name;
484
- if (typeof key?.value === "string" || typeof key?.value === "number") return String(key.value);
485
- }
486
- function getRange(node) {
487
- if (typeof node.start === "number" && typeof node.end === "number") return {
488
- end: node.end,
489
- start: node.start
490
- };
491
- if (Array.isArray(node.range) && node.range.length === 2) return {
492
- end: node.range[1],
493
- start: node.range[0]
494
- };
495
- }
496
- function inferChildName(parent, key, child, state) {
497
- if (state.inferredNames.has(child)) return;
498
- if (parent.type === "VariableDeclarator" && key === "init") {
499
- const id = asAstNode(parent.id);
500
- if (typeof id?.name === "string") state.inferredNames.set(child, id.name);
501
- }
502
- if (parent.type === "Property" && key === "value") {
503
- const name = getNodeName(parent);
504
- if (name) state.inferredNames.set(child, name);
505
- }
506
- }
507
- function isAstNode(value) {
508
- return typeof value === "object" && value !== null && typeof value.type === "string";
509
- }
510
- function isClassNode(node) {
511
- return node.type === "ClassDeclaration" || node.type === "ClassExpression";
512
- }
513
- function isExportDeclaration(node) {
514
- return node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration";
515
- }
516
- function isExportedTarget(node, state) {
517
- return state.exportedNodes.has(node);
518
- }
519
- function isFunctionNode(node) {
520
- return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
521
- }
522
- function markExportedBinding(name, state) {
523
- const bindingNode = state.bindingNodes.get(name);
524
- if (bindingNode) state.exportedNodes.add(bindingNode);
525
- }
526
- function markExportedDeclaration(node, state) {
527
- state.exportedNodes.add(node);
528
- if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
529
- for (const declarator of node.declarations) {
530
- if (!isAstNode(declarator)) continue;
531
- const initializer = asAstNode(declarator.init);
532
- if (initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.exportedNodes.add(initializer);
533
- }
534
- }
535
- function visit(node, state) {
536
- if (!node) return;
537
- if (Array.isArray(node)) {
538
- for (const child of node) visit(child, state);
539
- return;
540
- }
541
- if (state.visited.has(node)) return;
542
- state.visited.add(node);
543
- if (node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration") {
544
- if (node.declaration) {
545
- markExportedDeclaration(node.declaration, state);
546
- visit(node.declaration, state);
547
- }
548
- visitChildren(node, state, /* @__PURE__ */ new Set(["declaration"]));
549
- return;
550
- }
551
- if (isClassNode(node)) addClassTarget(node, state);
552
- else if (isFunctionNode(node) || node.type === "MethodDefinition") {
553
- addFunctionTarget(node, state);
554
- const methodValue = node.type === "MethodDefinition" ? asAstNode(node.value) : void 0;
555
- if (methodValue) state.seenFunctions.add(methodValue);
556
- }
557
- visitChildren(node, state);
558
- }
559
- function visitChildren(node, state, skippedKeys = /* @__PURE__ */ new Set()) {
560
- for (const [key, value] of Object.entries(node)) {
561
- if (skippedKeys.has(key) || key === "type" || key === "start" || key === "end" || key === "range") continue;
562
- if (Array.isArray(value)) {
563
- for (const child of value) if (isAstNode(child)) {
564
- inferChildName(node, key, child, state);
565
- visit(child, state);
566
- }
567
- } else if (isAstNode(value)) {
568
- inferChildName(node, key, value, state);
569
- visit(value, state);
570
- }
571
- }
572
- }
573
- function withStableIdentities(targets) {
574
- const semanticIdentityCounts = /* @__PURE__ */ new Map();
575
- for (const target of targets) {
576
- const semanticIdentity = createSemanticIdentity(target);
577
- if (semanticIdentity) semanticIdentityCounts.set(semanticIdentity, (semanticIdentityCounts.get(semanticIdentity) ?? 0) + 1);
578
- }
579
- return targets.map((target) => {
580
- const semanticIdentity = createSemanticIdentity(target);
581
- if (!semanticIdentity || semanticIdentityCounts.get(semanticIdentity) !== 1) return target;
582
- return {
583
- ...target,
584
- identity: semanticIdentity
585
- };
586
- });
587
- }
588
- //#endregion
589
235
  //#region src/core/languages/js/index.ts
590
236
  const javascriptLanguage = {
591
237
  extensions: [
@@ -934,99 +580,177 @@ function statSyncIsDirectory(path) {
934
580
  return statSync(path).isDirectory();
935
581
  }
936
582
  //#endregion
937
- //#region src/core/targets/directory.ts
938
- function createDirectoryExecutionPlans(directories, fileOffset) {
939
- return directories.map((directory, directoryOffset) => {
940
- const executions = directory.ruleRuntimes.map((runtime) => {
941
- if (runtime.handlers.onTargetWith) return {
942
- run: () => runtime.handlers.onTargetWith?.(directory.target),
943
- runtime
944
- };
945
- if (runtime.handlers.onTargetDirectory) return {
946
- run: () => runtime.handlers.onTargetDirectory?.(directory.target),
947
- runtime
948
- };
949
- }).filter((execution) => execution !== void 0);
950
- const targets = executions.length === 0 ? [] : [{
951
- activeFilePath: directory.target.path,
952
- cacheFilePaths: [],
953
- configHash: directory.configHash,
954
- executions,
955
- identity: directory.target.path,
956
- kind: "directory",
957
- language: "directory",
958
- text: directory.target.path
959
- }];
960
- return {
961
- emitFileProgress: true,
962
- fileIndex: fileOffset + directoryOffset + 1,
963
- path: directory.target.path,
964
- planned: executions.length,
965
- targets
966
- };
967
- });
968
- }
969
- //#endregion
970
583
  //#region package.json
971
- var version = "0.0.28";
584
+ var version = "0.0.30";
972
585
  //#endregion
973
- //#region src/core/targets/execution.ts
974
- var AlintRuleExecutionError = class extends Error {
975
- failure;
976
- constructor(error, path) {
977
- const message = errorMessageFrom(error) ?? String(error);
978
- super(message, { cause: error });
979
- this.name = "AlintRuleExecutionError";
980
- this.failure = {
981
- filePath: path.file.path,
982
- message,
983
- ruleId: path.rule.id,
984
- target: {
985
- kind: path.target.kind,
986
- name: path.target.name
987
- }
988
- };
586
+ //#region src/core/execution/job.ts
587
+ var CacheReplayProcessingError = class {
588
+ cause;
589
+ constructor(cause) {
590
+ this.cause = cause;
989
591
  }
990
592
  };
991
- async function executeTargetPlans(options) {
992
- for (const plan of options.plans) await executeTargetPlan(plan, options);
593
+ function createRuleJobs(plans) {
594
+ const jobs = [];
595
+ const total = plans.reduce((sum, plan) => sum + plan.planned, 0);
596
+ for (const plan of plans) for (const target of plan.targets) for (const execution of target.executions) {
597
+ const ruleId = execution.runtime.enabledRule.id;
598
+ const index = jobs.length + 1;
599
+ jobs.push({
600
+ execution,
601
+ job: {
602
+ id: stableHash({
603
+ index,
604
+ planId: plan.id,
605
+ ruleId,
606
+ targetIdentity: target.identity
607
+ }),
608
+ index,
609
+ inputPath: plan.path,
610
+ ruleId,
611
+ target: {
612
+ identity: target.identity,
613
+ kind: target.kind,
614
+ name: target.name
615
+ },
616
+ total
617
+ },
618
+ target
619
+ });
620
+ }
621
+ return jobs;
993
622
  }
994
- function createExecutionCacheKey(runtime, target, path, cache) {
995
- if (!cache.enabled || !runtime.cacheable || target.cacheFilePaths.length === 0) return;
996
- return createCacheKey({
997
- alintVersion: version,
998
- configHash: target.configHash,
999
- filePath: normalizeCachePath(cache.cwd, path.file.path),
1000
- modelHash: cache.modelHash,
1001
- ruleHash: runtime.ruleHash,
1002
- schemaVersion: 1,
1003
- targetHash: createTargetHash(target),
1004
- targetIdentity: target.identity,
1005
- targetKind: target.kind
623
+ async function executeRuleJob(job, options) {
624
+ const bucket = {
625
+ diagnostics: [],
626
+ usage: []
627
+ };
628
+ if (options.runSignal?.aborted) return finish(job, options, bucket, {
629
+ cache: "miss",
630
+ state: "cancelled"
631
+ });
632
+ const cacheKey = createExecutionCacheKey(job, options.cache);
633
+ const cachedEntry = cacheKey ? options.cache.store.get(cacheKey) : void 0;
634
+ if (cacheKey && cachedEntry) {
635
+ rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
636
+ try {
637
+ replayCachedEntry(cachedEntry, bucket, job.job, options.progress);
638
+ } catch (error) {
639
+ if (!(error instanceof CacheReplayProcessingError)) throw error;
640
+ return finish(job, options, bucket, {
641
+ cache: "hit",
642
+ cause: error.cause,
643
+ failure: failure(error.cause, job, "cache-replay"),
644
+ state: "failed"
645
+ });
646
+ }
647
+ return finish(job, options, bucket, {
648
+ cache: "hit",
649
+ state: "cached"
650
+ });
651
+ }
652
+ if (options.cacheOnly) return finish(job, options, bucket, {
653
+ cache: "miss",
654
+ state: "skipped"
655
+ });
656
+ const controller = new AbortController();
657
+ let timedOut = false;
658
+ let deadlineError;
659
+ const forwardRunAbort = () => controller.abort(options.runSignal?.reason);
660
+ if (options.runSignal?.aborted) forwardRunAbort();
661
+ else options.runSignal?.addEventListener("abort", forwardRunAbort, { once: true });
662
+ const timer = options.timeoutMs == null ? void 0 : setTimeout(() => {
663
+ timedOut = true;
664
+ deadlineError = /* @__PURE__ */ new Error(`Rule execution timed out after ${options.timeoutMs}ms.`);
665
+ controller.abort(deadlineError);
666
+ }, options.timeoutMs);
667
+ const state = {
668
+ activeFilePath: job.target.activeFilePath,
669
+ bucket,
670
+ job: job.job,
671
+ reporterFailed: false,
672
+ signal: controller.signal
673
+ };
674
+ let handlerCause;
675
+ let handlerFailed = false;
676
+ try {
677
+ await job.execution.runtime.executionState.run(state, job.execution.run);
678
+ } catch (cause) {
679
+ if (state.reporterFailed) throw state.reporterCause;
680
+ handlerCause = cause;
681
+ handlerFailed = true;
682
+ } finally {
683
+ if (timer != null) clearTimeout(timer);
684
+ options.runSignal?.removeEventListener("abort", forwardRunAbort);
685
+ }
686
+ if (state.reporterFailed) throw state.reporterCause;
687
+ if (!timedOut && !handlerFailed && cacheKey) try {
688
+ options.cache.store.set(cacheKey, createCacheEntry(job, options.cache, bucket));
689
+ rememberCacheEntry(options.cache, job.target.cacheFilePaths, cacheKey);
690
+ } catch {}
691
+ if (options.runSignal?.aborted) return finish(job, options, bucket, {
692
+ cache: "miss",
693
+ state: "cancelled"
1006
694
  });
695
+ if (timedOut) return finish(job, options, bucket, {
696
+ cache: "miss",
697
+ cause: deadlineError,
698
+ failure: failure(deadlineError, job, "timeout"),
699
+ state: "failed"
700
+ });
701
+ if (handlerFailed) return finish(job, options, bucket, {
702
+ cache: "miss",
703
+ cause: handlerCause,
704
+ failure: failure(handlerCause, job, "handler"),
705
+ state: "failed"
706
+ });
707
+ return finish(job, options, bucket, {
708
+ cache: "miss",
709
+ state: "completed"
710
+ });
711
+ }
712
+ function resolveRuleExecutionTimeout(timeoutMs) {
713
+ if (timeoutMs === void 0) return void 0;
714
+ if (!Number.isFinite(timeoutMs) || !Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("Rule execution timeout must be a finite positive integer.");
715
+ return timeoutMs;
1007
716
  }
1008
- function createProgressPath(filePath, ruleId, entry) {
717
+ function createCacheEntry(job, cache, bucket) {
1009
718
  return {
1010
- file: {
1011
- index: entry.fileIndex,
1012
- path: filePath,
1013
- planned: entry.filePlanned,
1014
- total: entry.fileTotal
1015
- },
1016
- rule: {
1017
- id: ruleId,
1018
- index: entry.ruleIndex,
1019
- total: entry.ruleTotal
719
+ diagnostics: bucket.diagnostics,
720
+ filePath: normalizeCachePath(cache.cwd, job.job.inputPath),
721
+ fingerprint: {
722
+ alintVersion: version,
723
+ configHash: job.target.configHash,
724
+ modelHash: cache.modelHash,
725
+ ruleHash: job.execution.runtime.ruleHash
1020
726
  },
1021
727
  target: {
1022
- index: entry.targetIndex,
1023
- kind: entry.targetKind,
1024
- name: entry.targetName,
1025
- total: entry.targetTotal
1026
- }
728
+ hash: createTargetHash(job),
729
+ identity: job.target.identity,
730
+ kind: job.target.kind,
731
+ loc: job.target.loc,
732
+ name: job.target.name,
733
+ range: job.target.range
734
+ },
735
+ usage: bucket.usage
1027
736
  };
1028
737
  }
1029
- function createTargetHash(target) {
738
+ function createExecutionCacheKey(job, cache) {
739
+ if (!cache.enabled || !job.execution.runtime.cacheable || job.target.cacheFilePaths.length === 0) return void 0;
740
+ return createCacheKey({
741
+ alintVersion: version,
742
+ configHash: job.target.configHash,
743
+ filePath: normalizeCachePath(cache.cwd, job.job.inputPath),
744
+ modelHash: cache.modelHash,
745
+ ruleHash: job.execution.runtime.ruleHash,
746
+ schemaVersion: 1,
747
+ targetHash: createTargetHash(job),
748
+ targetIdentity: job.target.identity,
749
+ targetKind: job.target.kind
750
+ });
751
+ }
752
+ function createTargetHash(job) {
753
+ const target = job.target;
1030
754
  return stableHash({
1031
755
  language: target.language,
1032
756
  loc: target.loc,
@@ -1037,191 +761,28 @@ function createTargetHash(target) {
1037
761
  text: target.text
1038
762
  });
1039
763
  }
1040
- function emitErroredRuleEnd(progress, clock, path, startedAt, cache) {
1041
- try {
1042
- progress?.onRuleEnd?.({
1043
- cache,
1044
- endedAt: clock(),
1045
- path,
1046
- startedAt,
1047
- state: "errored"
1048
- });
1049
- } catch {}
1050
- }
1051
- async function executeRule(execution, path, target, options) {
1052
- const { cache, clock, counters, diagnostics, progress, signal, usage } = options;
1053
- signal?.throwIfAborted();
1054
- const startedAt = clock();
1055
- const cacheKey = createExecutionCacheKey(execution.runtime, target, path, cache);
1056
- const cachedEntry = cacheKey ? cache.store.get(cacheKey) : void 0;
1057
- progress?.onRuleStart?.({
1058
- path,
1059
- startedAt
1060
- });
1061
- if (cacheKey && cachedEntry) {
1062
- rememberCacheEntry(cache, target.cacheFilePaths, cacheKey);
1063
- try {
1064
- replayCachedEntry(cachedEntry, path, diagnostics, usage, progress);
1065
- } catch (error) {
1066
- counters.error();
1067
- emitErroredRuleEnd(progress, clock, path, startedAt, "hit");
1068
- throw new AlintRuleExecutionError(error, path);
1069
- }
1070
- counters.cache();
1071
- progress?.onRuleEnd?.({
1072
- cache: "hit",
1073
- endedAt: clock(),
1074
- path,
1075
- startedAt,
1076
- state: "completed"
1077
- });
1078
- return;
1079
- }
1080
- if (options.cacheOnly) {
1081
- counters.skip();
1082
- progress?.onRuleEnd?.({
1083
- cache: "miss",
1084
- endedAt: clock(),
1085
- path,
1086
- startedAt,
1087
- state: "skipped"
1088
- });
1089
- return;
1090
- }
1091
- let handlerError;
1092
- let handlerFailed = false;
1093
- const cacheDiagnostics = cacheKey ? [] : void 0;
1094
- const cacheUsage = cacheKey ? [] : void 0;
1095
- try {
1096
- await execution.runtime.executionState.run({
1097
- activeFilePath: target.activeFilePath,
1098
- cacheDiagnostics,
1099
- cacheUsage,
1100
- progressPath: path
1101
- }, execution.run);
1102
- } catch (error) {
1103
- handlerError = error;
1104
- handlerFailed = true;
1105
- }
1106
- if (handlerFailed) {
1107
- signal?.throwIfAborted();
1108
- counters.error();
1109
- emitErroredRuleEnd(progress, clock, path, startedAt, "miss");
1110
- throw new AlintRuleExecutionError(handlerError, path);
1111
- }
1112
- if (cacheKey) {
1113
- cache.store.set(cacheKey, {
1114
- diagnostics: cacheDiagnostics ?? [],
1115
- filePath: normalizeCachePath(cache.cwd, path.file.path),
1116
- fingerprint: {
1117
- alintVersion: version,
1118
- configHash: target.configHash,
1119
- modelHash: cache.modelHash,
1120
- ruleHash: execution.runtime.ruleHash
1121
- },
1122
- target: {
1123
- hash: createTargetHash(target),
1124
- identity: target.identity,
1125
- kind: target.kind,
1126
- loc: target.loc,
1127
- name: target.name,
1128
- range: target.range
1129
- },
1130
- usage: cacheUsage ?? []
1131
- });
1132
- rememberCacheEntry(cache, target.cacheFilePaths, cacheKey);
1133
- }
1134
- counters.complete();
1135
- progress?.onRuleEnd?.({
1136
- cache: "miss",
1137
- endedAt: clock(),
1138
- path,
1139
- startedAt,
1140
- state: "completed"
1141
- });
764
+ function failure(cause, job, kind) {
765
+ return {
766
+ job: job.job,
767
+ kind,
768
+ message: errorMessageFrom(cause) ?? String(cause)
769
+ };
1142
770
  }
1143
- async function executeTargetPlan(plan, options) {
1144
- const { clock, filesTotal, progress } = options;
1145
- const fileStartedAt = clock();
1146
- const fileProgress = {
1147
- index: plan.fileIndex,
1148
- path: plan.path,
1149
- planned: plan.planned,
1150
- total: filesTotal
771
+ function finish(job, options, bucket, terminal) {
772
+ const outcome = {
773
+ bucket,
774
+ job,
775
+ ...terminal
1151
776
  };
1152
- if (plan.emitFileProgress) progress?.onFileStart?.({
1153
- file: fileProgress,
1154
- startedAt: fileStartedAt
777
+ options.progress?.onJobEnd?.({
778
+ cache: outcome.cache,
779
+ endedAt: options.clock(),
780
+ failure: outcome.failure,
781
+ job: job.job,
782
+ startedAt: options.startedAt,
783
+ state: outcome.state
1155
784
  });
1156
- let planError;
1157
- let planFailed = false;
1158
- try {
1159
- for (const [targetOffset, target] of plan.targets.entries()) {
1160
- const targetPath = createProgressPath(plan.path, target.executions[0]?.runtime.enabledRule.id ?? "", {
1161
- fileIndex: plan.fileIndex,
1162
- filePlanned: plan.planned,
1163
- fileTotal: filesTotal,
1164
- ruleIndex: 1,
1165
- ruleTotal: target.executions.length,
1166
- targetIndex: targetOffset + 1,
1167
- targetKind: target.kind,
1168
- targetName: target.name,
1169
- targetTotal: plan.targets.length
1170
- });
1171
- const targetStartedAt = clock();
1172
- progress?.onTargetStart?.({
1173
- path: targetPath,
1174
- startedAt: targetStartedAt
1175
- });
1176
- let targetError;
1177
- let targetFailed = false;
1178
- try {
1179
- for (const [executionOffset, execution] of target.executions.entries()) await executeRule(execution, createProgressPath(plan.path, execution.runtime.enabledRule.id, {
1180
- fileIndex: plan.fileIndex,
1181
- filePlanned: plan.planned,
1182
- fileTotal: filesTotal,
1183
- ruleIndex: executionOffset + 1,
1184
- ruleTotal: target.executions.length,
1185
- targetIndex: targetOffset + 1,
1186
- targetKind: target.kind,
1187
- targetName: target.name,
1188
- targetTotal: plan.targets.length
1189
- }), target, options);
1190
- } catch (error) {
1191
- targetError = error;
1192
- targetFailed = true;
1193
- }
1194
- try {
1195
- progress?.onTargetEnd?.({
1196
- endedAt: clock(),
1197
- path: targetPath,
1198
- startedAt: targetStartedAt
1199
- });
1200
- } catch (error) {
1201
- if (!targetFailed) {
1202
- targetError = error;
1203
- targetFailed = true;
1204
- }
1205
- }
1206
- if (targetFailed) throw targetError;
1207
- }
1208
- } catch (error) {
1209
- planError = error;
1210
- planFailed = true;
1211
- }
1212
- if (plan.emitFileProgress) try {
1213
- progress?.onFileEnd?.({
1214
- endedAt: clock(),
1215
- file: fileProgress,
1216
- startedAt: fileStartedAt
1217
- });
1218
- } catch (error) {
1219
- if (!planFailed) {
1220
- planError = error;
1221
- planFailed = true;
1222
- }
1223
- }
1224
- if (planFailed) throw planError;
785
+ return outcome;
1225
786
  }
1226
787
  function rememberCacheEntry(cache, filePaths, cacheKey) {
1227
788
  for (const filePath of filePaths) {
@@ -1231,29 +792,69 @@ function rememberCacheEntry(cache, filePaths, cacheKey) {
1231
792
  cache.fileEntryKeys.set(normalizedPath, entries);
1232
793
  }
1233
794
  }
1234
- function replayCachedEntry(entry, path, diagnostics, usage, progress) {
1235
- for (const cachedDiagnostic of entry.diagnostics) {
795
+ function replayCachedEntry(entry, bucket, job, progress) {
796
+ let diagnostics;
797
+ let usage;
798
+ try {
799
+ diagnostics = entry.diagnostics;
800
+ usage = entry.usage;
801
+ } catch (cause) {
802
+ throw new CacheReplayProcessingError(cause);
803
+ }
804
+ for (const cachedDiagnostic of diagnostics) {
1236
805
  const diagnostic = {
1237
806
  ...cachedDiagnostic,
1238
807
  cached: true
1239
808
  };
1240
- diagnostics.push(diagnostic);
809
+ bucket.diagnostics.push(diagnostic);
1241
810
  progress?.onDiagnostic?.({
1242
811
  diagnostic,
1243
- diagnostics: [...diagnostics],
1244
- path
812
+ job
1245
813
  });
1246
814
  }
1247
- for (const cachedUsage of entry.usage) {
1248
- const record = usage.recordCached({ ...cachedUsage });
815
+ for (const record of usage) {
816
+ bucket.usage.push(record);
1249
817
  progress?.onUsage?.({
1250
- path,
1251
- record,
1252
- total: usage.toJSON()
818
+ job,
819
+ record
1253
820
  });
1254
821
  }
1255
822
  }
1256
823
  //#endregion
824
+ //#region src/core/targets/directory.ts
825
+ function createDirectoryExecutionPlans(directories, sourcePlanCount) {
826
+ return directories.map((directory, directoryOffset) => {
827
+ const executions = directory.ruleRuntimes.map((runtime) => {
828
+ if (runtime.handlers.onTargetWith) return {
829
+ run: () => runtime.handlers.onTargetWith?.(directory.target),
830
+ runtime
831
+ };
832
+ if (runtime.handlers.onTargetDirectory) return {
833
+ run: () => runtime.handlers.onTargetDirectory?.(directory.target),
834
+ runtime
835
+ };
836
+ }).filter((execution) => execution !== void 0);
837
+ const targets = executions.length === 0 ? [] : [{
838
+ activeFilePath: directory.target.path,
839
+ cacheFilePaths: [],
840
+ configHash: directory.configHash,
841
+ executions,
842
+ identity: directory.target.path,
843
+ kind: "directory",
844
+ language: "directory",
845
+ text: directory.target.path
846
+ }];
847
+ return {
848
+ id: `directory:${directory.target.path}`,
849
+ index: sourcePlanCount + directoryOffset + 1,
850
+ kind: "directory",
851
+ path: directory.target.path,
852
+ planned: executions.length,
853
+ targets
854
+ };
855
+ });
856
+ }
857
+ //#endregion
1257
858
  //#region src/core/targets/project.ts
1258
859
  function createProjectExecutionPlan(options) {
1259
860
  const project = createProjectTarget(options.root, options.files);
@@ -1297,8 +898,9 @@ function createProjectExecutionPlan(options) {
1297
898
  })
1298
899
  };
1299
900
  return {
1300
- emitFileProgress: false,
1301
- fileIndex: 0,
901
+ id: `project:${options.root}`,
902
+ index: options.index,
903
+ kind: "project",
1302
904
  path: options.root,
1303
905
  planned: executions.length,
1304
906
  targets: [target]
@@ -1333,8 +935,9 @@ function createSourceExecutionPlans(files, cwd) {
1333
935
  const resolveTargetIdentity = createTargetIdentityResolver(targets.map((target) => toTargetIdentityInput(cwd, preparedFile.file.path, target)));
1334
936
  for (const target of targets) target.identity = resolveTargetIdentity(toTargetIdentityInput(cwd, preparedFile.file.path, target));
1335
937
  const filePlan = {
1336
- emitFileProgress: true,
1337
- fileIndex: fileOffset + 1,
938
+ id: `source:${preparedFile.file.path}`,
939
+ index: fileOffset + 1,
940
+ kind: "source",
1338
941
  path: preparedFile.file.path,
1339
942
  planned: 0,
1340
943
  preparedFile,
@@ -1401,29 +1004,34 @@ function toTargetIdentityInput(cwd, filePath, target) {
1401
1004
  }
1402
1005
  //#endregion
1403
1006
  //#region src/core/run.ts
1404
- var AlintRunError = class extends Error {
1405
- failure;
1007
+ var AlintRunCancelledError = class extends Error {
1406
1008
  result;
1407
- constructor(message, result, options = {}) {
1408
- super(message, { cause: options.cause });
1409
- this.name = "AlintRunError";
1410
- this.failure = options.failure;
1009
+ constructor(result, cause) {
1010
+ super("Alint run cancelled.", { cause });
1011
+ this.name = "AlintRunCancelledError";
1411
1012
  this.result = result;
1412
1013
  }
1413
1014
  };
1414
- /**
1415
- * Thrown when {@link RunOptions.signal} aborts a run.
1416
- *
1417
- * Extends {@link AlintRunError} so existing handlers still receive the partial `result`, while
1418
- * callers that treat cancellation differently from failure can test for this type.
1419
- */
1420
- var AlintAbortError = class extends AlintRunError {
1015
+ /** Cancellation error carrying the partial result gathered before the abort. */
1016
+ var AlintAbortError = class extends AlintRunCancelledError {
1421
1017
  constructor(result, options = {}) {
1422
- super("alint run was aborted", result, { cause: options.cause });
1018
+ super(result, options.cause);
1423
1019
  this.name = "AlintAbortError";
1424
1020
  }
1425
1021
  };
1022
+ var AlintRunError = class extends Error {
1023
+ failures;
1024
+ result;
1025
+ constructor(message, result, options = {}) {
1026
+ super(message, { cause: options.cause });
1027
+ this.name = "AlintRunError";
1028
+ this.failures = options.failures ?? [];
1029
+ this.result = result;
1030
+ }
1031
+ };
1426
1032
  async function runAlint(options = {}) {
1033
+ const timeoutMs = resolveRuleExecutionTimeout(options.runner?.timeoutMs);
1034
+ const concurrency = resolveRuleConcurrency(options.runner?.ruleConcurrency);
1427
1035
  const cwd$1 = options.cwd ?? cwd();
1428
1036
  const config = options.config ?? [];
1429
1037
  const setupConfig = options.setupConfig ?? {
@@ -1431,8 +1039,6 @@ async function runAlint(options = {}) {
1431
1039
  version: 1
1432
1040
  };
1433
1041
  const clock = Date.now;
1434
- const diagnostics = [];
1435
- const usage = createUsageAccumulator();
1436
1042
  const src = createSourceRuntime();
1437
1043
  const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
1438
1044
  const cacheStore = await createCacheStore({
@@ -1469,14 +1075,13 @@ async function runAlint(options = {}) {
1469
1075
  const registry = buildRuleRegistry(effectiveConfig);
1470
1076
  const ruleRuntimes = createRuleRuntimes({
1471
1077
  cwd: cwd$1,
1472
- diagnostics,
1473
1078
  effectiveAgent: effectiveConfig.agent,
1474
1079
  effectiveSettings: effectiveConfig.settings,
1475
1080
  options,
1081
+ progress: options.progress,
1476
1082
  registry,
1477
1083
  setupConfig,
1478
- src,
1479
- usage
1084
+ src
1480
1085
  });
1481
1086
  return {
1482
1087
  configHash: stableHash({
@@ -1504,14 +1109,13 @@ async function runAlint(options = {}) {
1504
1109
  configHash: stableHash({ settings: effectiveConfig.settings }),
1505
1110
  ruleRuntimes: createRuleRuntimes({
1506
1111
  cwd: cwd$1,
1507
- diagnostics,
1508
1112
  effectiveAgent: effectiveConfig.agent,
1509
1113
  effectiveSettings: effectiveConfig.settings,
1510
1114
  options,
1115
+ progress: options.progress,
1511
1116
  registry,
1512
1117
  setupConfig,
1513
- src,
1514
- usage
1118
+ src
1515
1119
  }),
1516
1120
  target
1517
1121
  };
@@ -1520,181 +1124,146 @@ async function runAlint(options = {}) {
1520
1124
  const projectConfig = resolvedProjectConfig.config;
1521
1125
  const projectRuleRuntimes = resolvedProjectConfig.ignored ? [] : createRuleRuntimes({
1522
1126
  cwd: cwd$1,
1523
- diagnostics,
1524
1127
  effectiveAgent: projectConfig.agent,
1525
1128
  effectiveSettings: projectConfig.settings,
1526
1129
  options,
1130
+ progress: options.progress,
1527
1131
  registry: buildRuleRegistry(projectConfig),
1528
1132
  setupConfig,
1529
- src,
1530
- usage
1133
+ src
1134
+ });
1135
+ const filePlans = createSourceExecutionPlans(files, cwd$1);
1136
+ const directoryPlans = createDirectoryExecutionPlans(directories, filePlans.length);
1137
+ const projectPlan = resolvedProjectConfig.ignored ? void 0 : createProjectExecutionPlan({
1138
+ configHash: stableHash({ settings: projectConfig.settings }),
1139
+ files,
1140
+ index: filePlans.length + directoryPlans.length + 1,
1141
+ root: cwd$1,
1142
+ ruleRuntimes: projectRuleRuntimes
1531
1143
  });
1532
- let planned = 0;
1533
- const counters = createRuleEndCounters();
1534
- let filePlans = [];
1535
- let directoryPlans = [];
1536
- let projectPlan;
1537
- let runStartedAt;
1538
- let runError;
1144
+ const jobs = createRuleJobs([
1145
+ ...filePlans,
1146
+ ...directoryPlans,
1147
+ ...projectPlan ? [projectPlan] : []
1148
+ ]);
1149
+ const runStartedAt = clock();
1150
+ const settledOutcomes = new Array(jobs.length);
1151
+ let infrastructureError;
1152
+ let infrastructureFailed = false;
1153
+ options.progress?.onRunStart?.({
1154
+ jobsTotal: jobs.length,
1155
+ startedAt: runStartedAt
1156
+ });
1157
+ for (const job of jobs) options.progress?.onJobQueued?.({ job: job.job });
1539
1158
  try {
1540
- options.signal?.throwIfAborted();
1541
- filePlans = createSourceExecutionPlans(files, cwd$1);
1542
- directoryPlans = createDirectoryExecutionPlans(directories, filePlans.length);
1543
- projectPlan = resolvedProjectConfig.ignored ? void 0 : createProjectExecutionPlan({
1544
- configHash: stableHash({ settings: projectConfig.settings }),
1545
- files,
1546
- root: cwd$1,
1547
- ruleRuntimes: projectRuleRuntimes
1548
- });
1549
- const activeDirectoryPlans = directoryPlans.filter((plan) => plan.targets.length > 0).map((plan, directoryOffset) => ({
1550
- ...plan,
1551
- fileIndex: filePlans.length + directoryOffset + 1
1552
- }));
1553
- const inputsTotal = files.length + activeDirectoryPlans.length;
1554
- planned = calculatePlannedExecutions([...filePlans, ...directoryPlans]) + (projectPlan?.planned ?? 0);
1555
- runStartedAt = clock();
1556
- options.progress?.onRunStart?.({
1557
- files: [...filePlans, ...activeDirectoryPlans].map((plan) => toProgressFilePath(plan, inputsTotal)),
1558
- filesTotal: inputsTotal,
1559
- planned,
1560
- rulesTotal: countEnabledRuleIds(files, directories, projectRuleRuntimes),
1561
- startedAt: runStartedAt
1562
- });
1563
- await runConcurrently(filePlans.filter((filePlan) => filePlan.targets.length > 0), resolveFileConcurrency(options.runner?.fileConcurrency), (filePlan) => executeTargetPlans({
1564
- cache: cacheContext,
1565
- cacheOnly: options.cacheOnly,
1566
- clock,
1567
- counters,
1568
- diagnostics,
1569
- filesTotal: inputsTotal,
1570
- plans: [filePlan],
1571
- progress: options.progress,
1572
- signal: options.signal,
1573
- usage
1574
- }));
1575
- await runConcurrently(activeDirectoryPlans, resolveFileConcurrency(options.runner?.fileConcurrency), (directoryPlan) => executeTargetPlans({
1576
- cache: cacheContext,
1577
- cacheOnly: options.cacheOnly,
1578
- clock,
1579
- counters,
1580
- diagnostics,
1581
- filesTotal: inputsTotal,
1582
- plans: [directoryPlan],
1583
- progress: options.progress,
1584
- signal: options.signal,
1585
- usage
1586
- }));
1587
- if (projectPlan) await executeTargetPlans({
1588
- cache: cacheContext,
1589
- cacheOnly: options.cacheOnly,
1590
- clock,
1591
- counters,
1592
- diagnostics,
1593
- filesTotal: inputsTotal,
1594
- plans: [projectPlan],
1595
- progress: options.progress,
1596
- signal: options.signal,
1597
- usage
1598
- });
1599
- } catch (error) {
1600
- runError = error;
1159
+ let cursor = 0;
1160
+ const worker = async () => {
1161
+ while (cursor < jobs.length) {
1162
+ const index = cursor;
1163
+ cursor += 1;
1164
+ const job = jobs[index];
1165
+ if (options.signal?.aborted) {
1166
+ settledOutcomes[index] = {
1167
+ bucket: {
1168
+ diagnostics: [],
1169
+ usage: []
1170
+ },
1171
+ cache: "miss",
1172
+ job,
1173
+ state: "cancelled"
1174
+ };
1175
+ continue;
1176
+ }
1177
+ try {
1178
+ const startedAt = clock();
1179
+ options.progress?.onJobStart?.({
1180
+ job: job.job,
1181
+ startedAt
1182
+ });
1183
+ settledOutcomes[index] = await executeRuleJob(job, {
1184
+ cache: cacheContext,
1185
+ cacheOnly: options.cacheOnly,
1186
+ clock,
1187
+ progress: options.progress,
1188
+ runSignal: options.signal,
1189
+ startedAt,
1190
+ timeoutMs
1191
+ });
1192
+ } catch (error) {
1193
+ if (!infrastructureFailed) {
1194
+ infrastructureError = error;
1195
+ infrastructureFailed = true;
1196
+ }
1197
+ }
1198
+ }
1199
+ };
1200
+ await Promise.all(Array.from({ length: Math.min(concurrency, jobs.length) }, worker));
1601
1201
  } finally {
1602
1202
  if (!options.cacheOnly) await reconcileCache(filePlans, cacheContext);
1603
- try {
1604
- options.progress?.onRunEnd?.({
1605
- ...counters.snapshot(planned),
1606
- diagnostics,
1607
- endedAt: clock(),
1608
- startedAt: runStartedAt ?? clock(),
1609
- usage: usage.toJSON()
1610
- });
1611
- } catch (error) {
1612
- runError ??= error;
1613
- }
1614
1203
  }
1204
+ const outcomes = Array.from({ length: jobs.length }, (_, index) => settledOutcomes[index] ?? {
1205
+ bucket: {
1206
+ diagnostics: [],
1207
+ usage: []
1208
+ },
1209
+ cache: "miss",
1210
+ job: jobs[index],
1211
+ state: "cancelled"
1212
+ });
1213
+ const failedOutcomes = outcomes.filter((outcome) => outcome.state === "failed");
1214
+ const failures = failedOutcomes.map((outcome) => outcome.failure);
1615
1215
  const result = {
1616
- diagnostics,
1617
- execution: counters.snapshot(planned),
1618
- usage: usage.toJSON()
1216
+ diagnostics: outcomes.flatMap((outcome) => outcome.bucket.diagnostics),
1217
+ execution: executionCounts(outcomes),
1218
+ usage: runUsage(outcomes)
1619
1219
  };
1620
- if (runError) {
1621
- if (options.signal?.aborted) throw new AlintAbortError(result, { cause: runError });
1622
- throw createAlintRunError(runError, result);
1623
- }
1624
- return result;
1625
- }
1626
- function addTokenCount(base, value) {
1627
- return typeof value === "number" && Number.isFinite(value) ? base + value : base;
1628
- }
1629
- function calculatePlannedExecutions(filePlans) {
1630
- return filePlans.reduce((total, filePlan) => total + filePlan.planned, 0);
1631
- }
1632
- function countEnabledRuleIds(files, directories, projectRuleRuntimes) {
1633
- const ids = /* @__PURE__ */ new Set();
1634
- for (const file of files) for (const runtime of file.ruleRuntimes) ids.add(runtime.enabledRule.id);
1635
- for (const directory of directories) for (const runtime of directory.ruleRuntimes) ids.add(runtime.enabledRule.id);
1636
- for (const runtime of projectRuleRuntimes) ids.add(runtime.enabledRule.id);
1637
- return ids.size;
1638
- }
1639
- function createAlintRunError(error, result) {
1640
- if (error instanceof AlintRunError) return error;
1641
- if (error instanceof AlintRuleExecutionError) return new AlintRunError(error.message, result, {
1642
- cause: errorCauseFrom(error),
1643
- failure: error.failure
1220
+ options.progress?.onRunEnd?.({
1221
+ diagnostics: result.diagnostics,
1222
+ endedAt: clock(),
1223
+ execution: result.execution,
1224
+ startedAt: runStartedAt,
1225
+ usage: result.usage
1644
1226
  });
1645
- return new AlintRunError(errorMessageFrom(error) ?? String(error), result, { cause: error });
1646
- }
1647
- function createRuleEndCounters() {
1648
- let cached = 0;
1649
- let completed = 0;
1650
- let errored = 0;
1651
- let skipped = 0;
1652
- return {
1653
- cache() {
1654
- cached += 1;
1655
- },
1656
- complete() {
1657
- completed += 1;
1658
- },
1659
- error() {
1660
- errored += 1;
1661
- },
1662
- skip() {
1663
- skipped += 1;
1664
- },
1665
- snapshot(planned) {
1666
- return {
1667
- cached,
1668
- completed,
1669
- errored,
1670
- planned,
1671
- skipped
1672
- };
1673
- }
1674
- };
1227
+ if (infrastructureFailed) throw infrastructureError;
1228
+ if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
1229
+ if (failures.length > 0) throw new AlintRunError(`${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
1230
+ cause: new AggregateError(failedOutcomes.map((outcome) => outcome.cause), "Rule execution failures."),
1231
+ failures
1232
+ });
1233
+ return result;
1675
1234
  }
1676
1235
  function createRuleRuntimes(options) {
1677
- const agent = options.effectiveAgent ? withAgentRetry(options.effectiveAgent, options.options.runner?.agentRetries) : void 0;
1678
1236
  return options.registry.enabledRules.map((enabledRule) => {
1679
1237
  const executionState = new AsyncLocalStorage();
1680
1238
  const context = {
1681
- agent,
1239
+ agent: options.effectiveAgent ? withAgentRetry((request) => options.effectiveAgent({
1240
+ ...request,
1241
+ signal: combineAbortSignals(executionState.getStore()?.signal, request.signal)
1242
+ }), options.options.runner?.agentRetries) : void 0,
1682
1243
  cwd: options.cwd,
1683
1244
  id: enabledRule.id,
1684
1245
  localId: enabledRule.localId,
1685
1246
  logger: { debug: () => {} },
1686
1247
  metering: { recordUsage: (record) => {
1687
- const usageRecord = options.usage.record({
1248
+ const state = executionState.getStore();
1249
+ if (!state) throw new Error("Cannot record usage outside an active rule job.");
1250
+ const usageRecord = {
1688
1251
  ...record,
1689
1252
  ruleId: record.ruleId ?? enabledRule.id
1690
- });
1691
- const state = executionState.getStore();
1692
- state?.cacheUsage?.push(usageRecord);
1693
- options.options.progress?.onUsage?.({
1694
- path: state?.progressPath,
1695
- record: usageRecord,
1696
- total: options.usage.toJSON()
1697
- });
1253
+ };
1254
+ state.bucket.usage.push(usageRecord);
1255
+ try {
1256
+ options.progress?.onUsage?.({
1257
+ job: state.job,
1258
+ record: usageRecord
1259
+ });
1260
+ } catch (cause) {
1261
+ if (!state.reporterFailed) {
1262
+ state.reporterCause = cause;
1263
+ state.reporterFailed = true;
1264
+ }
1265
+ throw cause;
1266
+ }
1698
1267
  } },
1699
1268
  model: async (selector) => {
1700
1269
  const request = options.options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
@@ -1711,10 +1280,11 @@ function createRuleRuntimes(options) {
1711
1280
  outputLanguage: options.options.outputLanguage,
1712
1281
  report: (descriptor) => {
1713
1282
  const state = executionState.getStore();
1714
- const filePath = descriptor.filePath ?? state?.activeFilePath;
1283
+ if (!state) throw new Error("Cannot report a diagnostic outside an active rule job.");
1284
+ const filePath = descriptor.filePath ?? state.activeFilePath;
1715
1285
  if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
1716
- const diagnosticModel = state?.currentModel ? { ...state.currentModel } : void 0;
1717
- if (state) state.currentModel = void 0;
1286
+ const diagnosticModel = state.currentModel ? { ...state.currentModel } : void 0;
1287
+ state.currentModel = void 0;
1718
1288
  const diagnostic = {
1719
1289
  evidence: descriptor.evidence,
1720
1290
  filePath,
@@ -1724,16 +1294,24 @@ function createRuleRuntimes(options) {
1724
1294
  ruleId: enabledRule.id,
1725
1295
  severity: enabledRule.severity
1726
1296
  };
1727
- options.diagnostics.push(diagnostic);
1728
- state?.cacheDiagnostics?.push(diagnostic);
1729
- options.options.progress?.onDiagnostic?.({
1730
- diagnostic,
1731
- diagnostics: [...options.diagnostics],
1732
- path: state?.progressPath
1733
- });
1297
+ state.bucket.diagnostics.push(diagnostic);
1298
+ try {
1299
+ options.progress?.onDiagnostic?.({
1300
+ diagnostic,
1301
+ job: state.job
1302
+ });
1303
+ } catch (cause) {
1304
+ if (!state.reporterFailed) {
1305
+ state.reporterCause = cause;
1306
+ state.reporterFailed = true;
1307
+ }
1308
+ throw cause;
1309
+ }
1734
1310
  },
1735
1311
  settings: options.effectiveSettings,
1736
- signal: options.options.signal,
1312
+ get signal() {
1313
+ return executionState.getStore()?.signal;
1314
+ },
1737
1315
  src: options.src
1738
1316
  };
1739
1317
  return {
@@ -1753,47 +1331,19 @@ function createRuleRuntimes(options) {
1753
1331
  };
1754
1332
  });
1755
1333
  }
1756
- function createUsageAccumulator() {
1757
- const live = createUsageTotals();
1758
- const cached = createUsageTotals();
1759
- return {
1760
- record(record) {
1761
- return live.record(record);
1762
- },
1763
- recordCached(record) {
1764
- return cached.record(record);
1765
- },
1766
- toJSON() {
1767
- const cachedUsage = cached.toJSON();
1768
- return {
1769
- ...live.toJSON(),
1770
- ...cachedUsage.records.length > 0 ? { cached: cachedUsage } : {}
1771
- };
1772
- }
1773
- };
1774
- }
1775
- function createUsageTotals() {
1776
- const records = [];
1777
- let inputTokens = 0;
1778
- let outputTokens = 0;
1779
- let totalTokens = 0;
1780
- return {
1781
- record(record) {
1782
- records.push(record);
1783
- inputTokens = addTokenCount(inputTokens, record.inputTokens);
1784
- outputTokens = addTokenCount(outputTokens, record.outputTokens);
1785
- totalTokens = addTokenCount(totalTokens, record.totalTokens);
1786
- return record;
1787
- },
1788
- toJSON() {
1789
- return {
1790
- inputTokens,
1791
- outputTokens,
1792
- records,
1793
- totalTokens
1794
- };
1795
- }
1334
+ function executionCounts(outcomes) {
1335
+ const counts = {
1336
+ cached: 0,
1337
+ cancelled: 0,
1338
+ completed: 0,
1339
+ failed: 0,
1340
+ planned: outcomes.length,
1341
+ queued: 0,
1342
+ running: 0,
1343
+ skipped: 0
1796
1344
  };
1345
+ for (const outcome of outcomes) counts[outcome.state] += 1;
1346
+ return counts;
1797
1347
  }
1798
1348
  function mergeCapabilities(base, extra) {
1799
1349
  if (!base && !extra) return;
@@ -1829,28 +1379,19 @@ async function reconcileCache(filePlans, cacheContext) {
1829
1379
  await cacheContext.store.reconcile();
1830
1380
  } catch {}
1831
1381
  }
1832
- function resolveFileConcurrency(fileConcurrency) {
1833
- return fileConcurrency ?? 1;
1834
- }
1835
- async function runConcurrently(items, concurrency, runItem) {
1836
- let firstError;
1837
- let nextIndex = 0;
1838
- const workerCount = Math.min(Math.max(concurrency, 1), items.length);
1839
- async function runWorker() {
1840
- while (firstError === void 0) {
1841
- const currentIndex = nextIndex;
1842
- nextIndex += 1;
1843
- if (currentIndex >= items.length) return;
1844
- try {
1845
- await runItem(items[currentIndex]);
1846
- } catch (error) {
1847
- firstError ??= error;
1848
- return;
1849
- }
1850
- }
1851
- }
1852
- await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
1853
- if (firstError !== void 0) throw firstError;
1382
+ function resolveRuleConcurrency(ruleConcurrency) {
1383
+ if (ruleConcurrency === void 0) return 4;
1384
+ if (!Number.isFinite(ruleConcurrency) || !Number.isInteger(ruleConcurrency) || ruleConcurrency <= 0) throw new TypeError("Rule execution concurrency must be a finite positive integer.");
1385
+ return ruleConcurrency;
1386
+ }
1387
+ function runUsage(outcomes) {
1388
+ const live = [];
1389
+ const cached = [];
1390
+ for (const outcome of outcomes) (outcome.state === "cached" ? cached : live).push(...outcome.bucket.usage);
1391
+ return {
1392
+ ...usageTotals(live),
1393
+ ...cached.length > 0 ? { cached: usageTotals(cached) } : {}
1394
+ };
1854
1395
  }
1855
1396
  function toDiagnosticModel(model, request) {
1856
1397
  return {
@@ -1859,12 +1400,20 @@ function toDiagnosticModel(model, request) {
1859
1400
  resolvedId: model.id
1860
1401
  };
1861
1402
  }
1862
- function toProgressFilePath(filePlan, filesTotal) {
1403
+ function usageTotals(records) {
1404
+ let inputTokens = 0;
1405
+ let outputTokens = 0;
1406
+ let totalTokens = 0;
1407
+ for (const record of records) {
1408
+ if (record.inputTokens != null && Number.isFinite(record.inputTokens)) inputTokens += record.inputTokens;
1409
+ if (record.outputTokens != null && Number.isFinite(record.outputTokens)) outputTokens += record.outputTokens;
1410
+ if (record.totalTokens != null && Number.isFinite(record.totalTokens)) totalTokens += record.totalTokens;
1411
+ }
1863
1412
  return {
1864
- index: filePlan.fileIndex,
1865
- path: filePlan.path,
1866
- planned: filePlan.planned,
1867
- total: filesTotal
1413
+ inputTokens,
1414
+ outputTokens,
1415
+ records: [...records],
1416
+ totalTokens
1868
1417
  };
1869
1418
  }
1870
1419
  //#endregion
@@ -1879,4 +1428,4 @@ function defineRule(rule) {
1879
1428
  return rule;
1880
1429
  }
1881
1430
  //#endregion
1882
- export { AlintRunError, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, extractJsSourceTargets, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange };
1431
+ export { AlintAbortError, AlintRunCancelledError, AlintRunError, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange };