@alint-js/core 0.1.4 → 0.1.5
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 +11 -7
- package/dist/agent.d.mts +1 -1
- package/dist/{define-8BzWDcPs.d.mts → define-CJ2QJPZ8.d.mts} +1 -1
- package/dist/dsl.d.mts +2 -2
- package/dist/extract-BVbjWXMC.mjs +676 -0
- package/dist/index.d.mts +109 -7
- package/dist/index.mjs +867 -384
- package/dist/languages-js.d.mts +1 -1
- package/dist/languages-js.mjs +1 -1
- package/dist/structured-output.d.mts +1 -1
- package/dist/types-DM4idR4w.d.mts +179 -0
- package/dist/{types-DKylUrAn.d.mts → types-deBCDA4_.d.mts} +49 -6
- package/package.json +2 -1
- package/dist/extract-CIWssxNK.mjs +0 -358
- package/dist/types-DSvXUVZ7.d.mts +0 -86
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { clamp } from "es-toolkit";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { parseSync } from "oxc-parser";
|
|
6
|
+
//#region src/core/hash.ts
|
|
7
|
+
function createStableHasher() {
|
|
8
|
+
const hash = createHash("sha256");
|
|
9
|
+
const api = {
|
|
10
|
+
digest: () => hash.digest("hex"),
|
|
11
|
+
update: (value) => {
|
|
12
|
+
const serialized = stableStringify(value);
|
|
13
|
+
hash.update(`${serialized.length}:`);
|
|
14
|
+
hash.update(serialized);
|
|
15
|
+
return api;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return api;
|
|
19
|
+
}
|
|
20
|
+
function hashText(text) {
|
|
21
|
+
return createHash("sha256").update(text).digest("hex");
|
|
22
|
+
}
|
|
23
|
+
function stableHash(value) {
|
|
24
|
+
return hashText(stableStringify(value));
|
|
25
|
+
}
|
|
26
|
+
function stableStringify(value) {
|
|
27
|
+
if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
28
|
+
if (typeof value === "object" && value !== null) {
|
|
29
|
+
const record = value;
|
|
30
|
+
return `{${Object.keys(record).sort().flatMap((key) => {
|
|
31
|
+
const property = record[key];
|
|
32
|
+
return property === void 0 ? [] : [`${JSON.stringify(key)}:${stableStringify(property)}`];
|
|
33
|
+
}).join(",")}}`;
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify(value);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/core/source/runtime.ts
|
|
39
|
+
var SourceChangedError = class extends Error {
|
|
40
|
+
path;
|
|
41
|
+
expectedHash;
|
|
42
|
+
actualHash;
|
|
43
|
+
constructor(path, expectedHash, actualHash) {
|
|
44
|
+
super(`Source changed after planning: "${path}" (expected ${expectedHash}, read ${actualHash}).`);
|
|
45
|
+
this.path = path;
|
|
46
|
+
this.expectedHash = expectedHash;
|
|
47
|
+
this.actualHash = actualHash;
|
|
48
|
+
this.name = "SourceChangedError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function createSourceFile(path, text) {
|
|
52
|
+
return {
|
|
53
|
+
contentHash: hashText(text),
|
|
54
|
+
language: inferLanguage(path),
|
|
55
|
+
lines: text.split(/\r?\n/),
|
|
56
|
+
path,
|
|
57
|
+
text
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function createSourceRuntime(options = {}) {
|
|
61
|
+
return {
|
|
62
|
+
extract: options.extract ?? extractThrowUndefined,
|
|
63
|
+
getText,
|
|
64
|
+
readFile: options.readFile ?? readSourceFile,
|
|
65
|
+
sliceLines,
|
|
66
|
+
sliceRange
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getText(file) {
|
|
70
|
+
return file.text;
|
|
71
|
+
}
|
|
72
|
+
function sliceLines(file, range) {
|
|
73
|
+
const startLine = clampLine(range.startLine, file.lines.length);
|
|
74
|
+
const endLine = clampLine(range.endLine, file.lines.length);
|
|
75
|
+
const orderedStartLine = Math.min(startLine, endLine);
|
|
76
|
+
const orderedEndLine = Math.max(startLine, endLine);
|
|
77
|
+
const text = file.lines.slice(orderedStartLine - 1, orderedEndLine).join("\n");
|
|
78
|
+
return {
|
|
79
|
+
filePath: file.path,
|
|
80
|
+
loc: {
|
|
81
|
+
end: {
|
|
82
|
+
column: file.lines[orderedEndLine - 1]?.length ?? 0,
|
|
83
|
+
line: orderedEndLine
|
|
84
|
+
},
|
|
85
|
+
start: {
|
|
86
|
+
column: 0,
|
|
87
|
+
line: orderedStartLine
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
text
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function sliceRange(file, range) {
|
|
94
|
+
const start = clampOffset(range.start, file.text.length);
|
|
95
|
+
const end = clampOffset(range.end, file.text.length);
|
|
96
|
+
const orderedStart = Math.min(start, end);
|
|
97
|
+
const orderedEnd = Math.max(start, end);
|
|
98
|
+
return {
|
|
99
|
+
filePath: file.path,
|
|
100
|
+
loc: {
|
|
101
|
+
end: getPosition(file.text, orderedEnd),
|
|
102
|
+
start: getPosition(file.text, orderedStart)
|
|
103
|
+
},
|
|
104
|
+
text: file.text.slice(orderedStart, orderedEnd)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function withLanguage(file, language) {
|
|
108
|
+
if (file.language === language) return file;
|
|
109
|
+
return {
|
|
110
|
+
...file,
|
|
111
|
+
language
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function clampLine(line, lineCount) {
|
|
115
|
+
if (lineCount === 0) return 1;
|
|
116
|
+
return clamp(Math.trunc(line), 1, lineCount);
|
|
117
|
+
}
|
|
118
|
+
function clampOffset(offset, textLength) {
|
|
119
|
+
return clamp(Math.trunc(offset), 0, textLength);
|
|
120
|
+
}
|
|
121
|
+
/** Throws a TypeError indicating that the extractor is not defined. */
|
|
122
|
+
function extractThrowUndefined(filePath) {
|
|
123
|
+
return Promise.reject(/* @__PURE__ */ new TypeError(`Cannot extract "${filePath}": this source runtime was created without an extractor, so there is no config to resolve the file's language against. Only the runtime a run hands to rules (ctx.src) can extract.`));
|
|
124
|
+
}
|
|
125
|
+
function getPosition(text, offset) {
|
|
126
|
+
let line = 1;
|
|
127
|
+
let column = 0;
|
|
128
|
+
let index = 0;
|
|
129
|
+
while (index < offset) {
|
|
130
|
+
const character = text[index];
|
|
131
|
+
if (character === "\r") {
|
|
132
|
+
if (text[index + 1] === "\n" && index + 1 < offset) index += 1;
|
|
133
|
+
line += 1;
|
|
134
|
+
column = 0;
|
|
135
|
+
} else if (character === "\n") {
|
|
136
|
+
line += 1;
|
|
137
|
+
column = 0;
|
|
138
|
+
} else column += 1;
|
|
139
|
+
index += 1;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
column,
|
|
143
|
+
line
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function inferLanguage(path) {
|
|
147
|
+
switch (extname(path)) {
|
|
148
|
+
case ".cjs":
|
|
149
|
+
case ".js":
|
|
150
|
+
case ".jsx":
|
|
151
|
+
case ".mjs": return "javascript";
|
|
152
|
+
case ".cts":
|
|
153
|
+
case ".mts":
|
|
154
|
+
case ".ts":
|
|
155
|
+
case ".tsx": return "typescript";
|
|
156
|
+
default: return "unknown";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function readSourceFile(input) {
|
|
160
|
+
const path = typeof input === "string" ? input : input.path;
|
|
161
|
+
const file = createSourceFile(path, await readFile(path, "utf8"));
|
|
162
|
+
if (typeof input !== "string") {
|
|
163
|
+
if (file.contentHash !== input.contentHash) throw new SourceChangedError(input.path, input.contentHash, file.contentHash);
|
|
164
|
+
}
|
|
165
|
+
return file;
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/core/source/identity.ts
|
|
169
|
+
/** A target's identity from its position. */
|
|
170
|
+
function targetIdentity(kind, name, range) {
|
|
171
|
+
return `${kind}:${name ?? "anonymous"}:${range.start}:${range.end}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Swaps a target's positional identity for its name, where that name is unique among these targets.
|
|
175
|
+
*
|
|
176
|
+
* Identities key caches, so a function that only moved should keep the identity it had. Two targets
|
|
177
|
+
* sharing a name cannot be told apart that way, so both keep the positional form.
|
|
178
|
+
*
|
|
179
|
+
* Run every function and class target through this. Skip it in one producer and the same function
|
|
180
|
+
* ends up with a different identity depending on which language read it.
|
|
181
|
+
*/
|
|
182
|
+
function withStableIdentities(targets) {
|
|
183
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const target of targets) {
|
|
185
|
+
const identity = nameIdentity(target);
|
|
186
|
+
if (identity) nameCounts.set(identity, (nameCounts.get(identity) ?? 0) + 1);
|
|
187
|
+
}
|
|
188
|
+
return targets.map((target) => {
|
|
189
|
+
const identity = nameIdentity(target);
|
|
190
|
+
if (!identity || nameCounts.get(identity) !== 1) return target;
|
|
191
|
+
return {
|
|
192
|
+
...target,
|
|
193
|
+
identity
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function nameIdentity(target) {
|
|
198
|
+
if (target.kind !== "class" && target.kind !== "function" || !target.name) return;
|
|
199
|
+
return `${target.kind}:${target.name}`;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/core/languages/js/ast.ts
|
|
203
|
+
function asAstNode(value) {
|
|
204
|
+
return isAstNode(value) ? value : void 0;
|
|
205
|
+
}
|
|
206
|
+
function childNodes(node) {
|
|
207
|
+
const children = [];
|
|
208
|
+
for (const [key, value] of Object.entries(node)) {
|
|
209
|
+
if (key === "type" || key === "start" || key === "end" || key === "range") continue;
|
|
210
|
+
if (Array.isArray(value)) children.push(...value.filter(isAstNode));
|
|
211
|
+
else if (isAstNode(value)) children.push(value);
|
|
212
|
+
}
|
|
213
|
+
return children;
|
|
214
|
+
}
|
|
215
|
+
function getRange(node) {
|
|
216
|
+
if (typeof node.start === "number" && typeof node.end === "number") return {
|
|
217
|
+
end: node.end,
|
|
218
|
+
start: node.start
|
|
219
|
+
};
|
|
220
|
+
if (Array.isArray(node.range) && node.range.length === 2) return {
|
|
221
|
+
end: node.range[1],
|
|
222
|
+
start: node.range[0]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function isAstNode(value) {
|
|
226
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/core/languages/js/info.ts
|
|
230
|
+
/**
|
|
231
|
+
* A `TS…` node is a type, and a type is never renameable, so the subtree is skipped — that drops
|
|
232
|
+
* annotations, interfaces and type aliases under one rule. tree-sitter needs no such rule: its
|
|
233
|
+
* grammar gives type names their own node type, `type_identifier`, separate from `identifier`.
|
|
234
|
+
*
|
|
235
|
+
* These few `TS…` nodes wrap real code, so they are walked at the named field and nowhere else.
|
|
236
|
+
*/
|
|
237
|
+
const TS_INNER_FIELD = {
|
|
238
|
+
TSAsExpression: "expression",
|
|
239
|
+
TSInstantiationExpression: "expression",
|
|
240
|
+
TSNonNullExpression: "expression",
|
|
241
|
+
TSParameterProperty: "parameter",
|
|
242
|
+
TSSatisfiesExpression: "expression",
|
|
243
|
+
TSTypeAnnotation: "typeAnnotation",
|
|
244
|
+
TSTypeAssertion: "expression"
|
|
245
|
+
};
|
|
246
|
+
function collectSourceInfo(program, comments) {
|
|
247
|
+
const info = {
|
|
248
|
+
calls: [],
|
|
249
|
+
commentRanges: [],
|
|
250
|
+
declaredNames: /* @__PURE__ */ new Map(),
|
|
251
|
+
identifierRanges: /* @__PURE__ */ new Map()
|
|
252
|
+
};
|
|
253
|
+
for (const comment of comments) {
|
|
254
|
+
const range = isAstNode(comment) ? getRange(comment) : void 0;
|
|
255
|
+
if (range) info.commentRanges.push(range);
|
|
256
|
+
}
|
|
257
|
+
walk(program, info);
|
|
258
|
+
info.calls.sort((left, right) => left.range.start - right.range.start);
|
|
259
|
+
return info;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* `name` is resolved by the caller, because a function may be named by something outside itself:
|
|
263
|
+
* `const trim = () => {}` names the arrow from its declarator.
|
|
264
|
+
*/
|
|
265
|
+
function functionInfo(node, range, name, exported, info) {
|
|
266
|
+
const { arrowExpression, statements } = bodyOf(node);
|
|
267
|
+
const declaredNames = new Set([...info.declaredNames].filter(([start]) => start >= range.start && start < range.end).sort(([left], [right]) => left - right).map(([, name]) => name));
|
|
268
|
+
if (name !== void 0) declaredNames.add(name);
|
|
269
|
+
return {
|
|
270
|
+
bodyIsSingleExpression: arrowExpression || statements.length === 1 && !holdsBlock(statements[0]),
|
|
271
|
+
bodyStatements: statements.length,
|
|
272
|
+
commentRanges: rangesInside(info.commentRanges, range),
|
|
273
|
+
declaredNames: [...declaredNames],
|
|
274
|
+
exported,
|
|
275
|
+
identifierRanges: withOwnName(rangesInside([...info.identifierRanges.values()], range), node, range)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function asNodeList(value) {
|
|
279
|
+
return Array.isArray(value) ? value.filter(isAstNode) : [];
|
|
280
|
+
}
|
|
281
|
+
function bodyOf(node) {
|
|
282
|
+
const callable = (node.type === "MethodDefinition" ? asAstNode(node.value) : void 0) ?? node;
|
|
283
|
+
const body = asAstNode(callable.body);
|
|
284
|
+
if (!body) return {
|
|
285
|
+
arrowExpression: false,
|
|
286
|
+
statements: []
|
|
287
|
+
};
|
|
288
|
+
if (callable.type === "ArrowFunctionExpression" && body.type !== "BlockStatement") return {
|
|
289
|
+
arrowExpression: true,
|
|
290
|
+
statements: [body]
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
arrowExpression: false,
|
|
294
|
+
statements: asNodeList(body.body)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function callSiteOf(node) {
|
|
298
|
+
const callee = asAstNode(node.callee);
|
|
299
|
+
if (!callee) return;
|
|
300
|
+
if (callee.type === "Identifier") {
|
|
301
|
+
const range = nameRangeOf(callee);
|
|
302
|
+
return range && callee.name !== void 0 ? {
|
|
303
|
+
name: callee.name,
|
|
304
|
+
range
|
|
305
|
+
} : void 0;
|
|
306
|
+
}
|
|
307
|
+
if (callee.type !== "MemberExpression" || callee.computed === true) return;
|
|
308
|
+
const property = asAstNode(callee.property);
|
|
309
|
+
const range = property ? nameRangeOf(property) : void 0;
|
|
310
|
+
return range && property?.name !== void 0 ? {
|
|
311
|
+
name: property.name,
|
|
312
|
+
range
|
|
313
|
+
} : void 0;
|
|
314
|
+
}
|
|
315
|
+
/** A nested block is how a branch or a loop is written, so it disqualifies a single-expression body. */
|
|
316
|
+
function holdsBlock(node) {
|
|
317
|
+
if (!node) return false;
|
|
318
|
+
const pending = childNodes(node);
|
|
319
|
+
while (pending.length > 0) {
|
|
320
|
+
const next = pending.pop();
|
|
321
|
+
if (next === void 0) continue;
|
|
322
|
+
if (next.type === "BlockStatement") return true;
|
|
323
|
+
pending.push(...childNodes(next));
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* An identifier node spans its type annotation: `entry: Entry` is one `Identifier` whose `name` is
|
|
329
|
+
* `entry`. The annotation has to be cut back off, or replacing the identifier would take the type
|
|
330
|
+
* with it and a copy that renamed nothing but its parameter would stop matching.
|
|
331
|
+
*
|
|
332
|
+
* NOTICE: `name` is the DECODED identifier, so one written with a unicode escape measures shorter
|
|
333
|
+
* than the token it came from and this range cuts inside it, where tree-sitter reports the whole
|
|
334
|
+
* token — the two extractors disagree for such identifiers. Left as is because a correct fix needs
|
|
335
|
+
* the parser to report the name's own span. Removal condition: oxc exposes that span.
|
|
336
|
+
*/
|
|
337
|
+
function nameRangeOf(node) {
|
|
338
|
+
const range = getRange(node);
|
|
339
|
+
if (!range || typeof node.name !== "string") return;
|
|
340
|
+
return {
|
|
341
|
+
end: Math.min(range.end, range.start + node.name.length),
|
|
342
|
+
start: range.start
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function rangesInside(ranges, outer) {
|
|
346
|
+
return ranges.filter((range) => range.start >= outer.start && range.end <= outer.end).map((range) => ({
|
|
347
|
+
end: range.end - outer.start,
|
|
348
|
+
start: range.start - outer.start
|
|
349
|
+
})).sort((left, right) => left.start - right.start);
|
|
350
|
+
}
|
|
351
|
+
function recordDeclared(node, info) {
|
|
352
|
+
const range = nameRangeOf(node);
|
|
353
|
+
if (range && node.name !== void 0) info.declaredNames.set(range.start, node.name);
|
|
354
|
+
}
|
|
355
|
+
function recordIdentifier(node, info) {
|
|
356
|
+
const range = nameRangeOf(node);
|
|
357
|
+
if (range) info.identifierRanges.set(range.start, range);
|
|
358
|
+
}
|
|
359
|
+
function recordPatternNames(node, info) {
|
|
360
|
+
if (!node) return;
|
|
361
|
+
switch (node.type) {
|
|
362
|
+
case "ArrayPattern":
|
|
363
|
+
for (const element of asNodeList(node.elements)) recordPatternNames(element, info);
|
|
364
|
+
break;
|
|
365
|
+
case "AssignmentPattern":
|
|
366
|
+
recordPatternNames(asAstNode(node.left), info);
|
|
367
|
+
break;
|
|
368
|
+
case "Identifier":
|
|
369
|
+
recordDeclared(node, info);
|
|
370
|
+
break;
|
|
371
|
+
case "ObjectPattern":
|
|
372
|
+
for (const property of asNodeList(node.properties)) if (property.type === "RestElement") recordPatternNames(asAstNode(property.argument), info);
|
|
373
|
+
else if (!property.shorthand) recordPatternNames(asAstNode(property.value), info);
|
|
374
|
+
break;
|
|
375
|
+
case "RestElement":
|
|
376
|
+
recordPatternNames(asAstNode(node.argument), info);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function walk(node, info) {
|
|
381
|
+
const type = node.type ?? "";
|
|
382
|
+
if (type === "TSTypePredicate") {
|
|
383
|
+
const parameterName = asAstNode(node.parameterName);
|
|
384
|
+
if (parameterName) recordIdentifier(parameterName, info);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (type.startsWith("TS")) {
|
|
388
|
+
const inner = asAstNode(node[TS_INNER_FIELD[type] ?? ""]);
|
|
389
|
+
if (inner) walk(inner, info);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
switch (type) {
|
|
393
|
+
case "ArrowFunctionExpression":
|
|
394
|
+
case "FunctionDeclaration":
|
|
395
|
+
case "FunctionExpression":
|
|
396
|
+
recordPatternNames(asAstNode(node.id), info);
|
|
397
|
+
for (const parameter of asNodeList(node.params)) recordPatternNames(parameter, info);
|
|
398
|
+
break;
|
|
399
|
+
case "CallExpression": {
|
|
400
|
+
const call = callSiteOf(node);
|
|
401
|
+
if (call) info.calls.push(call);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "CatchClause":
|
|
405
|
+
recordPatternNames(asAstNode(node.param), info);
|
|
406
|
+
break;
|
|
407
|
+
case "Identifier":
|
|
408
|
+
recordIdentifier(node, info);
|
|
409
|
+
return;
|
|
410
|
+
case "MemberExpression": {
|
|
411
|
+
const object = asAstNode(node.object);
|
|
412
|
+
if (object) walk(object, info);
|
|
413
|
+
if (node.computed === true) {
|
|
414
|
+
const property = asAstNode(node.property);
|
|
415
|
+
if (property) walk(property, info);
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case "MethodDefinition":
|
|
420
|
+
case "Property":
|
|
421
|
+
case "PropertyDefinition": {
|
|
422
|
+
if (node.shorthand === true) return;
|
|
423
|
+
if (node.computed === true) {
|
|
424
|
+
const key = asAstNode(node.key);
|
|
425
|
+
if (key) walk(key, info);
|
|
426
|
+
}
|
|
427
|
+
const value = asAstNode(node.value);
|
|
428
|
+
if (value) walk(value, info);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
case "VariableDeclarator":
|
|
432
|
+
recordPatternNames(asAstNode(node.id), info);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
for (const child of childNodes(node)) walk(child, info);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* A method's name is a key, which the walk keeps out of the identifier set, so it is put back here.
|
|
439
|
+
* A declared function's name is already there, hence the dedupe by start offset.
|
|
440
|
+
*/
|
|
441
|
+
function withOwnName(ranges, node, outer) {
|
|
442
|
+
const nameNode = asAstNode(node.id) ?? (node.type === "MethodDefinition" ? asAstNode(node.key) : void 0);
|
|
443
|
+
const name = nameNode ? nameRangeOf(nameNode) : void 0;
|
|
444
|
+
if (!name || name.start < outer.start || name.end > outer.end) return ranges;
|
|
445
|
+
const rebased = {
|
|
446
|
+
end: name.end - outer.start,
|
|
447
|
+
start: name.start - outer.start
|
|
448
|
+
};
|
|
449
|
+
if (ranges.some((range) => range.start === rebased.start)) return ranges;
|
|
450
|
+
return [...ranges, rebased].sort((left, right) => left.start - right.start);
|
|
451
|
+
}
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/core/languages/js/parser.ts
|
|
454
|
+
function parseSync$1(filename, sourceText, options) {
|
|
455
|
+
return parseSync(filename, sourceText, options);
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/core/languages/js/extract.ts
|
|
459
|
+
function extractJsSourceTargets(file) {
|
|
460
|
+
const result = parseSync$1(file.path, file.text, { sourceType: "module" });
|
|
461
|
+
const program = result.program;
|
|
462
|
+
const state = {
|
|
463
|
+
bindingNodes: /* @__PURE__ */ new Map(),
|
|
464
|
+
exportedNodes: /* @__PURE__ */ new Set(),
|
|
465
|
+
file,
|
|
466
|
+
inferredNames: /* @__PURE__ */ new Map(),
|
|
467
|
+
info: collectSourceInfo(program, result.comments),
|
|
468
|
+
seenClasses: /* @__PURE__ */ new Set(),
|
|
469
|
+
seenFunctions: /* @__PURE__ */ new Set(),
|
|
470
|
+
targets: [],
|
|
471
|
+
visited: /* @__PURE__ */ new Set()
|
|
472
|
+
};
|
|
473
|
+
collectModuleBindings(program, state);
|
|
474
|
+
collectExportedBindings(program, state);
|
|
475
|
+
visit(program, state);
|
|
476
|
+
const sortedTargets = [...state.targets].sort((left, right) => (left.range?.start ?? 0) - (right.range?.start ?? 0));
|
|
477
|
+
return [createFileTarget(file, state.info), ...withStableIdentities(sortedTargets)];
|
|
478
|
+
}
|
|
479
|
+
function addClassTarget(node, state) {
|
|
480
|
+
if (state.seenClasses.has(node)) return;
|
|
481
|
+
const target = createClassTarget(node, state);
|
|
482
|
+
if (!target) return;
|
|
483
|
+
state.seenClasses.add(node);
|
|
484
|
+
state.targets.push(target);
|
|
485
|
+
}
|
|
486
|
+
function addFunctionTarget(node, state) {
|
|
487
|
+
if (state.seenFunctions.has(node)) return;
|
|
488
|
+
const target = createFunctionTarget(node, state);
|
|
489
|
+
if (!target) return;
|
|
490
|
+
state.seenFunctions.add(node);
|
|
491
|
+
state.targets.push(target);
|
|
492
|
+
}
|
|
493
|
+
function collectDeclarationBindings(node, state) {
|
|
494
|
+
const name = getNodeName(node);
|
|
495
|
+
if (name && (isClassNode(node) || isFunctionNode(node))) {
|
|
496
|
+
state.bindingNodes.set(name, node);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
|
|
500
|
+
for (const declarator of node.declarations) {
|
|
501
|
+
if (!isAstNode(declarator)) continue;
|
|
502
|
+
const id = asAstNode(declarator.id);
|
|
503
|
+
const initializer = asAstNode(declarator.init);
|
|
504
|
+
if (typeof id?.name === "string" && initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.bindingNodes.set(id.name, initializer);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function collectExportedBindings(node, state) {
|
|
508
|
+
if (!node) return;
|
|
509
|
+
if (Array.isArray(node)) {
|
|
510
|
+
for (const child of node) collectExportedBindings(child, state);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (node.type === "ExportNamedDeclaration" && !node.source && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
|
|
514
|
+
if (!isAstNode(specifier) || specifier.type !== "ExportSpecifier") continue;
|
|
515
|
+
const local = asAstNode(specifier.local);
|
|
516
|
+
if (typeof local?.name === "string") markExportedBinding(local.name, state);
|
|
517
|
+
}
|
|
518
|
+
else if (node.type === "ExportDefaultDeclaration") {
|
|
519
|
+
const declaration = asAstNode(node.declaration);
|
|
520
|
+
if (typeof declaration?.name === "string") markExportedBinding(declaration.name, state);
|
|
521
|
+
}
|
|
522
|
+
for (const value of Object.values(node)) if (Array.isArray(value)) collectExportedBindings(value.filter(isAstNode), state);
|
|
523
|
+
else if (isAstNode(value)) collectExportedBindings(value, state);
|
|
524
|
+
}
|
|
525
|
+
function collectModuleBindings(program, state) {
|
|
526
|
+
if (!Array.isArray(program.body)) return;
|
|
527
|
+
for (const statement of program.body) {
|
|
528
|
+
if (!isAstNode(statement)) continue;
|
|
529
|
+
const declaration = isExportDeclaration(statement) ? asAstNode(statement.declaration) : statement;
|
|
530
|
+
if (declaration) collectDeclarationBindings(declaration, state);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function createClassTarget(node, state) {
|
|
534
|
+
const range = getRange(node);
|
|
535
|
+
if (!range) return;
|
|
536
|
+
const source = sliceRange(state.file, range);
|
|
537
|
+
const name = getNodeName(node) ?? state.inferredNames.get(node);
|
|
538
|
+
return {
|
|
539
|
+
file: state.file,
|
|
540
|
+
identity: targetIdentity("class", name, range),
|
|
541
|
+
kind: "class",
|
|
542
|
+
language: state.file.language,
|
|
543
|
+
loc: source.loc,
|
|
544
|
+
metadata: { exported: isExportedTarget(node, state) },
|
|
545
|
+
name,
|
|
546
|
+
origin: {
|
|
547
|
+
physicalPath: state.file.path,
|
|
548
|
+
range
|
|
549
|
+
},
|
|
550
|
+
range,
|
|
551
|
+
text: source.text
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function createFileTarget(file, info) {
|
|
555
|
+
return {
|
|
556
|
+
file,
|
|
557
|
+
identity: "file",
|
|
558
|
+
kind: "file",
|
|
559
|
+
language: file.language,
|
|
560
|
+
metadata: { calls: info.calls },
|
|
561
|
+
origin: { physicalPath: file.path },
|
|
562
|
+
text: file.text
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function createFunctionTarget(node, state) {
|
|
566
|
+
const range = getRange(node);
|
|
567
|
+
if (!range) return;
|
|
568
|
+
const source = sliceRange(state.file, range);
|
|
569
|
+
const functionNode = node.type === "MethodDefinition" ? asAstNode(node.value) : node;
|
|
570
|
+
const name = getNodeName(node) ?? getNodeName(functionNode) ?? state.inferredNames.get(node);
|
|
571
|
+
const exported = isExportedTarget(node, state);
|
|
572
|
+
return {
|
|
573
|
+
file: state.file,
|
|
574
|
+
identity: targetIdentity("function", name, range),
|
|
575
|
+
kind: "function",
|
|
576
|
+
language: state.file.language,
|
|
577
|
+
loc: source.loc,
|
|
578
|
+
metadata: {
|
|
579
|
+
async: functionNode?.async === true,
|
|
580
|
+
exported,
|
|
581
|
+
function: functionInfo(node, range, name, exported, state.info)
|
|
582
|
+
},
|
|
583
|
+
name,
|
|
584
|
+
origin: {
|
|
585
|
+
physicalPath: state.file.path,
|
|
586
|
+
range
|
|
587
|
+
},
|
|
588
|
+
range,
|
|
589
|
+
text: source.text
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function getNodeName(node) {
|
|
593
|
+
if (!node) return;
|
|
594
|
+
if (typeof node.name === "string") return node.name;
|
|
595
|
+
const id = asAstNode(node.id);
|
|
596
|
+
if (typeof id?.name === "string") return id.name;
|
|
597
|
+
const key = asAstNode(node.key);
|
|
598
|
+
if (typeof key?.name === "string") return key.name;
|
|
599
|
+
if (typeof key?.value === "string" || typeof key?.value === "number") return String(key.value);
|
|
600
|
+
}
|
|
601
|
+
function inferChildName(parent, key, child, state) {
|
|
602
|
+
if (state.inferredNames.has(child)) return;
|
|
603
|
+
if (parent.type === "VariableDeclarator" && key === "init") {
|
|
604
|
+
const id = asAstNode(parent.id);
|
|
605
|
+
if (typeof id?.name === "string") state.inferredNames.set(child, id.name);
|
|
606
|
+
}
|
|
607
|
+
if (parent.type === "Property" && key === "value") {
|
|
608
|
+
const name = getNodeName(parent);
|
|
609
|
+
if (name) state.inferredNames.set(child, name);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function isClassNode(node) {
|
|
613
|
+
return node.type === "ClassDeclaration" || node.type === "ClassExpression";
|
|
614
|
+
}
|
|
615
|
+
function isExportDeclaration(node) {
|
|
616
|
+
return node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration";
|
|
617
|
+
}
|
|
618
|
+
function isExportedTarget(node, state) {
|
|
619
|
+
return state.exportedNodes.has(node);
|
|
620
|
+
}
|
|
621
|
+
function isFunctionNode(node) {
|
|
622
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
|
623
|
+
}
|
|
624
|
+
function markExportedBinding(name, state) {
|
|
625
|
+
const bindingNode = state.bindingNodes.get(name);
|
|
626
|
+
if (bindingNode) state.exportedNodes.add(bindingNode);
|
|
627
|
+
}
|
|
628
|
+
function markExportedDeclaration(node, state) {
|
|
629
|
+
state.exportedNodes.add(node);
|
|
630
|
+
if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
|
|
631
|
+
for (const declarator of node.declarations) {
|
|
632
|
+
if (!isAstNode(declarator)) continue;
|
|
633
|
+
const initializer = asAstNode(declarator.init);
|
|
634
|
+
if (initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.exportedNodes.add(initializer);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function visit(node, state) {
|
|
638
|
+
if (!node) return;
|
|
639
|
+
if (Array.isArray(node)) {
|
|
640
|
+
for (const child of node) visit(child, state);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (state.visited.has(node)) return;
|
|
644
|
+
state.visited.add(node);
|
|
645
|
+
if (node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration") {
|
|
646
|
+
if (node.declaration) {
|
|
647
|
+
markExportedDeclaration(node.declaration, state);
|
|
648
|
+
visit(node.declaration, state);
|
|
649
|
+
}
|
|
650
|
+
visitChildren(node, state, /* @__PURE__ */ new Set(["declaration"]));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (isClassNode(node)) addClassTarget(node, state);
|
|
654
|
+
else if (isFunctionNode(node) || node.type === "MethodDefinition") {
|
|
655
|
+
addFunctionTarget(node, state);
|
|
656
|
+
const methodValue = node.type === "MethodDefinition" ? asAstNode(node.value) : void 0;
|
|
657
|
+
if (methodValue) state.seenFunctions.add(methodValue);
|
|
658
|
+
}
|
|
659
|
+
visitChildren(node, state);
|
|
660
|
+
}
|
|
661
|
+
function visitChildren(node, state, skippedKeys = /* @__PURE__ */ new Set()) {
|
|
662
|
+
for (const [key, value] of Object.entries(node)) {
|
|
663
|
+
if (skippedKeys.has(key) || key === "type" || key === "start" || key === "end" || key === "range") continue;
|
|
664
|
+
if (Array.isArray(value)) {
|
|
665
|
+
for (const child of value) if (isAstNode(child)) {
|
|
666
|
+
inferChildName(node, key, child, state);
|
|
667
|
+
visit(child, state);
|
|
668
|
+
}
|
|
669
|
+
} else if (isAstNode(value)) {
|
|
670
|
+
inferChildName(node, key, value, state);
|
|
671
|
+
visit(value, state);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
676
|
+
export { createSourceFile as a, sliceRange as c, stableHash as d, SourceChangedError as i, withLanguage as l, targetIdentity as n, createSourceRuntime as o, withStableIdentities as r, sliceLines as s, extractJsSourceTargets as t, createStableHasher as u };
|