@alint-js/core 0.1.3 → 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.
@@ -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 };