@alint-js/core 0.0.4 → 0.0.6
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.d.mts +131 -38
- package/dist/index.mjs +767 -407
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -1,270 +1,214 @@
|
|
|
1
|
+
import { minimatch } from "minimatch";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "pathe";
|
|
3
|
+
import { parseSync } from "oxc-parser";
|
|
4
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5
|
+
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";
|
|
1
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
8
|
import process, { cwd } from "node:process";
|
|
3
9
|
import { errorCauseFrom, errorMessageFrom } from "@moeru/std/error";
|
|
4
|
-
import { resolve } from "pathe";
|
|
5
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
11
|
import { statSync } from "node:fs";
|
|
7
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname, extname, isAbsolute, join, relative, resolve as resolve$1, sep } from "node:path";
|
|
9
12
|
import { array, check, description, is, literal, number, object, optional, pipe, record, string, union, unknown } from "valibot";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
//#region src/config/config-array.ts
|
|
14
|
+
const effectiveBasePathSymbol = Symbol("effectiveBasePath");
|
|
15
|
+
const inheritedMatcherScopesSymbol = Symbol("inheritedMatcherScopes");
|
|
16
|
+
function hasDiscoveryFilePatterns(input) {
|
|
17
|
+
return expandConfig(input).some(hasDiscoveryMatcher);
|
|
18
|
+
}
|
|
19
|
+
function matchesDiscoveryFile(filePath, input, options) {
|
|
20
|
+
const result = resolveConfigForFile(filePath, input, options);
|
|
21
|
+
if (result.ignored) return false;
|
|
22
|
+
return result.matched.some(hasDiscoveryMatcher);
|
|
23
|
+
}
|
|
24
|
+
function normalizeConfig(input) {
|
|
25
|
+
return normalizeConfigItems(input, []);
|
|
26
|
+
}
|
|
27
|
+
function resolveConfigForFile(filePath, input, options) {
|
|
28
|
+
const items = expandConfig(input);
|
|
29
|
+
const matched = [];
|
|
30
|
+
const skipped = [];
|
|
31
|
+
const config = createEmptyConfig();
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
if (isGlobalIgnoreItem(item)) {
|
|
34
|
+
if (matchesInheritedScopes(filePath, item, options.cwd) && matchesAny(filePath, item.ignores ?? [], options.cwd, getEffectiveBasePath(item))) return {
|
|
35
|
+
config: createEmptyConfig(),
|
|
36
|
+
ignored: true,
|
|
37
|
+
matched: [],
|
|
38
|
+
skipped
|
|
39
|
+
};
|
|
40
|
+
skipped.push({
|
|
41
|
+
item,
|
|
42
|
+
reason: "global ignores did not match"
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!matchesConfigItem(filePath, item, options.cwd)) {
|
|
47
|
+
skipped.push({
|
|
48
|
+
item,
|
|
49
|
+
reason: "files or ignores did not match"
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
matched.push(item);
|
|
54
|
+
mergeConfig(config, item);
|
|
31
55
|
}
|
|
32
|
-
for (const id of Object.keys(config.rules ?? {})) if (!rules.has(id)) throw new Error(`Unknown rule "${id}".`);
|
|
33
56
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
config,
|
|
58
|
+
ignored: false,
|
|
59
|
+
matched,
|
|
60
|
+
skipped
|
|
36
61
|
};
|
|
37
62
|
}
|
|
38
|
-
function
|
|
39
|
-
if (Array.isArray(entry)) return entry[0] ?? "warn";
|
|
40
|
-
return entry ?? "warn";
|
|
41
|
-
}
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/models/resolve.ts
|
|
44
|
-
function resolveModel(registry, options = {}) {
|
|
45
|
-
const candidates = flattenModels(registry);
|
|
46
|
-
const ruleId = options.ruleId ?? "<unknown>";
|
|
47
|
-
if (options.request !== void 0) {
|
|
48
|
-
const request = options.request;
|
|
49
|
-
const candidate = candidates.find(({ model }) => matchesRequest(model, request));
|
|
50
|
-
if (candidate === void 0) throw new Error(`Unknown model "${request}".`);
|
|
51
|
-
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
52
|
-
return toResolvedModel(candidate, options.requirement);
|
|
53
|
-
}
|
|
54
|
-
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
55
|
-
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
56
|
-
return toResolvedModel(candidate, options.requirement);
|
|
57
|
-
}
|
|
58
|
-
function flattenModels(registry) {
|
|
59
|
-
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
60
|
-
model,
|
|
61
|
-
provider
|
|
62
|
-
})));
|
|
63
|
-
}
|
|
64
|
-
function matchesRequest(model, request) {
|
|
65
|
-
return model.id === request || model.name === request || (model.aliases ?? []).includes(request);
|
|
66
|
-
}
|
|
67
|
-
function preferSize(candidates, requirement) {
|
|
68
|
-
if (requirement?.size === void 0) return candidates[0];
|
|
69
|
-
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
70
|
-
}
|
|
71
|
-
function satisfiesHardRequirements(model, requirement) {
|
|
72
|
-
if (requirement === void 0) return true;
|
|
73
|
-
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
74
|
-
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
function toResolvedModel(candidate, requirement) {
|
|
78
|
-
const { model, provider } = candidate;
|
|
63
|
+
function createEmptyConfig() {
|
|
79
64
|
return {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
params: {
|
|
86
|
-
...model.defaultParams ?? {},
|
|
87
|
-
...requirement?.params ?? {}
|
|
88
|
-
},
|
|
89
|
-
provider: {
|
|
90
|
-
endpoint: provider.endpoint,
|
|
91
|
-
headers: { ...provider.headers ?? {} },
|
|
92
|
-
id: provider.id,
|
|
93
|
-
type: provider.type
|
|
94
|
-
},
|
|
95
|
-
size: model.size
|
|
65
|
+
languageOptions: {},
|
|
66
|
+
linterOptions: {},
|
|
67
|
+
plugins: {},
|
|
68
|
+
rules: {},
|
|
69
|
+
settings: {}
|
|
96
70
|
};
|
|
97
71
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const CACHE_SCHEMA_VERSION = 1;
|
|
101
|
-
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
102
|
-
const CacheFileSchema = pipe(object({
|
|
103
|
-
createdAt: pipe(string(), description("Cache file creation timestamp.")),
|
|
104
|
-
entries: pipe(unknown(), check((value) => typeof value === "object" && value !== null && !Array.isArray(value)), record(string(), object({
|
|
105
|
-
diagnostics: pipe(array(object({
|
|
106
|
-
filePath: pipe(string(), description("Diagnostic file path.")),
|
|
107
|
-
message: pipe(string(), description("Diagnostic message.")),
|
|
108
|
-
ruleId: pipe(string(), description("Diagnostic rule id.")),
|
|
109
|
-
severity: pipe(union([literal("error"), literal("warn")]), description("Diagnostic severity."))
|
|
110
|
-
})), description("Diagnostics produced for the cached target.")),
|
|
111
|
-
filePath: pipe(string(), description("Cache entry file path.")),
|
|
112
|
-
fingerprint: pipe(object({
|
|
113
|
-
alintVersion: pipe(string(), description("Alint version used to create the cache entry.")),
|
|
114
|
-
configHash: pipe(string(), description("Runner config hash used to create the cache entry.")),
|
|
115
|
-
modelHash: pipe(string(), description("Model configuration hash used to create the cache entry.")),
|
|
116
|
-
ruleHash: pipe(string(), description("Rule configuration hash used to create the cache entry."))
|
|
117
|
-
}), description("Cache entry fingerprint.")),
|
|
118
|
-
target: pipe(object({
|
|
119
|
-
hash: pipe(string(), description("Cached target hash.")),
|
|
120
|
-
identity: pipe(string(), description("Stable cached target identity.")),
|
|
121
|
-
kind: pipe(union([
|
|
122
|
-
literal("file"),
|
|
123
|
-
literal("class"),
|
|
124
|
-
literal("function")
|
|
125
|
-
]), description("Cached target kind."))
|
|
126
|
-
}), description("Cached target metadata.")),
|
|
127
|
-
usage: pipe(array(object({
|
|
128
|
-
inputTokens: pipe(optional(number()), description("Optional input token count.")),
|
|
129
|
-
modelId: pipe(string(), description("Usage model id.")),
|
|
130
|
-
outputTokens: pipe(optional(number()), description("Optional output token count.")),
|
|
131
|
-
providerId: pipe(string(), description("Usage provider id.")),
|
|
132
|
-
ruleId: pipe(string(), description("Usage rule id.")),
|
|
133
|
-
totalTokens: pipe(optional(number()), description("Optional total token count."))
|
|
134
|
-
})), description("Inference usage records for the cache entry."))
|
|
135
|
-
})), description("Cache entries keyed by cache key.")),
|
|
136
|
-
files: pipe(unknown(), check((value) => typeof value === "object" && value !== null && !Array.isArray(value)), record(string(), object({
|
|
137
|
-
contentHash: pipe(string(), description("Cached file content hash.")),
|
|
138
|
-
entries: pipe(array(string()), description("Cache entry keys associated with the file.")),
|
|
139
|
-
path: pipe(string(), description("Normalized cached file path."))
|
|
140
|
-
})), description("Cached files keyed by normalized file path.")),
|
|
141
|
-
schemaVersion: pipe(literal(CACHE_SCHEMA_VERSION), description("Cache schema version.")),
|
|
142
|
-
updatedAt: pipe(string(), description("Cache file update timestamp."))
|
|
143
|
-
}), description("Alint cache file."));
|
|
144
|
-
function createCacheKey(input) {
|
|
145
|
-
return stableHash(input);
|
|
146
|
-
}
|
|
147
|
-
async function createCacheStore(options) {
|
|
148
|
-
const location = resolveCacheLocation(options.cwd, options.location);
|
|
149
|
-
if (!options.enabled) return createNoopCacheStore(location);
|
|
150
|
-
const cacheFile = await readCacheFile(location);
|
|
72
|
+
function createMatcherScope(item, basePath) {
|
|
73
|
+
if (!item.files && !item.ignores) return;
|
|
151
74
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const path = normalizeCachePath(options.cwd, filePath);
|
|
156
|
-
cacheFile.files[path] = {
|
|
157
|
-
contentHash,
|
|
158
|
-
entries,
|
|
159
|
-
path
|
|
160
|
-
};
|
|
161
|
-
},
|
|
162
|
-
reconcile: async () => {
|
|
163
|
-
await mkdir(dirname(location), { recursive: true });
|
|
164
|
-
cacheFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
165
|
-
const tempPath = join(dirname(location), `.${DEFAULT_CACHE_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`);
|
|
166
|
-
await writeFile(tempPath, `${JSON.stringify(cacheFile, null, 2)}\n`);
|
|
167
|
-
await rename(tempPath, location);
|
|
168
|
-
},
|
|
169
|
-
set: (key, entry) => {
|
|
170
|
-
cacheFile.entries[key] = entry;
|
|
171
|
-
}
|
|
75
|
+
basePath,
|
|
76
|
+
files: item.files,
|
|
77
|
+
ignores: item.ignores
|
|
172
78
|
};
|
|
173
79
|
}
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
80
|
+
function expandConfig(input) {
|
|
81
|
+
return expandExtends(normalizeConfig(input), {
|
|
82
|
+
inheritedBasePath: void 0,
|
|
83
|
+
inheritedMatcherScopes: [],
|
|
84
|
+
objectStack: [],
|
|
85
|
+
plugins: {},
|
|
86
|
+
stringStack: []
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function expandExtends(items, state) {
|
|
90
|
+
const expanded = [];
|
|
91
|
+
for (const item of items) {
|
|
92
|
+
if (state.objectStack.includes(item)) throw new Error("Circular inline config extends.");
|
|
93
|
+
const effectiveBasePath = item.basePath ?? state.inheritedBasePath;
|
|
94
|
+
const itemObjectStack = [...state.objectStack, item];
|
|
95
|
+
const itemMatcherScope = createMatcherScope(item, effectiveBasePath);
|
|
96
|
+
const childMatcherScopes = itemMatcherScope ? [...state.inheritedMatcherScopes, itemMatcherScope] : state.inheritedMatcherScopes;
|
|
97
|
+
const plugins = {
|
|
98
|
+
...state.plugins,
|
|
99
|
+
...item.plugins
|
|
100
|
+
};
|
|
101
|
+
for (const extension of item.extends ?? []) if (typeof extension === "string") expanded.push(...resolvePluginConfig(extension, {
|
|
102
|
+
inheritedBasePath: effectiveBasePath,
|
|
103
|
+
inheritedMatcherScopes: childMatcherScopes,
|
|
104
|
+
objectStack: itemObjectStack,
|
|
105
|
+
plugins,
|
|
106
|
+
stringStack: state.stringStack
|
|
107
|
+
}));
|
|
108
|
+
else {
|
|
109
|
+
if (state.objectStack.includes(extension)) throw new Error("Circular inline config extends.");
|
|
110
|
+
expanded.push(...expandExtends(normalizeConfig([extension]), {
|
|
111
|
+
inheritedBasePath: effectiveBasePath,
|
|
112
|
+
inheritedMatcherScopes: childMatcherScopes,
|
|
113
|
+
objectStack: itemObjectStack,
|
|
114
|
+
plugins,
|
|
115
|
+
stringStack: state.stringStack
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
expanded.push(withExpansionMetadata({
|
|
119
|
+
...item,
|
|
120
|
+
extends: void 0
|
|
121
|
+
}, state.inheritedMatcherScopes, effectiveBasePath));
|
|
179
122
|
}
|
|
180
|
-
return
|
|
181
|
-
const base = createBaseTargetIdentity(target);
|
|
182
|
-
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
183
|
-
if (target.range) return `${base}:${target.range.start}:${target.range.end}`;
|
|
184
|
-
return base;
|
|
185
|
-
};
|
|
123
|
+
return expanded;
|
|
186
124
|
}
|
|
187
|
-
function
|
|
188
|
-
return
|
|
125
|
+
function getEffectiveBasePath(item) {
|
|
126
|
+
return item[effectiveBasePathSymbol] ?? item.basePath;
|
|
189
127
|
}
|
|
190
|
-
function
|
|
191
|
-
return
|
|
128
|
+
function getInheritedMatcherScopes(item) {
|
|
129
|
+
return item[inheritedMatcherScopesSymbol] ?? [];
|
|
192
130
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
enabled: false,
|
|
196
|
-
location: resolveCacheLocation(cwd)
|
|
197
|
-
};
|
|
198
|
-
if (cache === true || cache === void 0) return {
|
|
199
|
-
enabled: true,
|
|
200
|
-
location: resolveCacheLocation(cwd)
|
|
201
|
-
};
|
|
202
|
-
return {
|
|
203
|
-
enabled: cache.enabled ?? true,
|
|
204
|
-
location: resolveCacheLocation(cwd, cache.location)
|
|
205
|
-
};
|
|
131
|
+
function hasDiscoveryMatcher(item) {
|
|
132
|
+
return hasPositiveFilePattern(item.files) || getInheritedMatcherScopes(item).some((scope) => hasPositiveFilePattern(scope.files));
|
|
206
133
|
}
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
const resolved = isAbsolute(location) ? resolve$1(location) : resolve$1(cwd, location);
|
|
210
|
-
if (location.endsWith("/") || location.endsWith("\\")) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
211
|
-
try {
|
|
212
|
-
if (statSyncIsDirectory(resolved)) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
213
|
-
} catch {}
|
|
214
|
-
return resolved;
|
|
134
|
+
function hasPositiveFilePattern(files) {
|
|
135
|
+
return files?.some((pattern) => isPatternList(pattern) ? pattern.some(isPositivePattern) : isPositivePattern(pattern)) ?? false;
|
|
215
136
|
}
|
|
216
|
-
function
|
|
217
|
-
return
|
|
137
|
+
function isConfigArrayInput(input) {
|
|
138
|
+
return Array.isArray(input);
|
|
218
139
|
}
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (target.range) return `${target.kind}:${target.range.start}:${target.range.end}`;
|
|
223
|
-
return target.kind;
|
|
140
|
+
function isGlobalIgnoreItem(item) {
|
|
141
|
+
const keys = Object.keys(item).filter((key) => item[key] !== void 0);
|
|
142
|
+
return item.ignores !== void 0 && keys.every((key) => key === "ignores" || key === "name");
|
|
224
143
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
createdAt: now,
|
|
229
|
-
entries: {},
|
|
230
|
-
files: {},
|
|
231
|
-
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
232
|
-
updatedAt: now
|
|
233
|
-
};
|
|
144
|
+
function isPatternList(pattern) {
|
|
145
|
+
return Array.isArray(pattern);
|
|
234
146
|
}
|
|
235
|
-
function
|
|
236
|
-
return
|
|
237
|
-
get: () => void 0,
|
|
238
|
-
location,
|
|
239
|
-
markFile: () => {},
|
|
240
|
-
reconcile: async () => {},
|
|
241
|
-
set: () => {}
|
|
242
|
-
};
|
|
147
|
+
function isPositivePattern(pattern) {
|
|
148
|
+
return !pattern.startsWith("!");
|
|
243
149
|
}
|
|
244
|
-
function
|
|
245
|
-
return
|
|
150
|
+
function matchesAny(filePath, patterns, cwd, basePath) {
|
|
151
|
+
return patterns.some((pattern) => matchesPattern(filePath, pattern, cwd, basePath));
|
|
246
152
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
153
|
+
function matchesConfigItem(filePath, item, cwd) {
|
|
154
|
+
const basePath = getEffectiveBasePath(item);
|
|
155
|
+
if (!matchesInheritedScopes(filePath, item, cwd)) return false;
|
|
156
|
+
if (matchesAny(filePath, item.ignores ?? [], cwd, basePath)) return false;
|
|
157
|
+
if (!item.files || item.files.length === 0) return true;
|
|
158
|
+
return item.files.some((pattern) => matchesPattern(filePath, pattern, cwd, basePath));
|
|
249
159
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
160
|
+
function matchesInheritedScopes(filePath, item, cwd) {
|
|
161
|
+
return getInheritedMatcherScopes(item).every((scope) => {
|
|
162
|
+
if (matchesAny(filePath, scope.ignores ?? [], cwd, scope.basePath)) return false;
|
|
163
|
+
if (!scope.files || scope.files.length === 0) return true;
|
|
164
|
+
return scope.files.some((pattern) => matchesPattern(filePath, pattern, cwd, scope.basePath));
|
|
165
|
+
});
|
|
256
166
|
}
|
|
257
|
-
function
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
167
|
+
function matchesPattern(filePath, pattern, cwd, basePath) {
|
|
168
|
+
if (isPatternList(pattern)) return pattern.every((entry) => matchesPattern(filePath, entry, cwd, basePath));
|
|
169
|
+
const relativePath = relative(basePath ? resolve(cwd, basePath) : cwd, isAbsolute(filePath) ? filePath : resolve(cwd, filePath)).replaceAll("\\", "/");
|
|
170
|
+
if (relativePath === ".." || relativePath.startsWith("../")) return false;
|
|
171
|
+
return minimatch(relativePath, pattern, { dot: true });
|
|
172
|
+
}
|
|
173
|
+
function mergeConfig(config, item) {
|
|
174
|
+
if (item.plugins) for (const [alias, plugin] of Object.entries(item.plugins)) {
|
|
175
|
+
const existingPlugin = config.plugins[alias];
|
|
176
|
+
if (existingPlugin && existingPlugin !== plugin) throw new Error(`Duplicate plugin alias "${alias}".`);
|
|
177
|
+
config.plugins[alias] = plugin;
|
|
178
|
+
}
|
|
179
|
+
Object.assign(config.rules, item.rules);
|
|
180
|
+
Object.assign(config.settings, item.settings);
|
|
181
|
+
Object.assign(config.languageOptions, item.languageOptions);
|
|
182
|
+
Object.assign(config.linterOptions, item.linterOptions);
|
|
183
|
+
if (item.language !== void 0) config.language = item.language;
|
|
184
|
+
if (item.processor !== void 0) config.processor = item.processor;
|
|
185
|
+
if (item.runner !== void 0) config.runner = {
|
|
186
|
+
...config.runner,
|
|
187
|
+
...item.runner
|
|
188
|
+
};
|
|
265
189
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
190
|
+
function normalizeConfigItems(input, arrayStack) {
|
|
191
|
+
if (arrayStack.includes(input)) throw new Error("Circular config array.");
|
|
192
|
+
return input.flatMap((item) => isConfigArrayInput(item) ? normalizeConfigItems(item, [...arrayStack, input]) : [item]);
|
|
193
|
+
}
|
|
194
|
+
function resolvePluginConfig(reference, state) {
|
|
195
|
+
if (state.stringStack.includes(reference)) throw new Error(`Circular config extends: ${[...state.stringStack, reference].join(" -> ")}`);
|
|
196
|
+
const separator = reference.indexOf("/");
|
|
197
|
+
const alias = reference.slice(0, separator);
|
|
198
|
+
const name = reference.slice(separator + 1);
|
|
199
|
+
const config = state.plugins[alias]?.configs?.[name];
|
|
200
|
+
if (!config) throw new Error(`Unknown config "${reference}".`);
|
|
201
|
+
return expandExtends(normalizeConfig([config]), {
|
|
202
|
+
...state,
|
|
203
|
+
stringStack: [...state.stringStack, reference]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function withExpansionMetadata(item, inheritedMatcherScopes, effectiveBasePath) {
|
|
207
|
+
Object.defineProperties(item, {
|
|
208
|
+
[effectiveBasePathSymbol]: { value: effectiveBasePath },
|
|
209
|
+
[inheritedMatcherScopesSymbol]: { value: inheritedMatcherScopes }
|
|
210
|
+
});
|
|
211
|
+
return item;
|
|
268
212
|
}
|
|
269
213
|
//#endregion
|
|
270
214
|
//#region src/core/source/runtime.ts
|
|
@@ -278,12 +222,15 @@ function createSourceFile(path, text) {
|
|
|
278
222
|
}
|
|
279
223
|
function createSourceRuntime() {
|
|
280
224
|
return {
|
|
281
|
-
getText
|
|
225
|
+
getText,
|
|
282
226
|
readFile: async (filePath) => createSourceFile(filePath, await readFile(filePath, "utf8")),
|
|
283
227
|
sliceLines,
|
|
284
228
|
sliceRange
|
|
285
229
|
};
|
|
286
230
|
}
|
|
231
|
+
function getText(target) {
|
|
232
|
+
return target.text;
|
|
233
|
+
}
|
|
287
234
|
function sliceLines(file, range) {
|
|
288
235
|
const startLine = clampLine(range.startLine, file.lines.length);
|
|
289
236
|
const endLine = clampLine(range.endLine, file.lines.length);
|
|
@@ -361,41 +308,38 @@ function inferLanguage(path) {
|
|
|
361
308
|
}
|
|
362
309
|
}
|
|
363
310
|
//#endregion
|
|
364
|
-
//#region src/core/
|
|
365
|
-
function
|
|
311
|
+
//#region src/core/languages/js/extract.ts
|
|
312
|
+
function extractJsSourceTargets(file) {
|
|
366
313
|
const result = parseSync(file.path, file.text, { sourceType: "module" });
|
|
367
314
|
const state = {
|
|
368
315
|
bindingNodes: /* @__PURE__ */ new Map(),
|
|
369
|
-
classes: [],
|
|
370
316
|
exportedNodes: /* @__PURE__ */ new Set(),
|
|
371
317
|
file,
|
|
372
|
-
functions: [],
|
|
373
318
|
inferredNames: /* @__PURE__ */ new Map(),
|
|
374
319
|
seenClasses: /* @__PURE__ */ new Set(),
|
|
375
320
|
seenFunctions: /* @__PURE__ */ new Set(),
|
|
321
|
+
targets: [],
|
|
376
322
|
visited: /* @__PURE__ */ new Set()
|
|
377
323
|
};
|
|
378
324
|
collectModuleBindings(result.program, state);
|
|
379
325
|
collectExportedBindings(result.program, state);
|
|
380
326
|
visit(result.program, state);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
functions: state.functions
|
|
384
|
-
};
|
|
327
|
+
const sortedTargets = [...state.targets].sort((left, right) => (left.range?.start ?? 0) - (right.range?.start ?? 0));
|
|
328
|
+
return [createFileTarget(file), ...withStableIdentities(sortedTargets)];
|
|
385
329
|
}
|
|
386
|
-
function
|
|
330
|
+
function addClassTarget(node, state) {
|
|
387
331
|
if (state.seenClasses.has(node)) return;
|
|
388
|
-
const
|
|
389
|
-
if (!
|
|
332
|
+
const target = createClassTarget(node, state);
|
|
333
|
+
if (!target) return;
|
|
390
334
|
state.seenClasses.add(node);
|
|
391
|
-
state.
|
|
335
|
+
state.targets.push(target);
|
|
392
336
|
}
|
|
393
|
-
function
|
|
337
|
+
function addFunctionTarget(node, state) {
|
|
394
338
|
if (state.seenFunctions.has(node)) return;
|
|
395
|
-
const
|
|
396
|
-
if (!
|
|
339
|
+
const target = createFunctionTarget(node, state);
|
|
340
|
+
if (!target) return;
|
|
397
341
|
state.seenFunctions.add(node);
|
|
398
|
-
state.
|
|
342
|
+
state.targets.push(target);
|
|
399
343
|
}
|
|
400
344
|
function asAstNode(value) {
|
|
401
345
|
return isAstNode(value) ? value : void 0;
|
|
@@ -440,36 +384,69 @@ function collectModuleBindings(program, state) {
|
|
|
440
384
|
if (declaration) collectDeclarationBindings(declaration, state);
|
|
441
385
|
}
|
|
442
386
|
}
|
|
443
|
-
function
|
|
387
|
+
function createClassTarget(node, state) {
|
|
444
388
|
const range = getRange(node);
|
|
445
389
|
if (!range) return;
|
|
446
390
|
const source = sliceRange(state.file, range);
|
|
391
|
+
const name = getNodeName(node) ?? state.inferredNames.get(node);
|
|
447
392
|
return {
|
|
448
|
-
exported: isExportedUnit(node, state),
|
|
449
393
|
file: state.file,
|
|
394
|
+
identity: createRangeIdentity("class", name, range),
|
|
450
395
|
kind: "class",
|
|
396
|
+
language: state.file.language,
|
|
451
397
|
loc: source.loc,
|
|
452
|
-
|
|
398
|
+
metadata: { exported: isExportedTarget(node, state) },
|
|
399
|
+
name,
|
|
400
|
+
origin: {
|
|
401
|
+
physicalPath: state.file.path,
|
|
402
|
+
range
|
|
403
|
+
},
|
|
453
404
|
range,
|
|
454
405
|
text: source.text
|
|
455
406
|
};
|
|
456
407
|
}
|
|
457
|
-
function
|
|
408
|
+
function createFileTarget(file) {
|
|
409
|
+
return {
|
|
410
|
+
file,
|
|
411
|
+
identity: "file",
|
|
412
|
+
kind: "file",
|
|
413
|
+
language: file.language,
|
|
414
|
+
origin: { physicalPath: file.path },
|
|
415
|
+
text: file.text
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function createFunctionTarget(node, state) {
|
|
458
419
|
const range = getRange(node);
|
|
459
420
|
if (!range) return;
|
|
460
421
|
const source = sliceRange(state.file, range);
|
|
461
422
|
const functionNode = node.type === "MethodDefinition" ? asAstNode(node.value) : node;
|
|
423
|
+
const name = getNodeName(node) ?? getNodeName(functionNode) ?? state.inferredNames.get(node);
|
|
462
424
|
return {
|
|
463
|
-
async: functionNode?.async === true,
|
|
464
|
-
exported: isExportedUnit(node, state),
|
|
465
425
|
file: state.file,
|
|
426
|
+
identity: createRangeIdentity("function", name, range),
|
|
466
427
|
kind: "function",
|
|
428
|
+
language: state.file.language,
|
|
467
429
|
loc: source.loc,
|
|
468
|
-
|
|
430
|
+
metadata: {
|
|
431
|
+
async: functionNode?.async === true,
|
|
432
|
+
exported: isExportedTarget(node, state)
|
|
433
|
+
},
|
|
434
|
+
name,
|
|
435
|
+
origin: {
|
|
436
|
+
physicalPath: state.file.path,
|
|
437
|
+
range
|
|
438
|
+
},
|
|
469
439
|
range,
|
|
470
440
|
text: source.text
|
|
471
441
|
};
|
|
472
442
|
}
|
|
443
|
+
function createRangeIdentity(kind, name, range) {
|
|
444
|
+
return `${kind}:${name ?? "anonymous"}:${range.start}:${range.end}`;
|
|
445
|
+
}
|
|
446
|
+
function createSemanticIdentity(target) {
|
|
447
|
+
if (target.kind !== "class" && target.kind !== "function" || !target.name) return;
|
|
448
|
+
return `${target.kind}:${target.name}`;
|
|
449
|
+
}
|
|
473
450
|
function getNodeName(node) {
|
|
474
451
|
if (!node) return;
|
|
475
452
|
if (typeof node.name === "string") return node.name;
|
|
@@ -509,7 +486,7 @@ function isClassNode(node) {
|
|
|
509
486
|
function isExportDeclaration(node) {
|
|
510
487
|
return node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration";
|
|
511
488
|
}
|
|
512
|
-
function
|
|
489
|
+
function isExportedTarget(node, state) {
|
|
513
490
|
return state.exportedNodes.has(node);
|
|
514
491
|
}
|
|
515
492
|
function isFunctionNode(node) {
|
|
@@ -544,9 +521,9 @@ function visit(node, state) {
|
|
|
544
521
|
visitChildren(node, state, /* @__PURE__ */ new Set(["declaration"]));
|
|
545
522
|
return;
|
|
546
523
|
}
|
|
547
|
-
if (isClassNode(node))
|
|
524
|
+
if (isClassNode(node)) addClassTarget(node, state);
|
|
548
525
|
else if (isFunctionNode(node) || node.type === "MethodDefinition") {
|
|
549
|
-
|
|
526
|
+
addFunctionTarget(node, state);
|
|
550
527
|
const methodValue = node.type === "MethodDefinition" ? asAstNode(node.value) : void 0;
|
|
551
528
|
if (methodValue) state.seenFunctions.add(methodValue);
|
|
552
529
|
}
|
|
@@ -566,11 +543,373 @@ function visitChildren(node, state, skippedKeys = /* @__PURE__ */ new Set()) {
|
|
|
566
543
|
}
|
|
567
544
|
}
|
|
568
545
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
546
|
+
function withStableIdentities(targets) {
|
|
547
|
+
const semanticIdentityCounts = /* @__PURE__ */ new Map();
|
|
548
|
+
for (const target of targets) {
|
|
549
|
+
const semanticIdentity = createSemanticIdentity(target);
|
|
550
|
+
if (semanticIdentity) semanticIdentityCounts.set(semanticIdentity, (semanticIdentityCounts.get(semanticIdentity) ?? 0) + 1);
|
|
551
|
+
}
|
|
552
|
+
return targets.map((target) => {
|
|
553
|
+
const semanticIdentity = createSemanticIdentity(target);
|
|
554
|
+
if (!semanticIdentity || semanticIdentityCounts.get(semanticIdentity) !== 1) return target;
|
|
555
|
+
return {
|
|
556
|
+
...target,
|
|
557
|
+
identity: semanticIdentity
|
|
558
|
+
};
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/core/languages/js/index.ts
|
|
563
|
+
const javascriptLanguage = {
|
|
564
|
+
extensions: [
|
|
565
|
+
".cjs",
|
|
566
|
+
".js",
|
|
567
|
+
".jsx",
|
|
568
|
+
".mjs"
|
|
569
|
+
],
|
|
570
|
+
extract: (file) => extractJsSourceTargets(withLanguage$1(file, "javascript")),
|
|
571
|
+
name: "javascript"
|
|
572
|
+
};
|
|
573
|
+
function withLanguage$1(file, language) {
|
|
574
|
+
if (file.language === language) return file;
|
|
575
|
+
return {
|
|
576
|
+
...file,
|
|
577
|
+
language
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/core/languages/registry.ts
|
|
582
|
+
function createLanguageRegistry() {
|
|
583
|
+
return {
|
|
584
|
+
byExtension: /* @__PURE__ */ new Map(),
|
|
585
|
+
languages: /* @__PURE__ */ new Map()
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function registerLanguage(registry, language) {
|
|
589
|
+
const existing = registry.languages.get(language.name);
|
|
590
|
+
if (existing && existing !== language) throw new Error(`Duplicate language "${language.name}".`);
|
|
591
|
+
for (const extension of language.extensions ?? []) {
|
|
592
|
+
const existingOwner = registry.byExtension.get(extension);
|
|
593
|
+
if (existingOwner && existingOwner !== language.name) throw new Error(`Duplicate language extension "${extension}".`);
|
|
594
|
+
}
|
|
595
|
+
registry.languages.set(language.name, language);
|
|
596
|
+
for (const extension of language.extensions ?? []) registry.byExtension.set(extension, language.name);
|
|
597
|
+
}
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/core/languages/text.ts
|
|
600
|
+
const textLanguage = {
|
|
601
|
+
extensions: [],
|
|
602
|
+
extract: (file) => [{
|
|
603
|
+
file,
|
|
604
|
+
identity: "file",
|
|
605
|
+
kind: "file",
|
|
606
|
+
language: "text/plain",
|
|
607
|
+
origin: { physicalPath: file.path },
|
|
608
|
+
text: file.text
|
|
609
|
+
}],
|
|
610
|
+
name: "text/plain"
|
|
611
|
+
};
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/core/languages/ts/index.ts
|
|
614
|
+
const typescriptLanguage = {
|
|
615
|
+
extensions: [
|
|
616
|
+
".cts",
|
|
617
|
+
".mts",
|
|
618
|
+
".ts",
|
|
619
|
+
".tsx"
|
|
620
|
+
],
|
|
621
|
+
extract: (file) => extractJsSourceTargets(withLanguage(file, "typescript")),
|
|
622
|
+
name: "typescript"
|
|
623
|
+
};
|
|
624
|
+
function withLanguage(file, language) {
|
|
625
|
+
if (file.language === language) return file;
|
|
626
|
+
return {
|
|
627
|
+
...file,
|
|
628
|
+
language
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/core/languages/resolve.ts
|
|
633
|
+
function resolveLanguage(file, registry, options) {
|
|
634
|
+
const languageName = options.language ?? options.processedLanguage ?? registry.byExtension.get(extname(file.path)) ?? "text/plain";
|
|
635
|
+
const language = registry.languages.get(languageName);
|
|
636
|
+
if (!language) throw new Error(`Unknown language "${languageName}".`);
|
|
637
|
+
return language;
|
|
638
|
+
}
|
|
639
|
+
//#endregion
|
|
640
|
+
//#region src/core/languages/index.ts
|
|
641
|
+
function createBuiltInLanguageRegistry() {
|
|
642
|
+
const registry = createLanguageRegistry();
|
|
643
|
+
registerLanguage(registry, textLanguage);
|
|
644
|
+
registerLanguage(registry, javascriptLanguage);
|
|
645
|
+
registerLanguage(registry, typescriptLanguage);
|
|
646
|
+
return registry;
|
|
647
|
+
}
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region package.json
|
|
650
|
+
var version = "0.0.6";
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/dsl/registry.ts
|
|
653
|
+
function buildRuleRegistry(config) {
|
|
654
|
+
const rules = /* @__PURE__ */ new Map();
|
|
655
|
+
const localIds = /* @__PURE__ */ new Map();
|
|
656
|
+
const enabledRules = [];
|
|
657
|
+
for (const [alias, plugin] of Object.entries(config.plugins)) for (const [localId, rule] of Object.entries(plugin.rules ?? {})) {
|
|
658
|
+
const id = `${alias}/${localId}`;
|
|
659
|
+
if (rules.has(id)) throw new Error(`Duplicate rule id "${id}".`);
|
|
660
|
+
rules.set(id, rule);
|
|
661
|
+
localIds.set(id, localId);
|
|
662
|
+
}
|
|
663
|
+
for (const [id, entry] of Object.entries(config.rules)) {
|
|
664
|
+
const rule = rules.get(id);
|
|
665
|
+
if (!rule) throw new Error(`Unknown rule "${id}".`);
|
|
666
|
+
const severity = normalizeSeverity(entry);
|
|
667
|
+
if (severity === "off") continue;
|
|
668
|
+
enabledRules.push({
|
|
669
|
+
id,
|
|
670
|
+
localId: localIds.get(id) ?? id,
|
|
671
|
+
rule,
|
|
672
|
+
severity
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
return {
|
|
676
|
+
enabledRules,
|
|
677
|
+
rules
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function normalizeSeverity(entry) {
|
|
681
|
+
if (Array.isArray(entry)) return entry[0] ?? "warn";
|
|
682
|
+
return entry ?? "warn";
|
|
683
|
+
}
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/models/resolve.ts
|
|
686
|
+
function resolveModel(registry, options = {}) {
|
|
687
|
+
const candidates = flattenModels(registry);
|
|
688
|
+
const ruleId = options.ruleId ?? "<unknown>";
|
|
689
|
+
if (options.request !== void 0) {
|
|
690
|
+
const request = options.request;
|
|
691
|
+
const candidate = candidates.find(({ model }) => matchesRequest(model, request));
|
|
692
|
+
if (candidate === void 0) throw new Error(`Unknown model "${request}".`);
|
|
693
|
+
if (!satisfiesHardRequirements(candidate.model, options.requirement)) throw new Error(`Model "${request}" does not satisfy requirement for rule "${ruleId}".`);
|
|
694
|
+
return toResolvedModel(candidate, options.requirement);
|
|
695
|
+
}
|
|
696
|
+
const candidate = preferSize(candidates.filter(({ model }) => satisfiesHardRequirements(model, options.requirement)), options.requirement);
|
|
697
|
+
if (candidate === void 0) throw new Error(`No model satisfies requirement for rule "${ruleId}".`);
|
|
698
|
+
return toResolvedModel(candidate, options.requirement);
|
|
699
|
+
}
|
|
700
|
+
function flattenModels(registry) {
|
|
701
|
+
return registry.providers.flatMap((provider) => provider.models.map((model) => ({
|
|
702
|
+
model,
|
|
703
|
+
provider
|
|
704
|
+
})));
|
|
705
|
+
}
|
|
706
|
+
function matchesRequest(model, request) {
|
|
707
|
+
return model.id === request || model.name === request || (model.aliases ?? []).includes(request);
|
|
708
|
+
}
|
|
709
|
+
function preferSize(candidates, requirement) {
|
|
710
|
+
if (requirement?.size === void 0) return candidates[0];
|
|
711
|
+
return candidates.find(({ model }) => model.size === requirement.size) ?? candidates[0];
|
|
712
|
+
}
|
|
713
|
+
function satisfiesHardRequirements(model, requirement) {
|
|
714
|
+
if (requirement === void 0) return true;
|
|
715
|
+
if (requirement.capabilities !== void 0 && !requirement.capabilities.every((capability) => (model.capabilities ?? []).includes(capability))) return false;
|
|
716
|
+
if (requirement.minContextWindow !== void 0 && (model.contextWindow === void 0 || model.contextWindow < requirement.minContextWindow)) return false;
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
function toResolvedModel(candidate, requirement) {
|
|
720
|
+
const { model, provider } = candidate;
|
|
721
|
+
return {
|
|
722
|
+
aliases: [...model.aliases ?? []],
|
|
723
|
+
capabilities: [...model.capabilities ?? []],
|
|
724
|
+
contextWindow: model.contextWindow,
|
|
725
|
+
id: model.id,
|
|
726
|
+
name: model.name ?? model.id,
|
|
727
|
+
params: {
|
|
728
|
+
...model.defaultParams ?? {},
|
|
729
|
+
...requirement?.params ?? {}
|
|
730
|
+
},
|
|
731
|
+
provider: {
|
|
732
|
+
endpoint: provider.endpoint,
|
|
733
|
+
headers: { ...provider.headers ?? {} },
|
|
734
|
+
id: provider.id,
|
|
735
|
+
type: provider.type
|
|
736
|
+
},
|
|
737
|
+
size: model.size
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/core/cache.ts
|
|
742
|
+
const CACHE_SCHEMA_VERSION = 1;
|
|
743
|
+
const DEFAULT_CACHE_FILE_NAME = ".alintcache";
|
|
744
|
+
const CacheFileSchema = pipe(object({
|
|
745
|
+
createdAt: pipe(string(), description("Cache file creation timestamp.")),
|
|
746
|
+
entries: pipe(unknown(), check((value) => typeof value === "object" && value !== null && !Array.isArray(value)), record(string(), object({
|
|
747
|
+
diagnostics: pipe(array(object({
|
|
748
|
+
filePath: pipe(string(), description("Diagnostic file path.")),
|
|
749
|
+
message: pipe(string(), description("Diagnostic message.")),
|
|
750
|
+
ruleId: pipe(string(), description("Diagnostic rule id.")),
|
|
751
|
+
severity: pipe(union([literal("error"), literal("warn")]), description("Diagnostic severity."))
|
|
752
|
+
})), description("Diagnostics produced for the cached target.")),
|
|
753
|
+
filePath: pipe(string(), description("Cache entry file path.")),
|
|
754
|
+
fingerprint: pipe(object({
|
|
755
|
+
alintVersion: pipe(string(), description("Alint version used to create the cache entry.")),
|
|
756
|
+
configHash: pipe(string(), description("Runner config hash used to create the cache entry.")),
|
|
757
|
+
modelHash: pipe(string(), description("Model configuration hash used to create the cache entry.")),
|
|
758
|
+
ruleHash: pipe(string(), description("Rule configuration hash used to create the cache entry."))
|
|
759
|
+
}), description("Cache entry fingerprint.")),
|
|
760
|
+
target: pipe(object({
|
|
761
|
+
hash: pipe(string(), description("Cached target hash.")),
|
|
762
|
+
identity: pipe(string(), description("Stable cached target identity.")),
|
|
763
|
+
kind: pipe(string(), description("Cached target kind."))
|
|
764
|
+
}), description("Cached target metadata.")),
|
|
765
|
+
usage: pipe(array(object({
|
|
766
|
+
inputTokens: pipe(optional(number()), description("Optional input token count.")),
|
|
767
|
+
modelId: pipe(string(), description("Usage model id.")),
|
|
768
|
+
outputTokens: pipe(optional(number()), description("Optional output token count.")),
|
|
769
|
+
providerId: pipe(string(), description("Usage provider id.")),
|
|
770
|
+
ruleId: pipe(string(), description("Usage rule id.")),
|
|
771
|
+
totalTokens: pipe(optional(number()), description("Optional total token count."))
|
|
772
|
+
})), description("Inference usage records for the cache entry."))
|
|
773
|
+
})), description("Cache entries keyed by cache key.")),
|
|
774
|
+
files: pipe(unknown(), check((value) => typeof value === "object" && value !== null && !Array.isArray(value)), record(string(), object({
|
|
775
|
+
contentHash: pipe(string(), description("Cached file content hash.")),
|
|
776
|
+
entries: pipe(array(string()), description("Cache entry keys associated with the file.")),
|
|
777
|
+
path: pipe(string(), description("Normalized cached file path."))
|
|
778
|
+
})), description("Cached files keyed by normalized file path.")),
|
|
779
|
+
schemaVersion: pipe(literal(CACHE_SCHEMA_VERSION), description("Cache schema version.")),
|
|
780
|
+
updatedAt: pipe(string(), description("Cache file update timestamp."))
|
|
781
|
+
}), description("Alint cache file."));
|
|
782
|
+
function createCacheKey(input) {
|
|
783
|
+
return stableHash(input);
|
|
784
|
+
}
|
|
785
|
+
async function createCacheStore(options) {
|
|
786
|
+
const location = resolveCacheLocation(options.cwd, options.location);
|
|
787
|
+
if (!options.enabled) return createNoopCacheStore(location);
|
|
788
|
+
const cacheFile = await readCacheFile(location);
|
|
789
|
+
return {
|
|
790
|
+
get: (key) => cacheFile.entries[key],
|
|
791
|
+
location,
|
|
792
|
+
markFile: (filePath, contentHash, entries) => {
|
|
793
|
+
const path = normalizeCachePath(options.cwd, filePath);
|
|
794
|
+
cacheFile.files[path] = {
|
|
795
|
+
contentHash,
|
|
796
|
+
entries,
|
|
797
|
+
path
|
|
798
|
+
};
|
|
799
|
+
},
|
|
800
|
+
reconcile: async () => {
|
|
801
|
+
await mkdir(dirname(location), { recursive: true });
|
|
802
|
+
cacheFile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
803
|
+
const tempPath = join(dirname(location), `.${DEFAULT_CACHE_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`);
|
|
804
|
+
await writeFile(tempPath, `${JSON.stringify(cacheFile, null, 2)}\n`);
|
|
805
|
+
await rename(tempPath, location);
|
|
806
|
+
},
|
|
807
|
+
set: (key, entry) => {
|
|
808
|
+
cacheFile.entries[key] = entry;
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function createTargetIdentityResolver(targets) {
|
|
813
|
+
const baseCounts = /* @__PURE__ */ new Map();
|
|
814
|
+
for (const target of targets) {
|
|
815
|
+
const base = createBaseTargetIdentity(target);
|
|
816
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
817
|
+
}
|
|
818
|
+
return (target) => {
|
|
819
|
+
const base = createBaseTargetIdentity(target);
|
|
820
|
+
if ((baseCounts.get(base) ?? 0) <= 1) return base;
|
|
821
|
+
if (target.range) return `${base}:${target.range.start}:${target.range.end}`;
|
|
822
|
+
return base;
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function hashText(text) {
|
|
826
|
+
return createHash("sha256").update(text).digest("hex");
|
|
827
|
+
}
|
|
828
|
+
function normalizeCachePath(cwd, filePath) {
|
|
829
|
+
return relative$1(cwd, isAbsolute$1(filePath) ? resolve$1(filePath) : resolve$1(cwd, filePath)).split(sep).join("/");
|
|
830
|
+
}
|
|
831
|
+
function normalizeRunnerCacheConfig(cache, cwd) {
|
|
832
|
+
if (cache === false) return {
|
|
833
|
+
enabled: false,
|
|
834
|
+
location: resolveCacheLocation(cwd)
|
|
835
|
+
};
|
|
836
|
+
if (cache === true || cache === void 0) return {
|
|
837
|
+
enabled: true,
|
|
838
|
+
location: resolveCacheLocation(cwd)
|
|
839
|
+
};
|
|
840
|
+
return {
|
|
841
|
+
enabled: cache.enabled ?? true,
|
|
842
|
+
location: resolveCacheLocation(cwd, cache.location)
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function resolveCacheLocation(cwd, location) {
|
|
846
|
+
if (!location) return join(cwd, DEFAULT_CACHE_FILE_NAME);
|
|
847
|
+
const resolved = isAbsolute$1(location) ? resolve$1(location) : resolve$1(cwd, location);
|
|
848
|
+
if (location.endsWith("/") || location.endsWith("\\")) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
849
|
+
try {
|
|
850
|
+
if (statSyncIsDirectory(resolved)) return join(resolved, DEFAULT_CACHE_FILE_NAME);
|
|
851
|
+
} catch {}
|
|
852
|
+
return resolved;
|
|
853
|
+
}
|
|
854
|
+
function stableHash(value) {
|
|
855
|
+
return hashText(stableStringify(value));
|
|
856
|
+
}
|
|
857
|
+
function createBaseTargetIdentity(target) {
|
|
858
|
+
if (target.identity && (target.kind !== "file" || target.identity !== "file")) return target.filePath ? `${target.kind}:${target.filePath}:${target.identity}` : `${target.kind}:${target.identity}`;
|
|
859
|
+
if (target.kind === "file") return target.filePath ? `file:${target.filePath}` : "file";
|
|
860
|
+
if (target.name) return `${target.kind}:${target.name}`;
|
|
861
|
+
if (target.range) return `${target.kind}:${target.range.start}:${target.range.end}`;
|
|
862
|
+
return target.kind;
|
|
863
|
+
}
|
|
864
|
+
function createEmptyCacheFile() {
|
|
865
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
866
|
+
return {
|
|
867
|
+
createdAt: now,
|
|
868
|
+
entries: {},
|
|
869
|
+
files: {},
|
|
870
|
+
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
871
|
+
updatedAt: now
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function createNoopCacheStore(location) {
|
|
875
|
+
return {
|
|
876
|
+
get: () => void 0,
|
|
877
|
+
location,
|
|
878
|
+
markFile: () => {},
|
|
879
|
+
reconcile: async () => {},
|
|
880
|
+
set: () => {}
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function isCacheFile(value) {
|
|
884
|
+
return is(CacheFileSchema, value);
|
|
885
|
+
}
|
|
886
|
+
function isRecord(value) {
|
|
887
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
888
|
+
}
|
|
889
|
+
async function readCacheFile(location) {
|
|
890
|
+
try {
|
|
891
|
+
const parsed = JSON.parse(await readFile(location, "utf8"));
|
|
892
|
+
if (isCacheFile(parsed)) return parsed;
|
|
893
|
+
} catch {}
|
|
894
|
+
return createEmptyCacheFile();
|
|
895
|
+
}
|
|
896
|
+
function stableStringify(value) {
|
|
897
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
898
|
+
if (isRecord(value)) return `{${Object.keys(value).sort().map((key) => {
|
|
899
|
+
const property = value[key];
|
|
900
|
+
if (property === void 0) return;
|
|
901
|
+
return `${JSON.stringify(key)}:${stableStringify(property)}`;
|
|
902
|
+
}).filter((entry) => entry !== void 0).join(",")}}`;
|
|
903
|
+
return JSON.stringify(value);
|
|
904
|
+
}
|
|
905
|
+
function statSyncIsDirectory(path) {
|
|
906
|
+
return statSync(path).isDirectory();
|
|
907
|
+
}
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region src/core/run.ts
|
|
910
|
+
var AlintRuleExecutionError = class extends Error {
|
|
911
|
+
failure;
|
|
912
|
+
constructor(error, path) {
|
|
574
913
|
const message = errorMessageFrom(error) ?? String(error);
|
|
575
914
|
super(message, { cause: error });
|
|
576
915
|
this.name = "AlintRuleExecutionError";
|
|
@@ -597,7 +936,7 @@ var AlintRunError = class extends Error {
|
|
|
597
936
|
};
|
|
598
937
|
async function runAlint(options = {}) {
|
|
599
938
|
const cwd$1 = options.cwd ?? cwd();
|
|
600
|
-
const config = options.config ??
|
|
939
|
+
const config = options.config ?? [];
|
|
601
940
|
const setupConfig = options.setupConfig ?? {
|
|
602
941
|
providers: [],
|
|
603
942
|
version: 1
|
|
@@ -605,7 +944,6 @@ async function runAlint(options = {}) {
|
|
|
605
944
|
const clock = options.runner?.clock ?? Date.now;
|
|
606
945
|
const diagnostics = [];
|
|
607
946
|
const usage = createUsageAccumulator();
|
|
608
|
-
const registry = buildRuleRegistry(config);
|
|
609
947
|
const src = createSourceRuntime();
|
|
610
948
|
const normalizedCacheConfig = normalizeRunnerCacheConfig(options.runner?.cache, cwd$1);
|
|
611
949
|
const cacheStore = await createCacheStore({
|
|
@@ -614,7 +952,6 @@ async function runAlint(options = {}) {
|
|
|
614
952
|
location: normalizedCacheConfig.location
|
|
615
953
|
});
|
|
616
954
|
const cacheContext = {
|
|
617
|
-
configHash: stableHash({ rules: config.rules ?? {} }),
|
|
618
955
|
cwd: cwd$1,
|
|
619
956
|
enabled: normalizedCacheConfig.enabled,
|
|
620
957
|
fileEntryKeys: /* @__PURE__ */ new Map(),
|
|
@@ -624,13 +961,44 @@ async function runAlint(options = {}) {
|
|
|
624
961
|
}),
|
|
625
962
|
store: cacheStore
|
|
626
963
|
};
|
|
627
|
-
const files = await Promise.all((options.files ?? []).map(async (filePath) => {
|
|
964
|
+
const files = (await Promise.all((options.files ?? []).map(async (filePath) => {
|
|
628
965
|
const file = await src.readFile(resolve(cwd$1, filePath));
|
|
966
|
+
const resolvedConfig = resolveConfigForFile(file.path, config, { cwd: cwd$1 });
|
|
967
|
+
if (resolvedConfig.ignored) return;
|
|
968
|
+
const effectiveConfig = resolvedConfig.config;
|
|
969
|
+
const languageRegistry = createBuiltInLanguageRegistry();
|
|
970
|
+
for (const plugin of Object.values(effectiveConfig.plugins)) for (const language of Object.values(plugin.languages ?? {})) registerLanguage(languageRegistry, language);
|
|
971
|
+
const language = resolveLanguage(file, languageRegistry, { language: effectiveConfig.language });
|
|
972
|
+
const targets = await language.extract(file, {
|
|
973
|
+
cwd: cwd$1,
|
|
974
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
975
|
+
src
|
|
976
|
+
});
|
|
977
|
+
const registry = buildRuleRegistry(effectiveConfig);
|
|
978
|
+
const ruleRuntimes = createRuleRuntimes({
|
|
979
|
+
cwd: cwd$1,
|
|
980
|
+
diagnostics,
|
|
981
|
+
effectiveSettings: effectiveConfig.settings,
|
|
982
|
+
options,
|
|
983
|
+
registry,
|
|
984
|
+
setupConfig,
|
|
985
|
+
src,
|
|
986
|
+
usage
|
|
987
|
+
});
|
|
629
988
|
return {
|
|
989
|
+
configHash: stableHash({
|
|
990
|
+
language: effectiveConfig.language,
|
|
991
|
+
languageOptions: effectiveConfig.languageOptions,
|
|
992
|
+
processor: effectiveConfig.processor,
|
|
993
|
+
resolvedLanguage: language.name,
|
|
994
|
+
rules: effectiveConfig.rules,
|
|
995
|
+
settings: effectiveConfig.settings
|
|
996
|
+
}),
|
|
630
997
|
file,
|
|
631
|
-
|
|
998
|
+
ruleRuntimes,
|
|
999
|
+
targets
|
|
632
1000
|
};
|
|
633
|
-
}));
|
|
1001
|
+
}))).filter((file) => file !== void 0);
|
|
634
1002
|
let planned = 0;
|
|
635
1003
|
const counters = createRuleEndCounters();
|
|
636
1004
|
const primaryError = createPrimaryErrorState();
|
|
@@ -638,86 +1006,14 @@ async function runAlint(options = {}) {
|
|
|
638
1006
|
let runStartedAt;
|
|
639
1007
|
let runError;
|
|
640
1008
|
try {
|
|
641
|
-
filePlans = createPreparedFileExecutionPlans(
|
|
642
|
-
const executionState = new AsyncLocalStorage();
|
|
643
|
-
const context = {
|
|
644
|
-
cwd: cwd$1,
|
|
645
|
-
id: enabledRule.id,
|
|
646
|
-
localId: enabledRule.localId,
|
|
647
|
-
logger: { debug: () => {} },
|
|
648
|
-
metering: { recordUsage: (record) => {
|
|
649
|
-
const usageRecord = usage.record({
|
|
650
|
-
...record,
|
|
651
|
-
ruleId: record.ruleId ?? enabledRule.id
|
|
652
|
-
});
|
|
653
|
-
const state = executionState.getStore();
|
|
654
|
-
state?.cacheUsage?.push(usageRecord);
|
|
655
|
-
options.progress?.onUsage?.({
|
|
656
|
-
path: state?.progressPath,
|
|
657
|
-
record: usageRecord,
|
|
658
|
-
total: usage.toJSON()
|
|
659
|
-
});
|
|
660
|
-
} },
|
|
661
|
-
model: async (selector) => {
|
|
662
|
-
const request = options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
|
|
663
|
-
const resolvedModel = resolveModel(setupConfig, {
|
|
664
|
-
request,
|
|
665
|
-
requirement: mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector),
|
|
666
|
-
ruleId: enabledRule.id
|
|
667
|
-
});
|
|
668
|
-
const state = executionState.getStore();
|
|
669
|
-
if (state) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
670
|
-
return resolvedModel;
|
|
671
|
-
},
|
|
672
|
-
report: (descriptor) => {
|
|
673
|
-
const state = executionState.getStore();
|
|
674
|
-
const filePath = descriptor.filePath ?? state?.activeFilePath;
|
|
675
|
-
if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
|
|
676
|
-
const diagnosticModel = state?.currentModel ? { ...state.currentModel } : void 0;
|
|
677
|
-
if (state) state.currentModel = void 0;
|
|
678
|
-
const diagnostic = {
|
|
679
|
-
evidence: descriptor.evidence,
|
|
680
|
-
filePath,
|
|
681
|
-
loc: descriptor.loc,
|
|
682
|
-
message: descriptor.message,
|
|
683
|
-
model: diagnosticModel,
|
|
684
|
-
ruleId: enabledRule.id,
|
|
685
|
-
severity: enabledRule.severity
|
|
686
|
-
};
|
|
687
|
-
diagnostics.push(diagnostic);
|
|
688
|
-
state?.cacheDiagnostics?.push(diagnostic);
|
|
689
|
-
options.progress?.onDiagnostic?.({
|
|
690
|
-
diagnostic,
|
|
691
|
-
diagnostics: [...diagnostics],
|
|
692
|
-
path: state?.progressPath
|
|
693
|
-
});
|
|
694
|
-
},
|
|
695
|
-
scope: enabledRule.scope,
|
|
696
|
-
src
|
|
697
|
-
};
|
|
698
|
-
return {
|
|
699
|
-
cacheable: enabledRule.rule.cache !== false,
|
|
700
|
-
enabledRule,
|
|
701
|
-
executionState,
|
|
702
|
-
handlers: enabledRule.rule.create(context),
|
|
703
|
-
ruleHash: stableHash({
|
|
704
|
-
cache: enabledRule.rule.cache ?? true,
|
|
705
|
-
create: String(enabledRule.rule.create),
|
|
706
|
-
id: enabledRule.id,
|
|
707
|
-
localId: enabledRule.localId,
|
|
708
|
-
model: enabledRule.rule.model,
|
|
709
|
-
scope: enabledRule.scope,
|
|
710
|
-
severity: enabledRule.severity
|
|
711
|
-
})
|
|
712
|
-
};
|
|
713
|
-
}), files, cwd$1);
|
|
1009
|
+
filePlans = createPreparedFileExecutionPlans(files, cwd$1);
|
|
714
1010
|
planned = calculatePlannedExecutions(filePlans);
|
|
715
1011
|
runStartedAt = clock();
|
|
716
1012
|
options.progress?.onRunStart?.({
|
|
717
1013
|
files: filePlans.map((filePlan) => toProgressFilePath(filePlan, files.length)),
|
|
718
1014
|
filesTotal: files.length,
|
|
719
1015
|
planned,
|
|
720
|
-
rulesTotal:
|
|
1016
|
+
rulesTotal: countEnabledRuleIds(files),
|
|
721
1017
|
startedAt: runStartedAt
|
|
722
1018
|
});
|
|
723
1019
|
await runConcurrently(filePlans.filter((filePlan) => filePlan.targets.length > 0), resolveFileConcurrency(options.runner?.fileConcurrency), (filePlan) => executeFilePlan(filePlan, files.length, clock, counters, diagnostics, usage, cacheContext, options));
|
|
@@ -750,61 +1046,38 @@ function calculateFilePlanExecutions(filePlan) {
|
|
|
750
1046
|
function calculatePlannedExecutions(filePlans) {
|
|
751
1047
|
return filePlans.reduce((total, filePlan) => total + filePlan.planned, 0);
|
|
752
1048
|
}
|
|
753
|
-
function collectExecutionTargets(
|
|
1049
|
+
function collectExecutionTargets(preparedFile) {
|
|
754
1050
|
const targets = [];
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
executions
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (executions.length > 0) targets.push({
|
|
778
|
-
executions,
|
|
779
|
-
identity: "",
|
|
780
|
-
kind: "class",
|
|
781
|
-
loc: classNode.loc,
|
|
782
|
-
name: classNode.name,
|
|
783
|
-
range: classNode.range,
|
|
784
|
-
text: classNode.text
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
for (const functionNode of preparedFile.units.functions) {
|
|
788
|
-
const executions = ruleRuntimes.map((runtime) => {
|
|
789
|
-
if (!runtime.handlers.onFunction) return void 0;
|
|
790
|
-
return {
|
|
791
|
-
run: () => runtime.handlers.onFunction?.(functionNode),
|
|
792
|
-
runtime
|
|
793
|
-
};
|
|
794
|
-
}).filter((execution) => execution !== void 0);
|
|
795
|
-
if (executions.length > 0) targets.push({
|
|
796
|
-
executions,
|
|
797
|
-
identity: "",
|
|
798
|
-
kind: "function",
|
|
799
|
-
loc: functionNode.loc,
|
|
800
|
-
name: functionNode.name,
|
|
801
|
-
range: functionNode.range,
|
|
802
|
-
text: functionNode.text
|
|
803
|
-
});
|
|
804
|
-
}
|
|
1051
|
+
for (const sourceTarget of preparedFile.targets) {
|
|
1052
|
+
const executions = preparedFile.ruleRuntimes.map((runtime) => {
|
|
1053
|
+
if (!runtime.handlers.onTarget) return;
|
|
1054
|
+
return {
|
|
1055
|
+
run: () => runtime.handlers.onTarget?.(sourceTarget),
|
|
1056
|
+
runtime
|
|
1057
|
+
};
|
|
1058
|
+
}).filter((execution) => execution !== void 0);
|
|
1059
|
+
if (executions.length === 0) continue;
|
|
1060
|
+
targets.push({
|
|
1061
|
+
configHash: preparedFile.configHash,
|
|
1062
|
+
executions,
|
|
1063
|
+
identity: sourceTarget.identity,
|
|
1064
|
+
kind: sourceTarget.kind,
|
|
1065
|
+
language: sourceTarget.language,
|
|
1066
|
+
loc: sourceTarget.loc,
|
|
1067
|
+
metadata: sourceTarget.metadata,
|
|
1068
|
+
name: sourceTarget.name,
|
|
1069
|
+
origin: sourceTarget.origin,
|
|
1070
|
+
range: sourceTarget.range,
|
|
1071
|
+
text: sourceTarget.text
|
|
1072
|
+
});
|
|
805
1073
|
}
|
|
806
1074
|
return targets;
|
|
807
1075
|
}
|
|
1076
|
+
function countEnabledRuleIds(files) {
|
|
1077
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1078
|
+
for (const file of files) for (const runtime of file.ruleRuntimes) ids.add(runtime.enabledRule.id);
|
|
1079
|
+
return ids.size;
|
|
1080
|
+
}
|
|
808
1081
|
function createAlintRunError(error, result) {
|
|
809
1082
|
if (error instanceof AlintRunError) return error;
|
|
810
1083
|
if (error instanceof AlintRuleExecutionError) return new AlintRunError(error.message, result, {
|
|
@@ -817,19 +1090,19 @@ function createExecutionCacheKey(runtime, target, path, cacheContext) {
|
|
|
817
1090
|
if (!cacheContext.enabled || !runtime.cacheable) return;
|
|
818
1091
|
return createCacheKey({
|
|
819
1092
|
alintVersion: version,
|
|
820
|
-
configHash:
|
|
1093
|
+
configHash: target.configHash,
|
|
821
1094
|
filePath: normalizeCachePath(cacheContext.cwd, path.file.path),
|
|
822
1095
|
modelHash: cacheContext.modelHash,
|
|
823
1096
|
ruleHash: runtime.ruleHash,
|
|
824
1097
|
schemaVersion: 1,
|
|
825
|
-
targetHash:
|
|
1098
|
+
targetHash: createTargetHash(target),
|
|
826
1099
|
targetIdentity: target.identity,
|
|
827
1100
|
targetKind: target.kind
|
|
828
1101
|
});
|
|
829
1102
|
}
|
|
830
|
-
function createPreparedFileExecutionPlans(
|
|
1103
|
+
function createPreparedFileExecutionPlans(files, cwd) {
|
|
831
1104
|
return files.map((preparedFile, fileOffset) => {
|
|
832
|
-
const targets = collectExecutionTargets(
|
|
1105
|
+
const targets = collectExecutionTargets(preparedFile);
|
|
833
1106
|
const resolveTargetIdentity = createTargetIdentityResolver(targets.map((target) => toTargetIdentityInput(cwd, preparedFile.file.path, target)));
|
|
834
1107
|
for (const target of targets) target.identity = resolveTargetIdentity(toTargetIdentityInput(cwd, preparedFile.file.path, target));
|
|
835
1108
|
const filePlan = {
|
|
@@ -898,6 +1171,92 @@ function createRuleEndCounters() {
|
|
|
898
1171
|
}
|
|
899
1172
|
};
|
|
900
1173
|
}
|
|
1174
|
+
function createRuleRuntimes(options) {
|
|
1175
|
+
return options.registry.enabledRules.map((enabledRule) => {
|
|
1176
|
+
const executionState = new AsyncLocalStorage();
|
|
1177
|
+
const context = {
|
|
1178
|
+
cwd: options.cwd,
|
|
1179
|
+
id: enabledRule.id,
|
|
1180
|
+
localId: enabledRule.localId,
|
|
1181
|
+
logger: { debug: () => {} },
|
|
1182
|
+
metering: { recordUsage: (record) => {
|
|
1183
|
+
const usageRecord = options.usage.record({
|
|
1184
|
+
...record,
|
|
1185
|
+
ruleId: record.ruleId ?? enabledRule.id
|
|
1186
|
+
});
|
|
1187
|
+
const state = executionState.getStore();
|
|
1188
|
+
state?.cacheUsage?.push(usageRecord);
|
|
1189
|
+
options.options.progress?.onUsage?.({
|
|
1190
|
+
path: state?.progressPath,
|
|
1191
|
+
record: usageRecord,
|
|
1192
|
+
total: options.usage.toJSON()
|
|
1193
|
+
});
|
|
1194
|
+
} },
|
|
1195
|
+
model: async (selector) => {
|
|
1196
|
+
const request = options.options.modelOverride ?? (typeof selector === "string" ? selector : void 0);
|
|
1197
|
+
const requirement = mergeModelRequirement(enabledRule.rule.model, typeof selector === "string" ? void 0 : selector);
|
|
1198
|
+
const resolvedModel = resolveModel(options.setupConfig, {
|
|
1199
|
+
request,
|
|
1200
|
+
requirement,
|
|
1201
|
+
ruleId: enabledRule.id
|
|
1202
|
+
});
|
|
1203
|
+
const state = executionState.getStore();
|
|
1204
|
+
if (state) state.currentModel = toDiagnosticModel(resolvedModel, request);
|
|
1205
|
+
return resolvedModel;
|
|
1206
|
+
},
|
|
1207
|
+
report: (descriptor) => {
|
|
1208
|
+
const state = executionState.getStore();
|
|
1209
|
+
const filePath = descriptor.filePath ?? state?.activeFilePath;
|
|
1210
|
+
if (!filePath) throw new Error(`Diagnostic for rule "${enabledRule.id}" is missing filePath.`);
|
|
1211
|
+
const diagnosticModel = state?.currentModel ? { ...state.currentModel } : void 0;
|
|
1212
|
+
if (state) state.currentModel = void 0;
|
|
1213
|
+
const diagnostic = {
|
|
1214
|
+
evidence: descriptor.evidence,
|
|
1215
|
+
filePath,
|
|
1216
|
+
loc: descriptor.loc,
|
|
1217
|
+
message: descriptor.message,
|
|
1218
|
+
model: diagnosticModel,
|
|
1219
|
+
ruleId: enabledRule.id,
|
|
1220
|
+
severity: enabledRule.severity
|
|
1221
|
+
};
|
|
1222
|
+
options.diagnostics.push(diagnostic);
|
|
1223
|
+
state?.cacheDiagnostics?.push(diagnostic);
|
|
1224
|
+
options.options.progress?.onDiagnostic?.({
|
|
1225
|
+
diagnostic,
|
|
1226
|
+
diagnostics: [...options.diagnostics],
|
|
1227
|
+
path: state?.progressPath
|
|
1228
|
+
});
|
|
1229
|
+
},
|
|
1230
|
+
settings: options.effectiveSettings,
|
|
1231
|
+
src: options.src
|
|
1232
|
+
};
|
|
1233
|
+
return {
|
|
1234
|
+
cacheable: enabledRule.rule.cache !== false,
|
|
1235
|
+
enabledRule,
|
|
1236
|
+
executionState,
|
|
1237
|
+
handlers: enabledRule.rule.create(context),
|
|
1238
|
+
ruleHash: stableHash({
|
|
1239
|
+
cache: enabledRule.rule.cache ?? true,
|
|
1240
|
+
create: String(enabledRule.rule.create),
|
|
1241
|
+
id: enabledRule.id,
|
|
1242
|
+
localId: enabledRule.localId,
|
|
1243
|
+
model: enabledRule.rule.model,
|
|
1244
|
+
severity: enabledRule.severity
|
|
1245
|
+
})
|
|
1246
|
+
};
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function createTargetHash(target) {
|
|
1250
|
+
return stableHash({
|
|
1251
|
+
language: target.language,
|
|
1252
|
+
loc: target.loc,
|
|
1253
|
+
metadata: target.metadata,
|
|
1254
|
+
name: target.name,
|
|
1255
|
+
origin: target.origin,
|
|
1256
|
+
range: target.range,
|
|
1257
|
+
text: target.text
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
901
1260
|
function createUsageAccumulator() {
|
|
902
1261
|
const records = [];
|
|
903
1262
|
let inputTokens = 0;
|
|
@@ -1052,12 +1411,12 @@ async function executeProgressTarget(execution, path, target, clock, counters, d
|
|
|
1052
1411
|
filePath: normalizeCachePath(cacheContext.cwd, path.file.path),
|
|
1053
1412
|
fingerprint: {
|
|
1054
1413
|
alintVersion: version,
|
|
1055
|
-
configHash:
|
|
1414
|
+
configHash: target.configHash,
|
|
1056
1415
|
modelHash: cacheContext.modelHash,
|
|
1057
1416
|
ruleHash: execution.runtime.ruleHash
|
|
1058
1417
|
},
|
|
1059
1418
|
target: {
|
|
1060
|
-
hash:
|
|
1419
|
+
hash: createTargetHash(target),
|
|
1061
1420
|
identity: target.identity,
|
|
1062
1421
|
kind: target.kind,
|
|
1063
1422
|
loc: target.loc,
|
|
@@ -1190,6 +1549,7 @@ function toProgressFilePath(filePlan, filesTotal) {
|
|
|
1190
1549
|
function toTargetIdentityInput(cwd, filePath, target) {
|
|
1191
1550
|
return {
|
|
1192
1551
|
filePath: target.kind === "file" ? normalizeCachePath(cwd, filePath) : void 0,
|
|
1552
|
+
identity: target.identity,
|
|
1193
1553
|
kind: target.kind,
|
|
1194
1554
|
name: target.name,
|
|
1195
1555
|
range: target.range
|
|
@@ -1207,4 +1567,4 @@ function defineRule(rule) {
|
|
|
1207
1567
|
return rule;
|
|
1208
1568
|
}
|
|
1209
1569
|
//#endregion
|
|
1210
|
-
export { AlintRunError, buildRuleRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule,
|
|
1570
|
+
export { AlintRunError, buildRuleRegistry, createBuiltInLanguageRegistry, createSourceFile, createSourceRuntime, defineConfig, definePlugin, defineRule, extractJsSourceTargets, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, registerLanguage, resolveConfigForFile, resolveLanguage, resolveModel, runAlint, sliceLines, sliceRange };
|