@alint-js/core 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/agent.d.mts +1 -1
- package/dist/{define-CNfm3wZW.d.mts → define-DO36VJ4W.d.mts} +1 -1
- package/dist/dsl.d.mts +3 -3
- package/dist/index.d.mts +45 -19
- package/dist/index.mjs +993 -674
- package/dist/languages-js.mjs +1 -1
- package/dist/structured-output.d.mts +1 -1
- package/dist/{types-BK5CADYK.d.mts → types-Cm5Z7Fll.d.mts} +11 -1
- package/package.json +20 -2
- package/dist/{extract-CLXRRiJW.mjs → extract-DhLFgRej.mjs} +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { o as withAgentRetry, t as combineAbortSignals } from "./agent-B6GG68NX.mjs";
|
|
2
2
|
import { n as definePlugin, r as defineRule, t as defineConfig } from "./define-Da342Fcx.mjs";
|
|
3
|
-
import { a as createSourceFile, c as sliceRange, d as stableHash, i as SourceChangedError, l as withLanguage, n as targetIdentity, o as createSourceRuntime, r as withStableIdentities, s as sliceLines, t as extractJsSourceTargets, u as createStableHasher } from "./extract-
|
|
3
|
+
import { a as createSourceFile, c as sliceRange, d as stableHash, i as SourceChangedError, l as withLanguage, n as targetIdentity, o as createSourceRuntime, r as withStableIdentities, s as sliceLines, t as extractJsSourceTargets, u as createStableHasher } from "./extract-DhLFgRej.mjs";
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
5
|
import { basename, extname, isAbsolute, relative, resolve } from "pathe";
|
|
6
|
-
import {
|
|
7
|
-
import { basename as basename$1, dirname, extname as extname$1, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { array, boolean, check, custom, finite, getDotPath, integer, literal, looseObject, maxValue, minValue, number, object, optional, parse, picklist, pipe, record, safeParse, string, union, unknown } from "valibot";
|
|
9
7
|
import process, { cwd } from "node:process";
|
|
10
|
-
import { array, boolean, check, custom, finite, getDotPath, literal, number, object, optional, parse, pipe, record, safeParse, string, union, unknown } from "valibot";
|
|
11
8
|
import { Buffer } from "node:buffer";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
12
10
|
import { statSync } from "node:fs";
|
|
11
|
+
import { access, appendFile, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
12
|
+
import { basename as basename$1, dirname, extname as extname$1, isAbsolute as isAbsolute$1, join, relative as relative$1, resolve as resolve$1, sep } from "node:path";
|
|
13
|
+
import lockfile from "proper-lockfile";
|
|
14
|
+
import { isNodeErrorCode } from "@alint-js/utils/node";
|
|
13
15
|
import { errorMessageFrom } from "@moeru/std/error";
|
|
14
16
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15
17
|
import { streamText } from "@xsai/stream-text";
|
|
@@ -236,744 +238,1049 @@ function withExpansionMetadata(item, inheritedMatcherScopes, effectiveBasePath)
|
|
|
236
238
|
return item;
|
|
237
239
|
}
|
|
238
240
|
//#endregion
|
|
239
|
-
//#region src/
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
".mjs"
|
|
246
|
-
],
|
|
247
|
-
extract: (file) => extractJsSourceTargets(withLanguage(file, "javascript")),
|
|
248
|
-
name: "javascript"
|
|
241
|
+
//#region src/config/stop-gate.ts
|
|
242
|
+
const maximumStopGateTimeoutMs = 861e5;
|
|
243
|
+
const defaultStopGateConfig = {
|
|
244
|
+
enabled: false,
|
|
245
|
+
target: "dirty-files",
|
|
246
|
+
timeoutMs: 9e5
|
|
249
247
|
};
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
const stopGateConfigSchema = looseObject({
|
|
249
|
+
enabled: optional(boolean("integrations.stopGate.enabled must be a boolean.")),
|
|
250
|
+
target: optional(picklist(["all", "dirty-files"], "integrations.stopGate.target must be \"all\" or \"dirty-files\".")),
|
|
251
|
+
timeoutMs: optional(pipe(number(`integrations.stopGate.timeoutMs must be an integer from 1 to ${maximumStopGateTimeoutMs}.`), integer(`integrations.stopGate.timeoutMs must be an integer from 1 to ${maximumStopGateTimeoutMs}.`), minValue(1, `integrations.stopGate.timeoutMs must be an integer from 1 to ${maximumStopGateTimeoutMs}.`), maxValue(maximumStopGateTimeoutMs, `integrations.stopGate.timeoutMs must be an integer from 1 to ${maximumStopGateTimeoutMs}.`)))
|
|
252
|
+
}, "integrations.stopGate must be an object.");
|
|
253
|
+
function resolveStopGateConfig(config, cwd) {
|
|
254
|
+
const project = resolveConfigForProject(cwd, config, { cwd });
|
|
255
|
+
for (const skipped of project.skipped) if (skipped.item.integrations?.stopGate !== void 0) throw new Error("integrations.stopGate must be declared in a global config item without files, directories, or ignores.");
|
|
256
|
+
return project.matched.reduce((resolved, item) => mergeStopGateConfig(resolved, item), { ...defaultStopGateConfig });
|
|
257
|
+
}
|
|
258
|
+
function assertStopGateConfig(value) {
|
|
259
|
+
parse(stopGateConfigSchema, value);
|
|
260
|
+
}
|
|
261
|
+
function mergeStopGateConfig(resolved, item) {
|
|
262
|
+
const stopGate = item.integrations?.stopGate;
|
|
263
|
+
if (stopGate === void 0) return resolved;
|
|
264
|
+
assertStopGateConfig(stopGate);
|
|
253
265
|
return {
|
|
254
|
-
|
|
255
|
-
|
|
266
|
+
enabled: stopGate.enabled ?? resolved.enabled,
|
|
267
|
+
target: stopGate.target ?? resolved.target,
|
|
268
|
+
timeoutMs: stopGate.timeoutMs ?? resolved.timeoutMs
|
|
256
269
|
};
|
|
257
270
|
}
|
|
258
|
-
function registerLanguage(registry, language) {
|
|
259
|
-
const existing = registry.languages.get(language.name);
|
|
260
|
-
if (existing && existing !== language) throw new Error(`Duplicate language "${language.name}".`);
|
|
261
|
-
for (const extension of language.extensions ?? []) {
|
|
262
|
-
const existingOwner = registry.byExtension.get(extension);
|
|
263
|
-
if (existingOwner && existingOwner !== language.name) throw new Error(`Duplicate language extension "${extension}".`);
|
|
264
|
-
}
|
|
265
|
-
registry.languages.set(language.name, language);
|
|
266
|
-
for (const extension of language.extensions ?? []) registry.byExtension.set(extension, language.name);
|
|
267
|
-
}
|
|
268
271
|
//#endregion
|
|
269
|
-
//#region
|
|
270
|
-
|
|
271
|
-
extensions: [],
|
|
272
|
-
extract: (file) => [{
|
|
273
|
-
file,
|
|
274
|
-
identity: "file",
|
|
275
|
-
kind: "file",
|
|
276
|
-
language: "plaintext",
|
|
277
|
-
origin: { physicalPath: file.path },
|
|
278
|
-
text: file.text
|
|
279
|
-
}],
|
|
280
|
-
name: "plaintext"
|
|
281
|
-
};
|
|
272
|
+
//#region package.json
|
|
273
|
+
var version = "0.7.0";
|
|
282
274
|
//#endregion
|
|
283
|
-
//#region src/core/
|
|
284
|
-
const
|
|
285
|
-
extensions: [
|
|
286
|
-
".cts",
|
|
287
|
-
".mts",
|
|
288
|
-
".ts",
|
|
289
|
-
".tsx"
|
|
290
|
-
],
|
|
291
|
-
extract: (file) => extractJsSourceTargets(withLanguage(file, "typescript")),
|
|
292
|
-
name: "typescript"
|
|
293
|
-
};
|
|
275
|
+
//#region src/core/cache/types.ts
|
|
276
|
+
const CACHE_MAGIC = "ALINT_CACHE";
|
|
294
277
|
//#endregion
|
|
295
|
-
//#region src/core/
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
278
|
+
//#region src/core/cache/store.ts
|
|
279
|
+
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
280
|
+
const LOCK_STALE_MS = 2e4;
|
|
281
|
+
const LOCK_UPDATE_MS = 3e3;
|
|
282
|
+
const METADATA_READ_LIMIT = 4096;
|
|
283
|
+
const FiniteNumberSchema = pipe(number(), finite());
|
|
284
|
+
const PositionSchema = object({
|
|
285
|
+
column: FiniteNumberSchema,
|
|
286
|
+
line: FiniteNumberSchema
|
|
287
|
+
});
|
|
288
|
+
const SourceLocationSchema = object({
|
|
289
|
+
end: PositionSchema,
|
|
290
|
+
start: PositionSchema
|
|
291
|
+
});
|
|
292
|
+
const DiagnosticLocationSchema = object({
|
|
293
|
+
end: optional(PositionSchema),
|
|
294
|
+
start: PositionSchema
|
|
295
|
+
});
|
|
296
|
+
const SourceRangeSchema = object({
|
|
297
|
+
end: FiniteNumberSchema,
|
|
298
|
+
start: FiniteNumberSchema
|
|
299
|
+
});
|
|
300
|
+
const DiagnosticSchema = object({
|
|
301
|
+
cached: optional(boolean()),
|
|
302
|
+
evidence: optional(custom(isJsonValue)),
|
|
303
|
+
filePath: string(),
|
|
304
|
+
loc: optional(DiagnosticLocationSchema),
|
|
305
|
+
message: string(),
|
|
306
|
+
model: optional(object({
|
|
307
|
+
providerId: string(),
|
|
308
|
+
requested: optional(string()),
|
|
309
|
+
resolvedId: string()
|
|
310
|
+
})),
|
|
311
|
+
ruleId: string(),
|
|
312
|
+
severity: union([literal("error"), literal("warn")])
|
|
313
|
+
});
|
|
314
|
+
const UsageSchema = object({
|
|
315
|
+
filePath: optional(string()),
|
|
316
|
+
inputTokens: optional(FiniteNumberSchema),
|
|
317
|
+
metadata: optional(custom(isJsonValue)),
|
|
318
|
+
modelId: string(),
|
|
319
|
+
outputTokens: optional(FiniteNumberSchema),
|
|
320
|
+
providerId: string(),
|
|
321
|
+
ruleId: string(),
|
|
322
|
+
totalTokens: optional(FiniteNumberSchema)
|
|
323
|
+
});
|
|
324
|
+
const CacheEntrySchema = object({
|
|
325
|
+
diagnostics: array(DiagnosticSchema),
|
|
326
|
+
fingerprint: object({
|
|
327
|
+
configHash: string(),
|
|
328
|
+
modelHash: string(),
|
|
329
|
+
ruleHash: string(),
|
|
330
|
+
targetHash: string()
|
|
331
|
+
}),
|
|
332
|
+
target: object({
|
|
333
|
+
hash: string(),
|
|
334
|
+
identity: string(),
|
|
335
|
+
kind: string(),
|
|
336
|
+
loc: optional(SourceLocationSchema),
|
|
337
|
+
name: optional(string()),
|
|
338
|
+
range: optional(SourceRangeSchema)
|
|
339
|
+
}),
|
|
340
|
+
usage: array(UsageSchema)
|
|
341
|
+
});
|
|
342
|
+
const CachedOwnerSchema = object({
|
|
343
|
+
contentHash: optional(string()),
|
|
344
|
+
kind: union([literal("file"), literal("project")]),
|
|
345
|
+
path: string(),
|
|
346
|
+
slots: array(string())
|
|
347
|
+
});
|
|
348
|
+
function objectRecord(value) {
|
|
349
|
+
return pipe(unknown(), check((input) => typeof input === "object" && input !== null && !Array.isArray(input)), record(string(), value));
|
|
307
350
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
351
|
+
const CacheFileBodySchema = pipe(object({
|
|
352
|
+
createdAt: string(),
|
|
353
|
+
entries: objectRecord(CacheEntrySchema),
|
|
354
|
+
owners: objectRecord(CachedOwnerSchema),
|
|
355
|
+
updatedAt: string()
|
|
356
|
+
}), check((body) => {
|
|
357
|
+
const entryKeys = Object.keys(body.entries);
|
|
358
|
+
const referencedSlots = Object.values(body.owners).flatMap((owner) => owner.slots);
|
|
359
|
+
const uniqueSlots = new Set(referencedSlots);
|
|
360
|
+
return uniqueSlots.size === referencedSlots.length && uniqueSlots.size === entryKeys.length && entryKeys.every((key) => uniqueSlots.has(key));
|
|
361
|
+
}));
|
|
362
|
+
const CacheMetadataSchema = object({
|
|
363
|
+
alintVersion: string(),
|
|
364
|
+
createdAt: string(),
|
|
365
|
+
magic: literal(CACHE_MAGIC),
|
|
366
|
+
schemaVersion: literal(2),
|
|
367
|
+
type: literal("metadata")
|
|
368
|
+
});
|
|
369
|
+
const PutEventSchema = object({
|
|
370
|
+
at: string(),
|
|
371
|
+
entry: CacheEntrySchema,
|
|
372
|
+
owner: CachedOwnerSchema,
|
|
373
|
+
ownerKey: string(),
|
|
374
|
+
slotKey: string(),
|
|
375
|
+
type: literal("put")
|
|
376
|
+
});
|
|
377
|
+
const ReplaceOwnerEventSchema = object({
|
|
378
|
+
at: string(),
|
|
379
|
+
entries: array(object({
|
|
380
|
+
entry: CacheEntrySchema,
|
|
381
|
+
slotKey: string()
|
|
382
|
+
})),
|
|
383
|
+
mode: union([literal("merge"), literal("replace")]),
|
|
384
|
+
owner: CachedOwnerSchema,
|
|
385
|
+
ownerKey: string(),
|
|
386
|
+
type: literal("replace-owner")
|
|
387
|
+
});
|
|
388
|
+
const RemoveOwnerEventSchema = object({
|
|
389
|
+
at: string(),
|
|
390
|
+
ownerKey: string(),
|
|
391
|
+
type: literal("remove-owner")
|
|
392
|
+
});
|
|
393
|
+
const CacheEventSchema = union([
|
|
394
|
+
PutEventSchema,
|
|
395
|
+
ReplaceOwnerEventSchema,
|
|
396
|
+
RemoveOwnerEventSchema
|
|
397
|
+
]);
|
|
398
|
+
async function createCacheStore(options) {
|
|
399
|
+
const location = resolveCacheLocation(options.cwd, options.location);
|
|
400
|
+
if (!options.enabled) return createNoopCacheStore(location);
|
|
401
|
+
const alintVersion = options.alintVersion ?? version;
|
|
402
|
+
const initial = await loadCacheLog(location, alintVersion);
|
|
403
|
+
if (options.readOnly) return createReadOnlyCacheStore(location, initial.body, options.cwd);
|
|
404
|
+
const dependencies = {
|
|
405
|
+
append: options.appendFile ?? appendFile,
|
|
406
|
+
lock: options.lock ?? { acquire: lockfile.lock },
|
|
407
|
+
write: options.writeFile ?? writeFile
|
|
408
|
+
};
|
|
409
|
+
const trimmed = await trimOnce(location, alintVersion, initial, dependencies);
|
|
410
|
+
const writer = createEventWriter(location, trimmed.metadata, dependencies);
|
|
411
|
+
const fileExists = options.fileExists ?? defaultFileExists;
|
|
412
|
+
return {
|
|
413
|
+
beginOwner: (owner, metadata) => beginOwner(trimmed.body, owner, options.cwd, writer, metadata),
|
|
414
|
+
flush: () => writer.flush(false),
|
|
415
|
+
location,
|
|
416
|
+
reconcile: async () => {
|
|
417
|
+
const removedOwners = await collectMissingFileOwners(trimmed.body, options.cwd, fileExists);
|
|
418
|
+
for (const ownerKey of removedOwners) writer.enqueue({
|
|
419
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
420
|
+
ownerKey,
|
|
421
|
+
type: "remove-owner"
|
|
422
|
+
});
|
|
423
|
+
await writer.flush(true);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
316
426
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
for (const [alias, plugin] of Object.entries(config.plugins)) for (const [localId, rule] of Object.entries(plugin.rules ?? {})) {
|
|
324
|
-
const id = `${alias}/${localId}`;
|
|
325
|
-
if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
|
|
326
|
-
rules.set(id, rule);
|
|
327
|
-
localIds.set(id, localId);
|
|
427
|
+
function createTargetIdentityResolver(targets) {
|
|
428
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
429
|
+
const duplicateCandidateCounts = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const target of targets) {
|
|
431
|
+
const base = createBaseTargetIdentity(target);
|
|
432
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
328
433
|
}
|
|
329
|
-
for (const
|
|
330
|
-
const
|
|
331
|
-
if (
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
if (severity === "off") continue;
|
|
335
|
-
enabledRules.push({
|
|
336
|
-
id,
|
|
337
|
-
localId: localIds.get(id) ?? id,
|
|
338
|
-
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
339
|
-
rule,
|
|
340
|
-
severity
|
|
341
|
-
});
|
|
434
|
+
for (const target of targets) {
|
|
435
|
+
const base = createBaseTargetIdentity(target);
|
|
436
|
+
if ((baseCounts.get(base) ?? 0) <= 1) continue;
|
|
437
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
438
|
+
duplicateCandidateCounts.set(candidate, (duplicateCandidateCounts.get(candidate) ?? 0) + 1);
|
|
342
439
|
}
|
|
440
|
+
return (target, targetIndex) => {
|
|
441
|
+
const base = createBaseTargetIdentity(target);
|
|
442
|
+
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
443
|
+
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
444
|
+
if ((duplicateCandidateCounts.get(candidate) ?? 0) <= 1) return candidate;
|
|
445
|
+
return `${candidate}:${targetIndex}`;
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function normalizeCachePath(cwd, filePath) {
|
|
449
|
+
const resolvedPath = isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath);
|
|
450
|
+
return relative$1(cwd, resolvedPath).split(sep).join("/") || ".";
|
|
451
|
+
}
|
|
452
|
+
function normalizeRunnerCacheConfig(cache, cwd) {
|
|
453
|
+
if (cache === false) return {
|
|
454
|
+
enabled: false,
|
|
455
|
+
location: resolveCacheLocation(cwd)
|
|
456
|
+
};
|
|
457
|
+
if (cache === true || cache === void 0) return {
|
|
458
|
+
enabled: true,
|
|
459
|
+
location: resolveCacheLocation(cwd)
|
|
460
|
+
};
|
|
343
461
|
return {
|
|
344
|
-
|
|
345
|
-
|
|
462
|
+
enabled: cache.enabled ?? true,
|
|
463
|
+
location: resolveCacheLocation(cwd, cache.location)
|
|
346
464
|
};
|
|
347
465
|
}
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
466
|
+
function resolveCacheLocation(cwd, location) {
|
|
467
|
+
if (!location) return join(cwd, DEFAULT_CACHE_FILE_NAME);
|
|
468
|
+
const resolved = isAbsolute$1(location) ? resolve$1(location) : resolve$1(cwd, location);
|
|
469
|
+
if (location.endsWith("/") || location.endsWith("\\")) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
470
|
+
try {
|
|
471
|
+
if (statSync(resolved).isDirectory()) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
472
|
+
} catch {}
|
|
473
|
+
return resolved;
|
|
353
474
|
}
|
|
354
|
-
function
|
|
355
|
-
|
|
475
|
+
async function appendBatch(location, payload, append) {
|
|
476
|
+
const handle = await open(location, "a+");
|
|
477
|
+
try {
|
|
478
|
+
const { size } = await handle.stat();
|
|
479
|
+
let separator = "";
|
|
480
|
+
if (size > 0) {
|
|
481
|
+
const finalByte = Buffer.alloc(1);
|
|
482
|
+
await handle.read(finalByte, 0, 1, size - 1);
|
|
483
|
+
separator = finalByte[0] === 10 ? "" : "\n";
|
|
484
|
+
}
|
|
485
|
+
await append(location, `${separator}${payload}`, "utf8");
|
|
486
|
+
} finally {
|
|
487
|
+
await handle.close();
|
|
488
|
+
}
|
|
356
489
|
}
|
|
357
|
-
function
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
490
|
+
function applyEvent(body, event) {
|
|
491
|
+
if (event.type === "remove-owner") {
|
|
492
|
+
removeOwner(body, event.ownerKey);
|
|
493
|
+
body.updatedAt = event.at;
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (event.type === "put") {
|
|
497
|
+
const current = body.owners[event.ownerKey];
|
|
498
|
+
const slots = new Set(current?.slots ?? []);
|
|
499
|
+
slots.add(event.slotKey);
|
|
500
|
+
body.entries[event.slotKey] = event.entry;
|
|
501
|
+
body.owners[event.ownerKey] = {
|
|
502
|
+
...event.owner,
|
|
503
|
+
slots: [...slots].sort()
|
|
363
504
|
};
|
|
505
|
+
body.updatedAt = event.at;
|
|
506
|
+
return;
|
|
364
507
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
508
|
+
const entries = event.mode === "merge" ? ownerEntries(body, body.owners[event.ownerKey]) : /* @__PURE__ */ new Map();
|
|
509
|
+
for (const item of event.entries) entries.set(item.slotKey, item.entry);
|
|
510
|
+
for (const slot of body.owners[event.ownerKey]?.slots ?? []) delete body.entries[slot];
|
|
511
|
+
for (const [slotKey, entry] of entries) body.entries[slotKey] = entry;
|
|
512
|
+
body.owners[event.ownerKey] = {
|
|
513
|
+
...event.owner,
|
|
514
|
+
slots: [...entries.keys()].sort()
|
|
368
515
|
};
|
|
516
|
+
body.updatedAt = event.at;
|
|
369
517
|
}
|
|
370
|
-
function
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
/** A linted file's extension belongs to no registered language. This is a builtin rule. Not a real rule either. */
|
|
392
|
-
const UNREGISTERED_LANGUAGE_RULE = "alint/unregistered-language";
|
|
393
|
-
/**
|
|
394
|
-
* One diagnostic per missing language, always an error: a rule scoped to a language nothing provides
|
|
395
|
-
* matches no file, so leaving it at a warning would let a run pass having silently checked nothing.
|
|
396
|
-
*/
|
|
397
|
-
function missingLanguageDiagnostics(missing) {
|
|
398
|
-
return [...missing.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([languageId, entry]) => {
|
|
399
|
-
const ruleIds = [...entry.ruleIds].sort();
|
|
400
|
-
const subject = ruleIds.length === 1 ? `Rule "${ruleIds[0]}" needs` : `Rules ${ruleIds.map((id) => `"${id}"`).join(", ")} need`;
|
|
401
|
-
return {
|
|
402
|
-
filePath: entry.filePath,
|
|
403
|
-
message: `${subject} the "${languageId}" language, which no plugin registered. Add a plugin that provides "${languageId}", or turn the rule off.`,
|
|
404
|
-
ruleId: MISSING_LANGUAGE_RULE,
|
|
405
|
-
severity: "error"
|
|
518
|
+
function beginOwner(body, owner, cwd, writer, ownerMetadata = {}) {
|
|
519
|
+
const normalizedOwner = {
|
|
520
|
+
kind: owner.kind,
|
|
521
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
522
|
+
};
|
|
523
|
+
const key = ownerKey(normalizedOwner, cwd);
|
|
524
|
+
const previousOwner = body.owners[key];
|
|
525
|
+
const nextEntries = /* @__PURE__ */ new Map();
|
|
526
|
+
const checkpointEntries = /* @__PURE__ */ new Map();
|
|
527
|
+
const commit = (metadata = {}) => {
|
|
528
|
+
const contentHash = metadata.contentHash ?? ownerMetadata.contentHash;
|
|
529
|
+
const committedEntries = metadata.mode === "merge" ? ownerEntries(body, body.owners[key]) : /* @__PURE__ */ new Map();
|
|
530
|
+
for (const [entryKey, cacheEntry] of nextEntries) committedEntries.set(entryKey, cacheEntry);
|
|
531
|
+
const replacedSlots = /* @__PURE__ */ new Set([...previousOwner?.slots ?? [], ...body.owners[key]?.slots ?? []]);
|
|
532
|
+
for (const replacedSlot of replacedSlots) delete body.entries[replacedSlot];
|
|
533
|
+
for (const [entryKey, cacheEntry] of committedEntries) body.entries[entryKey] = cacheEntry;
|
|
534
|
+
const cachedOwner = {
|
|
535
|
+
contentHash,
|
|
536
|
+
kind: normalizedOwner.kind,
|
|
537
|
+
path: normalizedOwner.path,
|
|
538
|
+
slots: [...committedEntries.keys()].sort()
|
|
406
539
|
};
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
540
|
+
body.owners[key] = cachedOwner;
|
|
541
|
+
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
542
|
+
writer.enqueue({
|
|
543
|
+
at: body.updatedAt,
|
|
544
|
+
entries: [...committedEntries].map(([slotKey, entry]) => ({
|
|
545
|
+
entry,
|
|
546
|
+
slotKey
|
|
547
|
+
})),
|
|
548
|
+
mode: metadata.mode ?? "replace",
|
|
549
|
+
owner: cachedOwner,
|
|
550
|
+
ownerKey: key,
|
|
551
|
+
type: "replace-owner"
|
|
416
552
|
});
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
553
|
+
checkpointEntries.clear();
|
|
554
|
+
};
|
|
555
|
+
return {
|
|
556
|
+
checkpoint: async () => {
|
|
557
|
+
const cachedOwner = {
|
|
558
|
+
contentHash: ownerMetadata.contentHash,
|
|
559
|
+
kind: normalizedOwner.kind,
|
|
560
|
+
path: normalizedOwner.path,
|
|
561
|
+
slots: []
|
|
562
|
+
};
|
|
563
|
+
for (const [entryKey, cacheEntry] of checkpointEntries) {
|
|
564
|
+
const event = {
|
|
565
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
566
|
+
entry: cacheEntry,
|
|
567
|
+
owner: cachedOwner,
|
|
568
|
+
ownerKey: key,
|
|
569
|
+
slotKey: entryKey,
|
|
570
|
+
type: "put"
|
|
571
|
+
};
|
|
572
|
+
applyEvent(body, event);
|
|
573
|
+
writer.enqueue(event);
|
|
574
|
+
}
|
|
575
|
+
checkpointEntries.clear();
|
|
576
|
+
await writer.flush(false);
|
|
577
|
+
},
|
|
578
|
+
commit,
|
|
579
|
+
discard: (cacheSlot) => {
|
|
580
|
+
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
581
|
+
nextEntries.delete(entryKey);
|
|
582
|
+
checkpointEntries.delete(entryKey);
|
|
583
|
+
},
|
|
584
|
+
lookup: (cacheSlot, fingerprint) => {
|
|
585
|
+
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
586
|
+
const cached = body.entries[entryKey];
|
|
587
|
+
if (!cached || !fingerprintsEqual(cached.fingerprint, fingerprint)) return void 0;
|
|
588
|
+
nextEntries.set(entryKey, cached);
|
|
589
|
+
return cached;
|
|
590
|
+
},
|
|
591
|
+
put: (cacheSlot, cacheEntry) => {
|
|
592
|
+
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
593
|
+
nextEntries.set(entryKey, cacheEntry);
|
|
594
|
+
checkpointEntries.set(entryKey, cacheEntry);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
435
597
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
598
|
+
async function collectMissingFileOwners(body, cwd, fileExists) {
|
|
599
|
+
const removed = [];
|
|
600
|
+
for (const [key, owner] of Object.entries(body.owners)) {
|
|
601
|
+
if (owner.kind !== "file" || await fileExists(resolve$1(cwd, owner.path))) continue;
|
|
602
|
+
removeOwner(body, key);
|
|
603
|
+
removed.push(key);
|
|
604
|
+
}
|
|
605
|
+
return removed;
|
|
444
606
|
}
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
607
|
+
function createBaseTargetIdentity(target) {
|
|
608
|
+
if (target.identity && (target.kind !== "file" || target.identity !== "file")) return target.filePath ? `${target.kind}:${target.filePath}:${target.identity}` : `${target.kind}:${target.identity}`;
|
|
609
|
+
if (target.kind === "file") return target.filePath ? `file:${target.filePath}` : "file";
|
|
610
|
+
if (target.name) return `${target.kind}:${target.name}`;
|
|
611
|
+
if (target.range) return `${target.kind}:${target.range.start}:${target.range.end}`;
|
|
612
|
+
return target.kind;
|
|
448
613
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
614
|
+
function createEmptyCacheBody(createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
615
|
+
return {
|
|
616
|
+
createdAt,
|
|
617
|
+
entries: {},
|
|
618
|
+
owners: {},
|
|
619
|
+
updatedAt: createdAt
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function createEventWriter(location, metadata, dependencies) {
|
|
623
|
+
const backlog = [];
|
|
624
|
+
let validationError;
|
|
625
|
+
let previousWrite = Promise.resolve();
|
|
626
|
+
const persistBacklog = async () => {
|
|
627
|
+
if (validationError) throw validationError;
|
|
628
|
+
if (backlog.length === 0) return;
|
|
629
|
+
await mkdir(dirname(location), { recursive: true });
|
|
630
|
+
await withCacheLock(location, dependencies.lock, getCacheLockOptions({
|
|
631
|
+
factor: 2,
|
|
632
|
+
maxTimeout: 16e3,
|
|
633
|
+
minTimeout: 2e3,
|
|
634
|
+
retries: 4
|
|
635
|
+
}), async () => {
|
|
636
|
+
await ensureCompatibleMetadata(location, metadata, dependencies.write);
|
|
637
|
+
const batchSize = backlog.length;
|
|
638
|
+
await appendBatch(location, backlog.slice(0, batchSize).map((event) => `${JSON.stringify(event)}\n`).join(""), dependencies.append);
|
|
639
|
+
backlog.splice(0, batchSize);
|
|
640
|
+
});
|
|
641
|
+
validationError = void 0;
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
enqueue: (event) => {
|
|
645
|
+
try {
|
|
646
|
+
backlog.push(parse(CacheEventSchema, event));
|
|
647
|
+
} catch (error) {
|
|
648
|
+
validationError ??= error;
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
flush: (finalAttempt) => {
|
|
652
|
+
const write = previousWrite.then(persistBacklog);
|
|
653
|
+
previousWrite = write.catch(() => {});
|
|
654
|
+
return finalAttempt ? write : write.catch(() => {});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
472
657
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
658
|
+
function createNoopCacheStore(location) {
|
|
659
|
+
const transaction = {
|
|
660
|
+
checkpoint: async () => {},
|
|
661
|
+
commit: () => {},
|
|
662
|
+
discard: () => {},
|
|
663
|
+
lookup: () => void 0,
|
|
664
|
+
put: () => {}
|
|
480
665
|
};
|
|
481
666
|
return {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
667
|
+
beginOwner: () => transaction,
|
|
668
|
+
flush: async () => {},
|
|
669
|
+
location,
|
|
670
|
+
reconcile: async () => {}
|
|
485
671
|
};
|
|
486
672
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return async (filePath, options = {}) => {
|
|
503
|
-
const path = resolve(cwd, filePath);
|
|
504
|
-
const resolvedConfig = resolveConfigForFile(path, config, { cwd });
|
|
505
|
-
if (resolvedConfig.ignored) return [];
|
|
506
|
-
const effectiveConfig = resolvedConfig.config;
|
|
507
|
-
const language = resolveLanguageForPath(path, createLanguageRegistry(effectiveConfig), { language: options.language ?? effectiveConfig.language });
|
|
508
|
-
const src = getSrc();
|
|
509
|
-
const file = await src.readFile(path);
|
|
510
|
-
return language.extract(file, {
|
|
511
|
-
cwd,
|
|
512
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
513
|
-
src
|
|
514
|
-
});
|
|
673
|
+
function createReadOnlyCacheStore(location, body, cwd) {
|
|
674
|
+
return {
|
|
675
|
+
beginOwner: (owner) => ({
|
|
676
|
+
checkpoint: async () => {},
|
|
677
|
+
commit: () => {},
|
|
678
|
+
discard: () => {},
|
|
679
|
+
lookup: (slot, fingerprint) => {
|
|
680
|
+
const cached = body.entries[slotKey(owner, slot, cwd)];
|
|
681
|
+
return cached && fingerprintsEqual(cached.fingerprint, fingerprint) ? cached : void 0;
|
|
682
|
+
},
|
|
683
|
+
put: () => {}
|
|
684
|
+
}),
|
|
685
|
+
flush: async () => {},
|
|
686
|
+
location,
|
|
687
|
+
reconcile: async () => {}
|
|
515
688
|
};
|
|
516
689
|
}
|
|
517
|
-
function
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
for (const filePath of options.files ?? []) {
|
|
525
|
-
const path = resolve(cwd$2, filePath);
|
|
526
|
-
const resolvedConfig = resolveConfigForFile(path, config, { cwd: cwd$2 });
|
|
527
|
-
if (resolvedConfig.ignored) continue;
|
|
528
|
-
const effectiveConfig = resolvedConfig.config;
|
|
529
|
-
const languageRegistry = createLanguageRegistry(effectiveConfig);
|
|
530
|
-
const language = resolveLanguageForPath(path, languageRegistry, { language: effectiveConfig.language });
|
|
531
|
-
const rules = prepareRules(effectiveConfig);
|
|
532
|
-
recordMissingLanguages(missingLanguages, rules, languageRegistry, path);
|
|
533
|
-
if (effectiveConfig.language === void 0 && language.name === "plaintext" && rules.some(({ enabledRule }) => !isTargetLanguageAccepted(resolveRuleLanguages(enabledRule.rule.languages), "file", "plaintext"))) recordUnregistered(unregisteredLanguages, path, unregisteredLanguageSeverity(effectiveConfig.linterOptions));
|
|
534
|
-
files.push({
|
|
535
|
-
agent: effectiveConfig.agent,
|
|
536
|
-
configHash: stableHash({
|
|
537
|
-
language: effectiveConfig.language,
|
|
538
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
539
|
-
processor: effectiveConfig.processor,
|
|
540
|
-
resolvedLanguage: language.name,
|
|
541
|
-
settings: effectiveConfig.settings
|
|
542
|
-
}),
|
|
543
|
-
fileIndex: files.length,
|
|
544
|
-
language,
|
|
545
|
-
languageOptions: effectiveConfig.languageOptions,
|
|
546
|
-
path,
|
|
547
|
-
rules,
|
|
548
|
-
settings: effectiveConfig.settings
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
for (const directoryPath of options.directories ?? []) {
|
|
552
|
-
const path = resolve(cwd$2, directoryPath);
|
|
553
|
-
const resolvedConfig = resolveConfigForDirectory(path, config, { cwd: cwd$2 });
|
|
554
|
-
if (resolvedConfig.ignored) continue;
|
|
555
|
-
const effectiveConfig = resolvedConfig.config;
|
|
556
|
-
directories.push({
|
|
557
|
-
agent: effectiveConfig.agent,
|
|
558
|
-
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
559
|
-
directoryIndex: directories.length,
|
|
560
|
-
rules: prepareRules(effectiveConfig),
|
|
561
|
-
settings: effectiveConfig.settings,
|
|
562
|
-
target: {
|
|
563
|
-
kind: "directory",
|
|
564
|
-
path
|
|
565
|
-
}
|
|
566
|
-
});
|
|
690
|
+
async function defaultFileExists(path) {
|
|
691
|
+
try {
|
|
692
|
+
await access(path);
|
|
693
|
+
return true;
|
|
694
|
+
} catch (error) {
|
|
695
|
+
if (isMissingFileError(error)) return false;
|
|
696
|
+
throw error;
|
|
567
697
|
}
|
|
698
|
+
}
|
|
699
|
+
function emptyLoadedLog(alintVersion) {
|
|
700
|
+
const metadata = {
|
|
701
|
+
alintVersion,
|
|
702
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
703
|
+
magic: CACHE_MAGIC,
|
|
704
|
+
schemaVersion: 2,
|
|
705
|
+
type: "metadata"
|
|
706
|
+
};
|
|
568
707
|
return {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
missingLanguages,
|
|
572
|
-
project: options.projectTargets === false ? void 0 : prepareProject(cwd$2, config),
|
|
573
|
-
unregisteredLanguages
|
|
708
|
+
body: createEmptyCacheBody(metadata.createdAt),
|
|
709
|
+
metadata
|
|
574
710
|
};
|
|
575
711
|
}
|
|
576
|
-
function
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
712
|
+
async function ensureCompatibleMetadata(location, metadata, write) {
|
|
713
|
+
try {
|
|
714
|
+
if (parseMetadataLine(await readMetadataLine(location), metadata.alintVersion)) return;
|
|
715
|
+
} catch (error) {
|
|
716
|
+
if (!isMissingFileError(error)) throw error;
|
|
717
|
+
}
|
|
718
|
+
await write(location, `${JSON.stringify(metadata)}\n`);
|
|
580
719
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
720
|
+
function fingerprintsEqual(left, right) {
|
|
721
|
+
return left.configHash === right.configHash && left.modelHash === right.modelHash && left.ruleHash === right.ruleHash && left.targetHash === right.targetHash;
|
|
722
|
+
}
|
|
723
|
+
function getCacheLockOptions(retries) {
|
|
585
724
|
return {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
settings: effectiveConfig.settings
|
|
725
|
+
realpath: false,
|
|
726
|
+
retries,
|
|
727
|
+
stale: LOCK_STALE_MS,
|
|
728
|
+
update: LOCK_UPDATE_MS
|
|
591
729
|
};
|
|
592
730
|
}
|
|
593
|
-
function
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
731
|
+
function hasJsonArrayProperties(input, ancestors) {
|
|
732
|
+
let arrayLength;
|
|
733
|
+
let indexCount = 0;
|
|
734
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
735
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
736
|
+
if (!descriptor || !("value" in descriptor)) return false;
|
|
737
|
+
if (key === "length") {
|
|
738
|
+
if (descriptor.enumerable || typeof descriptor.value !== "number") return false;
|
|
739
|
+
arrayLength = descriptor.value;
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
const index = typeof key === "string" ? Number(key) : NaN;
|
|
743
|
+
if (typeof key !== "string" || !Number.isInteger(index) || index < 0 || index >= 2 ** 32 - 1 || String(index) !== key || !descriptor.enumerable) return false;
|
|
744
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
745
|
+
indexCount += 1;
|
|
746
|
+
}
|
|
747
|
+
return arrayLength !== void 0 && indexCount === arrayLength;
|
|
598
748
|
}
|
|
599
|
-
function
|
|
600
|
-
for (const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
749
|
+
function hasJsonObjectProperties(input, ancestors) {
|
|
750
|
+
for (const key of Reflect.ownKeys(input)) {
|
|
751
|
+
if (typeof key !== "string") return false;
|
|
752
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
753
|
+
if (!descriptor || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
754
|
+
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
604
755
|
}
|
|
756
|
+
return true;
|
|
605
757
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
var version = "0.5.0";
|
|
609
|
-
//#endregion
|
|
610
|
-
//#region src/core/cache/types.ts
|
|
611
|
-
const CACHE_MAGIC = "ALINT_CACHE";
|
|
612
|
-
//#endregion
|
|
613
|
-
//#region src/core/cache/store.ts
|
|
614
|
-
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
615
|
-
const FiniteNumberSchema = pipe(number(), finite());
|
|
616
|
-
const PositionSchema = object({
|
|
617
|
-
column: FiniteNumberSchema,
|
|
618
|
-
line: FiniteNumberSchema
|
|
619
|
-
});
|
|
620
|
-
const SourceLocationSchema = object({
|
|
621
|
-
end: PositionSchema,
|
|
622
|
-
start: PositionSchema
|
|
623
|
-
});
|
|
624
|
-
const DiagnosticLocationSchema = object({
|
|
625
|
-
end: optional(PositionSchema),
|
|
626
|
-
start: PositionSchema
|
|
627
|
-
});
|
|
628
|
-
const SourceRangeSchema = object({
|
|
629
|
-
end: FiniteNumberSchema,
|
|
630
|
-
start: FiniteNumberSchema
|
|
631
|
-
});
|
|
632
|
-
const DiagnosticSchema = object({
|
|
633
|
-
cached: optional(boolean()),
|
|
634
|
-
evidence: optional(custom(isJsonValue)),
|
|
635
|
-
filePath: string(),
|
|
636
|
-
loc: optional(DiagnosticLocationSchema),
|
|
637
|
-
message: string(),
|
|
638
|
-
model: optional(object({
|
|
639
|
-
providerId: string(),
|
|
640
|
-
requested: optional(string()),
|
|
641
|
-
resolvedId: string()
|
|
642
|
-
})),
|
|
643
|
-
ruleId: string(),
|
|
644
|
-
severity: union([literal("error"), literal("warn")])
|
|
645
|
-
});
|
|
646
|
-
const UsageSchema = object({
|
|
647
|
-
filePath: optional(string()),
|
|
648
|
-
inputTokens: optional(FiniteNumberSchema),
|
|
649
|
-
metadata: optional(custom(isJsonValue)),
|
|
650
|
-
modelId: string(),
|
|
651
|
-
outputTokens: optional(FiniteNumberSchema),
|
|
652
|
-
providerId: string(),
|
|
653
|
-
ruleId: string(),
|
|
654
|
-
totalTokens: optional(FiniteNumberSchema)
|
|
655
|
-
});
|
|
656
|
-
const CacheEntrySchema = object({
|
|
657
|
-
diagnostics: array(DiagnosticSchema),
|
|
658
|
-
fingerprint: object({
|
|
659
|
-
configHash: string(),
|
|
660
|
-
modelHash: string(),
|
|
661
|
-
ruleHash: string(),
|
|
662
|
-
targetHash: string()
|
|
663
|
-
}),
|
|
664
|
-
target: object({
|
|
665
|
-
hash: string(),
|
|
666
|
-
identity: string(),
|
|
667
|
-
kind: string(),
|
|
668
|
-
loc: optional(SourceLocationSchema),
|
|
669
|
-
name: optional(string()),
|
|
670
|
-
range: optional(SourceRangeSchema)
|
|
671
|
-
}),
|
|
672
|
-
usage: array(UsageSchema)
|
|
673
|
-
});
|
|
674
|
-
function objectRecord(value) {
|
|
675
|
-
return pipe(unknown(), check((input) => typeof input === "object" && input !== null && !Array.isArray(input)), record(string(), value));
|
|
758
|
+
function isJsonValue(input) {
|
|
759
|
+
return isJsonValueAt(input, /* @__PURE__ */ new WeakSet());
|
|
676
760
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}), check((body) => {
|
|
688
|
-
const entryKeys = Object.keys(body.entries);
|
|
689
|
-
const referencedSlots = Object.values(body.owners).flatMap((owner) => owner.slots);
|
|
690
|
-
const uniqueSlots = new Set(referencedSlots);
|
|
691
|
-
return uniqueSlots.size === referencedSlots.length && uniqueSlots.size === entryKeys.length && entryKeys.every((key) => uniqueSlots.has(key));
|
|
692
|
-
}));
|
|
693
|
-
async function createCacheStore(options) {
|
|
694
|
-
const location = resolveCacheLocation(options.cwd, options.location);
|
|
695
|
-
if (!options.enabled) return createNoopCacheStore(location);
|
|
696
|
-
const alintVersion = options.alintVersion ?? version;
|
|
697
|
-
const header = `${CACHE_MAGIC} 2 ${alintVersion}`;
|
|
698
|
-
const body = await loadCacheBody(location, header, options.readOnly === true);
|
|
699
|
-
if (options.readOnly) return createReadOnlyCacheStore(location, body, options.cwd);
|
|
700
|
-
const fileExists = options.fileExists ?? defaultFileExists;
|
|
701
|
-
const writeFile$1 = options.writeFile ?? writeFile;
|
|
702
|
-
return {
|
|
703
|
-
beginOwner: (owner) => beginOwner(body, owner, options.cwd),
|
|
704
|
-
location,
|
|
705
|
-
reconcile: async () => {
|
|
706
|
-
await collectMissingFileOwners(body, options.cwd, fileExists);
|
|
707
|
-
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
708
|
-
await persistCacheBody(location, header, body, writeFile$1);
|
|
761
|
+
function isJsonValueAt(input, ancestors) {
|
|
762
|
+
if (input === null || typeof input === "string" || typeof input === "boolean") return true;
|
|
763
|
+
if (typeof input === "number") return Number.isFinite(input);
|
|
764
|
+
if (typeof input !== "object") return false;
|
|
765
|
+
if (ancestors.has(input)) return false;
|
|
766
|
+
ancestors.add(input);
|
|
767
|
+
try {
|
|
768
|
+
if (Array.isArray(input)) {
|
|
769
|
+
if (Object.getPrototypeOf(input) !== Array.prototype) return false;
|
|
770
|
+
return hasJsonArrayProperties(input, ancestors);
|
|
709
771
|
}
|
|
710
|
-
|
|
772
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) return false;
|
|
773
|
+
return hasJsonObjectProperties(input, ancestors);
|
|
774
|
+
} catch {
|
|
775
|
+
return false;
|
|
776
|
+
} finally {
|
|
777
|
+
ancestors.delete(input);
|
|
778
|
+
}
|
|
711
779
|
}
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
780
|
+
function isMissingFileError(error) {
|
|
781
|
+
return isNodeErrorCode(error, "ENOENT") || isNodeErrorCode(error, "ENOTDIR");
|
|
782
|
+
}
|
|
783
|
+
async function loadCacheLog(location, alintVersion) {
|
|
784
|
+
try {
|
|
785
|
+
const metadata = parseMetadataLine(await readMetadataLine(location), alintVersion);
|
|
786
|
+
if (!metadata) return emptyLoadedLog(alintVersion);
|
|
787
|
+
return replayLog(await readFile(location, "utf8"), metadata);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
if (isMissingFileError(error)) return emptyLoadedLog(alintVersion);
|
|
790
|
+
throw error;
|
|
718
791
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
792
|
+
}
|
|
793
|
+
function ownerEntries(body, owner) {
|
|
794
|
+
const entries = /* @__PURE__ */ new Map();
|
|
795
|
+
for (const slot of owner?.slots ?? []) {
|
|
796
|
+
const cacheEntry = body.entries[slot];
|
|
797
|
+
if (cacheEntry) entries.set(slot, cacheEntry);
|
|
724
798
|
}
|
|
725
|
-
return
|
|
726
|
-
const base = createBaseTargetIdentity(target);
|
|
727
|
-
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
728
|
-
const candidate = target.range ? `${base}:${target.range.start}:${target.range.end}` : base;
|
|
729
|
-
if ((duplicateCandidateCounts.get(candidate) ?? 0) <= 1) return candidate;
|
|
730
|
-
return `${candidate}:${targetIndex}`;
|
|
731
|
-
};
|
|
799
|
+
return entries;
|
|
732
800
|
}
|
|
733
|
-
function
|
|
734
|
-
|
|
735
|
-
|
|
801
|
+
function ownerKey(owner, cwd) {
|
|
802
|
+
return stableHash({
|
|
803
|
+
kind: owner.kind,
|
|
804
|
+
path: normalizeCachePath(cwd, owner.path)
|
|
805
|
+
});
|
|
736
806
|
}
|
|
737
|
-
function
|
|
738
|
-
if (
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
enabled: cache.enabled ?? true,
|
|
748
|
-
location: resolveCacheLocation(cwd, cache.location)
|
|
749
|
-
};
|
|
807
|
+
function parseMetadataLine(line, expectedAlintVersion) {
|
|
808
|
+
if (!line) return void 0;
|
|
809
|
+
try {
|
|
810
|
+
const value = JSON.parse(line);
|
|
811
|
+
const metadata = parse(CacheMetadataSchema, value);
|
|
812
|
+
if (expectedAlintVersion !== void 0 && metadata.alintVersion !== expectedAlintVersion) return void 0;
|
|
813
|
+
return metadata;
|
|
814
|
+
} catch {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
750
817
|
}
|
|
751
|
-
function
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
818
|
+
async function persistCompactedLog(location, loaded, write) {
|
|
819
|
+
const metadata = parse(CacheMetadataSchema, loaded.metadata);
|
|
820
|
+
const events = Object.entries(loaded.body.owners).map(([ownerKey, owner]) => ({
|
|
821
|
+
at: loaded.body.updatedAt,
|
|
822
|
+
entries: [...ownerEntries(loaded.body, owner)].map(([slotKey, entry]) => ({
|
|
823
|
+
entry,
|
|
824
|
+
slotKey
|
|
825
|
+
})),
|
|
826
|
+
mode: "replace",
|
|
827
|
+
owner,
|
|
828
|
+
ownerKey,
|
|
829
|
+
type: "replace-owner"
|
|
830
|
+
})).map((event) => parse(CacheEventSchema, event));
|
|
831
|
+
const contents = [`${JSON.stringify(metadata)}\n`, ...events.map((event) => `${JSON.stringify(event)}\n`)].join("");
|
|
832
|
+
const tempPath = join(dirname(location), `.${basename$1(location)}.${process.pid}.${randomUUID()}.tmp`);
|
|
755
833
|
try {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
834
|
+
await write(tempPath, contents);
|
|
835
|
+
await rename(tempPath, location);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
await rm(tempPath, { force: true }).catch(() => {});
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function readMetadataLine(location) {
|
|
842
|
+
const handle = await open(location, "r");
|
|
843
|
+
try {
|
|
844
|
+
const buffer = Buffer.alloc(METADATA_READ_LIMIT);
|
|
845
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
846
|
+
const newlineIndex = buffer.subarray(0, bytesRead).indexOf(10);
|
|
847
|
+
if (newlineIndex < 0) return void 0;
|
|
848
|
+
const lineEnd = newlineIndex > 0 && buffer[newlineIndex - 1] === 13 ? newlineIndex - 1 : newlineIndex;
|
|
849
|
+
return buffer.toString("utf8", 0, lineEnd);
|
|
850
|
+
} finally {
|
|
851
|
+
await handle.close();
|
|
852
|
+
}
|
|
759
853
|
}
|
|
760
|
-
function
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const
|
|
766
|
-
const
|
|
767
|
-
|
|
854
|
+
function removeOwner(body, key) {
|
|
855
|
+
for (const slot of body.owners[key]?.slots ?? []) delete body.entries[slot];
|
|
856
|
+
delete body.owners[key];
|
|
857
|
+
}
|
|
858
|
+
function replayLog(text, metadata) {
|
|
859
|
+
const body = createEmptyCacheBody(metadata.createdAt);
|
|
860
|
+
const lines = text.split(/\r?\n/);
|
|
861
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
862
|
+
const line = lines[index];
|
|
863
|
+
if (!line) continue;
|
|
864
|
+
try {
|
|
865
|
+
const value = JSON.parse(line);
|
|
866
|
+
applyEvent(body, parse(CacheEventSchema, value));
|
|
867
|
+
} catch {}
|
|
868
|
+
}
|
|
768
869
|
return {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
for (const [entryKey, cacheEntry] of nextEntries) committedEntries.set(entryKey, cacheEntry);
|
|
772
|
-
const replacedSlots = /* @__PURE__ */ new Set([...previousOwner?.slots ?? [], ...body.owners[key]?.slots ?? []]);
|
|
773
|
-
for (const replacedSlot of replacedSlots) delete body.entries[replacedSlot];
|
|
774
|
-
for (const [entryKey, cacheEntry] of committedEntries) body.entries[entryKey] = cacheEntry;
|
|
775
|
-
body.owners[key] = {
|
|
776
|
-
contentHash: metadata.contentHash,
|
|
777
|
-
kind: normalizedOwner.kind,
|
|
778
|
-
path: normalizedOwner.path,
|
|
779
|
-
slots: [...committedEntries.keys()].sort()
|
|
780
|
-
};
|
|
781
|
-
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
782
|
-
},
|
|
783
|
-
discard: (cacheSlot) => nextEntries.delete(slotKey(normalizedOwner, cacheSlot, cwd)),
|
|
784
|
-
lookup: (cacheSlot, fingerprint) => {
|
|
785
|
-
const entryKey = slotKey(normalizedOwner, cacheSlot, cwd);
|
|
786
|
-
const cached = body.entries[entryKey];
|
|
787
|
-
if (!cached || !fingerprintsEqual(cached.fingerprint, fingerprint)) return void 0;
|
|
788
|
-
nextEntries.set(entryKey, cached);
|
|
789
|
-
return cached;
|
|
790
|
-
},
|
|
791
|
-
put: (cacheSlot, cacheEntry) => nextEntries.set(slotKey(normalizedOwner, cacheSlot, cwd), cacheEntry)
|
|
870
|
+
body: parse(CacheFileBodySchema, body),
|
|
871
|
+
metadata
|
|
792
872
|
};
|
|
793
873
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
874
|
+
function slotKey(owner, slot, cwd) {
|
|
875
|
+
return stableHash({
|
|
876
|
+
owner: ownerKey(owner, cwd),
|
|
877
|
+
...slot
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
async function trimOnce(location, alintVersion, fallback, dependencies) {
|
|
881
|
+
await mkdir(dirname(location), { recursive: true });
|
|
882
|
+
try {
|
|
883
|
+
return await withCacheLock(location, dependencies.lock, getCacheLockOptions(0), async () => {
|
|
884
|
+
const latest = await loadCacheLog(location, alintVersion);
|
|
885
|
+
await persistCompactedLog(location, latest, dependencies.write);
|
|
886
|
+
return latest;
|
|
887
|
+
});
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (isNodeErrorCode(error, "ELOCKED")) return fallback;
|
|
890
|
+
throw error;
|
|
799
891
|
}
|
|
800
892
|
}
|
|
801
|
-
function
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
893
|
+
async function withCacheLock(location, lock, options, operation) {
|
|
894
|
+
let compromised;
|
|
895
|
+
const release = await lock.acquire(location, {
|
|
896
|
+
...options,
|
|
897
|
+
onCompromised: (error) => {
|
|
898
|
+
compromised = error;
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
let operationError;
|
|
902
|
+
try {
|
|
903
|
+
const result = await operation();
|
|
904
|
+
if (compromised) throw compromised;
|
|
905
|
+
return result;
|
|
906
|
+
} catch (error) {
|
|
907
|
+
operationError = error;
|
|
908
|
+
throw error;
|
|
909
|
+
} finally {
|
|
910
|
+
try {
|
|
911
|
+
await release();
|
|
912
|
+
} catch (error) {
|
|
913
|
+
if (operationError === void 0) throw error;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
807
916
|
}
|
|
808
|
-
|
|
809
|
-
|
|
917
|
+
//#endregion
|
|
918
|
+
//#region src/core/languages/js/index.ts
|
|
919
|
+
const javascriptLanguage = {
|
|
920
|
+
extensions: [
|
|
921
|
+
".cjs",
|
|
922
|
+
".js",
|
|
923
|
+
".jsx",
|
|
924
|
+
".mjs"
|
|
925
|
+
],
|
|
926
|
+
extract: (file) => extractJsSourceTargets(withLanguage(file, "javascript")),
|
|
927
|
+
name: "javascript"
|
|
928
|
+
};
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region src/core/languages/registry.ts
|
|
931
|
+
function createLanguageRegistry$1() {
|
|
810
932
|
return {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
owners: {},
|
|
814
|
-
updatedAt: now
|
|
933
|
+
byExtension: /* @__PURE__ */ new Map(),
|
|
934
|
+
languages: /* @__PURE__ */ new Map()
|
|
815
935
|
};
|
|
816
936
|
}
|
|
817
|
-
function
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
937
|
+
function registerLanguage(registry, language) {
|
|
938
|
+
const existing = registry.languages.get(language.name);
|
|
939
|
+
if (existing && existing !== language) throw new Error(`Duplicate language "${language.name}".`);
|
|
940
|
+
for (const extension of language.extensions ?? []) {
|
|
941
|
+
const existingOwner = registry.byExtension.get(extension);
|
|
942
|
+
if (existingOwner && existingOwner !== language.name) throw new Error(`Duplicate language extension "${extension}".`);
|
|
943
|
+
}
|
|
944
|
+
registry.languages.set(language.name, language);
|
|
945
|
+
for (const extension of language.extensions ?? []) registry.byExtension.set(extension, language.name);
|
|
946
|
+
}
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/core/languages/text.ts
|
|
949
|
+
const textLanguage = {
|
|
950
|
+
extensions: [],
|
|
951
|
+
extract: (file) => [{
|
|
952
|
+
file,
|
|
953
|
+
identity: "file",
|
|
954
|
+
kind: "file",
|
|
955
|
+
language: "plaintext",
|
|
956
|
+
origin: { physicalPath: file.path },
|
|
957
|
+
text: file.text
|
|
958
|
+
}],
|
|
959
|
+
name: "plaintext"
|
|
960
|
+
};
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/core/languages/ts/index.ts
|
|
963
|
+
const typescriptLanguage = {
|
|
964
|
+
extensions: [
|
|
965
|
+
".cts",
|
|
966
|
+
".mts",
|
|
967
|
+
".ts",
|
|
968
|
+
".tsx"
|
|
969
|
+
],
|
|
970
|
+
extract: (file) => extractJsSourceTargets(withLanguage(file, "typescript")),
|
|
971
|
+
name: "typescript"
|
|
972
|
+
};
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region src/core/languages/resolve.ts
|
|
975
|
+
function resolveLanguage(file, registry, options) {
|
|
976
|
+
return resolveLanguageForPath(file.path, registry, options);
|
|
977
|
+
}
|
|
978
|
+
function resolveLanguageForPath(filePath, registry, options) {
|
|
979
|
+
const languageName = options.language ?? options.processedLanguage ?? registry.byExtension.get(extname$1(filePath)) ?? "plaintext";
|
|
980
|
+
const language = registry.languages.get(languageName);
|
|
981
|
+
if (!language) {
|
|
982
|
+
const registered = [...registry.languages.keys()].sort().join(", ");
|
|
983
|
+
throw new Error(`Unknown language "${languageName}". Languages come from plugins, and these are registered: ${registered}.`);
|
|
984
|
+
}
|
|
985
|
+
return language;
|
|
986
|
+
}
|
|
987
|
+
//#endregion
|
|
988
|
+
//#region src/core/languages/index.ts
|
|
989
|
+
function createBuiltInLanguageRegistry() {
|
|
990
|
+
const registry = createLanguageRegistry$1();
|
|
991
|
+
registerLanguage(registry, textLanguage);
|
|
992
|
+
registerLanguage(registry, javascriptLanguage);
|
|
993
|
+
registerLanguage(registry, typescriptLanguage);
|
|
994
|
+
return registry;
|
|
995
|
+
}
|
|
996
|
+
//#endregion
|
|
997
|
+
//#region src/dsl/registry.ts
|
|
998
|
+
function buildRuleRegistry(config) {
|
|
999
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1000
|
+
const localIds = /* @__PURE__ */ new Map();
|
|
1001
|
+
const enabledRules = [];
|
|
1002
|
+
for (const [alias, plugin] of Object.entries(config.plugins)) for (const [localId, rule] of Object.entries(plugin.rules ?? {})) {
|
|
1003
|
+
const id = `${alias}/${localId}`;
|
|
1004
|
+
if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
|
|
1005
|
+
rules.set(id, rule);
|
|
1006
|
+
localIds.set(id, localId);
|
|
1007
|
+
}
|
|
1008
|
+
for (const [id, entry] of Object.entries(config.rules)) {
|
|
1009
|
+
const rule = rules.get(id);
|
|
1010
|
+
if (!rule) throw new Error(`Unknown rule "${id}".`);
|
|
1011
|
+
const normalizedEntry = normalizeRuleConfigEntry(entry);
|
|
1012
|
+
const severity = normalizedEntry.severity;
|
|
1013
|
+
if (severity === "off") continue;
|
|
1014
|
+
enabledRules.push({
|
|
1015
|
+
id,
|
|
1016
|
+
localId: localIds.get(id) ?? id,
|
|
1017
|
+
options: parseRuleOptions(id, rule.options, normalizedEntry.options),
|
|
1018
|
+
rule,
|
|
1019
|
+
severity
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
824
1022
|
return {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
reconcile: async () => {}
|
|
1023
|
+
enabledRules,
|
|
1024
|
+
rules
|
|
828
1025
|
};
|
|
829
1026
|
}
|
|
830
|
-
function
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1027
|
+
function formatRuleOptionIssues(index, issues) {
|
|
1028
|
+
return issues.map((issue) => {
|
|
1029
|
+
const path = getDotPath(issue);
|
|
1030
|
+
return path === null ? `"${index}": ${issue.message}` : `"${index}.${path}": ${issue.message}`;
|
|
1031
|
+
}).join("; ");
|
|
1032
|
+
}
|
|
1033
|
+
function isRuleConfigTuple(entry) {
|
|
1034
|
+
return Array.isArray(entry);
|
|
1035
|
+
}
|
|
1036
|
+
function normalizeRuleConfigEntry(entry) {
|
|
1037
|
+
if (isRuleConfigTuple(entry)) {
|
|
1038
|
+
const [severity = "warn", ...options] = entry;
|
|
1039
|
+
return {
|
|
1040
|
+
options,
|
|
1041
|
+
severity
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
840
1044
|
return {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
reconcile: async () => {}
|
|
1045
|
+
options: [],
|
|
1046
|
+
severity: entry ?? "warn"
|
|
844
1047
|
};
|
|
845
1048
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (
|
|
852
|
-
throw error;
|
|
1049
|
+
function parseRuleOption(id, index, schema, value) {
|
|
1050
|
+
const result = safeParse(schema, value);
|
|
1051
|
+
if (result.success) return result.output;
|
|
1052
|
+
if (value === void 0) {
|
|
1053
|
+
const emptyObjectResult = safeParse(schema, {});
|
|
1054
|
+
if (emptyObjectResult.success) return emptyObjectResult.output;
|
|
853
1055
|
}
|
|
1056
|
+
throw new TypeError(`Invalid options for rule "${id}": ${formatRuleOptionIssues(index, result.issues)}`);
|
|
854
1057
|
}
|
|
855
|
-
function
|
|
856
|
-
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
let arrayLength;
|
|
860
|
-
let indexCount = 0;
|
|
861
|
-
for (const key of Reflect.ownKeys(input)) {
|
|
862
|
-
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
863
|
-
if (!descriptor || !("value" in descriptor)) return false;
|
|
864
|
-
if (key === "length") {
|
|
865
|
-
if (descriptor.enumerable || typeof descriptor.value !== "number") return false;
|
|
866
|
-
arrayLength = descriptor.value;
|
|
867
|
-
continue;
|
|
868
|
-
}
|
|
869
|
-
if (typeof key !== "string" || !isCanonicalArrayIndex(key) || !descriptor.enumerable) return false;
|
|
870
|
-
if (!isJsonValueAt(descriptor.value, ancestors)) return false;
|
|
871
|
-
indexCount += 1;
|
|
1058
|
+
function parseRuleOptions(id, schema, options) {
|
|
1059
|
+
if (!schema) {
|
|
1060
|
+
if (options.length > 0) throw new TypeError(`Rule "${id}" does not accept options.`);
|
|
1061
|
+
return [];
|
|
872
1062
|
}
|
|
873
|
-
|
|
1063
|
+
if (options.length > schema.length) throw new TypeError(`Invalid options for rule "${id}": Unexpected option at index ${schema.length}.`);
|
|
1064
|
+
return schema.map((optionSchema, index) => parseRuleOption(id, index, optionSchema, options[index]));
|
|
1065
|
+
}
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/core/languages/diagnostics.ts
|
|
1068
|
+
/** A rule named a language and nothing registered it. This is a builtin rule. Not a real rule. */
|
|
1069
|
+
const MISSING_LANGUAGE_RULE = "alint/missing-language";
|
|
1070
|
+
/** A linted file's extension belongs to no registered language. This is a builtin rule. Not a real rule either. */
|
|
1071
|
+
const UNREGISTERED_LANGUAGE_RULE = "alint/unregistered-language";
|
|
1072
|
+
/**
|
|
1073
|
+
* One diagnostic per missing language, always an error: a rule scoped to a language nothing provides
|
|
1074
|
+
* matches no file, so leaving it at a warning would let a run pass having silently checked nothing.
|
|
1075
|
+
*/
|
|
1076
|
+
function missingLanguageDiagnostics(missing) {
|
|
1077
|
+
return [...missing.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([languageId, entry]) => {
|
|
1078
|
+
const ruleIds = [...entry.ruleIds].sort();
|
|
1079
|
+
const subject = ruleIds.length === 1 ? `Rule "${ruleIds[0]}" needs` : `Rules ${ruleIds.map((id) => `"${id}"`).join(", ")} need`;
|
|
1080
|
+
return {
|
|
1081
|
+
filePath: entry.filePath,
|
|
1082
|
+
message: `${subject} the "${languageId}" language, which no plugin registered. Add a plugin that provides "${languageId}", or turn the rule off.`,
|
|
1083
|
+
ruleId: MISSING_LANGUAGE_RULE,
|
|
1084
|
+
severity: "error"
|
|
1085
|
+
};
|
|
1086
|
+
});
|
|
874
1087
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1088
|
+
/** Records one rule that named a language the config never registered, grouped by language id. */
|
|
1089
|
+
function recordMissingLanguage(into, languageId, ruleId, path) {
|
|
1090
|
+
const existing = into.get(languageId);
|
|
1091
|
+
if (existing === void 0) {
|
|
1092
|
+
into.set(languageId, {
|
|
1093
|
+
filePath: path,
|
|
1094
|
+
ruleIds: /* @__PURE__ */ new Set([ruleId])
|
|
1095
|
+
});
|
|
1096
|
+
return;
|
|
881
1097
|
}
|
|
882
|
-
|
|
1098
|
+
existing.ruleIds.add(ruleId);
|
|
1099
|
+
if (path < existing.filePath) existing.filePath = path;
|
|
883
1100
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* Records one linted file no language claimed, grouped by extension so a missing pack is one
|
|
1103
|
+
* diagnostic, not one per file. `'off'` records nothing.
|
|
1104
|
+
*/
|
|
1105
|
+
function recordUnregistered(into, path, severity) {
|
|
1106
|
+
if (severity === "off") return;
|
|
1107
|
+
const key = extname(path) || basename(path);
|
|
1108
|
+
const existing = into.get(key);
|
|
1109
|
+
into.set(key, {
|
|
1110
|
+
count: (existing?.count ?? 0) + 1,
|
|
1111
|
+
filePath: existing !== void 0 && existing.filePath < path ? existing.filePath : path,
|
|
1112
|
+
severity: existing?.severity === "error" ? "error" : severity
|
|
1113
|
+
});
|
|
887
1114
|
}
|
|
888
|
-
|
|
889
|
-
|
|
1115
|
+
/** One diagnostic per extension, sorted, for the run to prepend to its results. */
|
|
1116
|
+
function unregisteredLanguageDiagnostics(unregistered) {
|
|
1117
|
+
return [...unregistered.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([extension, entry]) => ({
|
|
1118
|
+
filePath: entry.filePath,
|
|
1119
|
+
message: `No language is registered for "${extension}": ${entry.count} ${entry.count === 1 ? "file was" : "files were"} handled as plain text. Register one through a plugin's "languages", or pin \`language: 'plaintext'\` to accept plain text.`,
|
|
1120
|
+
ruleId: UNREGISTERED_LANGUAGE_RULE,
|
|
1121
|
+
severity: entry.severity
|
|
1122
|
+
}));
|
|
890
1123
|
}
|
|
891
|
-
function
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if (typeof input !== "object") return false;
|
|
895
|
-
if (ancestors.has(input)) return false;
|
|
896
|
-
ancestors.add(input);
|
|
897
|
-
try {
|
|
898
|
-
if (Array.isArray(input)) {
|
|
899
|
-
if (Object.getPrototypeOf(input) !== Array.prototype) return false;
|
|
900
|
-
return hasJsonArrayProperties(input, ancestors);
|
|
901
|
-
}
|
|
902
|
-
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null) return false;
|
|
903
|
-
return hasJsonObjectProperties(input, ancestors);
|
|
904
|
-
} catch {
|
|
905
|
-
return false;
|
|
906
|
-
} finally {
|
|
907
|
-
ancestors.delete(input);
|
|
908
|
-
}
|
|
1124
|
+
function unregisteredLanguageSeverity(linterOptions) {
|
|
1125
|
+
const value = linterOptions.reportUnregisteredLanguages;
|
|
1126
|
+
return value === "error" || value === "off" ? value : "warn";
|
|
909
1127
|
}
|
|
910
|
-
|
|
911
|
-
|
|
1128
|
+
//#endregion
|
|
1129
|
+
//#region src/core/languages/rule-languages.ts
|
|
1130
|
+
/**
|
|
1131
|
+
* The language a file falls back to when no registered language claims its extension. Not a real
|
|
1132
|
+
* language, which is why `'any'` excludes it.
|
|
1133
|
+
*/
|
|
1134
|
+
const PLAIN_TEXT = "plaintext";
|
|
1135
|
+
/**
|
|
1136
|
+
* Whether a rule reads this target.
|
|
1137
|
+
*
|
|
1138
|
+
* A rule with language off still reads file targets, because reading raw text, or asking a model about a
|
|
1139
|
+
* whole file, needs no language. However, it never reads the function and class targets extraction produces.
|
|
1140
|
+
* `'file'` is the one kind every language yields, plain text included, so it is the dividing line.
|
|
1141
|
+
*
|
|
1142
|
+
* `'any'` means any real language, so a file that fell back to plain text because nothing claimed
|
|
1143
|
+
* its extension is withheld: the rule asked for structure and there is none to give. The run says so
|
|
1144
|
+
* once per extension through `alint/unregistered-language` rather than letting each rule report an
|
|
1145
|
+
* empty file.
|
|
1146
|
+
*/
|
|
1147
|
+
function isTargetLanguageAccepted(languages, targetKind, targetLanguage) {
|
|
1148
|
+
if (languages.kind === "off") return targetKind === "file";
|
|
1149
|
+
if (languages.kind === "any") return targetLanguage !== PLAIN_TEXT;
|
|
1150
|
+
return languages.ids.includes(targetLanguage);
|
|
912
1151
|
}
|
|
913
|
-
function
|
|
914
|
-
|
|
1152
|
+
function resolveRuleLanguages(languages) {
|
|
1153
|
+
if (languages === void 0) return { kind: "off" };
|
|
1154
|
+
if (languages === "any") return { kind: "any" };
|
|
1155
|
+
if ("ids" in languages) return {
|
|
1156
|
+
ids: languages.ids,
|
|
1157
|
+
kind: "list",
|
|
1158
|
+
skipMissing: languages.skipMissing ?? false
|
|
1159
|
+
};
|
|
1160
|
+
return {
|
|
1161
|
+
ids: languages,
|
|
1162
|
+
kind: "list",
|
|
1163
|
+
skipMissing: false
|
|
1164
|
+
};
|
|
915
1165
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1166
|
+
//#endregion
|
|
1167
|
+
//#region src/core/preparation.ts
|
|
1168
|
+
/**
|
|
1169
|
+
* The `ctx.src.extract` a run hands to rules, for parsing files it was never asked to lint, which
|
|
1170
|
+
* an index builder sweeping the workspace needs.
|
|
1171
|
+
*
|
|
1172
|
+
* It resolves each file's OWN config rather than a caller's, because the language a file resolves to
|
|
1173
|
+
* is a config decision and two config groups may register different plugins. It extracts on demand
|
|
1174
|
+
* and holds nothing: source planning releases rich extractor values after compact jobs are admitted,
|
|
1175
|
+
* and a run-wide parse memo here would put that retention back. A caller keeps only what it derives.
|
|
1176
|
+
*
|
|
1177
|
+
* `getSrc` defers reading the runtime because the runtime is built around this closure. It is assigned
|
|
1178
|
+
* before any rule runs, so by the first call it is present.
|
|
1179
|
+
*/
|
|
1180
|
+
function createSourceExtractor(cwd, config, getSrc) {
|
|
1181
|
+
return async (filePath, options = {}) => {
|
|
1182
|
+
const path = resolve(cwd, filePath);
|
|
1183
|
+
const resolvedConfig = resolveConfigForFile(path, config, { cwd });
|
|
1184
|
+
if (resolvedConfig.ignored) return [];
|
|
1185
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1186
|
+
const language = resolveLanguageForPath(path, createLanguageRegistry(effectiveConfig), { language: options.language ?? effectiveConfig.language });
|
|
1187
|
+
const src = getSrc();
|
|
1188
|
+
const file = await src.readFile(path);
|
|
1189
|
+
return language.extract(file, {
|
|
1190
|
+
cwd,
|
|
1191
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1192
|
+
src
|
|
1193
|
+
});
|
|
1194
|
+
};
|
|
935
1195
|
}
|
|
936
|
-
function
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1196
|
+
function prepareRun(options = {}) {
|
|
1197
|
+
const cwd$2 = options.cwd ?? cwd();
|
|
1198
|
+
const config = options.config ?? [];
|
|
1199
|
+
const files = [];
|
|
1200
|
+
const directories = [];
|
|
1201
|
+
const missingLanguages = /* @__PURE__ */ new Map();
|
|
1202
|
+
const unregisteredLanguages = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const filePath of options.files ?? []) {
|
|
1204
|
+
const path = resolve(cwd$2, filePath);
|
|
1205
|
+
const resolvedConfig = resolveConfigForFile(path, config, { cwd: cwd$2 });
|
|
1206
|
+
if (resolvedConfig.ignored) continue;
|
|
1207
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1208
|
+
const languageRegistry = createLanguageRegistry(effectiveConfig);
|
|
1209
|
+
const language = resolveLanguageForPath(path, languageRegistry, { language: effectiveConfig.language });
|
|
1210
|
+
const rules = prepareRules(effectiveConfig);
|
|
1211
|
+
recordMissingLanguages(missingLanguages, rules, languageRegistry, path);
|
|
1212
|
+
if (effectiveConfig.language === void 0 && language.name === "plaintext" && rules.some(({ enabledRule }) => !isTargetLanguageAccepted(resolveRuleLanguages(enabledRule.rule.languages), "file", "plaintext"))) recordUnregistered(unregisteredLanguages, path, unregisteredLanguageSeverity(effectiveConfig.linterOptions));
|
|
1213
|
+
files.push({
|
|
1214
|
+
agent: effectiveConfig.agent,
|
|
1215
|
+
configHash: stableHash({
|
|
1216
|
+
language: effectiveConfig.language,
|
|
1217
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1218
|
+
processor: effectiveConfig.processor,
|
|
1219
|
+
resolvedLanguage: language.name,
|
|
1220
|
+
settings: effectiveConfig.settings
|
|
1221
|
+
}),
|
|
1222
|
+
fileIndex: files.length,
|
|
1223
|
+
language,
|
|
1224
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
1225
|
+
path,
|
|
1226
|
+
rules,
|
|
1227
|
+
settings: effectiveConfig.settings
|
|
1228
|
+
});
|
|
941
1229
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1230
|
+
for (const directoryPath of options.directories ?? []) {
|
|
1231
|
+
const path = resolve(cwd$2, directoryPath);
|
|
1232
|
+
const resolvedConfig = resolveConfigForDirectory(path, config, { cwd: cwd$2 });
|
|
1233
|
+
if (resolvedConfig.ignored) continue;
|
|
1234
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1235
|
+
directories.push({
|
|
1236
|
+
agent: effectiveConfig.agent,
|
|
1237
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1238
|
+
directoryIndex: directories.length,
|
|
1239
|
+
rules: prepareRules(effectiveConfig),
|
|
1240
|
+
settings: effectiveConfig.settings,
|
|
1241
|
+
target: {
|
|
1242
|
+
kind: "directory",
|
|
1243
|
+
path
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
directories,
|
|
1249
|
+
files,
|
|
1250
|
+
missingLanguages,
|
|
1251
|
+
project: options.projectTargets === false ? void 0 : prepareProject(cwd$2, config),
|
|
1252
|
+
unregisteredLanguages
|
|
1253
|
+
};
|
|
949
1254
|
}
|
|
950
|
-
function
|
|
951
|
-
const
|
|
952
|
-
|
|
1255
|
+
function createLanguageRegistry(config) {
|
|
1256
|
+
const registry = createBuiltInLanguageRegistry();
|
|
1257
|
+
for (const plugin of Object.values(config.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(registry, language);
|
|
1258
|
+
return registry;
|
|
953
1259
|
}
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
}
|
|
1260
|
+
function prepareProject(root, config) {
|
|
1261
|
+
const resolvedConfig = resolveConfigForProject(root, config, { cwd: root });
|
|
1262
|
+
if (resolvedConfig.ignored) return void 0;
|
|
1263
|
+
const effectiveConfig = resolvedConfig.config;
|
|
1264
|
+
return {
|
|
1265
|
+
agent: effectiveConfig.agent,
|
|
1266
|
+
configHash: stableHash({ settings: effectiveConfig.settings }),
|
|
1267
|
+
root,
|
|
1268
|
+
rules: prepareRules(effectiveConfig),
|
|
1269
|
+
settings: effectiveConfig.settings
|
|
1270
|
+
};
|
|
965
1271
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1272
|
+
function prepareRules(config) {
|
|
1273
|
+
return buildRuleRegistry(config).enabledRules.map((enabledRule, ruleIndex) => ({
|
|
1274
|
+
enabledRule,
|
|
1275
|
+
ruleIndex
|
|
1276
|
+
}));
|
|
971
1277
|
}
|
|
972
|
-
function
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1278
|
+
function recordMissingLanguages(into, rules, registry, path) {
|
|
1279
|
+
for (const { enabledRule } of rules) {
|
|
1280
|
+
const languages = resolveRuleLanguages(enabledRule.rule.languages);
|
|
1281
|
+
if (languages.kind !== "list" || languages.skipMissing) continue;
|
|
1282
|
+
for (const languageId of languages.ids) if (!registry.languages.has(languageId)) recordMissingLanguage(into, languageId, enabledRule.id, path);
|
|
1283
|
+
}
|
|
977
1284
|
}
|
|
978
1285
|
//#endregion
|
|
979
1286
|
//#region src/core/execution/records.ts
|
|
@@ -1112,9 +1419,10 @@ async function executeRuleJob(job, options) {
|
|
|
1112
1419
|
failure: failure(handlerCause, job, handlerCause instanceof SourceChangedError ? "source-changed" : "handler"),
|
|
1113
1420
|
state: "failed"
|
|
1114
1421
|
});
|
|
1115
|
-
if (slot && fingerprint)
|
|
1422
|
+
if (slot && fingerprint) {
|
|
1116
1423
|
job.target.cacheOwner?.put(slot, createCacheEntry(job, fingerprint, bucket));
|
|
1117
|
-
|
|
1424
|
+
await job.target.cacheOwner?.checkpoint();
|
|
1425
|
+
}
|
|
1118
1426
|
return finish(job, bucket, {
|
|
1119
1427
|
cache: "miss",
|
|
1120
1428
|
state: "completed"
|
|
@@ -2097,7 +2405,7 @@ async function planSource(input, options) {
|
|
|
2097
2405
|
cacheOwner = options.cacheStore.beginOwner({
|
|
2098
2406
|
kind: "file",
|
|
2099
2407
|
path: file.path
|
|
2100
|
-
});
|
|
2408
|
+
}, { contentHash });
|
|
2101
2409
|
const baseFile = {
|
|
2102
2410
|
contentHash,
|
|
2103
2411
|
language: file.language,
|
|
@@ -2112,10 +2420,7 @@ async function planSource(input, options) {
|
|
|
2112
2420
|
};
|
|
2113
2421
|
}
|
|
2114
2422
|
if (options.signal?.aborted) {
|
|
2115
|
-
cacheOwner.commit({
|
|
2116
|
-
contentHash,
|
|
2117
|
-
mode: "merge"
|
|
2118
|
-
});
|
|
2423
|
+
cacheOwner.commit({ mode: "merge" });
|
|
2119
2424
|
return {
|
|
2120
2425
|
outcomes: Promise.resolve([]),
|
|
2121
2426
|
project
|
|
@@ -2124,11 +2429,9 @@ async function planSource(input, options) {
|
|
|
2124
2429
|
const batch = options.scheduler.schedule(jobs);
|
|
2125
2430
|
reportFilePlanningComplete(input, batch.jobsAdded, options);
|
|
2126
2431
|
return {
|
|
2127
|
-
outcomes: batch.outcomes.then((settled) => {
|
|
2128
|
-
cacheOwner.commit(options.signal?.aborted ? {
|
|
2129
|
-
|
|
2130
|
-
mode: "merge"
|
|
2131
|
-
} : { contentHash });
|
|
2432
|
+
outcomes: batch.outcomes.then(async (settled) => {
|
|
2433
|
+
cacheOwner.commit(options.signal?.aborted ? { mode: "merge" } : void 0);
|
|
2434
|
+
await options.cacheStore.flush();
|
|
2132
2435
|
return settled;
|
|
2133
2436
|
}),
|
|
2134
2437
|
project
|
|
@@ -2405,6 +2708,14 @@ var AlintAbortError = class extends AlintRunCancelledError {
|
|
|
2405
2708
|
this.name = "AlintAbortError";
|
|
2406
2709
|
}
|
|
2407
2710
|
};
|
|
2711
|
+
var AlintCachePersistenceError = class extends Error {
|
|
2712
|
+
result;
|
|
2713
|
+
constructor(result, cause) {
|
|
2714
|
+
super("Failed to persist the alint cache.", { cause });
|
|
2715
|
+
this.name = "AlintCachePersistenceError";
|
|
2716
|
+
this.result = result;
|
|
2717
|
+
}
|
|
2718
|
+
};
|
|
2408
2719
|
var AlintRunError = class extends Error {
|
|
2409
2720
|
failures;
|
|
2410
2721
|
result;
|
|
@@ -2482,6 +2793,7 @@ async function runAlint(options = {}) {
|
|
|
2482
2793
|
const outcomes = [];
|
|
2483
2794
|
const fileFailures = [];
|
|
2484
2795
|
let infrastructureError;
|
|
2796
|
+
let cachePersistenceError;
|
|
2485
2797
|
let infrastructureFailed = false;
|
|
2486
2798
|
let admissionFailed = false;
|
|
2487
2799
|
try {
|
|
@@ -2545,7 +2857,9 @@ async function runAlint(options = {}) {
|
|
|
2545
2857
|
}
|
|
2546
2858
|
if (!admissionFailed && !options.cacheOnly) try {
|
|
2547
2859
|
await cacheStore.reconcile();
|
|
2548
|
-
} catch {
|
|
2860
|
+
} catch (error) {
|
|
2861
|
+
cachePersistenceError = error;
|
|
2862
|
+
}
|
|
2549
2863
|
}
|
|
2550
2864
|
runProgress.finalize();
|
|
2551
2865
|
if (admissionFailed) throw infrastructureError;
|
|
@@ -2574,6 +2888,7 @@ async function runAlint(options = {}) {
|
|
|
2574
2888
|
usage: snapshotRunUsage(result.usage)
|
|
2575
2889
|
});
|
|
2576
2890
|
if (infrastructureFailed) throw infrastructureError;
|
|
2891
|
+
if (cachePersistenceError !== void 0) throw new AlintCachePersistenceError(result, cachePersistenceError);
|
|
2577
2892
|
if (options.signal?.aborted) throw new AlintAbortError(result, { cause: options.signal.reason });
|
|
2578
2893
|
if (failures.length > 0) throw new AlintRunError(fileFailures.length > 0 ? `${failures.length} alint execution${failures.length === 1 ? "" : "s"} failed.` : `${failures.length} rule execution${failures.length === 1 ? "" : "s"} failed.`, result, {
|
|
2579
2894
|
cause: new AggregateError(failures.map((failure) => new Error(failure.message)), "Alint execution failures."),
|
|
@@ -2805,6 +3120,7 @@ async function measureModel({ model, prompt, signal }) {
|
|
|
2805
3120
|
}];
|
|
2806
3121
|
const startedAt = performance.now();
|
|
2807
3122
|
let firstOutputAt;
|
|
3123
|
+
let outputDeltaCount = 0;
|
|
2808
3124
|
const result = streamText({
|
|
2809
3125
|
...model.params,
|
|
2810
3126
|
abortSignal: signal,
|
|
@@ -2824,7 +3140,10 @@ async function measureModel({ model, prompt, signal }) {
|
|
|
2824
3140
|
]);
|
|
2825
3141
|
let settled;
|
|
2826
3142
|
try {
|
|
2827
|
-
for await (const event of result.eventStream) if (
|
|
3143
|
+
for await (const event of result.eventStream) if ((event.type === "reasoning.delta" || event.type === "text.delta") && event.delta.length > 0) {
|
|
3144
|
+
outputDeltaCount += 1;
|
|
3145
|
+
firstOutputAt ??= performance.now();
|
|
3146
|
+
}
|
|
2828
3147
|
} finally {
|
|
2829
3148
|
settled = await completion;
|
|
2830
3149
|
}
|
|
@@ -2833,7 +3152,7 @@ async function measureModel({ model, prompt, signal }) {
|
|
|
2833
3152
|
if (usageResult.status === "rejected") throw usageResult.reason;
|
|
2834
3153
|
return {
|
|
2835
3154
|
durationMs: endedAt - startedAt,
|
|
2836
|
-
firstOutputMs: firstOutputAt === void 0 ? void 0 : firstOutputAt - startedAt,
|
|
3155
|
+
firstOutputMs: firstOutputAt === void 0 || outputDeltaCount < 2 ? void 0 : firstOutputAt - startedAt,
|
|
2837
3156
|
outputTokens: usageResult.value?.outputTokens
|
|
2838
3157
|
};
|
|
2839
3158
|
}
|
|
@@ -2930,4 +3249,4 @@ function sampleCorpus(seed) {
|
|
|
2930
3249
|
].map((section) => `--- corpus section ${section} ---\n${rotated}`).join("\n\n");
|
|
2931
3250
|
}
|
|
2932
3251
|
//#endregion
|
|
2933
|
-
export { AlintAbortError, AlintRunCancelledError, AlintRunError, benchmarkModels, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceExtractor, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange, targetIdentity, withLanguage, withStableIdentities };
|
|
3252
|
+
export { AlintAbortError, AlintCachePersistenceError, AlintRunCancelledError, AlintRunError, benchmarkModels, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceExtractor, createSourceFile, createSourceRuntime, defaultStopGateConfig, defineConfig, definePlugin, defineRule, hasDiscoveryFilePatterns, matchesDiscoveryFile, maximumStopGateTimeoutMs, normalizeConfig, normalizeRunnerCacheConfig, registerLanguage, resolveConfigForDirectory, resolveConfigForFile, resolveConfigForProject, resolveLanguage, resolveModel, resolveStopGateConfig, runAlint, sliceLines, sliceRange, stableHash, targetIdentity, withLanguage, withStableIdentities };
|