ai-code-map 0.0.0-stage → 0.1.0-alpha.3

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,4395 @@
1
+ // src/compiler-worker.ts
2
+ import { Session } from "inspector/promises";
3
+ import { parentPort, workerData } from "worker_threads";
4
+
5
+ // src/analyze.ts
6
+ import { promises as fs3 } from "fs";
7
+ import path9 from "path";
8
+ import ts8 from "typescript";
9
+
10
+ // src/constants.ts
11
+ var SCHEMA_VERSION = 4;
12
+ var CONFIG_VERSION = 1;
13
+ var ANALYZER_VERSION = "0.4.0";
14
+ var DEFAULT_OUTPUT = ".ai/code-map";
15
+ var DEFAULT_INCLUDE = [
16
+ "src/**/*",
17
+ "app/**/*",
18
+ "pages/**/*",
19
+ "lib/**/*",
20
+ "server/**/*",
21
+ "*.{ts,tsx,js,jsx,mts,cts,mjs,cjs}"
22
+ ];
23
+ var DEFAULT_EXCLUDE = [
24
+ "**/node_modules/**",
25
+ "**/.next/**",
26
+ "**/dist/**",
27
+ "**/build/**",
28
+ "**/coverage/**",
29
+ "**/.git/**",
30
+ "**/.ai/**",
31
+ "**/*.generated.*"
32
+ ];
33
+ var MAP_COMPANION_ARTIFACTS = [
34
+ "overview.md",
35
+ "project.json",
36
+ "files.jsonl",
37
+ "symbols.jsonl",
38
+ "relations.jsonl",
39
+ "diagnostics.json"
40
+ ];
41
+
42
+ // src/utils/path.ts
43
+ import { promises as fs } from "fs";
44
+ import path from "path";
45
+
46
+ // src/errors.ts
47
+ var CodeMapError = class extends Error {
48
+ code;
49
+ constructor(code, message, options) {
50
+ super(message, options);
51
+ this.name = "CodeMapError";
52
+ this.code = code;
53
+ }
54
+ };
55
+
56
+ // src/utils/path.ts
57
+ function isAbsolutePath(value) {
58
+ const normalized = toPosixPath(value);
59
+ return path.posix.isAbsolute(normalized) || /^[a-zA-Z]:[\\/]/.test(normalized) || normalized.startsWith("//");
60
+ }
61
+ function isFileSystemUri(value) {
62
+ return /^file:(?:\/\/|\/)/i.test(value.trim());
63
+ }
64
+ function hasEmbeddedDoubleSlashPath(value) {
65
+ const normalized = toPosixPath(value);
66
+ let index = normalized.indexOf("//");
67
+ while (index >= 0) {
68
+ const previous = index > 0 ? normalized[index - 1] : void 0;
69
+ if (previous !== ":") return true;
70
+ index = normalized.indexOf("//", index + 1);
71
+ }
72
+ return false;
73
+ }
74
+ function hasEmbeddedAbsolutePath(value) {
75
+ const normalized = toPosixPath(value);
76
+ return isAbsolutePath(normalized) || isFileSystemUri(normalized) || hasEmbeddedDoubleSlashPath(normalized) || /(?:^|\/)[A-Za-z]:(?:\/|$)/.test(normalized) || /(?:^|\/)file:(?:\/|$)/i.test(normalized);
77
+ }
78
+ function isSafeRelativePathPattern(value) {
79
+ const normalized = toPosixPath(value);
80
+ return normalized.length > 0 && !hasEmbeddedAbsolutePath(normalized) && !normalized.split("/").includes("..");
81
+ }
82
+ function looksLikeHostFilePath(value, root) {
83
+ const trimmed = value.trim();
84
+ if (/file:(?:\/\/|\/)/i.test(trimmed) || /(?:^|[/])[A-Za-z]:[\\/]/.test(trimmed) || /^\\\\/.test(trimmed) || hasEmbeddedDoubleSlashPath(trimmed)) {
85
+ return true;
86
+ }
87
+ const normalized = toPosixPath(trimmed);
88
+ if (root) {
89
+ const normalizedRoot = toPosixPath(path.resolve(root));
90
+ if (normalized === normalizedRoot || normalized.startsWith(`${normalizedRoot}/`) || normalized.includes(normalizedRoot))
91
+ return true;
92
+ }
93
+ return /^\/(?:Users|home|root|private|tmp|var|etc|usr|opt|Volumes|mnt|proc|dev)(?:\/|$)/.test(
94
+ normalized
95
+ );
96
+ }
97
+ function toPosixPath(value) {
98
+ return value.replaceAll("\\", "/");
99
+ }
100
+ function relativePosix(root, absolutePath) {
101
+ if (!isAbsolutePath(absolutePath)) {
102
+ throw new CodeMapError(
103
+ "PATH_OUTSIDE_ROOT",
104
+ `Path ${absolutePath} is outside project root ${root}.`
105
+ );
106
+ }
107
+ const relative = path.relative(root, absolutePath);
108
+ if (relative === "" || isRelativeInside(relative)) {
109
+ return toPosixPath(relative || ".");
110
+ }
111
+ throw new CodeMapError(
112
+ "PATH_OUTSIDE_ROOT",
113
+ `Path ${absolutePath} is outside project root ${root}.`
114
+ );
115
+ }
116
+ function isPathInside(root, candidate) {
117
+ if (isAbsolutePath(candidate) && !path.isAbsolute(candidate)) return false;
118
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
119
+ return relative === "" || isRelativeInside(relative);
120
+ }
121
+ function isRelativeInside(relative) {
122
+ const normalized = toPosixPath(relative);
123
+ if (normalized === "" || normalized === ".") return true;
124
+ if (isAbsolutePath(normalized)) return false;
125
+ return normalized !== ".." && !normalized.startsWith("../") && !normalized.startsWith("..\\");
126
+ }
127
+ function redactAbsolutePathsInText(message, root) {
128
+ const normalizedRoot = path.resolve(root);
129
+ const redact = (candidate) => {
130
+ const trimmed = candidate.replace(/[)\].,;:]+$/, "");
131
+ if (!isAbsolutePath(trimmed) && !isFileSystemUri(trimmed)) return candidate;
132
+ if (isFileSystemUri(trimmed)) return "<outside-root>";
133
+ const foreignWindowsPath = path.win32.isAbsolute(trimmed) && !path.posix.isAbsolute(trimmed);
134
+ if (foreignWindowsPath && path.posix.isAbsolute(normalizedRoot)) return "<outside-root>";
135
+ try {
136
+ if (isPathInside(normalizedRoot, trimmed)) {
137
+ return relativePosix(normalizedRoot, trimmed);
138
+ }
139
+ } catch {
140
+ }
141
+ return "<outside-root>";
142
+ };
143
+ let redacted = message.replace(
144
+ /(["'`])([^"'`\r\n]+)\1/g,
145
+ (quoted, quote, contents) => isAbsolutePath(contents) || isFileSystemUri(contents) ? `${quote}${redact(contents)}${quote}` : quoted
146
+ );
147
+ for (const rootVariant of [normalizedRoot, toPosixPath(normalizedRoot)].sort(
148
+ (a, b) => b.length - a.length
149
+ )) {
150
+ if (rootVariant) redacted = redacted.replaceAll(rootVariant, ".");
151
+ }
152
+ const chunks = redacted.split(
153
+ /(\s+(?:is|was|at|from|to|outside|inside|cannot|could|failed|for|and|or)\s+)/gi
154
+ );
155
+ return chunks.map(
156
+ (chunk) => chunk.replace(
157
+ /(?<![A-Za-z0-9_.-])[A-Za-z]:[\\/](?:[^\\/\r\n'"`)\],;:]+[\\/])*[^\\/\s'"`)\],;:]+|(?<![A-Za-z0-9_.\\-])\\\\(?:[^\\\r\n'"`)\],;:]+\\)+[^\\\s'"`)\],;:]+|(?<![A-Za-z0-9_.:/-])\/\/(?:[^/\r\n'"`)\],;:]+\/)*[^/\s'"`)\],;:]+|(?<![A-Za-z0-9_.:/-])\/(?:[^/\r\n'"`)\],;:]+\/)*[^/\s'"`)\],;:]+/g,
158
+ redact
159
+ )
160
+ ).join("");
161
+ }
162
+ function normalizeStoredPath(value) {
163
+ const normalized = path.posix.normalize(toPosixPath(value));
164
+ return normalized.startsWith("./") ? normalized.slice(2) : normalized;
165
+ }
166
+ function sanitizeModuleSpecifier(value) {
167
+ return isAbsolutePath(value) || isFileSystemUri(value) || hasEmbeddedAbsolutePath(value) ? "<absolute-import>" : value;
168
+ }
169
+
170
+ // src/ids.ts
171
+ function fileId(filePath) {
172
+ return `file:${normalizeStoredPath(filePath)}`;
173
+ }
174
+ function symbolId(kind, filePath, name, parent) {
175
+ const prefix = symbolPrefix(kind);
176
+ const qualifiedName = parent ? `${parent}.${name}` : name;
177
+ return `${prefix}:${normalizeStoredPath(filePath)}#${qualifiedName}`;
178
+ }
179
+ function externalId(moduleName, name = "default") {
180
+ return `external:${sanitizeModuleSpecifier(moduleName)}#${name}`;
181
+ }
182
+ function generatedCompilerUnitId(name) {
183
+ return `compiler-unit:generated:${normalizeStoredPath(name)}`;
184
+ }
185
+ function routeId(method, route) {
186
+ return `route:${method.toUpperCase()}:${route || "/"}`;
187
+ }
188
+ function symbolPrefix(kind) {
189
+ switch (kind) {
190
+ case "react-component":
191
+ return "component";
192
+ case "react-hook":
193
+ return "hook";
194
+ case "next-page":
195
+ case "next-layout":
196
+ case "next-route-handler":
197
+ case "next-special-file":
198
+ return "next";
199
+ case "nest-module":
200
+ case "nest-controller":
201
+ case "nest-provider":
202
+ case "nest-guard":
203
+ case "nest-interceptor":
204
+ case "nest-pipe":
205
+ case "nest-filter":
206
+ case "nest-gateway":
207
+ return "nest";
208
+ case "type-alias":
209
+ return "type";
210
+ default:
211
+ return kind;
212
+ }
213
+ }
214
+
215
+ // src/schemas.ts
216
+ import path3 from "path";
217
+ import { z } from "zod";
218
+
219
+ // src/utils/stable.ts
220
+ import { promises as fs2 } from "fs";
221
+ import path2 from "path";
222
+ function compareText(a, b) {
223
+ return a < b ? -1 : a > b ? 1 : 0;
224
+ }
225
+ function safeRecordEntry(value) {
226
+ if (Array.isArray(value)) {
227
+ return value.map((entry) => safeRecordEntry(entry));
228
+ }
229
+ if (value !== null && typeof value === "object") {
230
+ const record = value;
231
+ const next = {};
232
+ for (const key of Object.keys(record).sort(compareText)) {
233
+ const child = record[key];
234
+ if (child !== void 0) {
235
+ next[key] = safeRecordEntry(child);
236
+ }
237
+ }
238
+ return next;
239
+ }
240
+ return value;
241
+ }
242
+ function stableValue(value) {
243
+ if (Array.isArray(value)) {
244
+ return safeRecordEntry(value);
245
+ }
246
+ if (value !== null && typeof value === "object") {
247
+ return safeRecordEntry(value);
248
+ }
249
+ return value;
250
+ }
251
+ function stableJson(value, pretty = false) {
252
+ return `${JSON.stringify(stableValue(value), null, pretty ? 2 : void 0)}
253
+ `;
254
+ }
255
+
256
+ // src/schemas.ts
257
+ var JsonValueSchema = z.json();
258
+ var LanguageSchema = z.enum(["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"]);
259
+ var FrameworkSchema = z.enum(["typescript", "node", "react", "nextjs", "nestjs"]);
260
+ var FrameworkSelectionSchema = z.union([FrameworkSchema, z.literal("auto")]);
261
+ var ConfigSchema = z.object({
262
+ version: z.literal(CONFIG_VERSION).default(CONFIG_VERSION),
263
+ root: z.string().min(1).default("../.."),
264
+ include: z.array(z.string().min(1)).min(1).default([...DEFAULT_INCLUDE]),
265
+ exclude: z.array(z.string().min(1)).default([...DEFAULT_EXCLUDE]),
266
+ tsconfig: z.string().min(1).optional(),
267
+ frameworks: z.array(FrameworkSelectionSchema).min(1).default(["auto"]),
268
+ output: z.string().min(1).default(DEFAULT_OUTPUT),
269
+ includePrivateSymbols: z.boolean().default(false),
270
+ concurrency: z.number().int().min(1).max(64).optional()
271
+ }).strict().superRefine((config, context) => {
272
+ if (new Set(config.frameworks).size !== config.frameworks.length) {
273
+ context.addIssue({
274
+ code: "custom",
275
+ path: ["frameworks"],
276
+ message: "framework selections must be unique"
277
+ });
278
+ }
279
+ if (config.frameworks.includes("auto") && config.frameworks.length > 1) {
280
+ context.addIssue({
281
+ code: "custom",
282
+ path: ["frameworks"],
283
+ message: "auto cannot be combined with explicit framework selections"
284
+ });
285
+ }
286
+ });
287
+ var SourceRangeSchema = z.object({
288
+ startLine: z.number().int().positive(),
289
+ startColumn: z.number().int().positive(),
290
+ endLine: z.number().int().positive(),
291
+ endColumn: z.number().int().positive()
292
+ }).strict().superRefine((range, context) => {
293
+ if (range.endLine < range.startLine || range.endLine === range.startLine && range.endColumn < range.startColumn) {
294
+ context.addIssue({ code: "custom", message: "range end must not precede its start" });
295
+ }
296
+ });
297
+ var StoredPathSchema = z.string().min(1).refine(
298
+ (value) => !value.includes("\\") && !value.startsWith("/") && !hasEmbeddedAbsolutePath(value) && !value.split("/").includes(".."),
299
+ "must be a repository-relative POSIX path"
300
+ ).refine((value) => value === normalizeStoredPath(value), "must be normalized");
301
+ var StoredFilePathSchema = StoredPathSchema.refine(
302
+ (value) => value !== ".",
303
+ "must identify a file or non-root project path"
304
+ );
305
+ var StoredModuleAliasSchema = z.string().min(1).refine(
306
+ (value) => !isAbsolutePath(value) && !toPosixPath(value).split("/").includes(".."),
307
+ "must not contain an absolute or parent-traversing path"
308
+ );
309
+ var ImportSpecifierRecordSchema = z.object({
310
+ imported: z.string(),
311
+ local: z.string(),
312
+ typeOnly: z.boolean().optional()
313
+ }).strict();
314
+ var ImportRecordSchema = z.object({
315
+ source: z.string().min(1).refine((value) => !isAbsolutePath(value), "must not expose an absolute module path"),
316
+ specifiers: z.array(ImportSpecifierRecordSchema),
317
+ resolved: StoredFilePathSchema.optional(),
318
+ external: z.boolean(),
319
+ kind: z.enum(["esm", "commonjs"]),
320
+ line: z.number().int().positive()
321
+ }).strict();
322
+ var ExportRecordSchema = z.object({
323
+ name: z.string(),
324
+ local: z.string().optional(),
325
+ source: z.string().refine((value) => !isAbsolutePath(value), "must not expose an absolute module path").optional(),
326
+ default: z.boolean(),
327
+ typeOnly: z.boolean().optional(),
328
+ line: z.number().int().positive()
329
+ }).strict();
330
+ var ExternalOperationKindSchema = z.enum(["filesystem", "http", "database", "queue"]);
331
+ var ExternalOperationSchema = z.object({
332
+ kind: ExternalOperationKindSchema,
333
+ api: z.string().min(1),
334
+ line: z.number().int().positive()
335
+ }).strict();
336
+ var SyntaxSummarySchema = z.object({
337
+ parser: z.string().min(1),
338
+ factCount: z.number().int().nonnegative(),
339
+ hadErrors: z.boolean(),
340
+ fallbackFrom: z.string().min(1).optional()
341
+ }).strict();
342
+ var FileRecordSchema = z.object({
343
+ schemaVersion: z.literal(SCHEMA_VERSION),
344
+ id: z.string().min(1),
345
+ compilerUnit: z.string().min(1),
346
+ path: StoredFilePathSchema,
347
+ language: LanguageSchema,
348
+ frameworks: z.array(FrameworkSchema),
349
+ hash: z.string().regex(/^[a-f0-9]{64}$/),
350
+ lines: z.number().int().nonnegative(),
351
+ imports: z.array(ImportRecordSchema),
352
+ exports: z.array(ExportRecordSchema),
353
+ symbolIds: z.array(z.string().min(1)),
354
+ env: z.array(z.string()),
355
+ operations: z.array(ExternalOperationSchema),
356
+ syntax: SyntaxSummarySchema
357
+ }).strict();
358
+ var CompilerUnitRecordSchema = z.object({
359
+ id: z.string().min(1),
360
+ kind: z.enum(["configured", "fallback"]),
361
+ root: StoredPathSchema,
362
+ config: StoredFilePathSchema.optional(),
363
+ references: z.array(StoredFilePathSchema),
364
+ extends: z.array(StoredFilePathSchema),
365
+ pathAliases: z.record(StoredModuleAliasSchema, z.array(StoredPathSchema))
366
+ }).strict();
367
+ var SymbolKindSchema = z.enum([
368
+ "function",
369
+ "class",
370
+ "method",
371
+ "interface",
372
+ "type-alias",
373
+ "variable",
374
+ "react-component",
375
+ "react-hook",
376
+ "react-context",
377
+ "next-page",
378
+ "next-layout",
379
+ "next-route-handler",
380
+ "next-special-file",
381
+ "nest-module",
382
+ "nest-controller",
383
+ "nest-provider",
384
+ "nest-guard",
385
+ "nest-interceptor",
386
+ "nest-pipe",
387
+ "nest-filter",
388
+ "nest-gateway",
389
+ "dto",
390
+ "route"
391
+ ]);
392
+ var SymbolRecordSchema = z.object({
393
+ schemaVersion: z.literal(SCHEMA_VERSION),
394
+ id: z.string().min(1),
395
+ name: z.string(),
396
+ kind: SymbolKindSchema,
397
+ file: StoredFilePathSchema,
398
+ range: SourceRangeSchema,
399
+ exported: z.boolean(),
400
+ defaultExport: z.boolean().optional(),
401
+ signature: z.string().optional(),
402
+ documentation: z.string().optional(),
403
+ facts: z.record(z.string(), JsonValueSchema),
404
+ sourceHash: z.string().regex(/^[a-f0-9]{64}$/)
405
+ }).strict();
406
+ var RelationTypeSchema = z.enum([
407
+ "imports",
408
+ "exports",
409
+ "reexports",
410
+ "calls",
411
+ "instantiates",
412
+ "renders",
413
+ "uses-hook",
414
+ "reads-state",
415
+ "depends-on",
416
+ "used-by",
417
+ "contains",
418
+ "references",
419
+ "injects",
420
+ "handles-route",
421
+ "consumes-dto",
422
+ "belongs-to-module",
423
+ "uses-layout"
424
+ ]);
425
+ var RelationEvidenceClassificationSchema = z.enum([
426
+ "exact",
427
+ "framework-derived",
428
+ "heuristic",
429
+ "derived"
430
+ ]);
431
+ var RelationOriginSchema = z.object({
432
+ file: StoredFilePathSchema,
433
+ line: z.number().int().positive(),
434
+ range: SourceRangeSchema,
435
+ explanation: z.string().min(1),
436
+ classification: RelationEvidenceClassificationSchema,
437
+ analyzer: z.string().min(1)
438
+ }).strict();
439
+ var RelationRecordSchema = z.object({
440
+ schemaVersion: z.literal(SCHEMA_VERSION),
441
+ from: z.string().min(1),
442
+ type: RelationTypeSchema,
443
+ to: z.string().min(1),
444
+ confidence: z.number().min(0).max(1),
445
+ origin: RelationOriginSchema
446
+ }).strict();
447
+ var DiagnosticSeveritySchema = z.enum(["info", "warning", "error"]);
448
+ var DiagnosticCodeSchema = z.enum([
449
+ "parse-error",
450
+ "unresolved-import",
451
+ "invalid-config",
452
+ "unsupported-file",
453
+ "analysis-error",
454
+ "parser-fallback",
455
+ "compiler-config"
456
+ ]);
457
+ var DiagnosticRecordSchema = z.object({
458
+ schemaVersion: z.literal(SCHEMA_VERSION),
459
+ code: DiagnosticCodeSchema,
460
+ severity: DiagnosticSeveritySchema,
461
+ message: z.string().min(1),
462
+ file: StoredFilePathSchema.optional(),
463
+ line: z.number().int().positive().optional(),
464
+ column: z.number().int().positive().optional()
465
+ }).strict();
466
+ var WorkspacePackageSchema = z.object({
467
+ name: z.string(),
468
+ path: StoredFilePathSchema,
469
+ entryPoints: z.array(StoredFilePathSchema)
470
+ }).strict();
471
+ var MapCountsSchema = z.object({
472
+ files: z.number().int().nonnegative(),
473
+ symbols: z.number().int().nonnegative(),
474
+ relations: z.number().int().nonnegative(),
475
+ diagnostics: z.number().int().nonnegative()
476
+ }).strict();
477
+ var ProjectRecordSchema = z.object({
478
+ schemaVersion: z.literal(SCHEMA_VERSION),
479
+ name: z.string(),
480
+ packageManager: z.string().optional(),
481
+ frameworks: z.array(FrameworkSchema),
482
+ compilerUnits: z.array(CompilerUnitRecordSchema),
483
+ tsconfig: StoredFilePathSchema.optional(),
484
+ pathAliases: z.record(StoredModuleAliasSchema, z.array(StoredPathSchema)),
485
+ entryPoints: z.array(StoredFilePathSchema),
486
+ workspaces: z.array(WorkspacePackageSchema),
487
+ counts: MapCountsSchema
488
+ }).strict();
489
+ var ManifestSourceFileSchema = z.object({
490
+ path: StoredFilePathSchema,
491
+ hash: z.string().regex(/^[a-f0-9]{64}$/),
492
+ compilerUnit: z.string().min(1),
493
+ lines: z.number().int().nonnegative()
494
+ }).strict();
495
+ var SemanticFileInputRecordSchema = z.object({
496
+ path: StoredPathSchema,
497
+ kind: z.literal("file"),
498
+ hash: z.string().regex(/^[a-f0-9]{64}$/)
499
+ }).strict();
500
+ var SemanticResolutionProbeRecordSchema = z.object({
501
+ path: StoredPathSchema,
502
+ kind: z.literal("resolution-probe"),
503
+ scope: z.enum(["project", "toolchain"]),
504
+ targetKind: z.enum(["file", "directory", "readable-file"]),
505
+ exists: z.boolean()
506
+ }).strict();
507
+ var SemanticDirectoryListingRecordSchema = z.object({
508
+ path: StoredPathSchema,
509
+ kind: z.literal("directory-listing"),
510
+ scope: z.enum(["project", "toolchain"]),
511
+ hash: z.string().regex(/^[a-f0-9]{64}$/)
512
+ }).strict();
513
+ var SemanticSymlinkRecordSchema = z.object({
514
+ path: StoredPathSchema,
515
+ kind: z.literal("symlink"),
516
+ scope: z.enum(["project", "toolchain"]),
517
+ target: StoredPathSchema
518
+ }).strict();
519
+ var SemanticToolchainFileRecordSchema = z.object({
520
+ path: StoredPathSchema,
521
+ kind: z.literal("toolchain-file"),
522
+ hash: z.string().regex(/^[a-f0-9]{64}$/)
523
+ }).strict();
524
+ var SemanticInputRecordSchema = z.discriminatedUnion("kind", [
525
+ SemanticFileInputRecordSchema,
526
+ SemanticResolutionProbeRecordSchema,
527
+ SemanticDirectoryListingRecordSchema,
528
+ SemanticSymlinkRecordSchema,
529
+ SemanticToolchainFileRecordSchema
530
+ ]);
531
+ var ArtifactIntegrityRecordSchema = z.object({
532
+ path: z.enum(MAP_COMPANION_ARTIFACTS),
533
+ hash: z.string().regex(/^[a-f0-9]{64}$/)
534
+ }).strict();
535
+ var ProducerRecordSchema = z.object({
536
+ id: z.string().min(1),
537
+ kind: z.enum(["built-in", "custom"]),
538
+ fingerprint: z.string().regex(/^[a-f0-9]{64}$/)
539
+ }).strict();
540
+ var VersionedToolSchema = z.object({
541
+ name: z.string().min(1),
542
+ version: z.string().min(1)
543
+ }).strict();
544
+ var ManifestRecordSchema = z.object({
545
+ schemaVersion: z.literal(SCHEMA_VERSION),
546
+ analyzerVersion: z.string().min(1),
547
+ producer: ProducerRecordSchema,
548
+ semanticResolver: VersionedToolSchema,
549
+ syntaxParser: VersionedToolSchema,
550
+ analyzers: z.array(VersionedToolSchema),
551
+ configHash: z.string().regex(/^[a-f0-9]{64}$/),
552
+ sourceFiles: z.array(ManifestSourceFileSchema),
553
+ semanticInputs: z.array(SemanticInputRecordSchema),
554
+ artifacts: z.array(ArtifactIntegrityRecordSchema),
555
+ integrityHash: z.string().regex(/^[a-f0-9]{64}$/)
556
+ }).strict();
557
+ var CodeMapShapeSchema = z.object({
558
+ manifest: ManifestRecordSchema,
559
+ project: ProjectRecordSchema,
560
+ files: z.array(FileRecordSchema),
561
+ symbols: z.array(SymbolRecordSchema),
562
+ relations: z.array(RelationRecordSchema),
563
+ diagnostics: z.array(DiagnosticRecordSchema)
564
+ }).strict();
565
+ var CodeMapSchema = CodeMapShapeSchema.superRefine((map, context) => {
566
+ const fileByPath = new Map(map.files.map((file) => [file.path, file]));
567
+ const fileIds = /* @__PURE__ */ new Set();
568
+ const filePaths = /* @__PURE__ */ new Set();
569
+ const compilerUnitsById = new Map(map.project.compilerUnits.map((unit) => [unit.id, unit]));
570
+ const compilerUnitIds = /* @__PURE__ */ new Set();
571
+ let fallbackCount = 0;
572
+ for (const [index, file] of map.files.entries()) {
573
+ addDuplicateIssue(context, fileIds, file.id, ["files", index, "id"]);
574
+ addDuplicateIssue(context, filePaths, file.path, ["files", index, "path"]);
575
+ if (file.id !== `file:${file.path}`) {
576
+ context.addIssue({
577
+ code: "custom",
578
+ path: ["files", index, "id"],
579
+ message: "file ID must be derived from its path"
580
+ });
581
+ }
582
+ }
583
+ for (const [index, unit] of map.project.compilerUnits.entries()) {
584
+ addDuplicateIssue(context, compilerUnitIds, unit.id, ["project", "compilerUnits", index, "id"]);
585
+ if (unit.kind === "configured" && !unit.config) {
586
+ context.addIssue({
587
+ code: "custom",
588
+ path: ["project", "compilerUnits", index, "config"],
589
+ message: "configured compiler units require config"
590
+ });
591
+ }
592
+ if (unit.kind === "fallback" && unit.config) {
593
+ context.addIssue({
594
+ code: "custom",
595
+ path: ["project", "compilerUnits", index, "config"],
596
+ message: "fallback compiler units must not have config"
597
+ });
598
+ }
599
+ if (unit.kind === "fallback") {
600
+ fallbackCount += 1;
601
+ if (unit.id !== "compiler-unit:generated:fallback" || unit.root !== "." || unit.references.length > 0 || unit.extends.length > 0 || Object.keys(unit.pathAliases).length > 0) {
602
+ context.addIssue({
603
+ code: "custom",
604
+ path: ["project", "compilerUnits", index],
605
+ message: "fallback compiler unit must use the canonical generated identity and shape"
606
+ });
607
+ }
608
+ }
609
+ if (unit.kind === "configured" && unit.config) {
610
+ if (unit.id !== `compiler-unit:configured:${unit.config}`) {
611
+ context.addIssue({
612
+ code: "custom",
613
+ path: ["project", "compilerUnits", index, "id"],
614
+ message: "configured compiler-unit ID must be derived from its config path"
615
+ });
616
+ }
617
+ if (unit.root !== normalizeStoredPath(path3.posix.dirname(unit.config))) {
618
+ context.addIssue({
619
+ code: "custom",
620
+ path: ["project", "compilerUnits", index, "root"],
621
+ message: "configured compiler-unit root must match its config directory"
622
+ });
623
+ }
624
+ }
625
+ }
626
+ if (fallbackCount !== 1) {
627
+ context.addIssue({
628
+ code: "custom",
629
+ path: ["project", "compilerUnits"],
630
+ message: "exactly one fallback compiler unit is required"
631
+ });
632
+ }
633
+ const configuredPaths = new Set(
634
+ map.project.compilerUnits.flatMap(
635
+ (unit) => unit.kind === "configured" && unit.config ? [unit.config] : []
636
+ )
637
+ );
638
+ if (map.project.tsconfig && !configuredPaths.has(map.project.tsconfig)) {
639
+ context.addIssue({
640
+ code: "custom",
641
+ path: ["project", "tsconfig"],
642
+ message: "project tsconfig must resolve to a configured compiler unit"
643
+ });
644
+ }
645
+ const compatibilityUnit = map.project.tsconfig ? map.project.compilerUnits.find((unit) => unit.config === map.project.tsconfig) : map.project.compilerUnits.filter((unit) => unit.kind === "configured").sort((left, right) => compareText(left.id, right.id))[0];
646
+ if (stableJson(map.project.pathAliases) !== stableJson(compatibilityUnit?.pathAliases ?? {})) {
647
+ context.addIssue({
648
+ code: "custom",
649
+ path: ["project", "pathAliases"],
650
+ message: "project path aliases must match the compatibility compiler unit"
651
+ });
652
+ }
653
+ for (const [index, unit] of map.project.compilerUnits.entries()) {
654
+ for (const reference of unit.references) {
655
+ if (!configuredPaths.has(reference)) {
656
+ context.addIssue({
657
+ code: "custom",
658
+ path: ["project", "compilerUnits", index, "references"],
659
+ message: "compiler-unit reference must resolve to a configured unit"
660
+ });
661
+ }
662
+ }
663
+ }
664
+ const symbolById = /* @__PURE__ */ new Map();
665
+ for (const [index, symbol] of map.symbols.entries()) {
666
+ if (symbolById.has(symbol.id)) {
667
+ context.addIssue({
668
+ code: "custom",
669
+ path: ["symbols", index, "id"],
670
+ message: `duplicate symbol ID ${symbol.id}`
671
+ });
672
+ }
673
+ symbolById.set(symbol.id, symbol);
674
+ const owner = fileByPath.get(symbol.file);
675
+ if (!owner) {
676
+ context.addIssue({
677
+ code: "custom",
678
+ path: ["symbols", index, "file"],
679
+ message: "symbol references a missing file"
680
+ });
681
+ } else if (owner.hash !== symbol.sourceHash) {
682
+ context.addIssue({
683
+ code: "custom",
684
+ path: ["symbols", index, "sourceHash"],
685
+ message: "symbol source hash does not match its file"
686
+ });
687
+ } else if (!rangeFitsFile(symbol.range, owner)) {
688
+ context.addIssue({
689
+ code: "custom",
690
+ path: ["symbols", index, "range"],
691
+ message: "symbol range must fit within its source file"
692
+ });
693
+ }
694
+ }
695
+ const importRelationKeys = new Set(
696
+ map.relations.filter((relation2) => relation2.type === "imports").map(
697
+ (relation2) => `${relation2.from}\0${relation2.to}\0${relation2.origin.file}\0${relation2.origin.line}`
698
+ )
699
+ );
700
+ const expectedImportRelationKeys = /* @__PURE__ */ new Set();
701
+ for (const [fileIndex, file] of map.files.entries()) {
702
+ const unit = compilerUnitsById.get(file.compilerUnit);
703
+ if (!unit) {
704
+ context.addIssue({
705
+ code: "custom",
706
+ path: ["files", fileIndex, "compilerUnit"],
707
+ message: "file compiler unit must resolve to exactly one compiler unit"
708
+ });
709
+ }
710
+ const expected = map.symbols.filter((symbol) => symbol.file === file.path).map((symbol) => symbol.id).sort();
711
+ const actual = [...file.symbolIds].sort();
712
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
713
+ context.addIssue({
714
+ code: "custom",
715
+ path: ["files", fileIndex, "symbolIds"],
716
+ message: "file symbol IDs must exactly match symbols owned by the file"
717
+ });
718
+ }
719
+ for (const [importIndex, importRecord] of file.imports.entries()) {
720
+ if (importRecord.line > file.lines) {
721
+ context.addIssue({
722
+ code: "custom",
723
+ path: ["files", fileIndex, "imports", importIndex, "line"],
724
+ message: "import line must fit within its source file"
725
+ });
726
+ }
727
+ if (importRecord.external && importRecord.resolved) {
728
+ context.addIssue({
729
+ code: "custom",
730
+ path: ["files", fileIndex, "imports", importIndex],
731
+ message: "external imports must not name an internal resolved path"
732
+ });
733
+ }
734
+ if (importRecord.resolved && !fileByPath.has(importRecord.resolved)) {
735
+ context.addIssue({
736
+ code: "custom",
737
+ path: ["files", fileIndex, "imports", importIndex, "resolved"],
738
+ message: "resolved import must reference a persisted file"
739
+ });
740
+ }
741
+ const target = importRecord.resolved ? `file:${importRecord.resolved}` : `external:${importRecord.source}#default`;
742
+ const key = `${file.id}\0${target}\0${file.path}\0${importRecord.line}`;
743
+ expectedImportRelationKeys.add(key);
744
+ if (!importRelationKeys.has(key)) {
745
+ context.addIssue({
746
+ code: "custom",
747
+ path: ["files", fileIndex, "imports", importIndex],
748
+ message: "import record must have matching relation evidence"
749
+ });
750
+ }
751
+ }
752
+ for (const [exportIndex, exportRecord] of file.exports.entries()) {
753
+ if (exportRecord.line > file.lines) {
754
+ context.addIssue({
755
+ code: "custom",
756
+ path: ["files", fileIndex, "exports", exportIndex, "line"],
757
+ message: "export line must fit within its source file"
758
+ });
759
+ }
760
+ }
761
+ for (const [operationIndex, operation] of file.operations.entries()) {
762
+ if (operation.line > file.lines) {
763
+ context.addIssue({
764
+ code: "custom",
765
+ path: ["files", fileIndex, "operations", operationIndex, "line"],
766
+ message: "operation line must fit within its source file"
767
+ });
768
+ }
769
+ }
770
+ }
771
+ const manifestPaths = /* @__PURE__ */ new Set();
772
+ for (const [index, source] of map.manifest.sourceFiles.entries()) {
773
+ addDuplicateIssue(context, manifestPaths, source.path, [
774
+ "manifest",
775
+ "sourceFiles",
776
+ index,
777
+ "path"
778
+ ]);
779
+ const file = fileByPath.get(source.path);
780
+ if (!file || file.hash !== source.hash) {
781
+ context.addIssue({
782
+ code: "custom",
783
+ path: ["manifest", "sourceFiles", index],
784
+ message: "manifest source must match a persisted file and hash"
785
+ });
786
+ } else if (file.compilerUnit !== source.compilerUnit) {
787
+ context.addIssue({
788
+ code: "custom",
789
+ path: ["manifest", "sourceFiles", index, "compilerUnit"],
790
+ message: "manifest source ownership must match the persisted file compiler unit"
791
+ });
792
+ } else if (file.lines !== source.lines) {
793
+ context.addIssue({
794
+ code: "custom",
795
+ path: ["manifest", "sourceFiles", index, "lines"],
796
+ message: "manifest source line count must match the persisted file"
797
+ });
798
+ }
799
+ }
800
+ if (manifestPaths.size !== fileByPath.size) {
801
+ context.addIssue({
802
+ code: "custom",
803
+ path: ["manifest", "sourceFiles"],
804
+ message: "manifest sources must exactly cover persisted files"
805
+ });
806
+ }
807
+ const semanticInputIdentities = /* @__PURE__ */ new Set();
808
+ for (const [index, input] of map.manifest.semanticInputs.entries()) {
809
+ addDuplicateIssue(context, semanticInputIdentities, semanticInputIdentity(input), [
810
+ "manifest",
811
+ "semanticInputs",
812
+ index
813
+ ]);
814
+ }
815
+ const artifactPaths = /* @__PURE__ */ new Set();
816
+ for (const [index, artifact] of map.manifest.artifacts.entries()) {
817
+ addDuplicateIssue(context, artifactPaths, artifact.path, [
818
+ "manifest",
819
+ "artifacts",
820
+ index,
821
+ "path"
822
+ ]);
823
+ }
824
+ const expectedArtifacts = [...MAP_COMPANION_ARTIFACTS].sort();
825
+ if (JSON.stringify([...artifactPaths].sort()) !== JSON.stringify(expectedArtifacts)) {
826
+ context.addIssue({
827
+ code: "custom",
828
+ path: ["manifest", "artifacts"],
829
+ message: "manifest artifact hashes must exactly cover every companion artifact"
830
+ });
831
+ }
832
+ const counts = {
833
+ files: map.files.length,
834
+ symbols: map.symbols.length,
835
+ relations: map.relations.length,
836
+ diagnostics: map.diagnostics.length
837
+ };
838
+ for (const key of Object.keys(counts)) {
839
+ if (map.project.counts[key] !== counts[key]) {
840
+ context.addIssue({
841
+ code: "custom",
842
+ path: ["project", "counts", key],
843
+ message: `${key} count does not match persisted records`
844
+ });
845
+ }
846
+ }
847
+ const validEndpoints = /* @__PURE__ */ new Set([...fileIds, ...symbolById.keys()]);
848
+ const relationIdentities = /* @__PURE__ */ new Set();
849
+ for (const [index, relation2] of map.relations.entries()) {
850
+ addDuplicateIssue(context, relationIdentities, relationIdentity(relation2), [
851
+ "relations",
852
+ index
853
+ ]);
854
+ if (relation2.type === "imports") {
855
+ const key = `${relation2.from}\0${relation2.to}\0${relation2.origin.file}\0${relation2.origin.line}`;
856
+ if (!expectedImportRelationKeys.has(key)) {
857
+ context.addIssue({
858
+ code: "custom",
859
+ path: ["relations", index],
860
+ message: "imports relation must have a matching file import record"
861
+ });
862
+ }
863
+ }
864
+ for (const endpoint of [relation2.from, relation2.to]) {
865
+ if (!validEndpoints.has(endpoint) && !endpoint.startsWith("external:") && !endpoint.startsWith("route:")) {
866
+ context.addIssue({
867
+ code: "custom",
868
+ path: ["relations", index],
869
+ message: `relation references missing endpoint ${endpoint}`
870
+ });
871
+ }
872
+ }
873
+ const originFile = fileByPath.get(relation2.origin.file);
874
+ if (!originFile) {
875
+ context.addIssue({
876
+ code: "custom",
877
+ path: ["relations", index, "origin", "file"],
878
+ message: "relation evidence references a missing file"
879
+ });
880
+ } else {
881
+ if (relation2.origin.line !== relation2.origin.range.startLine) {
882
+ context.addIssue({
883
+ code: "custom",
884
+ path: ["relations", index, "origin", "line"],
885
+ message: "relation origin line must match the start of its evidence range"
886
+ });
887
+ }
888
+ if (!rangeFitsFile(relation2.origin.range, originFile)) {
889
+ context.addIssue({
890
+ code: "custom",
891
+ path: ["relations", index, "origin", "range"],
892
+ message: "relation evidence range must fit within its source file"
893
+ });
894
+ }
895
+ if (relation2.type === "contains" || relation2.type === "exports") {
896
+ const fromFile = endpointSourceFile(relation2.from, fileByPath, symbolById);
897
+ const toFile = endpointSourceFile(relation2.to, fileByPath, symbolById);
898
+ if (fromFile !== relation2.origin.file || relation2.type === "exports" && toFile !== relation2.origin.file) {
899
+ context.addIssue({
900
+ code: "custom",
901
+ path: ["relations", index],
902
+ message: relation2.type === "exports" ? "exports endpoints and evidence must belong to the same file" : "contains evidence must belong to its source endpoint file"
903
+ });
904
+ }
905
+ const fromIsFile = relation2.from.startsWith("file:");
906
+ const toSymbol = symbolById.get(relation2.to);
907
+ const fromSymbol = symbolById.get(relation2.from);
908
+ if (relation2.type === "contains" && fromIsFile) {
909
+ if (!toSymbol || toSymbol.file !== relation2.origin.file || !rangesEqual(toSymbol.range, relation2.origin.range)) {
910
+ context.addIssue({
911
+ code: "custom",
912
+ path: ["relations", index],
913
+ message: "file containment evidence must exactly identify a symbol owned by the file"
914
+ });
915
+ }
916
+ } else if (relation2.type === "contains" && fromSymbol) {
917
+ if (!toSymbol || !rangeContains(fromSymbol.range, relation2.origin.range)) {
918
+ context.addIssue({
919
+ code: "custom",
920
+ path: ["relations", index, "origin", "range"],
921
+ message: "symbol containment must target a symbol and its evidence must fall within the source symbol"
922
+ });
923
+ }
924
+ }
925
+ if (relation2.type === "exports" && (!fromIsFile || !toSymbol || !toSymbol.exported || !rangesEqual(toSymbol.range, relation2.origin.range))) {
926
+ context.addIssue({
927
+ code: "custom",
928
+ path: ["relations", index],
929
+ message: "exports evidence must exactly identify an exported symbol owned by the file"
930
+ });
931
+ }
932
+ }
933
+ }
934
+ }
935
+ for (const [index, diagnostic] of map.diagnostics.entries()) {
936
+ const file = diagnostic.file ? fileByPath.get(diagnostic.file) : void 0;
937
+ if (file && diagnostic.line && diagnostic.line > file.lines) {
938
+ context.addIssue({
939
+ code: "custom",
940
+ path: ["diagnostics", index, "line"],
941
+ message: "diagnostic line must fit within its source file"
942
+ });
943
+ }
944
+ }
945
+ });
946
+ function addDuplicateIssue(context, seen, value, path10) {
947
+ if (seen.has(value)) {
948
+ context.addIssue({ code: "custom", path: path10, message: `duplicate value ${value}` });
949
+ }
950
+ seen.add(value);
951
+ }
952
+ function relationIdentity(relation2) {
953
+ const range = relation2.origin.range;
954
+ return [
955
+ relation2.from,
956
+ relation2.type,
957
+ relation2.to,
958
+ relation2.origin.file,
959
+ range.startLine,
960
+ range.startColumn,
961
+ range.endLine,
962
+ range.endColumn
963
+ ].join("\0");
964
+ }
965
+ function semanticInputIdentity(input) {
966
+ return input.kind === "resolution-probe" ? `${input.scope}\0${input.path}\0${input.kind}\0${input.targetKind}` : input.kind === "directory-listing" || input.kind === "symlink" ? `${input.scope}\0${input.path}\0${input.kind}` : `${input.path}\0${input.kind}`;
967
+ }
968
+ function rangeFitsFile(range, file) {
969
+ return range.startLine <= file.lines && range.endLine <= file.lines;
970
+ }
971
+ function rangesEqual(left, right) {
972
+ return left.startLine === right.startLine && left.startColumn === right.startColumn && left.endLine === right.endLine && left.endColumn === right.endColumn;
973
+ }
974
+ function rangeContains(outer, inner) {
975
+ const startsAfter = inner.startLine > outer.startLine || inner.startLine === outer.startLine && inner.startColumn >= outer.startColumn;
976
+ const endsBefore = inner.endLine < outer.endLine || inner.endLine === outer.endLine && inner.endColumn <= outer.endColumn;
977
+ return startsAfter && endsBefore;
978
+ }
979
+ function endpointSourceFile(endpoint, files, symbols) {
980
+ if (endpoint.startsWith("file:")) return files.get(endpoint.slice("file:".length))?.path;
981
+ return symbols.get(endpoint)?.file;
982
+ }
983
+
984
+ // src/utils/hash.ts
985
+ import { createHash } from "crypto";
986
+ function hashText(value) {
987
+ return createHash("sha256").update(value).digest("hex");
988
+ }
989
+
990
+ // src/analyzers/core.ts
991
+ import { isBuiltin } from "module";
992
+ import path4 from "path";
993
+ import ts2 from "typescript";
994
+
995
+ // src/analyzers/helpers.ts
996
+ import ts from "typescript";
997
+ function nodeRange(sourceFile, node) {
998
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile, false));
999
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
1000
+ return {
1001
+ startLine: start.line + 1,
1002
+ startColumn: start.character + 1,
1003
+ endLine: end.line + 1,
1004
+ endColumn: end.character + 1
1005
+ };
1006
+ }
1007
+ function nodeLine(sourceFile, node) {
1008
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile, false)).line + 1;
1009
+ }
1010
+ function hasModifier(node, kind) {
1011
+ return Boolean(
1012
+ ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((item) => item.kind === kind)
1013
+ );
1014
+ }
1015
+ function hasExportModifier(node) {
1016
+ return hasModifier(node, ts.SyntaxKind.ExportKeyword);
1017
+ }
1018
+ function hasDefaultModifier(node) {
1019
+ return hasModifier(node, ts.SyntaxKind.DefaultKeyword);
1020
+ }
1021
+ function getDeclarationName(node, fallback = "default") {
1022
+ const name = node.name;
1023
+ if (name && ts.isIdentifier(name)) return name.text;
1024
+ if (name && (ts.isStringLiteral(name) || ts.isNumericLiteral(name))) return name.text;
1025
+ return fallback;
1026
+ }
1027
+ function getTopLevelDefaultExportDeclaration(sourceFile) {
1028
+ for (const statement of sourceFile.statements) {
1029
+ if (ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression)) {
1030
+ const defaultName = statement.expression.text;
1031
+ const declaration = topLevelDeclarations(sourceFile).find(
1032
+ (item) => getDeclarationName(item, "") === defaultName
1033
+ );
1034
+ if (declaration) return declaration;
1035
+ continue;
1036
+ }
1037
+ if (ts.isFunctionDeclaration(statement) && hasDefaultModifier(statement)) return statement;
1038
+ if (ts.isClassDeclaration(statement) && hasDefaultModifier(statement)) return statement;
1039
+ if (ts.isInterfaceDeclaration(statement) && hasDefaultModifier(statement)) return statement;
1040
+ if (ts.isTypeAliasDeclaration(statement) && hasDefaultModifier(statement)) return statement;
1041
+ if (ts.isEnumDeclaration(statement) && hasDefaultModifier(statement)) return statement;
1042
+ if (ts.isVariableStatement(statement) && hasDefaultModifier(statement)) {
1043
+ const declaration = statement.declarationList.declarations.find(
1044
+ (item) => ts.isIdentifier(item.name)
1045
+ );
1046
+ return declaration;
1047
+ }
1048
+ }
1049
+ return void 0;
1050
+ }
1051
+ function hasAnyTopLevelExport(sourceFile) {
1052
+ return sourceFile.statements.some(
1053
+ (statement) => ts.isExportAssignment(statement) || hasExportModifier(statement)
1054
+ );
1055
+ }
1056
+ function topLevelDeclarations(sourceFile) {
1057
+ return sourceFile.statements.flatMap((statement) => {
1058
+ if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement)) {
1059
+ return [statement];
1060
+ }
1061
+ if (ts.isVariableStatement(statement)) return [...statement.declarationList.declarations];
1062
+ return [];
1063
+ });
1064
+ }
1065
+ function getSignature(context, node) {
1066
+ const type = context.checker.getTypeAtLocation(node);
1067
+ const signatures = type.getCallSignatures();
1068
+ if (signatures.length > 0) {
1069
+ return [
1070
+ ...new Set(
1071
+ signatures.map(
1072
+ (signature) => context.checker.signatureToString(
1073
+ signature,
1074
+ node,
1075
+ ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope
1076
+ )
1077
+ )
1078
+ )
1079
+ ].map(redactStringLiterals).join(" | ");
1080
+ }
1081
+ if (ts.isClassDeclaration(node)) {
1082
+ return compactText(node.getText(context.sourceFile).split(/[\r\n{]/, 1)[0] ?? "");
1083
+ }
1084
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
1085
+ return compactText(redactStringLiterals(node.getText(context.sourceFile)));
1086
+ }
1087
+ if (ts.isVariableDeclaration(node) && node.type) {
1088
+ return `${getDeclarationName(node)}: ${compactText(node.type.getText(context.sourceFile))}`;
1089
+ }
1090
+ return void 0;
1091
+ }
1092
+ function redactStringLiterals(value) {
1093
+ return value.replace(/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/g, '"<literal>"');
1094
+ }
1095
+ function makeSymbol(context, node, kind, options = {}) {
1096
+ const name = options.name ?? (ts.isSourceFile(node) ? "default" : getDeclarationName(node));
1097
+ const documentation = options.documentation;
1098
+ const signature = options.signature ?? (ts.isSourceFile(node) ? void 0 : getSignature(context, node));
1099
+ const safeSignature = signature ? redactAbsolutePathsInText(signature, context.root) : void 0;
1100
+ const safeDocumentation = documentation ? redactAbsolutePathsInText(documentation, context.root) : void 0;
1101
+ return {
1102
+ schemaVersion: SCHEMA_VERSION,
1103
+ id: symbolId(kind, context.relativePath, name, options.parent),
1104
+ name,
1105
+ kind,
1106
+ file: context.relativePath,
1107
+ range: nodeRange(context.sourceFile, node),
1108
+ exported: options.exported ?? (!ts.isSourceFile(node) && hasExportModifier(node)),
1109
+ ...options.defaultExport ?? (!ts.isSourceFile(node) && hasDefaultModifier(node)) ? { defaultExport: true } : {},
1110
+ ...safeSignature ? { signature: compactText(safeSignature) } : {},
1111
+ ...safeDocumentation ? { documentation: compactText(safeDocumentation) } : {},
1112
+ facts: options.facts ?? {},
1113
+ sourceHash: context.sourceHash
1114
+ };
1115
+ }
1116
+ function compactText(value, maxLength = 500) {
1117
+ const compact = value.replace(/\s+/g, " ").trim();
1118
+ return compact.length > maxLength ? `${compact.slice(0, maxLength - 1)}\u2026` : compact;
1119
+ }
1120
+ function visit(node, callback) {
1121
+ callback(node);
1122
+ node.forEachChild((child) => visit(child, callback));
1123
+ }
1124
+ function decoratorsOf(node) {
1125
+ return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
1126
+ }
1127
+ function propertyNameText(name) {
1128
+ if (!name) return void 0;
1129
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
1130
+ return name.text;
1131
+ return void 0;
1132
+ }
1133
+
1134
+ // src/analyzers/core.ts
1135
+ var CoreAnalyzer = class {
1136
+ name = "typescript-node";
1137
+ version = ANALYZER_VERSION;
1138
+ supports() {
1139
+ return true;
1140
+ }
1141
+ analyze(context) {
1142
+ const symbols = [];
1143
+ const relations = [];
1144
+ const seenSymbols = /* @__PURE__ */ new Map();
1145
+ for (const statement of context.sourceFile.statements) {
1146
+ extractTopLevelSymbol(context, statement, seenSymbols, relations);
1147
+ }
1148
+ symbols.push(...seenSymbols.values());
1149
+ const declarationIds = declarationIdMap(context, symbols);
1150
+ collectBehaviorRelations(context, declarationIds, relations);
1151
+ return {
1152
+ symbols: symbols.sort((a, b) => compareText(a.id, b.id)),
1153
+ relations,
1154
+ frameworks: ["typescript", "node"]
1155
+ };
1156
+ }
1157
+ };
1158
+ function extractTopLevelSymbol(context, statement, output, relations) {
1159
+ if (ts2.isFunctionDeclaration(statement)) {
1160
+ const name = statement.name?.text ?? "default";
1161
+ const record = makeSymbol(context, statement, "function", {
1162
+ name,
1163
+ exported: isNameExported(context, name, statement),
1164
+ facts: { async: hasModifier(statement, ts2.SyntaxKind.AsyncKeyword) }
1165
+ });
1166
+ keepBestDeclaration(output, record, Boolean(statement.body));
1167
+ return;
1168
+ }
1169
+ if (ts2.isClassDeclaration(statement)) {
1170
+ const className = statement.name?.text ?? "default";
1171
+ const classRecord = makeSymbol(context, statement, "class", {
1172
+ name: className,
1173
+ exported: isNameExported(context, className, statement)
1174
+ });
1175
+ keepBestDeclaration(output, classRecord, true);
1176
+ for (const member of statement.members) {
1177
+ if (!ts2.isMethodDeclaration(member) && !ts2.isConstructorDeclaration(member)) continue;
1178
+ if (hasModifier(member, ts2.SyntaxKind.PrivateKeyword) && !isPrivateEnabled(context)) {
1179
+ continue;
1180
+ }
1181
+ const name = ts2.isConstructorDeclaration(member) ? "constructor" : propertyNameText(member.name) ?? "anonymous";
1182
+ const method = makeSymbol(context, member, "method", {
1183
+ name,
1184
+ parent: className,
1185
+ exported: classRecord.exported,
1186
+ facts: {
1187
+ async: ts2.isMethodDeclaration(member) ? hasModifier(member, ts2.SyntaxKind.AsyncKeyword) : false
1188
+ }
1189
+ });
1190
+ keepBestDeclaration(output, method, Boolean(member.body));
1191
+ relations.push(
1192
+ relation(classRecord.id, "contains", method.id, context, member, 1, "class member")
1193
+ );
1194
+ }
1195
+ return;
1196
+ }
1197
+ if (ts2.isInterfaceDeclaration(statement)) {
1198
+ keepBestDeclaration(
1199
+ output,
1200
+ makeSymbol(context, statement, "interface", {
1201
+ exported: isNameExported(context, statement.name.text, statement)
1202
+ }),
1203
+ true
1204
+ );
1205
+ return;
1206
+ }
1207
+ if (ts2.isTypeAliasDeclaration(statement)) {
1208
+ keepBestDeclaration(
1209
+ output,
1210
+ makeSymbol(context, statement, "type-alias", {
1211
+ exported: isNameExported(context, statement.name.text, statement)
1212
+ }),
1213
+ true
1214
+ );
1215
+ return;
1216
+ }
1217
+ if (ts2.isVariableStatement(statement)) {
1218
+ const defaultExport = hasDefaultModifier(statement);
1219
+ for (const declaration of statement.declarationList.declarations) {
1220
+ if (!ts2.isIdentifier(declaration.name)) continue;
1221
+ const functionLike = declaration.initializer !== void 0 && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer));
1222
+ const record = makeSymbol(context, declaration, functionLike ? "function" : "variable", {
1223
+ exported: isNameExported(context, declaration.name.text, statement),
1224
+ defaultExport,
1225
+ facts: functionLike ? { async: hasModifier(declaration.initializer, ts2.SyntaxKind.AsyncKeyword) } : {}
1226
+ });
1227
+ keepBestDeclaration(output, record, true);
1228
+ }
1229
+ }
1230
+ }
1231
+ function isNameExported(context, name, node) {
1232
+ if (hasExportModifier(node) || hasDefaultModifier(node)) return true;
1233
+ return context.file.exports.some(
1234
+ (entry) => !entry.source && (entry.local === name || !entry.local && entry.name === name)
1235
+ );
1236
+ }
1237
+ function isPrivateEnabled(context) {
1238
+ return context.includePrivateSymbols;
1239
+ }
1240
+ function keepBestDeclaration(output, record, hasImplementation) {
1241
+ const existing = output.get(record.id);
1242
+ if (!existing || hasImplementation || !existing.signature) output.set(record.id, record);
1243
+ }
1244
+ function declarationIdMap(context, symbols) {
1245
+ const byKey = new Map(symbols.map((symbol) => [`${symbol.kind}:${symbol.name}`, symbol.id]));
1246
+ const result2 = /* @__PURE__ */ new Map();
1247
+ for (const statement of context.sourceFile.statements) {
1248
+ if (ts2.isFunctionDeclaration(statement)) {
1249
+ const name = statement.name?.text ?? "default";
1250
+ const id = byKey.get(`function:${name}`);
1251
+ if (id) result2.set(statement, id);
1252
+ } else if (ts2.isClassDeclaration(statement)) {
1253
+ const name = statement.name?.text ?? "default";
1254
+ const id = byKey.get(`class:${name}`);
1255
+ if (id) result2.set(statement, id);
1256
+ for (const member of statement.members) {
1257
+ if (!ts2.isMethodDeclaration(member) && !ts2.isConstructorDeclaration(member)) continue;
1258
+ const memberName = ts2.isConstructorDeclaration(member) ? "constructor" : propertyNameText(member.name) ?? "anonymous";
1259
+ const memberId = symbolId("method", context.relativePath, memberName, name);
1260
+ result2.set(member, memberId);
1261
+ }
1262
+ } else if (ts2.isVariableStatement(statement)) {
1263
+ for (const declaration of statement.declarationList.declarations) {
1264
+ if (!ts2.isIdentifier(declaration.name)) continue;
1265
+ const kind = declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer)) ? "function" : "variable";
1266
+ const id = byKey.get(`${kind}:${declaration.name.text}`);
1267
+ if (id) result2.set(declaration, id);
1268
+ }
1269
+ } else if (ts2.isInterfaceDeclaration(statement) || ts2.isTypeAliasDeclaration(statement)) {
1270
+ const kind = ts2.isInterfaceDeclaration(statement) ? "interface" : "type-alias";
1271
+ const id = byKey.get(`${kind}:${statement.name.text}`);
1272
+ if (id) result2.set(statement, id);
1273
+ }
1274
+ }
1275
+ return result2;
1276
+ }
1277
+ function collectBehaviorRelations(context, declarationIds, output) {
1278
+ let currentOwner = fileId(context.relativePath);
1279
+ const walk = (node) => {
1280
+ const previousOwner = currentOwner;
1281
+ if (ts2.isFunctionDeclaration(node) || ts2.isMethodDeclaration(node) || ts2.isConstructorDeclaration(node)) {
1282
+ currentOwner = declarationIds.get(node) ?? currentOwner;
1283
+ } else if ((ts2.isArrowFunction(node) || ts2.isFunctionExpression(node)) && ts2.isVariableDeclaration(node.parent)) {
1284
+ currentOwner = declarationIds.get(node.parent) ?? currentOwner;
1285
+ } else if (ts2.isClassDeclaration(node)) {
1286
+ currentOwner = declarationIds.get(node) ?? currentOwner;
1287
+ }
1288
+ if (ts2.isCallExpression(node) && !isRequireCall(node)) {
1289
+ const target = resolveExpressionTarget(context, node.expression);
1290
+ if (target && target.id !== currentOwner) {
1291
+ output.push(
1292
+ relation(
1293
+ currentOwner,
1294
+ "calls",
1295
+ target.id,
1296
+ context,
1297
+ node,
1298
+ target.exact ? 1 : 0.35,
1299
+ target.exact ? "TypeScript symbol resolution" : "unresolved call syntax"
1300
+ )
1301
+ );
1302
+ }
1303
+ } else if (ts2.isNewExpression(node)) {
1304
+ const target = resolveExpressionTarget(context, node.expression);
1305
+ if (target && target.id !== currentOwner) {
1306
+ output.push(
1307
+ relation(
1308
+ currentOwner,
1309
+ "instantiates",
1310
+ target.id,
1311
+ context,
1312
+ node,
1313
+ target.exact ? 1 : 0.35,
1314
+ target.exact ? "TypeScript constructor resolution" : "unresolved constructor syntax"
1315
+ )
1316
+ );
1317
+ }
1318
+ }
1319
+ if (ts2.isIdentifier(node) && isReferenceIdentifier(node)) {
1320
+ const target = resolveExpressionTarget(context, node, false);
1321
+ if (target?.exact && target.id !== currentOwner && !target.id.startsWith("external:")) {
1322
+ output.push(
1323
+ relation(
1324
+ currentOwner,
1325
+ "references",
1326
+ target.id,
1327
+ context,
1328
+ node,
1329
+ 1,
1330
+ "TypeScript symbol reference"
1331
+ )
1332
+ );
1333
+ }
1334
+ }
1335
+ node.forEachChild(walk);
1336
+ currentOwner = previousOwner;
1337
+ };
1338
+ walk(context.sourceFile);
1339
+ for (const statement of context.sourceFile.statements) {
1340
+ if (!ts2.isClassDeclaration(statement)) continue;
1341
+ const from = declarationIds.get(statement);
1342
+ if (!from) continue;
1343
+ for (const clause of statement.heritageClauses ?? []) {
1344
+ for (const type of clause.types) {
1345
+ const target = resolveExpressionTarget(context, type.expression);
1346
+ if (target) {
1347
+ output.push(
1348
+ relation(
1349
+ from,
1350
+ "depends-on",
1351
+ target.id,
1352
+ context,
1353
+ type,
1354
+ target.exact ? 1 : 0.35,
1355
+ target.exact ? "TypeScript heritage resolution" : "unresolved heritage syntax"
1356
+ )
1357
+ );
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+ }
1363
+ function resolveExpressionTarget(context, expression, allowFallback = true) {
1364
+ let symbol = context.checker.getSymbolAtLocation(expression);
1365
+ if (!symbol && ts2.isPropertyAccessExpression(expression)) {
1366
+ symbol = context.checker.getSymbolAtLocation(expression.name);
1367
+ }
1368
+ const externalBinding = importedExpressionBinding(context, expression);
1369
+ if (!symbol) {
1370
+ const imported = resolveImportedExpressionTarget(context, expression);
1371
+ if (imported) return { id: imported, exact: true };
1372
+ if (externalBinding) {
1373
+ return {
1374
+ id: externalId(externalBinding.module, externalBinding.exported),
1375
+ exact: true
1376
+ };
1377
+ }
1378
+ const fallback2 = allowFallback ? expressionFallbackId(expression) : void 0;
1379
+ return fallback2 ? { id: fallback2, exact: false } : void 0;
1380
+ }
1381
+ if (symbol.flags & ts2.SymbolFlags.Alias) {
1382
+ try {
1383
+ symbol = context.checker.getAliasedSymbol(symbol);
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
1388
+ if (!declaration) {
1389
+ const imported = resolveImportedExpressionTarget(context, expression);
1390
+ if (imported) return { id: imported, exact: true };
1391
+ if (externalBinding) {
1392
+ return {
1393
+ id: externalId(externalBinding.module, externalBinding.exported),
1394
+ exact: true
1395
+ };
1396
+ }
1397
+ const fallback2 = allowFallback ? expressionFallbackId(expression) : void 0;
1398
+ return fallback2 ? { id: fallback2, exact: false } : void 0;
1399
+ }
1400
+ const source = declaration.getSourceFile();
1401
+ if (!isPathInside(context.root, source.fileName)) {
1402
+ return externalBinding ? {
1403
+ id: externalId(externalBinding.module, externalBinding.exported),
1404
+ exact: true
1405
+ } : allowFallback ? { id: externalId("unresolved", symbol.getName()), exact: false } : void 0;
1406
+ }
1407
+ const relative = relativePosix(context.root, source.fileName);
1408
+ if (context.indexedSourcePaths && !context.indexedSourcePaths.has(relative)) {
1409
+ return externalBinding ? {
1410
+ id: externalId(externalBinding.module, externalBinding.exported),
1411
+ exact: true
1412
+ } : allowFallback ? { id: externalId("unresolved", symbol.getName()), exact: false } : void 0;
1413
+ }
1414
+ const id = declarationToId(declaration, relative);
1415
+ if (id) return { id, exact: true };
1416
+ if (externalBinding) {
1417
+ return {
1418
+ id: externalId(externalBinding.module, externalBinding.exported),
1419
+ exact: true
1420
+ };
1421
+ }
1422
+ const fallback = allowFallback ? expressionFallbackId(expression) : void 0;
1423
+ return fallback ? { id: fallback, exact: false } : void 0;
1424
+ }
1425
+ function resolveImportedExpressionTarget(context, expression) {
1426
+ const binding = importedExpressionBinding(context, expression, true);
1427
+ if (!binding?.resolved) return void 0;
1428
+ if (ts2.isIdentifier(expression)) {
1429
+ const sourceFile2 = context.program.getSourceFile(path4.resolve(context.root, binding.resolved));
1430
+ if (!sourceFile2) return void 0;
1431
+ const declaration2 = findExportedDeclaration(sourceFile2, binding.imported);
1432
+ return declaration2 ? declarationToId(declaration2, binding.resolved) : void 0;
1433
+ }
1434
+ if (!ts2.isPropertyAccessExpression(expression)) return void 0;
1435
+ const sourceFile = context.program.getSourceFile(path4.resolve(context.root, binding.resolved));
1436
+ if (!sourceFile) return void 0;
1437
+ const declaration = findExportedDeclaration(sourceFile, expression.name.text);
1438
+ return declaration ? declarationToId(declaration, binding.resolved) : void 0;
1439
+ }
1440
+ function findExportedDeclaration(sourceFile, exportedName) {
1441
+ for (const statement of sourceFile.statements) {
1442
+ if ((ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement) || ts2.isInterfaceDeclaration(statement) || ts2.isTypeAliasDeclaration(statement)) && (exportedName === "default" && hasDefaultModifier(statement) || statement.name?.text === exportedName)) {
1443
+ return statement;
1444
+ }
1445
+ if (ts2.isVariableStatement(statement)) {
1446
+ const declaration = statement.declarationList.declarations.find(
1447
+ (item) => ts2.isIdentifier(item.name) && item.name.text === exportedName
1448
+ );
1449
+ if (declaration) return declaration;
1450
+ }
1451
+ if (ts2.isExportAssignment(statement) && !statement.isExportEquals && ts2.isIdentifier(statement.expression)) {
1452
+ if (exportedName === "default") {
1453
+ const declaration = findDeclarationByName(sourceFile, statement.expression.text);
1454
+ if (declaration) return declaration;
1455
+ }
1456
+ if (statement.expression.text === exportedName) {
1457
+ return findDeclarationByName(sourceFile, statement.expression.text);
1458
+ }
1459
+ }
1460
+ if (ts2.isExportDeclaration(statement) && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
1461
+ const specifier = statement.exportClause.elements.find(
1462
+ (item) => item.name.text === exportedName
1463
+ );
1464
+ if (specifier && !statement.moduleSpecifier) {
1465
+ return findExportedDeclaration(
1466
+ sourceFile,
1467
+ specifier.propertyName?.text ?? specifier.name.text
1468
+ );
1469
+ }
1470
+ }
1471
+ }
1472
+ return void 0;
1473
+ }
1474
+ function findDeclarationByName(sourceFile, name) {
1475
+ for (const statement of sourceFile.statements) {
1476
+ if ((ts2.isFunctionDeclaration(statement) || ts2.isClassDeclaration(statement) || ts2.isInterfaceDeclaration(statement) || ts2.isTypeAliasDeclaration(statement)) && statement.name?.text === name) {
1477
+ return statement;
1478
+ }
1479
+ if (ts2.isVariableStatement(statement)) {
1480
+ const declaration = statement.declarationList.declarations.find(
1481
+ (item) => ts2.isIdentifier(item.name) && item.name.text === name
1482
+ );
1483
+ if (declaration) return declaration;
1484
+ }
1485
+ }
1486
+ return void 0;
1487
+ }
1488
+ function importedExpressionBinding(context, expression, includeLocal = false) {
1489
+ const root = leftMostIdentifierNode(expression);
1490
+ if (!root) return void 0;
1491
+ const rootSymbol = context.checker.getSymbolAtLocation(root);
1492
+ if (!rootSymbol) return void 0;
1493
+ const declaration = rootSymbol.declarations?.find(isImportBindingDeclaration);
1494
+ if (!declaration) return void 0;
1495
+ const importDeclaration = enclosingImportDeclaration(declaration);
1496
+ if (!importDeclaration || !ts2.isStringLiteral(importDeclaration.moduleSpecifier))
1497
+ return void 0;
1498
+ const source = sanitizeModuleSpecifier(importDeclaration.moduleSpecifier.text);
1499
+ const line = nodeLine(context.sourceFile, importDeclaration);
1500
+ const imported = context.file.imports.find(
1501
+ (record) => record.kind === "esm" && record.source === source && record.line === line && record.specifiers.some((specifier2) => specifier2.local === root.text)
1502
+ );
1503
+ if (!imported || !includeLocal && !imported.external) return void 0;
1504
+ const specifier = imported.specifiers.find((item) => item.local === root.text);
1505
+ if (!specifier) return void 0;
1506
+ return {
1507
+ module: imported.source,
1508
+ exported: ts2.isPropertyAccessExpression(expression) ? expression.name.text : specifier.imported,
1509
+ imported: specifier.imported,
1510
+ ...imported.resolved ? { resolved: imported.resolved } : {}
1511
+ };
1512
+ }
1513
+ function isImportBindingDeclaration(declaration) {
1514
+ return ts2.isImportSpecifier(declaration) || ts2.isImportClause(declaration) || ts2.isNamespaceImport(declaration);
1515
+ }
1516
+ function enclosingImportDeclaration(declaration) {
1517
+ let current = declaration;
1518
+ while (!ts2.isSourceFile(current)) {
1519
+ if (ts2.isImportDeclaration(current)) return current;
1520
+ current = current.parent;
1521
+ }
1522
+ return void 0;
1523
+ }
1524
+ function declarationToId(declaration, filePath) {
1525
+ if (ts2.isFunctionDeclaration(declaration)) {
1526
+ return symbolId("function", filePath, declaration.name?.text ?? "default");
1527
+ }
1528
+ if (ts2.isClassDeclaration(declaration)) {
1529
+ return symbolId("class", filePath, declaration.name?.text ?? "default");
1530
+ }
1531
+ if (ts2.isMethodDeclaration(declaration)) {
1532
+ const parent = declaration.parent;
1533
+ const parentName = ts2.isClassDeclaration(parent) || ts2.isInterfaceDeclaration(parent) ? parent.name?.text ?? "default" : "anonymous";
1534
+ return symbolId(
1535
+ "method",
1536
+ filePath,
1537
+ propertyNameText(declaration.name) ?? "anonymous",
1538
+ parentName
1539
+ );
1540
+ }
1541
+ if (ts2.isVariableDeclaration(declaration)) {
1542
+ const name = getDeclarationName(declaration);
1543
+ const kind = declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer)) ? "function" : "variable";
1544
+ return symbolId(kind, filePath, name);
1545
+ }
1546
+ if (ts2.isInterfaceDeclaration(declaration))
1547
+ return symbolId("interface", filePath, declaration.name.text);
1548
+ if (ts2.isTypeAliasDeclaration(declaration))
1549
+ return symbolId("type-alias", filePath, declaration.name.text);
1550
+ return void 0;
1551
+ }
1552
+ function isReferenceIdentifier(node) {
1553
+ const parent = node.parent;
1554
+ if (parent.name === node || ts2.isImportSpecifier(parent) || ts2.isImportClause(parent) || ts2.isNamespaceImport(parent) || ts2.isExportSpecifier(parent) || ts2.isBindingElement(parent) || ts2.isPropertyAccessExpression(parent) && parent.name === node || ts2.isPropertyAssignment(parent) && parent.name === node || ts2.isPropertySignature(parent) && parent.name === node || ts2.isPropertyDeclaration(parent) && parent.name === node || ts2.isMethodDeclaration(parent) && parent.name === node) {
1555
+ return false;
1556
+ }
1557
+ return true;
1558
+ }
1559
+ function expressionFallbackId(expression) {
1560
+ if (ts2.isIdentifier(expression)) return externalId("unresolved", expression.text);
1561
+ if (ts2.isPropertyAccessExpression(expression)) {
1562
+ return externalId("unresolved", expression.name.text);
1563
+ }
1564
+ return void 0;
1565
+ }
1566
+ function leftMostIdentifierNode(expression) {
1567
+ let current = expression;
1568
+ while (ts2.isPropertyAccessExpression(current)) current = current.expression;
1569
+ return ts2.isIdentifier(current) ? current : void 0;
1570
+ }
1571
+ function extractFileStructure(context) {
1572
+ const imports = [];
1573
+ const exports = [];
1574
+ const env = /* @__PURE__ */ new Set();
1575
+ const operations = [];
1576
+ const diagnostics = [];
1577
+ const relations = [];
1578
+ const importedModules = /* @__PURE__ */ new Map();
1579
+ for (const statement of context.sourceFile.statements) {
1580
+ if (ts2.isImportDeclaration(statement) && ts2.isStringLiteral(statement.moduleSpecifier)) {
1581
+ const rawSource = statement.moduleSpecifier.text;
1582
+ const source = sanitizeModuleSpecifier(rawSource);
1583
+ const specifiers = importSpecifiers(statement);
1584
+ for (const specifier of specifiers) importedModules.set(specifier.local, source);
1585
+ const resolved = resolveImport(context, rawSource);
1586
+ imports.push({
1587
+ source,
1588
+ specifiers,
1589
+ ...resolved.path ? { resolved: resolved.path } : {},
1590
+ external: resolved.external,
1591
+ kind: "esm",
1592
+ line: nodeLine(context.sourceFile, statement)
1593
+ });
1594
+ addImportRelation(context, relations, statement, source, resolved);
1595
+ if (!resolved.found && !isBuiltin(rawSource))
1596
+ diagnostics.push(unresolvedImport(context, statement, source));
1597
+ } else if (ts2.isExportDeclaration(statement)) {
1598
+ extractExportDeclaration(context, statement, exports, relations);
1599
+ } else if (ts2.isExportAssignment(statement)) {
1600
+ exports.push({
1601
+ name: "default",
1602
+ local: ts2.isIdentifier(statement.expression) ? statement.expression.text : "default",
1603
+ default: true,
1604
+ line: nodeLine(context.sourceFile, statement)
1605
+ });
1606
+ }
1607
+ }
1608
+ visit(context.sourceFile, (node) => {
1609
+ if (ts2.isCallExpression(node) && isRequireCall(node)) {
1610
+ const argument = node.arguments[0];
1611
+ if (!argument || !ts2.isStringLiteral(argument)) return;
1612
+ const rawSource = argument.text;
1613
+ const source = sanitizeModuleSpecifier(rawSource);
1614
+ const resolved = resolveImport(context, rawSource);
1615
+ imports.push({
1616
+ source,
1617
+ specifiers: [],
1618
+ ...resolved.path ? { resolved: resolved.path } : {},
1619
+ external: resolved.external,
1620
+ kind: "commonjs",
1621
+ line: nodeLine(context.sourceFile, node)
1622
+ });
1623
+ addImportRelation(context, relations, node, source, resolved);
1624
+ if (!resolved.found && !isBuiltin(rawSource))
1625
+ diagnostics.push(unresolvedImport(context, node, source));
1626
+ }
1627
+ const envName = processEnvName(node);
1628
+ if (envName) env.add(envName);
1629
+ const operation = externalOperation(node, context.sourceFile, importedModules);
1630
+ if (operation) operations.push(operation);
1631
+ });
1632
+ extractCommonJsExports(context, exports);
1633
+ return {
1634
+ imports: dedupeImports(imports),
1635
+ exports: dedupeExports(exports),
1636
+ env: [...env].sort(compareText),
1637
+ operations: dedupeOperations(operations),
1638
+ diagnostics,
1639
+ relations
1640
+ };
1641
+ }
1642
+ function importSpecifiers(statement) {
1643
+ const clause = statement.importClause;
1644
+ if (!clause) return [];
1645
+ const output = [];
1646
+ const clauseTypeOnly = clause.phaseModifier === ts2.SyntaxKind.TypeKeyword;
1647
+ if (clause.name) {
1648
+ output.push({
1649
+ imported: "default",
1650
+ local: clause.name.text,
1651
+ ...clauseTypeOnly ? { typeOnly: true } : {}
1652
+ });
1653
+ }
1654
+ const bindings = clause.namedBindings;
1655
+ if (bindings && ts2.isNamespaceImport(bindings)) {
1656
+ output.push({
1657
+ imported: "*",
1658
+ local: bindings.name.text,
1659
+ ...clauseTypeOnly ? { typeOnly: true } : {}
1660
+ });
1661
+ } else if (bindings && ts2.isNamedImports(bindings)) {
1662
+ for (const element of bindings.elements) {
1663
+ output.push({
1664
+ imported: element.propertyName?.text ?? element.name.text,
1665
+ local: element.name.text,
1666
+ ...clauseTypeOnly || element.isTypeOnly ? { typeOnly: true } : {}
1667
+ });
1668
+ }
1669
+ }
1670
+ return output.sort((a, b) => compareText(`${a.imported}:${a.local}`, `${b.imported}:${b.local}`));
1671
+ }
1672
+ function extractExportDeclaration(context, statement, exports, relations) {
1673
+ const rawSource = statement.moduleSpecifier && ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : void 0;
1674
+ const source = rawSource ? sanitizeModuleSpecifier(rawSource) : void 0;
1675
+ const line = nodeLine(context.sourceFile, statement);
1676
+ if (!statement.exportClause) {
1677
+ exports.push({ name: "*", ...source ? { source } : {}, default: false, line });
1678
+ } else if (ts2.isNamespaceExport(statement.exportClause)) {
1679
+ exports.push({
1680
+ name: statement.exportClause.name.text,
1681
+ ...source ? { source } : {},
1682
+ default: false,
1683
+ line
1684
+ });
1685
+ } else {
1686
+ for (const element of statement.exportClause.elements) {
1687
+ exports.push({
1688
+ name: element.name.text,
1689
+ local: element.propertyName?.text ?? element.name.text,
1690
+ ...source ? { source } : {},
1691
+ default: element.name.text === "default",
1692
+ ...statement.isTypeOnly || element.isTypeOnly ? { typeOnly: true } : {},
1693
+ line
1694
+ });
1695
+ }
1696
+ }
1697
+ if (rawSource && source) {
1698
+ const resolved = resolveImport(context, rawSource);
1699
+ relations.push(
1700
+ relation(
1701
+ fileId(context.relativePath),
1702
+ "reexports",
1703
+ resolved.path ? fileId(resolved.path) : externalId(source),
1704
+ context,
1705
+ statement,
1706
+ resolved.found ? 1 : 0.5,
1707
+ resolved.found ? "resolved re-export" : "unresolved re-export"
1708
+ )
1709
+ );
1710
+ }
1711
+ }
1712
+ function extractCommonJsExports(context, exports) {
1713
+ visit(context.sourceFile, (node) => {
1714
+ if (!ts2.isBinaryExpression(node) || node.operatorToken.kind !== ts2.SyntaxKind.EqualsToken)
1715
+ return;
1716
+ const left = node.left;
1717
+ const line = nodeLine(context.sourceFile, node);
1718
+ if (ts2.isPropertyAccessExpression(left) && ts2.isIdentifier(left.expression) && left.expression.text === "module" && left.name.text === "exports") {
1719
+ exports.push({
1720
+ name: "default",
1721
+ ...ts2.isIdentifier(node.right) ? { local: node.right.text } : {},
1722
+ default: true,
1723
+ line
1724
+ });
1725
+ } else if (ts2.isPropertyAccessExpression(left) && (ts2.isIdentifier(left.expression) && left.expression.text === "exports" || ts2.isPropertyAccessExpression(left.expression) && ts2.isIdentifier(left.expression.expression) && left.expression.expression.text === "module" && left.expression.name.text === "exports")) {
1726
+ exports.push({
1727
+ name: left.name.text,
1728
+ local: ts2.isIdentifier(node.right) ? node.right.text : left.name.text,
1729
+ default: false,
1730
+ line
1731
+ });
1732
+ }
1733
+ });
1734
+ }
1735
+ function resolveImport(context, moduleName) {
1736
+ if (isBuiltin(moduleName) || moduleName.startsWith("node:"))
1737
+ return { found: true, external: true };
1738
+ const resolution = context.resolveModule?.(moduleName, context.sourceFile.fileName);
1739
+ if (!resolution) return { found: false, external: !moduleName.startsWith(".") };
1740
+ if (resolution.isExternalLibraryImport) return { found: true, external: true };
1741
+ if (!isPathInside(context.root, resolution.resolvedFileName)) {
1742
+ return { found: true, external: true };
1743
+ }
1744
+ const relative = relativePosix(context.root, resolution.resolvedFileName);
1745
+ if (context.indexedSourcePaths && !context.indexedSourcePaths.has(relative)) {
1746
+ return { found: false, external: false };
1747
+ }
1748
+ return {
1749
+ found: true,
1750
+ external: false,
1751
+ path: relative
1752
+ };
1753
+ }
1754
+ function addImportRelation(context, output, node, source, resolved) {
1755
+ output.push(
1756
+ relation(
1757
+ fileId(context.relativePath),
1758
+ "imports",
1759
+ resolved.path ? fileId(resolved.path) : externalId(source),
1760
+ context,
1761
+ node,
1762
+ resolved.found ? 1 : 0.5,
1763
+ resolved.found ? "module resolution" : "unresolved import text"
1764
+ )
1765
+ );
1766
+ }
1767
+ function unresolvedImport(context, node, source) {
1768
+ return {
1769
+ schemaVersion: SCHEMA_VERSION,
1770
+ code: "unresolved-import",
1771
+ severity: "warning",
1772
+ message: `Could not resolve import ${source}.`,
1773
+ file: context.relativePath,
1774
+ line: nodeLine(context.sourceFile, node)
1775
+ };
1776
+ }
1777
+ function processEnvName(node) {
1778
+ if (ts2.isPropertyAccessExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.expression) && node.expression.expression.text === "process" && node.expression.name.text === "env") {
1779
+ return node.name.text;
1780
+ }
1781
+ if (ts2.isElementAccessExpression(node) && ts2.isPropertyAccessExpression(node.expression) && ts2.isIdentifier(node.expression.expression) && node.expression.expression.text === "process" && node.expression.name.text === "env" && ts2.isStringLiteral(node.argumentExpression)) {
1782
+ return node.argumentExpression.text;
1783
+ }
1784
+ return void 0;
1785
+ }
1786
+ function externalOperation(node, sourceFile, importedModules) {
1787
+ if (!ts2.isCallExpression(node)) return void 0;
1788
+ let root;
1789
+ let api;
1790
+ if (ts2.isIdentifier(node.expression)) {
1791
+ root = node.expression.text;
1792
+ api = root;
1793
+ } else if (ts2.isPropertyAccessExpression(node.expression)) {
1794
+ root = leftMostIdentifierNode(node.expression)?.text;
1795
+ api = node.expression.getText(sourceFile);
1796
+ } else return void 0;
1797
+ const source = root ? importedModules.get(root) : void 0;
1798
+ let kind;
1799
+ if (root === "fetch" || source === "axios" || source === "http" || source === "https" || source?.startsWith("node:http"))
1800
+ kind = "http";
1801
+ else if (source === "fs" || source === "fs/promises" || source?.startsWith("node:fs"))
1802
+ kind = "filesystem";
1803
+ else if (source && /(?:prisma|typeorm|mongoose|sequelize|pg|mysql|database|redis)/i.test(source))
1804
+ kind = "database";
1805
+ else if (source && /(?:bull|queue|kafka|amqp|rabbit)/i.test(source)) kind = "queue";
1806
+ return kind ? { kind, api, line: nodeLine(sourceFile, node) } : void 0;
1807
+ }
1808
+ function isRequireCall(node) {
1809
+ return ts2.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length > 0;
1810
+ }
1811
+ function dedupeImports(values) {
1812
+ const map = /* @__PURE__ */ new Map();
1813
+ for (const value of values) {
1814
+ map.set(`${value.kind}:${value.source}:${value.line}`, value);
1815
+ }
1816
+ return [...map.values()].sort(
1817
+ (a, b) => compareText(`${a.source}:${a.line}`, `${b.source}:${b.line}`)
1818
+ );
1819
+ }
1820
+ function dedupeExports(values) {
1821
+ const map = /* @__PURE__ */ new Map();
1822
+ for (const value of values) map.set(`${value.name}:${value.source ?? ""}:${value.line}`, value);
1823
+ return [...map.values()].sort(
1824
+ (a, b) => compareText(`${a.name}:${a.line}`, `${b.name}:${b.line}`)
1825
+ );
1826
+ }
1827
+ function dedupeOperations(values) {
1828
+ const map = /* @__PURE__ */ new Map();
1829
+ for (const value of values) map.set(`${value.kind}:${value.api}:${value.line}`, value);
1830
+ return [...map.values()].sort(
1831
+ (a, b) => compareText(`${a.kind}:${a.line}:${a.api}`, `${b.kind}:${b.line}:${b.api}`)
1832
+ );
1833
+ }
1834
+ function relation(from, type, to, context, node, confidence, explanation) {
1835
+ return {
1836
+ schemaVersion: SCHEMA_VERSION,
1837
+ from,
1838
+ type,
1839
+ to,
1840
+ confidence,
1841
+ origin: {
1842
+ file: context.relativePath,
1843
+ line: nodeLine(context.sourceFile, node),
1844
+ range: nodeRange(context.sourceFile, node),
1845
+ explanation,
1846
+ classification: confidence === 1 ? "exact" : "heuristic",
1847
+ analyzer: "typescript-node"
1848
+ }
1849
+ };
1850
+ }
1851
+
1852
+ // src/analyzers/nestjs.ts
1853
+ import ts3 from "typescript";
1854
+ var METHOD_DECORATORS = /* @__PURE__ */ new Set([
1855
+ "Get",
1856
+ "Post",
1857
+ "Put",
1858
+ "Patch",
1859
+ "Delete",
1860
+ "Head",
1861
+ "Options",
1862
+ "All"
1863
+ ]);
1864
+ var NEST_DECORATORS = /* @__PURE__ */ new Set([
1865
+ "Module",
1866
+ "Controller",
1867
+ "Injectable",
1868
+ "Guard",
1869
+ "UseGuards",
1870
+ "Body",
1871
+ "Param",
1872
+ ...METHOD_DECORATORS
1873
+ ]);
1874
+ var registeredNestJsAnalyzers = /* @__PURE__ */ new WeakSet();
1875
+ var NestJsAnalyzer = class {
1876
+ name = "nestjs";
1877
+ version = "0.1.0-experimental";
1878
+ constructor() {
1879
+ registeredNestJsAnalyzers.add(this);
1880
+ }
1881
+ supports(context) {
1882
+ if (!context.relativePath.endsWith(".ts")) return false;
1883
+ return hasAnyNestImport(context.sourceFile) || containsReferencedBodyDto(context);
1884
+ }
1885
+ analyze(context) {
1886
+ const symbols = [];
1887
+ const relations = [];
1888
+ const bodyDtoSymbolIds = collectBodyDtoSymbolIds(context);
1889
+ const aliases = collectImportAliases(context.sourceFile);
1890
+ const moduleMembership = collectModuleMembership(context);
1891
+ for (const statement of context.sourceFile.statements) {
1892
+ if (!ts3.isClassDeclaration(statement) || !statement.name) continue;
1893
+ const className = statement.name.text;
1894
+ const decoratorNames = collectDecoratorNames(decoratorsOf(statement), aliases);
1895
+ const kind = classifyClass(
1896
+ className,
1897
+ decoratorNames,
1898
+ symbolId("dto", context.relativePath, className),
1899
+ bodyDtoSymbolIds
1900
+ );
1901
+ if (kind === "dto") {
1902
+ symbols.push(
1903
+ makeSymbol(context, statement, "dto", {
1904
+ name: className,
1905
+ exported: hasExportModifier(statement),
1906
+ defaultExport: hasDefaultModifier(statement),
1907
+ facts: {
1908
+ name: className.toLowerCase(),
1909
+ kind: "dto"
1910
+ }
1911
+ })
1912
+ );
1913
+ continue;
1914
+ }
1915
+ if (!kind) continue;
1916
+ const symbol = makeSymbol(context, statement, kind, {
1917
+ name: className,
1918
+ exported: hasExportModifier(statement),
1919
+ defaultExport: hasDefaultModifier(statement),
1920
+ facts: {
1921
+ name: className.toLowerCase(),
1922
+ kind
1923
+ }
1924
+ });
1925
+ symbols.push(symbol);
1926
+ if (kind === "nest-module") {
1927
+ const metadata = collectModuleMetadata(statement, aliases);
1928
+ if (metadata.controllers.length > 0) {
1929
+ symbol.facts.controllers = compactText(metadata.controllers.join(","));
1930
+ for (const controller of metadata.controllers) {
1931
+ const controllerId = resolveLocalClassId(context, controller, "nest-controller");
1932
+ if (!controllerId) continue;
1933
+ relations.push(
1934
+ makeRelation(
1935
+ symbol.id,
1936
+ "contains",
1937
+ controllerId,
1938
+ context,
1939
+ statement,
1940
+ 1,
1941
+ "module controller"
1942
+ )
1943
+ );
1944
+ }
1945
+ }
1946
+ if (metadata.providers.length > 0) {
1947
+ symbol.facts.providers = compactText(metadata.providers.join(","));
1948
+ for (const provider of metadata.providers) {
1949
+ const providerId = resolveLocalClassId(context, provider, "nest-provider");
1950
+ if (!providerId) continue;
1951
+ relations.push(
1952
+ makeRelation(
1953
+ symbol.id,
1954
+ "contains",
1955
+ providerId,
1956
+ context,
1957
+ statement,
1958
+ 1,
1959
+ "module provider"
1960
+ )
1961
+ );
1962
+ }
1963
+ }
1964
+ if (metadata.imports.length > 0) {
1965
+ symbol.facts.imports = compactText(metadata.imports.join(","));
1966
+ }
1967
+ if (metadata.exports.length > 0) {
1968
+ symbol.facts.exports = compactText(metadata.exports.join(","));
1969
+ }
1970
+ }
1971
+ if (kind === "nest-controller") {
1972
+ const baseRoute = collectControllerRoute(statement, aliases);
1973
+ symbol.facts.baseRoute = baseRoute;
1974
+ symbol.facts.kind = "controller";
1975
+ const controllerRoutes = collectControllerMethods(context, statement, className, aliases);
1976
+ relations.push(...controllerRoutes.relations);
1977
+ if (controllerRoutes.summary.length > 0) {
1978
+ symbol.facts.routes = compactText(controllerRoutes.summary.join(","));
1979
+ }
1980
+ const guards = collectClassUseGuards(statement, aliases);
1981
+ if (guards.length > 0) {
1982
+ symbol.facts.guards = compactText(guards.join(","));
1983
+ }
1984
+ }
1985
+ for (const injected of collectConstructorInjections(context, statement)) {
1986
+ relations.push(
1987
+ makeRelation(
1988
+ symbol.id,
1989
+ "injects",
1990
+ injected,
1991
+ context,
1992
+ statement,
1993
+ 1,
1994
+ "constructor injection"
1995
+ )
1996
+ );
1997
+ }
1998
+ const moduleId = moduleMembership.get(className);
1999
+ if (moduleId) {
2000
+ relations.push(
2001
+ makeRelation(
2002
+ symbol.id,
2003
+ "belongs-to-module",
2004
+ moduleId,
2005
+ context,
2006
+ statement,
2007
+ 1,
2008
+ "module membership"
2009
+ )
2010
+ );
2011
+ }
2012
+ }
2013
+ return { symbols, relations, frameworks: ["nestjs"] };
2014
+ }
2015
+ };
2016
+ var nestJsSupports = NestJsAnalyzer.prototype.supports.bind(NestJsAnalyzer.prototype);
2017
+ var nestJsAnalyze = NestJsAnalyzer.prototype.analyze.bind(NestJsAnalyzer.prototype);
2018
+ function trustedNestJsAnalyzerMethods(analyzer) {
2019
+ if (!registeredNestJsAnalyzers.has(analyzer)) return void 0;
2020
+ if (Object.getPrototypeOf(analyzer) !== NestJsAnalyzer.prototype) return void 0;
2021
+ if (Object.hasOwn(analyzer, "supports") || Object.hasOwn(analyzer, "analyze")) {
2022
+ return void 0;
2023
+ }
2024
+ const name = Object.getOwnPropertyDescriptor(analyzer, "name");
2025
+ const version = Object.getOwnPropertyDescriptor(analyzer, "version");
2026
+ if (name?.value !== "nestjs" || version?.value !== "0.1.0-experimental") return void 0;
2027
+ return {
2028
+ supports: (context) => nestJsSupports(context),
2029
+ analyze: (context) => nestJsAnalyze(context)
2030
+ };
2031
+ }
2032
+ function hasAnyNestImport(sourceFile) {
2033
+ return sourceFile.statements.some(
2034
+ (statement) => ts3.isImportDeclaration(statement) && ts3.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text.startsWith("@nestjs/")
2035
+ );
2036
+ }
2037
+ function collectImportAliases(sourceFile) {
2038
+ const aliases = {
2039
+ named: {},
2040
+ namespaces: /* @__PURE__ */ new Set()
2041
+ };
2042
+ for (const statement of sourceFile.statements) {
2043
+ if (!ts3.isImportDeclaration(statement) || !ts3.isStringLiteral(statement.moduleSpecifier))
2044
+ continue;
2045
+ if (!statement.moduleSpecifier.text.startsWith("@nestjs/")) continue;
2046
+ const clause = statement.importClause;
2047
+ if (!clause || !clause.namedBindings) continue;
2048
+ if (ts3.isNamespaceImport(clause.namedBindings)) {
2049
+ aliases.namespaces.add(clause.namedBindings.name.text);
2050
+ continue;
2051
+ }
2052
+ if (!ts3.isNamedImports(clause.namedBindings)) continue;
2053
+ for (const item of clause.namedBindings.elements) {
2054
+ aliases.named[item.name.text] = item.propertyName ? item.propertyName.text : item.name.text;
2055
+ }
2056
+ }
2057
+ return aliases;
2058
+ }
2059
+ function classifyClass(className, decorators, symbolIdValue, dtoSymbolIds) {
2060
+ if (dtoSymbolIds.has(symbolIdValue)) return "dto";
2061
+ if (decorators.includes("Module")) return "nest-module";
2062
+ if (decorators.includes("Controller")) return "nest-controller";
2063
+ if (!decorators.includes("Injectable")) return void 0;
2064
+ if (className.endsWith("Guard")) return "nest-guard";
2065
+ if (className.endsWith("Interceptor")) return "nest-interceptor";
2066
+ if (className.endsWith("Pipe")) return "nest-pipe";
2067
+ if (className.endsWith("Filter")) return "nest-filter";
2068
+ if (className.endsWith("Gateway")) return "nest-gateway";
2069
+ return "nest-provider";
2070
+ }
2071
+ function collectDecoratorNames(decorators, aliases) {
2072
+ const names = [];
2073
+ for (const decorator of decorators) {
2074
+ const name = resolveDecoratorName(decorator.expression, aliases);
2075
+ if (name && NEST_DECORATORS.has(name)) {
2076
+ names.push(name);
2077
+ }
2078
+ }
2079
+ return names;
2080
+ }
2081
+ function resolveDecoratorName(expression, aliases) {
2082
+ const target = ts3.isCallExpression(expression) ? expression.expression : expression;
2083
+ if (ts3.isIdentifier(target)) {
2084
+ const resolved = aliases.named[target.text];
2085
+ if (resolved) return resolved;
2086
+ return void 0;
2087
+ }
2088
+ if (ts3.isPropertyAccessExpression(target) && ts3.isIdentifier(target.expression)) {
2089
+ if (aliases.namespaces.has(target.expression.text)) return target.name.text;
2090
+ }
2091
+ return void 0;
2092
+ }
2093
+ function collectControllerRoute(classNode, aliases) {
2094
+ for (const decorator of decoratorsOf(classNode)) {
2095
+ const expression = decorator.expression;
2096
+ const call = ts3.isCallExpression(expression) ? expression : void 0;
2097
+ if (!call) continue;
2098
+ const name = resolveDecoratorName(expression, aliases);
2099
+ if (!name) continue;
2100
+ if (name !== "Controller") continue;
2101
+ const arg = call.arguments[0];
2102
+ if (!arg) return "/";
2103
+ if (ts3.isStringLiteral(arg) || ts3.isNoSubstitutionTemplateLiteral(arg)) {
2104
+ const value = arg.text.trim();
2105
+ return value.startsWith("/") ? value : `/${value}`;
2106
+ }
2107
+ }
2108
+ return "/";
2109
+ }
2110
+ function collectControllerMethods(context, controller, className, aliases) {
2111
+ const relations = [];
2112
+ const summary = [];
2113
+ const baseRoute = collectControllerRoute(controller, aliases);
2114
+ for (const member of controller.members) {
2115
+ if (!ts3.isMethodDeclaration(member) || !ts3.isIdentifier(member.name)) continue;
2116
+ const methodName = member.name.text;
2117
+ const methodDecorators = collectDecoratorNames(decoratorsOf(member), aliases);
2118
+ const methodVerbs = methodDecorators.filter((name) => METHOD_DECORATORS.has(name));
2119
+ if (methodVerbs.length === 0) continue;
2120
+ const methodRoute = collectMethodRoute(member, aliases);
2121
+ const fullRoute = combineRoutes(baseRoute, methodRoute);
2122
+ for (const verb of methodVerbs) {
2123
+ const lower = verb.toLowerCase();
2124
+ const methodId = symbolId("method", context.relativePath, methodName, className);
2125
+ relations.push(
2126
+ makeRelation(
2127
+ methodId,
2128
+ "handles-route",
2129
+ routeId(lower, fullRoute),
2130
+ context,
2131
+ member,
2132
+ 1,
2133
+ "http method route"
2134
+ )
2135
+ );
2136
+ summary.push(`${lower} ${fullRoute}`);
2137
+ const dto = collectBodyDto(member, aliases, context);
2138
+ if (dto) {
2139
+ relations.push(
2140
+ makeRelation(methodId, "consumes-dto", dto, context, member, 1, "dto usage")
2141
+ );
2142
+ }
2143
+ }
2144
+ }
2145
+ return { relations, summary: dedupe(summary) };
2146
+ function collectMethodRoute(methodNode, aliasesMap) {
2147
+ for (const decorator of decoratorsOf(methodNode)) {
2148
+ const expression = decorator.expression;
2149
+ const call = ts3.isCallExpression(expression) ? expression : void 0;
2150
+ if (!call) continue;
2151
+ const name = resolveDecoratorName(expression, aliasesMap);
2152
+ if (!name) continue;
2153
+ if (!METHOD_DECORATORS.has(name)) continue;
2154
+ const arg = call.arguments[0];
2155
+ if (!arg) return "/";
2156
+ if (ts3.isStringLiteral(arg) || ts3.isNoSubstitutionTemplateLiteral(arg)) {
2157
+ const route = arg.text.trim();
2158
+ return route.startsWith("/") ? route : `/${route}`;
2159
+ }
2160
+ }
2161
+ return "/";
2162
+ }
2163
+ }
2164
+ function collectBodyDto(method, aliases, context) {
2165
+ return collectBodyDtoFromMethod(method, aliases, context);
2166
+ }
2167
+ function collectBodyDtoSymbolIds(context) {
2168
+ const ids = /* @__PURE__ */ new Set();
2169
+ for (const source of context.program.getSourceFiles().filter((source2) => !source2.isDeclarationFile)) {
2170
+ if (!hasAnyNestImport(source)) continue;
2171
+ const sourceRelative = relativeFromAbsolute(context.root, source.fileName);
2172
+ if (!sourceRelative) continue;
2173
+ const aliases = collectImportAliases(source);
2174
+ for (const statement of source.statements) {
2175
+ if (!ts3.isClassDeclaration(statement)) continue;
2176
+ for (const member of statement.members) {
2177
+ if (!ts3.isMethodDeclaration(member)) continue;
2178
+ const dtoId = collectBodyDtoFromMethod(member, aliases, context);
2179
+ if (dtoId) ids.add(dtoId);
2180
+ }
2181
+ }
2182
+ }
2183
+ return ids;
2184
+ }
2185
+ function collectBodyDtoFromMethod(method, aliases, context) {
2186
+ const param = method.parameters.find((item) => hasDecorator(item, "Body", aliases));
2187
+ if (!param?.type || !ts3.isTypeReferenceNode(param.type) || !ts3.isIdentifier(param.type.typeName))
2188
+ return void 0;
2189
+ const declaration = resolveIdentifierDeclaration(context, param.type.typeName);
2190
+ if (!declaration || !ts3.isClassDeclaration(declaration) || !declaration.name) return void 0;
2191
+ const relative = relativeFromRoot(context, declaration.getSourceFile().fileName);
2192
+ if (!relative) return void 0;
2193
+ return symbolId("dto", relative, declaration.name.text);
2194
+ }
2195
+ function containsReferencedBodyDto(context) {
2196
+ const dtoIds = collectBodyDtoSymbolIds(context);
2197
+ return context.sourceFile.statements.some(
2198
+ (statement) => ts3.isClassDeclaration(statement) && statement.name !== void 0 && dtoIds.has(symbolId("dto", context.relativePath, statement.name.text))
2199
+ );
2200
+ }
2201
+ function resolveIdentifierDeclaration(context, identifier) {
2202
+ let symbol = context.checker.getSymbolAtLocation(identifier);
2203
+ if (!symbol) return void 0;
2204
+ if ((symbol.flags & ts3.SymbolFlags.Alias) !== 0) {
2205
+ symbol = context.checker.getAliasedSymbol(symbol);
2206
+ }
2207
+ return symbol.valueDeclaration ?? symbol.declarations?.[0];
2208
+ }
2209
+ function resolveLocalClassId(context, localName, kind) {
2210
+ const identifier = findTopLevelIdentifier(context.sourceFile, localName);
2211
+ if (!identifier) return void 0;
2212
+ const declaration = resolveIdentifierDeclaration(context, identifier);
2213
+ if (!declaration || !ts3.isClassDeclaration(declaration) || !declaration.name) return void 0;
2214
+ const relative = relativeFromRoot(context, declaration.getSourceFile().fileName);
2215
+ if (!relative) return void 0;
2216
+ return symbolId(kind, relative, declaration.name.text);
2217
+ }
2218
+ function findTopLevelIdentifier(sourceFile, localName) {
2219
+ for (const statement of sourceFile.statements) {
2220
+ if (ts3.isClassDeclaration(statement) && statement.name?.text === localName) {
2221
+ return statement.name;
2222
+ }
2223
+ if (!ts3.isImportDeclaration(statement)) continue;
2224
+ const clause = statement.importClause;
2225
+ if (clause?.name?.text === localName) return clause.name;
2226
+ if (!clause?.namedBindings || !ts3.isNamedImports(clause.namedBindings)) continue;
2227
+ const match = clause.namedBindings.elements.find((item) => item.name.text === localName);
2228
+ if (match) return match.name;
2229
+ }
2230
+ return void 0;
2231
+ }
2232
+ function relativeFromAbsolute(root, absolutePath) {
2233
+ const normalized = absolutePath.replace(/\\/g, "/");
2234
+ const normalizedRoot = root.replace(/\\/g, "/");
2235
+ if (normalized !== normalizedRoot && !normalized.startsWith(`${normalizedRoot}/`))
2236
+ return void 0;
2237
+ const remainder = normalized.slice(normalizedRoot.length);
2238
+ return remainder.startsWith("/") ? remainder.slice(1) : remainder;
2239
+ }
2240
+ function hasDecorator(parameter, target, aliases) {
2241
+ for (const decorator of decoratorsOf(parameter)) {
2242
+ const resolved = resolveDecoratorName(decorator.expression, aliases);
2243
+ if (resolved === target) return true;
2244
+ }
2245
+ return false;
2246
+ }
2247
+ function collectConstructorInjections(context, classNode) {
2248
+ const constructorNode = classNode.members.find(ts3.isConstructorDeclaration);
2249
+ if (!constructorNode) return [];
2250
+ const dependencies = [];
2251
+ for (const parameter of constructorNode.parameters) {
2252
+ if (!parameter.type || !ts3.isTypeReferenceNode(parameter.type) || !ts3.isIdentifier(parameter.type.typeName))
2253
+ continue;
2254
+ const declaration = resolveIdentifierDeclaration(context, parameter.type.typeName);
2255
+ if (!declaration) continue;
2256
+ const relative = relativeFromRoot(context, declaration.getSourceFile().fileName);
2257
+ if (!relative) continue;
2258
+ const targetName = ts3.isClassDeclaration(declaration) && declaration.name ? declaration.name.text : parameter.type.typeName.text;
2259
+ dependencies.push(symbolId("nest-provider", relative, targetName));
2260
+ }
2261
+ return dedupe(dependencies);
2262
+ }
2263
+ function collectModuleMetadata(classNode, aliases) {
2264
+ const defaults = { controllers: [], providers: [], imports: [], exports: [] };
2265
+ for (const decorator of decoratorsOf(classNode)) {
2266
+ const expression = decorator.expression;
2267
+ const call = ts3.isCallExpression(expression) ? expression : void 0;
2268
+ if (!call) continue;
2269
+ const name = resolveDecoratorName(expression, aliases);
2270
+ if (!name) continue;
2271
+ if (name !== "Module" || call.arguments.length === 0) continue;
2272
+ const arg = call.arguments[0];
2273
+ if (!arg || !ts3.isObjectLiteralExpression(arg)) continue;
2274
+ for (const property of arg.properties) {
2275
+ if (!ts3.isPropertyAssignment(property) && !ts3.isShorthandPropertyAssignment(property))
2276
+ continue;
2277
+ const propertyName = ts3.isIdentifier(property.name) ? property.name.text : ts3.isStringLiteral(property.name) ? property.name.text : void 0;
2278
+ if (!propertyName) continue;
2279
+ if (!(propertyName in defaults)) continue;
2280
+ const assigned = ts3.isPropertyAssignment(property) ? property.initializer : property.name;
2281
+ if (propertyName === "controllers") {
2282
+ defaults.controllers.push(...collectIdentifiers(assigned));
2283
+ }
2284
+ if (propertyName === "providers") {
2285
+ defaults.providers.push(...collectIdentifiers(assigned));
2286
+ }
2287
+ if (propertyName === "imports") {
2288
+ defaults.imports.push(...collectIdentifiers(assigned));
2289
+ }
2290
+ if (propertyName === "exports") {
2291
+ defaults.exports.push(...collectIdentifiers(assigned));
2292
+ }
2293
+ }
2294
+ }
2295
+ return {
2296
+ controllers: dedupe(defaults.controllers),
2297
+ providers: dedupe(defaults.providers),
2298
+ imports: dedupe(defaults.imports),
2299
+ exports: dedupe(defaults.exports)
2300
+ };
2301
+ }
2302
+ function collectIdentifiers(node) {
2303
+ if (ts3.isIdentifier(node)) return [node.text];
2304
+ if (ts3.isPropertyAccessExpression(node)) return [node.name.text];
2305
+ if (ts3.isArrayLiteralExpression(node)) {
2306
+ return node.elements.map(
2307
+ (item) => ts3.isIdentifier(item) ? item.text : ts3.isPropertyAccessExpression(item) ? item.name.text : void 0
2308
+ ).filter((value) => Boolean(value));
2309
+ }
2310
+ return [];
2311
+ }
2312
+ function collectModuleMembership(context) {
2313
+ const mapping = /* @__PURE__ */ new Map();
2314
+ for (const source of context.program.getSourceFiles().filter((file) => !file.isDeclarationFile)) {
2315
+ const aliases = collectImportAliases(source);
2316
+ for (const statement of source.statements) {
2317
+ if (!ts3.isClassDeclaration(statement) || !statement.name) continue;
2318
+ const decorators = collectDecoratorNames(decoratorsOf(statement), aliases);
2319
+ if (!decorators.includes("Module")) continue;
2320
+ const metadata = collectModuleMetadata(statement, aliases);
2321
+ const moduleId = symbolId(
2322
+ "nest-module",
2323
+ relativeFromRoot(context, source.fileName) ?? context.relativePath,
2324
+ statement.name.text
2325
+ );
2326
+ for (const child of [...metadata.controllers, ...metadata.providers]) {
2327
+ mapping.set(child, moduleId);
2328
+ }
2329
+ }
2330
+ }
2331
+ return mapping;
2332
+ }
2333
+ function collectClassUseGuards(classNode, aliases) {
2334
+ const names = [];
2335
+ for (const decorator of decoratorsOf(classNode)) {
2336
+ const expression = decorator.expression;
2337
+ const call = ts3.isCallExpression(expression) ? expression : void 0;
2338
+ if (!call) continue;
2339
+ const name = resolveDecoratorName(expression, aliases);
2340
+ if (!name) continue;
2341
+ if (name !== "UseGuards") continue;
2342
+ for (const arg of call.arguments) {
2343
+ if (ts3.isIdentifier(arg)) names.push(arg.text);
2344
+ }
2345
+ }
2346
+ return dedupe(names);
2347
+ }
2348
+ function dedupe(values) {
2349
+ return [...new Set(values)];
2350
+ }
2351
+ function combineRoutes(base, child) {
2352
+ const segments = [base, child].map((value) => value.replace(/^\/+|\/+$/g, "")).filter(Boolean);
2353
+ return segments.length > 0 ? `/${segments.join("/")}` : "/";
2354
+ }
2355
+ function makeRelation(from, type, to, context, node, confidence, explanation) {
2356
+ return {
2357
+ schemaVersion: SCHEMA_VERSION,
2358
+ from,
2359
+ type,
2360
+ to,
2361
+ confidence,
2362
+ origin: {
2363
+ file: context.relativePath,
2364
+ line: nodeLine(context.sourceFile, node),
2365
+ range: nodeRange(context.sourceFile, node),
2366
+ explanation,
2367
+ classification: confidence === 1 ? "framework-derived" : "heuristic",
2368
+ analyzer: "nestjs"
2369
+ }
2370
+ };
2371
+ }
2372
+ function relativeFromRoot(context, absolutePath) {
2373
+ const normalized = absolutePath.replace(/\\/g, "/");
2374
+ const root = context.root.replace(/\\/g, "/");
2375
+ if (normalized !== root && !normalized.startsWith(`${root}/`)) return void 0;
2376
+ const remainder = normalized.slice(root.length);
2377
+ return remainder.startsWith("/") ? remainder.slice(1) : remainder;
2378
+ }
2379
+
2380
+ // src/analyzers/nextjs.ts
2381
+ import path5 from "path";
2382
+ import ts4 from "typescript";
2383
+ var ROUTE_GROUP_SEGMENT = /^\(.*\)$/;
2384
+ var ROUTE_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
2385
+ var APP_FILES_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
2386
+ var registeredNextJsAnalyzers = /* @__PURE__ */ new WeakSet();
2387
+ var NextJsAnalyzer = class {
2388
+ name = "nextjs";
2389
+ version = "0.1.0-experimental";
2390
+ constructor() {
2391
+ registeredNextJsAnalyzers.add(this);
2392
+ }
2393
+ supports(context) {
2394
+ if (!APP_FILES_EXTENSIONS.has(path5.extname(context.relativePath).toLowerCase())) return false;
2395
+ return context.relativePath.startsWith("app/") || context.relativePath.startsWith("pages/") || context.relativePath === "middleware.ts";
2396
+ }
2397
+ analyze(context) {
2398
+ const sourceFile = context.sourceFile;
2399
+ const route = parseNextRoute(context.relativePath);
2400
+ const defaultExportNode = getTopLevelDefaultExportDeclaration(sourceFile);
2401
+ const hasTopLevelExport = hasAnyTopLevelExport(sourceFile);
2402
+ const symbols = [];
2403
+ const relations = [];
2404
+ if (!route) {
2405
+ return { symbols, relations, frameworks: ["nextjs"] };
2406
+ }
2407
+ if (route.kind === "page") {
2408
+ const pageFacts = pageFactsForRoute(context, route);
2409
+ const symbol = makeSymbol(context, defaultExportNode ?? sourceFile, "next-page", {
2410
+ name: defaultExportNode ? getDeclarationName(defaultExportNode, route.name) : route.name,
2411
+ defaultExport: Boolean(defaultExportNode),
2412
+ exported: hasTopLevelExport,
2413
+ facts: pageFacts
2414
+ });
2415
+ symbols.push(symbol);
2416
+ if (route.router === "app") {
2417
+ for (const layout of collectContainingAppLayouts(context.relativePath, context)) {
2418
+ relations.push(
2419
+ makeRelation2(
2420
+ symbol.id,
2421
+ "uses-layout",
2422
+ symbolId("next-layout", layout, "layout"),
2423
+ context,
2424
+ sourceFile,
2425
+ 1,
2426
+ "app route layout chain"
2427
+ )
2428
+ );
2429
+ }
2430
+ }
2431
+ return { symbols, relations, frameworks: ["nextjs"] };
2432
+ }
2433
+ if (route.kind === "layout") {
2434
+ symbols.push(
2435
+ makeSymbol(context, defaultExportNode ?? sourceFile, "next-layout", {
2436
+ exported: hasTopLevelExport,
2437
+ defaultExport: Boolean(defaultExportNode),
2438
+ name: "layout",
2439
+ facts: {
2440
+ router: route.router,
2441
+ route: route.route,
2442
+ runtime: runtimeFromSource(sourceFile),
2443
+ params: route.params.join(","),
2444
+ metadata: compactText(collectMetadataMarkers(sourceFile).join(","))
2445
+ }
2446
+ })
2447
+ );
2448
+ return { symbols, relations, frameworks: ["nextjs"] };
2449
+ }
2450
+ if (route.kind === "route") {
2451
+ const methods = collectRouteMethods(sourceFile);
2452
+ symbols.push(
2453
+ makeSymbol(context, sourceFile, "next-route-handler", {
2454
+ exported: hasTopLevelExport,
2455
+ name: "route-handler",
2456
+ facts: {
2457
+ router: route.router,
2458
+ route: route.route,
2459
+ methods,
2460
+ runtime: runtimeFromSource(sourceFile),
2461
+ metadata: compactText(collectMetadataMarkers(sourceFile).join(","))
2462
+ }
2463
+ })
2464
+ );
2465
+ return { symbols, relations, frameworks: ["nextjs"] };
2466
+ }
2467
+ symbols.push(
2468
+ makeSymbol(context, sourceFile, "next-special-file", {
2469
+ exported: hasTopLevelExport,
2470
+ name: route.name,
2471
+ facts: {
2472
+ router: route.router,
2473
+ route: route.route,
2474
+ runtime: runtimeFromSource(sourceFile)
2475
+ }
2476
+ })
2477
+ );
2478
+ return { symbols, relations, frameworks: ["nextjs"] };
2479
+ }
2480
+ };
2481
+ var nextJsSupports = NextJsAnalyzer.prototype.supports.bind(NextJsAnalyzer.prototype);
2482
+ var nextJsAnalyze = NextJsAnalyzer.prototype.analyze.bind(NextJsAnalyzer.prototype);
2483
+ function trustedNextJsAnalyzerMethods(analyzer) {
2484
+ if (!registeredNextJsAnalyzers.has(analyzer)) return void 0;
2485
+ if (Object.getPrototypeOf(analyzer) !== NextJsAnalyzer.prototype) return void 0;
2486
+ if (Object.hasOwn(analyzer, "supports") || Object.hasOwn(analyzer, "analyze")) {
2487
+ return void 0;
2488
+ }
2489
+ const name = Object.getOwnPropertyDescriptor(analyzer, "name");
2490
+ const version = Object.getOwnPropertyDescriptor(analyzer, "version");
2491
+ if (name?.value !== "nextjs" || version?.value !== "0.1.0-experimental") return void 0;
2492
+ return {
2493
+ supports: (context) => nextJsSupports(context),
2494
+ analyze: (context) => nextJsAnalyze(context)
2495
+ };
2496
+ }
2497
+ function parseNextRoute(relativePath) {
2498
+ const normalized = relativePath.replace(/\\/g, "/");
2499
+ if (!normalized.startsWith("app/") && !normalized.startsWith("pages/")) return void 0;
2500
+ const router = normalized.startsWith("app/") ? "app" : "pages";
2501
+ const segments = normalized.split("/").filter(Boolean);
2502
+ const fileName = segments.at(-1);
2503
+ if (!fileName) return void 0;
2504
+ const fileBase = fileName.replace(/\.(ts|tsx|js|jsx)$/, "");
2505
+ if (router === "app") {
2506
+ const routeSegments2 = segments.slice(1, -1);
2507
+ const baseRoute = buildRouteFromSegments(routeSegments2);
2508
+ if (fileBase === "layout") {
2509
+ return {
2510
+ router,
2511
+ kind: "layout",
2512
+ route: baseRoute,
2513
+ name: "layout",
2514
+ params: dynamicSegmentNames(routeSegments2)
2515
+ };
2516
+ }
2517
+ if (fileBase === "page") {
2518
+ return {
2519
+ router,
2520
+ kind: "page",
2521
+ route: baseRoute,
2522
+ name: baseRoute.split("/")[baseRoute.split("/").length - 1] || "page",
2523
+ params: dynamicSegmentNames(routeSegments2)
2524
+ };
2525
+ }
2526
+ if (fileBase === "route") {
2527
+ return {
2528
+ router,
2529
+ kind: "route",
2530
+ route: baseRoute,
2531
+ name: "route",
2532
+ params: dynamicSegmentNames(routeSegments2)
2533
+ };
2534
+ }
2535
+ if (fileBase.startsWith("_")) {
2536
+ return {
2537
+ router,
2538
+ kind: "special",
2539
+ route: "/_" + fileBase.replace(/^_/, ""),
2540
+ name: fileName,
2541
+ params: []
2542
+ };
2543
+ }
2544
+ return void 0;
2545
+ }
2546
+ const pageSegments = segments.slice(1);
2547
+ if (pageSegments.length === 0) return void 0;
2548
+ const isApi = pageSegments[0] === "api";
2549
+ const routeSegments = pageSegments.slice(0, pageSegments.length - 1);
2550
+ const route = `/${buildRouteFromSegments([...routeSegments, fileBase === "index" ? "" : fileBase].filter(Boolean)).replace(/^\//, "")}`;
2551
+ return {
2552
+ router,
2553
+ kind: isApi ? "route" : "page",
2554
+ route,
2555
+ name: route.split("/")[route.split("/").length - 1] || "page",
2556
+ params: [...routeSegments, fileBase].filter(isDynamicSegment).map(stripDynamicMarkers)
2557
+ };
2558
+ }
2559
+ function pageFactsForRoute(context, route) {
2560
+ const markers = compactText(collectMetadataMarkers(context.sourceFile).join(","));
2561
+ return {
2562
+ router: route.router,
2563
+ route: route.route,
2564
+ params: route.params.join(","),
2565
+ runtime: runtimeFromSource(context.sourceFile),
2566
+ metadata: markers
2567
+ };
2568
+ }
2569
+ function collectMetadataMarkers(sourceFile) {
2570
+ const facts = [];
2571
+ const addMarker = (name) => {
2572
+ if (name === "generateMetadata") facts.push("generatemetadata");
2573
+ if (name === "getServerSideProps") facts.push("getserversideprops");
2574
+ if (name === "getStaticProps") facts.push("getstaticprops");
2575
+ if (name === "getStaticPaths") facts.push("getstaticpaths");
2576
+ };
2577
+ for (const statement of sourceFile.statements) {
2578
+ if (ts4.isFunctionDeclaration(statement) && statement.name) {
2579
+ addMarker(statement.name.text);
2580
+ continue;
2581
+ }
2582
+ if (ts4.isVariableStatement(statement)) {
2583
+ for (const declaration of statement.declarationList.declarations) {
2584
+ if (ts4.isIdentifier(declaration.name)) addMarker(declaration.name.text);
2585
+ }
2586
+ }
2587
+ }
2588
+ return [...new Set(facts)];
2589
+ }
2590
+ function collectRouteMethods(sourceFile) {
2591
+ const methods = /* @__PURE__ */ new Set();
2592
+ const addFromDeclaration = (name) => {
2593
+ if (!name) return;
2594
+ const normalized = name.toUpperCase();
2595
+ if (ROUTE_METHODS.has(normalized)) methods.add(normalized.toLowerCase());
2596
+ };
2597
+ for (const statement of sourceFile.statements) {
2598
+ if (ts4.isFunctionDeclaration(statement) && statement.name) {
2599
+ addFromDeclaration(statement.name.text);
2600
+ continue;
2601
+ }
2602
+ if (!ts4.isVariableStatement(statement)) continue;
2603
+ for (const declaration of statement.declarationList.declarations) {
2604
+ if (!ts4.isIdentifier(declaration.name)) continue;
2605
+ if (isFunctionLikeExpression(declaration.initializer)) {
2606
+ addFromDeclaration(declaration.name.text);
2607
+ }
2608
+ }
2609
+ }
2610
+ return [...methods].join(",");
2611
+ }
2612
+ function isFunctionLikeExpression(node) {
2613
+ return node !== void 0 && (ts4.isArrowFunction(node) || ts4.isFunctionExpression(node));
2614
+ }
2615
+ function runtimeFromSource(sourceFile) {
2616
+ if (hasDirective(sourceFile, "use client")) return "client";
2617
+ if (hasDirective(sourceFile, "use server")) return "server";
2618
+ return "server";
2619
+ }
2620
+ function hasDirective(sourceFile, directive) {
2621
+ return sourceFile.statements.slice(0, 8).some(
2622
+ (statement) => ts4.isExpressionStatement(statement) && ts4.isStringLiteral(statement.expression) && statement.expression.text === directive
2623
+ );
2624
+ }
2625
+ function collectContainingAppLayouts(relativePath, context) {
2626
+ const fileSegments = relativePath.replace(/\\/g, "/").split("/").filter(Boolean);
2627
+ if (fileSegments[0] !== "app") return [];
2628
+ const layoutSegments = fileSegments.slice(1, -1);
2629
+ const candidates = [];
2630
+ for (let index = 0; index <= layoutSegments.length; index += 1) {
2631
+ const prefix = layoutSegments.slice(0, index);
2632
+ const candidateWithoutExt = ["app", ...prefix, "layout"].join("/");
2633
+ const tsx = `${candidateWithoutExt}.tsx`;
2634
+ const ts9 = `${candidateWithoutExt}.ts`;
2635
+ if (programContainsSourceFile(context, tsx)) candidates.push(tsx);
2636
+ if (programContainsSourceFile(context, ts9)) candidates.push(ts9);
2637
+ }
2638
+ return candidates;
2639
+ }
2640
+ function programContainsSourceFile(context, candidateRelative) {
2641
+ const absolute = path5.resolve(context.root, candidateRelative);
2642
+ return context.program.getSourceFiles().some((source) => normalizeSourcePath(source.fileName) === normalizeSourcePath(absolute));
2643
+ }
2644
+ function normalizeSourcePath(value) {
2645
+ return value.replace(/\\/g, "/");
2646
+ }
2647
+ function isDynamicSegment(segment) {
2648
+ return /^\[{1,2}\.?.*\]$/.test(segment);
2649
+ }
2650
+ function dynamicSegmentNames(segments) {
2651
+ return segments.filter(isDynamicSegment).map(stripDynamicMarkers);
2652
+ }
2653
+ function stripDynamicMarkers(segment) {
2654
+ return segment.replace(/^\[\[?\.*/g, "").replace(/\]$/g, "");
2655
+ }
2656
+ function buildRouteFromSegments(segments) {
2657
+ const cleaned = segments.filter(Boolean).filter((segment) => !ROUTE_GROUP_SEGMENT.test(segment)).map((segment) => segment);
2658
+ return `/${cleaned.length > 0 ? cleaned.join("/") : ""}`;
2659
+ }
2660
+ function makeRelation2(from, type, to, context, node, confidence, explanation) {
2661
+ return {
2662
+ schemaVersion: SCHEMA_VERSION,
2663
+ from,
2664
+ type,
2665
+ to,
2666
+ confidence,
2667
+ origin: {
2668
+ file: context.relativePath,
2669
+ line: nodeLine(context.sourceFile, node),
2670
+ range: nodeRange(context.sourceFile, node),
2671
+ explanation,
2672
+ classification: confidence === 1 ? "framework-derived" : "heuristic",
2673
+ analyzer: "nextjs"
2674
+ }
2675
+ };
2676
+ }
2677
+
2678
+ // src/analyzers/react.ts
2679
+ import path6 from "path";
2680
+ import ts5 from "typescript";
2681
+ var REACT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
2682
+ var REACT_BUILTIN_HOOKS = /* @__PURE__ */ new Set([
2683
+ "useState",
2684
+ "useReducer",
2685
+ "useMemo",
2686
+ "useCallback",
2687
+ "useEffect",
2688
+ "useLayoutEffect",
2689
+ "useContext"
2690
+ ]);
2691
+ var REACT_FACTORYS = /* @__PURE__ */ new Set(["memo", "forwardRef"]);
2692
+ var ReactAnalyzer = class {
2693
+ name = "react";
2694
+ version = ANALYZER_VERSION;
2695
+ supports(context) {
2696
+ const extension = path6.extname(context.relativePath).toLowerCase();
2697
+ if (!REACT_EXTENSIONS.has(extension)) return false;
2698
+ return hasDirective2(context.sourceFile, "use client") || hasDirective2(context.sourceFile, "use server") || hasReactImport(context.sourceFile) || hasAnyJsx(context.sourceFile);
2699
+ }
2700
+ analyze(context) {
2701
+ const profile = collectImportProfile(context.sourceFile);
2702
+ const symbols = [];
2703
+ const relations = [];
2704
+ const componentIds = /* @__PURE__ */ new Map();
2705
+ const hookIds = /* @__PURE__ */ new Map();
2706
+ const components = [];
2707
+ for (const statement of context.sourceFile.statements) {
2708
+ if (ts5.isFunctionDeclaration(statement) && statement.name) {
2709
+ const name = statement.name.text;
2710
+ if (isCustomHookName(name) && statement.body) {
2711
+ const symbol = makeSymbol(context, statement, "react-hook", {
2712
+ name,
2713
+ exported: isNameExported2(context.sourceFile, statement),
2714
+ defaultExport: hasDefaultModifier(statement),
2715
+ facts: { runtime: inferRuntime(context.sourceFile) }
2716
+ });
2717
+ symbols.push(symbol);
2718
+ hookIds.set(name.toLowerCase(), symbol.id);
2719
+ continue;
2720
+ }
2721
+ if (statement.body && isComponentName(name, statement.body)) {
2722
+ const facts = collectComponentFacts(
2723
+ context,
2724
+ name,
2725
+ statement.parameters,
2726
+ statement.body,
2727
+ profile
2728
+ );
2729
+ const symbol = makeSymbol(context, statement, "react-component", {
2730
+ name,
2731
+ exported: isNameExported2(context.sourceFile, statement),
2732
+ defaultExport: hasDefaultModifier(statement),
2733
+ facts
2734
+ });
2735
+ symbols.push(symbol);
2736
+ componentIds.set(name.toLowerCase(), symbol.id);
2737
+ components.push({ name, id: symbol.id, body: statement.body });
2738
+ continue;
2739
+ }
2740
+ }
2741
+ if (ts5.isVariableStatement(statement)) {
2742
+ const exported = isNameExported2(context.sourceFile, statement);
2743
+ const defaultExport = hasDefaultModifier(statement);
2744
+ for (const declaration of statement.declarationList.declarations) {
2745
+ if (!ts5.isIdentifier(declaration.name) || !declaration.initializer) continue;
2746
+ const name = declaration.name.text;
2747
+ if (isContextFactory(declaration.initializer, profile)) {
2748
+ symbols.push(
2749
+ makeSymbol(context, declaration, "react-context", {
2750
+ name,
2751
+ exported,
2752
+ defaultExport,
2753
+ facts: { runtime: inferRuntime(context.sourceFile), type: "react-context" }
2754
+ })
2755
+ );
2756
+ continue;
2757
+ }
2758
+ if (isCustomHookName(name) && isFunctionLikeExpression2(declaration.initializer)) {
2759
+ const body = functionBody(declaration.initializer);
2760
+ if (body) {
2761
+ const symbol2 = makeSymbol(context, declaration, "react-hook", {
2762
+ name,
2763
+ exported,
2764
+ defaultExport,
2765
+ facts: { runtime: inferRuntime(context.sourceFile) }
2766
+ });
2767
+ symbols.push(symbol2);
2768
+ hookIds.set(name.toLowerCase(), symbol2.id);
2769
+ }
2770
+ continue;
2771
+ }
2772
+ const extracted = extractComponentFromExpression(declaration.initializer, name, profile);
2773
+ if (!extracted) continue;
2774
+ const facts = collectComponentFacts(
2775
+ context,
2776
+ name,
2777
+ extracted.parameters,
2778
+ extracted.body,
2779
+ profile
2780
+ );
2781
+ const symbol = makeSymbol(context, declaration, "react-component", {
2782
+ name,
2783
+ exported,
2784
+ defaultExport,
2785
+ facts
2786
+ });
2787
+ symbols.push(symbol);
2788
+ componentIds.set(name.toLowerCase(), symbol.id);
2789
+ components.push({ name, id: symbol.id, body: extracted.body });
2790
+ }
2791
+ }
2792
+ if (ts5.isClassDeclaration(statement) && statement.name && isReactClassComponent(statement)) {
2793
+ const name = statement.name.text;
2794
+ const renderMethod = statement.members.find(
2795
+ (member) => ts5.isMethodDeclaration(member) && ts5.isIdentifier(member.name) && member.name.text === "render" && Boolean(member.body)
2796
+ );
2797
+ if (!renderMethod?.body) continue;
2798
+ const symbol = makeSymbol(context, statement, "react-component", {
2799
+ name,
2800
+ exported: isNameExported2(context.sourceFile, statement),
2801
+ defaultExport: hasDefaultModifier(statement),
2802
+ facts: { runtime: inferRuntime(context.sourceFile), class: true }
2803
+ });
2804
+ symbols.push(symbol);
2805
+ componentIds.set(name.toLowerCase(), symbol.id);
2806
+ components.push({ name, id: symbol.id, body: renderMethod.body });
2807
+ }
2808
+ }
2809
+ for (const component of components) {
2810
+ const rendered = collectRenderedComponents(component.body);
2811
+ for (const candidate of rendered) {
2812
+ const to = componentIds.get(candidate.name.toLowerCase()) ?? resolveReactCandidateWithChecker(context, candidate, "react-component") ?? resolveImportedReactComponent(context, profile, candidate.name) ?? symbolId("react-component", context.relativePath, candidate.name);
2813
+ relations.push(
2814
+ makeRelation3(component.id, "renders", to, context, candidate.node, 0.95, "JSX render")
2815
+ );
2816
+ }
2817
+ for (const candidate of collectCalledCustomHooks(component.body, profile)) {
2818
+ const to = hookIds.get(candidate.name.toLowerCase()) ?? resolveReactCandidateWithChecker(context, candidate, "react-hook") ?? resolveImportedReactSymbol(context, profile, candidate.name, "react-hook") ?? symbolId("react-hook", context.relativePath, candidate.name);
2819
+ relations.push(
2820
+ makeRelation3(
2821
+ component.id,
2822
+ "uses-hook",
2823
+ to,
2824
+ context,
2825
+ candidate.node,
2826
+ 0.95,
2827
+ "custom hook call"
2828
+ )
2829
+ );
2830
+ }
2831
+ }
2832
+ return { symbols, relations, frameworks: ["react"] };
2833
+ }
2834
+ };
2835
+ function hasReactImport(sourceFile) {
2836
+ return sourceFile.statements.some(
2837
+ (statement) => ts5.isImportDeclaration(statement) && ts5.isStringLiteral(statement.moduleSpecifier) && statement.moduleSpecifier.text === "react"
2838
+ );
2839
+ }
2840
+ function hasDirective2(sourceFile, value) {
2841
+ return sourceFile.statements.slice(0, 8).some(
2842
+ (statement) => ts5.isExpressionStatement(statement) && ts5.isStringLiteral(statement.expression) && statement.expression.text === value
2843
+ );
2844
+ }
2845
+ function hasAnyJsx(sourceFile) {
2846
+ let found = false;
2847
+ visit2(sourceFile, (node) => {
2848
+ if (found) return;
2849
+ if (ts5.isJsxElement(node) || ts5.isJsxSelfClosingElement(node) || ts5.isJsxOpeningElement(node) || ts5.isJsxFragment(node)) {
2850
+ found = true;
2851
+ }
2852
+ });
2853
+ return found;
2854
+ }
2855
+ function inferRuntime(sourceFile) {
2856
+ if (hasDirective2(sourceFile, "use client")) return "client";
2857
+ if (hasDirective2(sourceFile, "use server")) return "server";
2858
+ return "unclassified";
2859
+ }
2860
+ function isNameExported2(sourceFile, node) {
2861
+ return hasExportModifier(node);
2862
+ }
2863
+ function collectImportProfile(sourceFile) {
2864
+ const hookAliases = /* @__PURE__ */ new Map();
2865
+ const namespaceAliases = /* @__PURE__ */ new Set();
2866
+ const localImports = /* @__PURE__ */ new Map();
2867
+ for (const statement of sourceFile.statements) {
2868
+ if (!ts5.isImportDeclaration(statement) || !ts5.isStringLiteral(statement.moduleSpecifier))
2869
+ continue;
2870
+ const source = statement.moduleSpecifier.text;
2871
+ const clause = statement.importClause;
2872
+ if (!clause) continue;
2873
+ if (source === "react") {
2874
+ if (clause.name) {
2875
+ hookAliases.set(clause.name.text, clause.name.text);
2876
+ localImports.set(clause.name.text, { source, imported: "default" });
2877
+ }
2878
+ if (!clause.namedBindings) continue;
2879
+ if (ts5.isNamespaceImport(clause.namedBindings)) {
2880
+ namespaceAliases.add(clause.namedBindings.name.text);
2881
+ continue;
2882
+ }
2883
+ for (const item of clause.namedBindings.elements) {
2884
+ const local = item.name.text;
2885
+ const imported = item.propertyName?.text ?? item.name.text;
2886
+ hookAliases.set(local, imported);
2887
+ localImports.set(local, { source, imported });
2888
+ }
2889
+ continue;
2890
+ }
2891
+ if (clause.name) {
2892
+ localImports.set(clause.name.text, { source, imported: "default" });
2893
+ }
2894
+ if (!clause.namedBindings) continue;
2895
+ if (ts5.isNamedImports(clause.namedBindings)) {
2896
+ for (const item of clause.namedBindings.elements) {
2897
+ localImports.set(item.name.text, {
2898
+ source,
2899
+ imported: item.propertyName?.text ?? item.name.text
2900
+ });
2901
+ }
2902
+ } else if (ts5.isNamespaceImport(clause.namedBindings)) {
2903
+ localImports.set(clause.namedBindings.name.text, {
2904
+ source,
2905
+ imported: "*"
2906
+ });
2907
+ }
2908
+ }
2909
+ return { hookAliases, namespaceAliases, localImports };
2910
+ }
2911
+ function isCustomHookName(name) {
2912
+ return /^use[A-Z]/.test(name);
2913
+ }
2914
+ function isComponentName(name, body) {
2915
+ return Boolean(body && isUpperCase(name) && !isCustomHookName(name) && containsJsx(body));
2916
+ }
2917
+ function isUpperCase(name) {
2918
+ return /^[A-Z]/.test(name);
2919
+ }
2920
+ function isContextFactory(value, profile) {
2921
+ return ts5.isCallExpression(value) && resolveCallName(value.expression, profile) === "createContext";
2922
+ }
2923
+ function extractComponentFromExpression(value, name, profile) {
2924
+ if (!isUpperCase(name) || isCustomHookName(name)) return void 0;
2925
+ if (isFunctionLikeExpression2(value)) {
2926
+ return { body: value.body, parameters: value.parameters };
2927
+ }
2928
+ if (ts5.isClassExpression(value)) {
2929
+ const renderMethod = value.members.find(
2930
+ (member) => ts5.isMethodDeclaration(member) && ts5.isIdentifier(member.name) && member.name.text === "render" && Boolean(member.body)
2931
+ );
2932
+ if (!renderMethod?.body) return void 0;
2933
+ return { body: renderMethod.body, parameters: renderMethod.parameters };
2934
+ }
2935
+ if (ts5.isCallExpression(value)) {
2936
+ const calledName = resolveCallName(value.expression, profile);
2937
+ if (!calledName || !REACT_FACTORYS.has(calledName) || value.arguments.length === 0)
2938
+ return void 0;
2939
+ const first = value.arguments[0];
2940
+ if (!ts5.isFunctionLike(first) || !containsJsx(first.body)) return void 0;
2941
+ return { body: first.body, parameters: first.parameters };
2942
+ }
2943
+ return void 0;
2944
+ }
2945
+ function isFunctionLikeExpression2(expression) {
2946
+ return ts5.isArrowFunction(expression) || ts5.isFunctionExpression(expression);
2947
+ }
2948
+ function isReactClassComponent(statement) {
2949
+ if (!statement.heritageClauses) return false;
2950
+ return statement.heritageClauses.some(
2951
+ (clause) => clause.types.some((item) => {
2952
+ const expr = item.expression;
2953
+ const typeName = ts5.isIdentifier(expr) ? expr.text : ts5.isPropertyAccessExpression(expr) ? expr.name.text : void 0;
2954
+ return typeName === "Component" || typeName === "PureComponent";
2955
+ })
2956
+ );
2957
+ }
2958
+ function functionBody(expression) {
2959
+ return isFunctionLikeExpression2(expression) ? expression.body : void 0;
2960
+ }
2961
+ function collectComponentFacts(context, name, parameters, body, profile) {
2962
+ const facts = {
2963
+ runtime: inferRuntime(context.sourceFile)
2964
+ };
2965
+ const props = collectProps(context, parameters[0]);
2966
+ if (props.length > 0) {
2967
+ facts.props = `${name.toLowerCase()}props:${props.join(",")}`;
2968
+ }
2969
+ const behavior = collectComponentBehavior(context, body, profile);
2970
+ if (behavior.state.length > 0) facts.state = behavior.state.join(",");
2971
+ if (behavior.memo.length > 0) facts.memo = behavior.memo.join(",");
2972
+ if (behavior.effects.length > 0) facts.effects = behavior.effects.join(",");
2973
+ if (behavior.customHooks.length > 0) facts.hooks = behavior.customHooks.join(",");
2974
+ return facts;
2975
+ }
2976
+ function collectProps(context, parameter) {
2977
+ if (!parameter) return [];
2978
+ const values = [];
2979
+ collectBindingNames(parameter.name, values);
2980
+ if (parameter.type) {
2981
+ values.push(...collectInterfaceMembers(parameter.type, context));
2982
+ }
2983
+ return dedupe2(values);
2984
+ }
2985
+ function collectComponentBehavior(context, body, profile) {
2986
+ const state = [];
2987
+ const memo = [];
2988
+ const effects = [];
2989
+ const customHooks = [];
2990
+ visit2(body, (node) => {
2991
+ if (!ts5.isCallExpression(node)) return;
2992
+ const called = resolveCallName(node.expression, profile);
2993
+ if (!called) return;
2994
+ const declaration = variableDeclarationOfCall(node);
2995
+ if (called === "useState" || called === "useReducer") {
2996
+ if (declaration?.name) {
2997
+ const declaredNames = [];
2998
+ collectBindingNames(declaration.name, declaredNames);
2999
+ state.push(...declaredNames);
3000
+ }
3001
+ if (called === "useReducer") state.push("reducer");
3002
+ return;
3003
+ }
3004
+ if (called === "useMemo" || called === "useCallback") {
3005
+ const target = declaration?.name && ts5.isIdentifier(declaration.name) ? declaration.name.text : called;
3006
+ const dependencies = dependencyText(node.arguments[1], context.sourceFile);
3007
+ memo.push(`${called.toLowerCase()}:${target.toLowerCase()}<-${dependencies}`);
3008
+ return;
3009
+ }
3010
+ if (called === "useEffect" || called === "useLayoutEffect") {
3011
+ const deps = node.arguments[1] ? compactText(redactStringLiterals(node.arguments[1].getText(context.sourceFile))) : "[]";
3012
+ effects.push(`useeffect:${deps}`);
3013
+ return;
3014
+ }
3015
+ if (called.startsWith("use") && !REACT_BUILTIN_HOOKS.has(called)) {
3016
+ customHooks.push(called.toLowerCase());
3017
+ }
3018
+ });
3019
+ return {
3020
+ state: dedupe2(state),
3021
+ memo: dedupe2(memo),
3022
+ effects: dedupe2(effects),
3023
+ customHooks: dedupe2(customHooks)
3024
+ };
3025
+ }
3026
+ function collectCalledCustomHooks(node, profile) {
3027
+ const hooks = [];
3028
+ visit2(node, (current) => {
3029
+ if (!ts5.isCallExpression(current)) return;
3030
+ const called = resolveCallName(current.expression, profile);
3031
+ if (called && called.startsWith("use") && !REACT_BUILTIN_HOOKS.has(called)) {
3032
+ hooks.push({ name: called, node: current });
3033
+ }
3034
+ });
3035
+ return hooks;
3036
+ }
3037
+ function collectRenderedComponents(node) {
3038
+ const components = [];
3039
+ visit2(node, (current) => {
3040
+ if (!ts5.isJsxElement(current) && !ts5.isJsxSelfClosingElement(current)) return;
3041
+ const evidenceNode = ts5.isJsxElement(current) ? current.openingElement : current;
3042
+ const tag = jsxTagName(evidenceNode.tagName);
3043
+ if (tag && isUpperCase(tag)) {
3044
+ components.push({ name: tag, node: evidenceNode });
3045
+ }
3046
+ });
3047
+ return components;
3048
+ }
3049
+ function resolveImportedReactComponent(context, profile, renderedName) {
3050
+ return resolveImportedReactSymbol(context, profile, renderedName, "react-component");
3051
+ }
3052
+ function resolveReactCandidateWithChecker(context, candidate, kind) {
3053
+ let location = candidate.node;
3054
+ if (ts5.isJsxOpeningElement(candidate.node) || ts5.isJsxSelfClosingElement(candidate.node)) {
3055
+ location = candidate.node.tagName;
3056
+ } else if (ts5.isCallExpression(candidate.node)) {
3057
+ location = candidate.node.expression;
3058
+ }
3059
+ let symbol = context.checker.getSymbolAtLocation(location);
3060
+ if (!symbol && ts5.isPropertyAccessExpression(location)) {
3061
+ symbol = context.checker.getSymbolAtLocation(location.name);
3062
+ }
3063
+ if (!symbol) return void 0;
3064
+ if (symbol.flags & ts5.SymbolFlags.Alias) {
3065
+ try {
3066
+ symbol = context.checker.getAliasedSymbol(symbol);
3067
+ } catch {
3068
+ return void 0;
3069
+ }
3070
+ }
3071
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
3072
+ if (!declaration) return void 0;
3073
+ const sourceFile = declaration.getSourceFile();
3074
+ let relative;
3075
+ try {
3076
+ relative = path6.relative(context.root, sourceFile.fileName).split(path6.sep).join("/");
3077
+ } catch {
3078
+ return void 0;
3079
+ }
3080
+ if (relative === "" || relative === ".." || relative.startsWith("../") || path6.isAbsolute(relative) || context.indexedSourcePaths && !context.indexedSourcePaths.has(relative)) {
3081
+ return void 0;
3082
+ }
3083
+ return symbolId(kind, relative, declarationToSymbolName(declaration) ?? symbol.getName());
3084
+ }
3085
+ function resolveImportedReactSymbol(context, profile, importedName, kind) {
3086
+ const source = profile.localImports.get(importedName);
3087
+ if (!source) return void 0;
3088
+ if (!source.source.startsWith("./") && !source.source.startsWith("../")) {
3089
+ const resolved = context.resolveModule?.(source.source, context.sourceFile.fileName);
3090
+ if (!resolved) return void 0;
3091
+ if (!isPathInside(context.root, resolved.resolvedFileName)) return void 0;
3092
+ const relative2 = relativePosix(context.root, resolved.resolvedFileName);
3093
+ if (context.indexedSourcePaths && !context.indexedSourcePaths.has(relative2)) return void 0;
3094
+ const sourceFile = context.program.getSourceFile(resolved.resolvedFileName);
3095
+ const declaration = sourceFile ? findExportedDeclaration2(sourceFile, source.imported) : void 0;
3096
+ const actualName = declaration ? declarationToSymbolName(declaration) : void 0;
3097
+ if (actualName) return symbolId(kind, relative2, actualName);
3098
+ return symbolId(kind, relative2, source.imported);
3099
+ }
3100
+ const relative = path6.posix.normalize(
3101
+ path6.posix.join(path6.posix.dirname(context.relativePath), source.source)
3102
+ );
3103
+ const candidates = [
3104
+ relative,
3105
+ `${relative}.ts`,
3106
+ `${relative}.tsx`,
3107
+ `${relative}.js`,
3108
+ `${relative}.jsx`,
3109
+ `${relative}.mjs`,
3110
+ `${relative}.cjs`,
3111
+ `${relative}/index.ts`,
3112
+ `${relative}/index.tsx`,
3113
+ `${relative}/index.jsx`
3114
+ ];
3115
+ for (const candidate of candidates) {
3116
+ const absolute = path6.resolve(context.root, candidate);
3117
+ const imported = context.program.getSourceFile(absolute);
3118
+ if (imported) {
3119
+ const declaration = findExportedDeclaration2(imported, source.imported);
3120
+ if (declaration) {
3121
+ const symbolName = declarationToSymbolName(declaration);
3122
+ if (symbolName) return symbolId(kind, candidate, symbolName);
3123
+ }
3124
+ if (source.imported !== "*") return symbolId(kind, candidate, source.imported);
3125
+ }
3126
+ }
3127
+ const fallback = source.imported === "*" ? importedName : source.imported;
3128
+ return symbolId(kind, relative, fallback);
3129
+ }
3130
+ function declarationToSymbolName(declaration) {
3131
+ if (ts5.isFunctionDeclaration(declaration)) return declaration.name?.text ?? "default";
3132
+ if (ts5.isClassDeclaration(declaration)) return declaration.name?.text ?? "default";
3133
+ if (ts5.isInterfaceDeclaration(declaration) || ts5.isTypeAliasDeclaration(declaration))
3134
+ return declaration.name.text;
3135
+ if (ts5.isVariableDeclaration(declaration) && ts5.isIdentifier(declaration.name))
3136
+ return declaration.name.text;
3137
+ return "default";
3138
+ }
3139
+ function findExportedDeclaration2(sourceFile, exportedName) {
3140
+ for (const statement of sourceFile.statements) {
3141
+ if (ts5.isFunctionDeclaration(statement) && (exportedName === "default" && hasDefaultModifier(statement) || statement.name?.text === exportedName))
3142
+ return statement;
3143
+ if (ts5.isClassDeclaration(statement) && (exportedName === "default" && hasDefaultModifier(statement) || statement.name?.text === exportedName))
3144
+ return statement;
3145
+ if (ts5.isInterfaceDeclaration(statement) && (exportedName === "default" && hasDefaultModifier(statement) || statement.name.text === exportedName))
3146
+ return statement;
3147
+ if (ts5.isTypeAliasDeclaration(statement) && (exportedName === "default" && hasDefaultModifier(statement) || statement.name.text === exportedName))
3148
+ return statement;
3149
+ if (ts5.isVariableStatement(statement)) {
3150
+ const declaration = exportedName === "default" ? statement.declarationList.declarations.find(
3151
+ (item) => hasDefaultModifier(statement) && ts5.isIdentifier(item.name)
3152
+ ) : statement.declarationList.declarations.find(
3153
+ (item) => ts5.isIdentifier(item.name) && item.name.text === exportedName
3154
+ );
3155
+ if (declaration && ts5.isIdentifier(declaration.name)) return declaration;
3156
+ }
3157
+ if (ts5.isExportDeclaration(statement) && statement.exportClause && ts5.isNamedExports(statement.exportClause)) {
3158
+ const specifier = statement.exportClause.elements.find(
3159
+ (item) => item.name.text === exportedName || item.name.text === "default" && item.propertyName?.text === exportedName
3160
+ );
3161
+ if (!specifier) continue;
3162
+ return findExportedDeclaration2(
3163
+ sourceFile,
3164
+ specifier.propertyName?.text ?? specifier.name.text
3165
+ );
3166
+ }
3167
+ if (ts5.isExportAssignment(statement) && !statement.isExportEquals && ts5.isIdentifier(statement.expression)) {
3168
+ return findDeclarationByName2(sourceFile, statement.expression.text);
3169
+ }
3170
+ }
3171
+ return void 0;
3172
+ }
3173
+ function findDeclarationByName2(sourceFile, name) {
3174
+ for (const statement of sourceFile.statements) {
3175
+ if (ts5.isFunctionDeclaration(statement) || ts5.isClassDeclaration(statement) || ts5.isInterfaceDeclaration(statement) || ts5.isTypeAliasDeclaration(statement)) {
3176
+ if (statement.name?.text === name) return statement;
3177
+ }
3178
+ if (ts5.isVariableStatement(statement)) {
3179
+ const declaration = statement.declarationList.declarations.find(
3180
+ (item) => ts5.isIdentifier(item.name) && item.name.text === name
3181
+ );
3182
+ if (declaration) return declaration;
3183
+ }
3184
+ }
3185
+ return void 0;
3186
+ }
3187
+ function dependencyText(expression, sourceFile) {
3188
+ if (!expression || !ts5.isArrayLiteralExpression(expression)) return "[]";
3189
+ return expression.elements.map((element) => compactText(redactStringLiterals(element.getText(sourceFile)))).join(",");
3190
+ }
3191
+ function resolveCallName(expression, profile) {
3192
+ if (ts5.isIdentifier(expression)) {
3193
+ return profile.hookAliases.get(expression.text) ?? expression.text;
3194
+ }
3195
+ if (ts5.isPropertyAccessExpression(expression) && ts5.isIdentifier(expression.expression) && profile.namespaceAliases.has(expression.expression.text)) {
3196
+ return expression.name.text;
3197
+ }
3198
+ return void 0;
3199
+ }
3200
+ function variableDeclarationOfCall(node) {
3201
+ let current = node.parent;
3202
+ while (current) {
3203
+ if (ts5.isVariableDeclaration(current)) return current;
3204
+ current = current.parent;
3205
+ }
3206
+ return void 0;
3207
+ }
3208
+ function collectBindingNames(name, values) {
3209
+ if (ts5.isIdentifier(name)) {
3210
+ values.push(name.text.toLowerCase());
3211
+ return;
3212
+ }
3213
+ if (ts5.isObjectBindingPattern(name) || ts5.isArrayBindingPattern(name)) {
3214
+ for (const element of name.elements) {
3215
+ if (ts5.isBindingElement(element)) collectBindingNames(element.name, values);
3216
+ }
3217
+ }
3218
+ }
3219
+ function collectInterfaceMembers(typeNode, context) {
3220
+ if (ts5.isTypeLiteralNode(typeNode)) {
3221
+ return typeNode.members.flatMap((member) => {
3222
+ const name = propertyNameText(member.name);
3223
+ return name ? [name.toLowerCase()] : [];
3224
+ });
3225
+ }
3226
+ if (ts5.isTypeReferenceNode(typeNode) && ts5.isIdentifier(typeNode.typeName)) {
3227
+ const symbol = context.checker.getSymbolAtLocation(typeNode.typeName);
3228
+ const resolved = symbol && symbol.flags & ts5.SymbolFlags.Alias ? context.checker.getAliasedSymbol(symbol) : symbol;
3229
+ const declaration = resolved?.declarations?.[0];
3230
+ if (declaration && ts5.isInterfaceDeclaration(declaration)) {
3231
+ return declaration.members.flatMap((member) => {
3232
+ const name = propertyNameText(member.name);
3233
+ return name ? [name.toLowerCase()] : [];
3234
+ });
3235
+ }
3236
+ }
3237
+ return [];
3238
+ }
3239
+ function containsJsx(node) {
3240
+ let found = false;
3241
+ visit2(node, (current) => {
3242
+ if (ts5.isJsxElement(current) || ts5.isJsxSelfClosingElement(current) || ts5.isJsxOpeningElement(current) || ts5.isJsxFragment(current)) {
3243
+ found = true;
3244
+ }
3245
+ });
3246
+ return found;
3247
+ }
3248
+ function jsxTagName(tag) {
3249
+ if (ts5.isIdentifier(tag) || ts5.isStringLiteral(tag)) return tag.text;
3250
+ if (ts5.isPropertyAccessExpression(tag)) return tag.name.text;
3251
+ return void 0;
3252
+ }
3253
+ function visit2(node, callback) {
3254
+ callback(node);
3255
+ ts5.forEachChild(node, (child) => visit2(child, callback));
3256
+ }
3257
+ function dedupe2(values) {
3258
+ return [...new Set(values)];
3259
+ }
3260
+ function makeRelation3(from, type, to, context, node, confidence, explanation) {
3261
+ return {
3262
+ schemaVersion: SCHEMA_VERSION,
3263
+ from,
3264
+ type,
3265
+ to,
3266
+ confidence,
3267
+ origin: {
3268
+ file: context.relativePath,
3269
+ line: nodeLine(context.sourceFile, node),
3270
+ range: nodeRange(context.sourceFile, node),
3271
+ explanation,
3272
+ classification: "framework-derived",
3273
+ analyzer: "react"
3274
+ }
3275
+ };
3276
+ }
3277
+
3278
+ // src/compiler-units.ts
3279
+ import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "fs";
3280
+ import path7 from "path";
3281
+ import fg from "fast-glob";
3282
+ import ts6 from "typescript";
3283
+ var FALLBACK_UNIT_ID = generatedCompilerUnitId("fallback");
3284
+ function sanitizeCompilerPathAliases(options) {
3285
+ if (!options.paths) return;
3286
+ const sanitized = {};
3287
+ for (const [alias, targets] of Object.entries(options.paths).sort(
3288
+ ([a], [b]) => compareText(a, b)
3289
+ )) {
3290
+ if (!isSafeRelativePathPattern(alias)) continue;
3291
+ const safeTargets = targets.filter(isSafeRelativePathPattern);
3292
+ if (safeTargets.length > 0) sanitized[alias] = safeTargets;
3293
+ }
3294
+ options.paths = sanitized;
3295
+ }
3296
+ function boundedTypeRoots(root, start) {
3297
+ const values = [];
3298
+ let current = path7.resolve(start);
3299
+ while (isPathInside(root, current)) {
3300
+ values.push(path7.join(current, "node_modules", "@types"));
3301
+ if (current === path7.resolve(root)) break;
3302
+ const parent = path7.dirname(current);
3303
+ if (parent === current) break;
3304
+ current = parent;
3305
+ }
3306
+ return [...new Set(values)];
3307
+ }
3308
+ var BoundedTypeScriptFileSystem = class {
3309
+ extendedConfigCache = /* @__PURE__ */ new Map();
3310
+ root;
3311
+ typeScriptLib;
3312
+ observedInputs = /* @__PURE__ */ new Map();
3313
+ constructor(root) {
3314
+ this.root = realpathSync(root);
3315
+ this.typeScriptLib = realpathSync(path7.dirname(ts6.getDefaultLibFilePath({})));
3316
+ }
3317
+ fileExists = (fileName) => {
3318
+ const absolute = this.absolute(fileName);
3319
+ if (!this.isPermitted(absolute)) return false;
3320
+ try {
3321
+ const exists = statSync(absolute).isFile();
3322
+ this.recordProbe(absolute, "file", exists);
3323
+ return exists;
3324
+ } catch {
3325
+ this.recordProbe(absolute, "file", false);
3326
+ return false;
3327
+ }
3328
+ };
3329
+ readFile = (fileName) => {
3330
+ const absolute = this.absolute(fileName);
3331
+ if (!this.isPermitted(absolute)) return void 0;
3332
+ try {
3333
+ const contents = readFileSync(absolute);
3334
+ this.recordRead(absolute, contents);
3335
+ return contents.toString("utf8");
3336
+ } catch {
3337
+ let exists = false;
3338
+ try {
3339
+ exists = statSync(absolute).isFile();
3340
+ } catch {
3341
+ }
3342
+ this.recordProbe(absolute, "file", exists);
3343
+ this.recordProbe(absolute, "readable-file", false);
3344
+ return void 0;
3345
+ }
3346
+ };
3347
+ directoryExists = (directory) => {
3348
+ const absolute = this.absolute(directory);
3349
+ if (!this.isPermitted(absolute)) return false;
3350
+ try {
3351
+ const exists = statSync(absolute).isDirectory();
3352
+ this.recordProbe(absolute, "directory", exists);
3353
+ return exists;
3354
+ } catch {
3355
+ this.recordProbe(absolute, "directory", false);
3356
+ return false;
3357
+ }
3358
+ };
3359
+ getDirectories = (directory) => {
3360
+ const absolute = this.absolute(directory);
3361
+ if (!this.isPermitted(absolute)) return [];
3362
+ if (!this.directoryExists(absolute)) return [];
3363
+ const directories = ts6.sys.getDirectories(absolute).filter((entry) => this.isPermitted(path7.resolve(absolute, entry))).sort(compareText);
3364
+ this.recordDirectoryListing(absolute, directories);
3365
+ return directories;
3366
+ };
3367
+ readDirectory = (rootDir, extensions, excludes, includes, depth) => {
3368
+ const absolute = this.absolute(rootDir);
3369
+ if (!this.isPermitted(absolute)) return [];
3370
+ return ts6.sys.readDirectory(absolute, extensions, excludes, includes, depth).filter((entry) => this.isPermitted(path7.resolve(absolute, entry))).sort(compareText);
3371
+ };
3372
+ realpath = (candidate) => {
3373
+ const absolute = this.absolute(candidate);
3374
+ if (!this.isPermitted(absolute)) return absolute;
3375
+ try {
3376
+ const real = realpathSync(absolute);
3377
+ this.recordSymlink(absolute, real);
3378
+ return real;
3379
+ } catch {
3380
+ return absolute;
3381
+ }
3382
+ };
3383
+ parseHost(currentDirectory) {
3384
+ return {
3385
+ useCaseSensitiveFileNames: ts6.sys.useCaseSensitiveFileNames,
3386
+ getCurrentDirectory: () => currentDirectory,
3387
+ fileExists: this.fileExists,
3388
+ readFile: this.readFile,
3389
+ readDirectory: this.readDirectory,
3390
+ directoryExists: this.directoryExists,
3391
+ getDirectories: this.getDirectories,
3392
+ realpath: this.realpath,
3393
+ onUnRecoverableConfigFileDiagnostic: () => void 0
3394
+ };
3395
+ }
3396
+ semanticInputs() {
3397
+ return [...this.observedInputs.values()].sort(
3398
+ (a, b) => compareText(semanticObservationKey(a), semanticObservationKey(b))
3399
+ );
3400
+ }
3401
+ recordProbe(absolute, targetKind, exists) {
3402
+ const scoped = this.semanticScope(absolute);
3403
+ if (!scoped) return;
3404
+ const relative = relativePosix(scoped.root, absolute);
3405
+ const contentKey = targetKind === "directory" ? `directory-listing\0${scoped.scope}\0${relative}` : scoped.scope === "toolchain" ? `toolchain-file\0${relative}` : `file\0${relative}`;
3406
+ if (this.observedInputs.has(contentKey)) return;
3407
+ const observation = {
3408
+ path: relative,
3409
+ kind: "resolution-probe",
3410
+ scope: scoped.scope,
3411
+ targetKind,
3412
+ exists
3413
+ };
3414
+ this.observedInputs.set(semanticObservationKey(observation), observation);
3415
+ }
3416
+ recordRead(absolute, contents) {
3417
+ if (isPathInside(this.root, absolute)) {
3418
+ const relative = relativePosix(this.root, absolute);
3419
+ this.observedInputs.delete(`resolution-probe\0project\0file\0${relative}`);
3420
+ this.observedInputs.delete(
3421
+ `resolution-probe\0project\0readable-file\0${relative}`
3422
+ );
3423
+ const observation = {
3424
+ path: relative,
3425
+ kind: "file",
3426
+ hash: hashText(contents)
3427
+ };
3428
+ this.observedInputs.set(semanticObservationKey(observation), observation);
3429
+ return;
3430
+ }
3431
+ if (isPathInside(this.typeScriptLib, absolute)) {
3432
+ const relative = relativePosix(this.typeScriptLib, absolute);
3433
+ this.observedInputs.delete(`resolution-probe\0toolchain\0file\0${relative}`);
3434
+ this.observedInputs.delete(
3435
+ `resolution-probe\0toolchain\0readable-file\0${relative}`
3436
+ );
3437
+ const observation = {
3438
+ // Preserve the lexical file TypeScript opened. Hashing only a resolved target would miss
3439
+ // a same-version standard-library symlink being repointed to different bytes.
3440
+ path: relative,
3441
+ kind: "toolchain-file",
3442
+ hash: hashText(contents)
3443
+ };
3444
+ this.observedInputs.set(semanticObservationKey(observation), observation);
3445
+ }
3446
+ }
3447
+ recordDirectoryListing(absolute, entries) {
3448
+ const scoped = this.semanticScope(absolute);
3449
+ if (!scoped) return;
3450
+ const relative = relativePosix(scoped.root, absolute);
3451
+ this.observedInputs.delete(
3452
+ `resolution-probe\0${scoped.scope}\0directory\0${relative}`
3453
+ );
3454
+ const observation = {
3455
+ path: relative,
3456
+ kind: "directory-listing",
3457
+ scope: scoped.scope,
3458
+ hash: semanticDirectoryListingHash(absolute, entries)
3459
+ };
3460
+ this.observedInputs.set(semanticObservationKey(observation), observation);
3461
+ }
3462
+ recordSymlink(absolute, real) {
3463
+ const scoped = this.semanticScope(absolute);
3464
+ if (!scoped) return;
3465
+ const relative = relativePosix(scoped.root, absolute);
3466
+ const target = isPathInside(scoped.root, real) ? relativePosix(scoped.root, real) : "<outside-root>";
3467
+ if (relative === target) return;
3468
+ const observation = {
3469
+ path: relative,
3470
+ kind: "symlink",
3471
+ scope: scoped.scope,
3472
+ target
3473
+ };
3474
+ this.observedInputs.set(semanticObservationKey(observation), observation);
3475
+ }
3476
+ semanticScope(absolute) {
3477
+ if (isPathInside(this.root, absolute)) return { scope: "project", root: this.root };
3478
+ if (isPathInside(this.typeScriptLib, absolute)) {
3479
+ return { scope: "toolchain", root: this.typeScriptLib };
3480
+ }
3481
+ return void 0;
3482
+ }
3483
+ isPermitted(candidate) {
3484
+ const absolute = this.absolute(candidate);
3485
+ const lexicalInRoot = isPathInside(this.root, absolute);
3486
+ const lexicalInToolchain = isPathInside(this.typeScriptLib, absolute);
3487
+ const lexicalAllowed = lexicalInRoot || lexicalInToolchain;
3488
+ if (!lexicalAllowed) return false;
3489
+ if (!existsSync(absolute)) return true;
3490
+ try {
3491
+ const real = realpathSync(absolute);
3492
+ this.recordSymlink(absolute, real);
3493
+ return lexicalInRoot && isPathInside(this.root, real) || lexicalInToolchain && isPathInside(this.typeScriptLib, real);
3494
+ } catch {
3495
+ return false;
3496
+ }
3497
+ }
3498
+ absolute(candidate) {
3499
+ return path7.isAbsolute(candidate) ? path7.resolve(candidate) : path7.resolve(this.root, candidate);
3500
+ }
3501
+ };
3502
+ function semanticDirectoryListingHash(directory, entries) {
3503
+ const normalized = entries.map((entry) => normalizeStoredPath(path7.relative(directory, path7.resolve(directory, entry)))).sort(compareText);
3504
+ return hashText(Buffer.from(normalized.join("\0"), "utf8"));
3505
+ }
3506
+ function semanticObservationKey(observation) {
3507
+ return observation.kind === "resolution-probe" ? `${observation.kind}\0${observation.scope ?? "project"}\0${observation.targetKind}\0${observation.path}` : observation.kind === "directory-listing" || observation.kind === "symlink" ? `${observation.kind}\0${observation.scope ?? "project"}\0${observation.path}` : `${observation.kind}\0${observation.path}`;
3508
+ }
3509
+ function fallbackCompilerOptions(root) {
3510
+ return {
3511
+ target: ts6.ScriptTarget.ES2022,
3512
+ module: ts6.ModuleKind.NodeNext,
3513
+ moduleResolution: ts6.ModuleResolutionKind.NodeNext,
3514
+ allowJs: true,
3515
+ checkJs: false,
3516
+ noEmit: true,
3517
+ skipLibCheck: true,
3518
+ jsx: ts6.JsxEmit.Preserve,
3519
+ experimentalDecorators: true,
3520
+ configFilePath: path7.join(root, "tsconfig.fallback.json"),
3521
+ typeRoots: boundedTypeRoots(root, root)
3522
+ };
3523
+ }
3524
+
3525
+ // src/semantic.ts
3526
+ import path8 from "path";
3527
+ import ts7 from "typescript";
3528
+ var TYPESCRIPT_SEMANTIC_VERSION = `compiler-units-v4+typescript-${ts7.version}`;
3529
+ var TypeScriptSemanticResolver = class {
3530
+ constructor(jsDocParsingMode = ts7.JSDocParsingMode.ParseAll) {
3531
+ this.jsDocParsingMode = jsDocParsingMode;
3532
+ }
3533
+ jsDocParsingMode;
3534
+ name = "typescript-program";
3535
+ version = TYPESCRIPT_SEMANTIC_VERSION;
3536
+ resolve(input) {
3537
+ const compiler = loadCompilerConfiguration(input.root, input.tsconfig);
3538
+ const currentDirectory = input.tsconfig ? path8.dirname(path8.resolve(input.root, input.tsconfig)) : input.root;
3539
+ const fileSystem = compiler.fileSystem;
3540
+ const host = createBoundedCompilerHost(
3541
+ input.root,
3542
+ currentDirectory,
3543
+ compiler.options,
3544
+ fileSystem,
3545
+ compiler.parsedCommands
3546
+ );
3547
+ host.jsDocParsingMode = this.jsDocParsingMode;
3548
+ const cache = ts7.createModuleResolutionCache(
3549
+ currentDirectory,
3550
+ (fileName) => host.getCanonicalFileName(fileName),
3551
+ compiler.options
3552
+ );
3553
+ const resolveProjectReferenceModule = createProjectReferenceModuleResolver(
3554
+ input.root,
3555
+ compiler.projectReferences ?? [],
3556
+ fileSystem,
3557
+ compiler.parsedCommands
3558
+ );
3559
+ const resolveModule = (moduleName, containingFile) => {
3560
+ const normal = ts7.resolveModuleName(
3561
+ moduleName,
3562
+ containingFile,
3563
+ compiler.options,
3564
+ host,
3565
+ cache
3566
+ ).resolvedModule;
3567
+ return resolveProjectReferenceModule(moduleName) ?? normal;
3568
+ };
3569
+ host.resolveModuleNameLiterals = (moduleLiterals, containingFile) => moduleLiterals.map((literal) => {
3570
+ const resolvedModule = resolveModule(literal.text, containingFile);
3571
+ return { resolvedModule };
3572
+ });
3573
+ const rootNames = [...new Set(input.sourceFiles.map((file) => path8.resolve(file)))].sort(
3574
+ compareText
3575
+ );
3576
+ const program = ts7.createProgram({
3577
+ rootNames,
3578
+ options: compiler.options,
3579
+ ...compiler.projectReferences?.length ? { projectReferences: compiler.projectReferences } : {},
3580
+ host,
3581
+ // Reference edges are parsed and fingerprinted by the compiler-unit graph. A read-only
3582
+ // scanner resolves their source imports directly so correctness never depends on prebuilt
3583
+ // declaration outputs being present beside a referenced project.
3584
+ configFileParsingDiagnostics: compiler.rawDiagnostics
3585
+ });
3586
+ return {
3587
+ program,
3588
+ checker: program.getTypeChecker(),
3589
+ pathAliases: compiler.pathAliases,
3590
+ diagnostics: compiler.diagnostics,
3591
+ semanticInputs: () => fileSystem.semanticInputs(),
3592
+ resolveModule(moduleName, containingFile) {
3593
+ const resolved = resolveModule(moduleName, containingFile);
3594
+ return resolved ? {
3595
+ resolvedFileName: resolved.resolvedFileName,
3596
+ isExternalLibraryImport: Boolean(resolved.isExternalLibraryImport)
3597
+ } : void 0;
3598
+ }
3599
+ };
3600
+ }
3601
+ };
3602
+ function createProjectReferenceModuleResolver(root, references, fileSystem, parsedCommands) {
3603
+ const packages = [];
3604
+ for (const reference of references) {
3605
+ const configPath = ts7.resolveProjectReferencePath(reference);
3606
+ if (!isPathInside(root, configPath)) continue;
3607
+ const parsed = parseCommand(root, configPath, fileSystem, parsedCommands);
3608
+ if (!parsed) continue;
3609
+ const packageRoot = path8.dirname(configPath);
3610
+ const packageJsonPath = path8.join(packageRoot, "package.json");
3611
+ const raw = fileSystem.readFile(packageJsonPath);
3612
+ if (!raw) continue;
3613
+ try {
3614
+ const manifest = JSON.parse(raw);
3615
+ if (typeof manifest.name !== "string" || manifest.name.length === 0) continue;
3616
+ packages.push({ name: manifest.name, root: packageRoot, manifest, parsed });
3617
+ } catch {
3618
+ }
3619
+ }
3620
+ packages.sort((a, b) => b.name.length - a.name.length || compareText(a.name, b.name));
3621
+ return (moduleName) => {
3622
+ const referenced = packages.find(
3623
+ (candidate) => moduleName === candidate.name || moduleName.startsWith(`${candidate.name}/`)
3624
+ );
3625
+ if (!referenced) return void 0;
3626
+ const subpath = moduleName === referenced.name ? "" : moduleName.slice(referenced.name.length + 1);
3627
+ const source = referencedPackageSource(referenced, subpath, fileSystem);
3628
+ if (!source) return void 0;
3629
+ return {
3630
+ resolvedFileName: source,
3631
+ extension: moduleExtension(source),
3632
+ isExternalLibraryImport: false
3633
+ };
3634
+ };
3635
+ }
3636
+ function referencedPackageSource(referenced, subpath, fileSystem) {
3637
+ const { parsed, root: packageRoot, manifest } = referenced;
3638
+ const rootDir = parsed.options.rootDir ?? packageRoot;
3639
+ if (!isPathInside(packageRoot, rootDir)) return void 0;
3640
+ const owned = new Set(parsed.fileNames.map((fileName) => path8.resolve(fileName)));
3641
+ const requestedTargets = packageEntryTargets(manifest, subpath);
3642
+ const bases = [];
3643
+ for (const target of requestedTargets) {
3644
+ const absoluteTarget = path8.resolve(packageRoot, target);
3645
+ if (!isPathInside(packageRoot, absoluteTarget)) continue;
3646
+ const outDir = parsed.options.outDir;
3647
+ if (outDir && isPathInside(outDir, absoluteTarget)) {
3648
+ bases.push(path8.resolve(rootDir, path8.relative(outDir, absoluteTarget)));
3649
+ }
3650
+ bases.push(absoluteTarget);
3651
+ }
3652
+ bases.push(path8.resolve(rootDir, subpath || "index"));
3653
+ for (const base of bases) {
3654
+ for (const candidate of sourceModuleCandidates(base)) {
3655
+ const absolute = path8.resolve(candidate);
3656
+ if (isPathInside(packageRoot, absolute) && owned.has(absolute) && fileSystem.fileExists(absolute)) {
3657
+ return fileSystem.realpath(absolute);
3658
+ }
3659
+ }
3660
+ }
3661
+ const expectedStem = normalizeModuleStem(subpath || "index");
3662
+ const matching = [...owned].filter((fileName) => isPathInside(rootDir, fileName)).filter((fileName) => {
3663
+ const relative = normalizeModuleStem(relativePosix(rootDir, fileName));
3664
+ return relative === expectedStem || relative === `${expectedStem}/index`;
3665
+ }).sort(compareText);
3666
+ return matching[0];
3667
+ }
3668
+ function packageEntryTargets(manifest, subpath) {
3669
+ const output = [];
3670
+ const exportKey = subpath ? `./${subpath}` : ".";
3671
+ const exportsValue = manifest.exports;
3672
+ if (exportsValue && typeof exportsValue === "object" && !Array.isArray(exportsValue)) {
3673
+ const exportsRecord = exportsValue;
3674
+ const keyed = exportsRecord[exportKey];
3675
+ collectStaticPackageTargets(
3676
+ keyed ?? (!subpath && !("." in exportsRecord) ? exportsValue : void 0),
3677
+ output
3678
+ );
3679
+ } else if (!subpath) {
3680
+ collectStaticPackageTargets(exportsValue, output);
3681
+ }
3682
+ if (!subpath) {
3683
+ for (const field of ["types", "typings", "source", "module", "main"]) {
3684
+ const value = manifest[field];
3685
+ if (typeof value === "string") output.push(value);
3686
+ }
3687
+ }
3688
+ return [...new Set(output)];
3689
+ }
3690
+ function collectStaticPackageTargets(value, output) {
3691
+ if (typeof value === "string") {
3692
+ output.push(value);
3693
+ return;
3694
+ }
3695
+ if (Array.isArray(value)) {
3696
+ for (const item of value) collectStaticPackageTargets(item, output);
3697
+ return;
3698
+ }
3699
+ if (!value || typeof value !== "object") return;
3700
+ const record = value;
3701
+ for (const key of ["types", "import", "default", "require", "node"]) {
3702
+ if (key in record) collectStaticPackageTargets(record[key], output);
3703
+ }
3704
+ }
3705
+ function sourceModuleCandidates(value) {
3706
+ const stem = value.replace(/\.d\.(?:ts|mts|cts)$/i, "").replace(/\.(?:tsx?|mts|cts|jsx?|mjs|cjs)$/i, "");
3707
+ const extensions = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
3708
+ return [
3709
+ value,
3710
+ ...extensions.map((extension) => `${stem}${extension}`),
3711
+ ...extensions.map((extension) => path8.join(stem, `index${extension}`))
3712
+ ];
3713
+ }
3714
+ function normalizeModuleStem(value) {
3715
+ return normalizeStoredPath(value).replace(/\.d\.(?:ts|mts|cts)$/i, "").replace(/\.(?:tsx?|mts|cts|jsx?|mjs|cjs)$/i, "");
3716
+ }
3717
+ function moduleExtension(fileName) {
3718
+ const lower = fileName.toLowerCase();
3719
+ if (lower.endsWith(".tsx")) return ts7.Extension.Tsx;
3720
+ if (lower.endsWith(".mts")) return ts7.Extension.Mts;
3721
+ if (lower.endsWith(".cts")) return ts7.Extension.Cts;
3722
+ if (lower.endsWith(".jsx")) return ts7.Extension.Jsx;
3723
+ if (lower.endsWith(".mjs")) return ts7.Extension.Mjs;
3724
+ if (lower.endsWith(".cjs")) return ts7.Extension.Cjs;
3725
+ if (lower.endsWith(".js")) return ts7.Extension.Js;
3726
+ return ts7.Extension.Ts;
3727
+ }
3728
+ function createBoundedModuleResolver(root, currentDirectory, options) {
3729
+ const fileSystem = new BoundedTypeScriptFileSystem(root);
3730
+ const cache = ts7.createModuleResolutionCache(
3731
+ currentDirectory,
3732
+ (fileName) => ts7.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
3733
+ options
3734
+ );
3735
+ const host = {
3736
+ fileExists: fileSystem.fileExists,
3737
+ readFile: fileSystem.readFile,
3738
+ directoryExists: fileSystem.directoryExists,
3739
+ getDirectories: fileSystem.getDirectories,
3740
+ realpath: fileSystem.realpath,
3741
+ getCurrentDirectory: () => currentDirectory,
3742
+ useCaseSensitiveFileNames: () => ts7.sys.useCaseSensitiveFileNames
3743
+ };
3744
+ return (moduleName, containingFile) => {
3745
+ const resolved = ts7.resolveModuleName(
3746
+ moduleName,
3747
+ containingFile,
3748
+ options,
3749
+ host,
3750
+ cache
3751
+ ).resolvedModule;
3752
+ return resolved ? {
3753
+ resolvedFileName: resolved.resolvedFileName,
3754
+ isExternalLibraryImport: Boolean(resolved.isExternalLibraryImport)
3755
+ } : void 0;
3756
+ };
3757
+ }
3758
+ function loadCompilerConfiguration(root, tsconfig) {
3759
+ const fileSystem = new BoundedTypeScriptFileSystem(root);
3760
+ if (!tsconfig) {
3761
+ return {
3762
+ options: fallbackCompilerOptions(root),
3763
+ pathAliases: {},
3764
+ diagnostics: [],
3765
+ rawDiagnostics: [],
3766
+ fileSystem,
3767
+ parsedCommands: /* @__PURE__ */ new Map()
3768
+ };
3769
+ }
3770
+ const absolute = path8.resolve(root, tsconfig);
3771
+ if (!isPathInside(root, absolute) || !fileSystem.fileExists(absolute)) {
3772
+ const diagnostic = {
3773
+ schemaVersion: SCHEMA_VERSION,
3774
+ code: "compiler-config",
3775
+ severity: "warning",
3776
+ message: "The owning TypeScript configuration was unavailable or unsafe; fallback options were used.",
3777
+ ...isPathInside(root, absolute) ? { file: relativePosix(root, absolute) } : {}
3778
+ };
3779
+ return {
3780
+ options: fallbackCompilerOptions(root),
3781
+ pathAliases: {},
3782
+ diagnostics: [diagnostic],
3783
+ rawDiagnostics: [],
3784
+ fileSystem,
3785
+ parsedCommands: /* @__PURE__ */ new Map()
3786
+ };
3787
+ }
3788
+ const parsedCommands = /* @__PURE__ */ new Map();
3789
+ const parsed = parseCommand(root, absolute, fileSystem, parsedCommands);
3790
+ if (!parsed) {
3791
+ return {
3792
+ options: fallbackCompilerOptions(root),
3793
+ pathAliases: {},
3794
+ diagnostics: [
3795
+ {
3796
+ schemaVersion: SCHEMA_VERSION,
3797
+ code: "compiler-config",
3798
+ severity: "warning",
3799
+ message: "The owning TypeScript configuration could not be parsed; fallback options were used.",
3800
+ file: relativePosix(root, absolute)
3801
+ }
3802
+ ],
3803
+ rawDiagnostics: [],
3804
+ fileSystem,
3805
+ parsedCommands
3806
+ };
3807
+ }
3808
+ const projectReferences = (parsed.projectReferences ?? []).filter((reference) => {
3809
+ const target = ts7.resolveProjectReferencePath(reference);
3810
+ return isPathInside(root, target) && fileSystem.fileExists(target);
3811
+ });
3812
+ const options = parsed.options;
3813
+ anchorCompilerOptions(root, path8.dirname(absolute), options);
3814
+ return {
3815
+ options,
3816
+ ...projectReferences.length > 0 ? { projectReferences } : {},
3817
+ pathAliases: pathAliases(root, path8.dirname(absolute), options),
3818
+ diagnostics: parsed.errors.map((diagnostic) => compilerDiagnostic(diagnostic, root)),
3819
+ rawDiagnostics: parsed.errors,
3820
+ fileSystem,
3821
+ parsedCommands
3822
+ };
3823
+ }
3824
+ function parseCommand(root, configPath, fileSystem, cache) {
3825
+ const absolute = path8.resolve(configPath);
3826
+ const cached = cache.get(absolute);
3827
+ if (cached) return cached;
3828
+ if (!isPathInside(root, absolute) || !fileSystem.fileExists(absolute)) return void 0;
3829
+ try {
3830
+ const source = ts7.readJsonConfigFile(absolute, fileSystem.readFile);
3831
+ const parsed = ts7.parseJsonSourceFileConfigFileContent(
3832
+ source,
3833
+ fileSystem.parseHost(path8.dirname(absolute)),
3834
+ path8.dirname(absolute),
3835
+ void 0,
3836
+ absolute,
3837
+ void 0,
3838
+ void 0,
3839
+ fileSystem.extendedConfigCache
3840
+ );
3841
+ parsed.projectReferences = (parsed.projectReferences ?? []).filter((reference) => {
3842
+ const target = ts7.resolveProjectReferencePath(reference);
3843
+ return isPathInside(root, target) && fileSystem.fileExists(target);
3844
+ });
3845
+ anchorCompilerOptions(root, path8.dirname(absolute), parsed.options);
3846
+ cache.set(absolute, parsed);
3847
+ return parsed;
3848
+ } catch {
3849
+ return void 0;
3850
+ }
3851
+ }
3852
+ function createBoundedCompilerHost(root, currentDirectory, options, fileSystem, parsedCommands) {
3853
+ const host = ts7.createCompilerHost(options, true);
3854
+ host.getCurrentDirectory = () => currentDirectory;
3855
+ host.fileExists = fileSystem.fileExists;
3856
+ host.readFile = fileSystem.readFile;
3857
+ host.directoryExists = fileSystem.directoryExists;
3858
+ host.getDirectories = fileSystem.getDirectories;
3859
+ host.readDirectory = (rootDir, extensions, excludes, includes, depth) => [
3860
+ ...fileSystem.readDirectory(rootDir, extensions, excludes, includes, depth)
3861
+ ];
3862
+ host.realpath = fileSystem.realpath;
3863
+ host.getEnvironmentVariable = () => "";
3864
+ host.writeFile = () => void 0;
3865
+ host.getParsedCommandLine = (configFileName) => parseCommand(root, configFileName, fileSystem, parsedCommands);
3866
+ host.useSourceOfProjectReferenceRedirect = () => true;
3867
+ return host;
3868
+ }
3869
+ function anchorCompilerOptions(root, unitDirectory, options) {
3870
+ options.configFilePath = options.configFilePath ?? path8.join(unitDirectory, "tsconfig.json");
3871
+ if (!options.typeRoots) options.typeRoots = boundedTypeRoots2(root, unitDirectory);
3872
+ else options.typeRoots = options.typeRoots.filter((entry) => isPathInside(root, entry));
3873
+ if (options.baseUrl && !isPathInside(root, options.baseUrl)) options.baseUrl = void 0;
3874
+ if (options.rootDirs)
3875
+ options.rootDirs = options.rootDirs.filter((entry) => isPathInside(root, entry));
3876
+ sanitizeCompilerPathAliases(options);
3877
+ }
3878
+ function boundedTypeRoots2(root, start) {
3879
+ const values = [];
3880
+ let current = path8.resolve(start);
3881
+ while (isPathInside(root, current)) {
3882
+ values.push(path8.join(current, "node_modules", "@types"));
3883
+ if (current === path8.resolve(root)) break;
3884
+ const parent = path8.dirname(current);
3885
+ if (parent === current) break;
3886
+ current = parent;
3887
+ }
3888
+ return [...new Set(values)];
3889
+ }
3890
+ function pathAliases(root, configDirectory, options) {
3891
+ const base = options.baseUrl ?? options.pathsBasePath ?? configDirectory;
3892
+ const paths = options.paths ?? {};
3893
+ return Object.fromEntries(
3894
+ Object.entries(paths).filter(([alias]) => isSafeRelativePathPattern(alias)).sort(([a], [b]) => compareText(a, b)).map(([alias, values]) => [
3895
+ alias,
3896
+ values.filter(isSafeRelativePathPattern).map((value) => path8.resolve(base, value)).filter((value) => isPathInside(root, value)).map((value) => normalizeStoredPath(relativePosix(root, value))).filter((value, index, entries) => entries.indexOf(value) === index).sort(compareText)
3897
+ ])
3898
+ );
3899
+ }
3900
+ function compilerDiagnostic(diagnostic, root) {
3901
+ const message = sanitizeCompilerMessage(
3902
+ ts7.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
3903
+ root
3904
+ );
3905
+ const file = diagnostic.file;
3906
+ const start = file && diagnostic.start !== void 0 ? file.getLineAndCharacterOfPosition(diagnostic.start) : void 0;
3907
+ let relativeFile;
3908
+ if (file) {
3909
+ try {
3910
+ relativeFile = relativePosix(root, file.fileName);
3911
+ } catch {
3912
+ }
3913
+ }
3914
+ return {
3915
+ schemaVersion: SCHEMA_VERSION,
3916
+ code: diagnostic.file ? "parse-error" : "compiler-config",
3917
+ severity: diagnostic.category === ts7.DiagnosticCategory.Error ? "error" : diagnostic.category === ts7.DiagnosticCategory.Warning ? "warning" : "info",
3918
+ message,
3919
+ ...relativeFile ? { file: relativeFile } : {},
3920
+ ...start ? { line: start.line + 1, column: start.character + 1 } : {}
3921
+ };
3922
+ }
3923
+ function sanitizeCompilerMessage(message, root) {
3924
+ return redactAbsolutePathsInText(message, root) || "TypeScript reported a compiler diagnostic.";
3925
+ }
3926
+
3927
+ // src/analyze.ts
3928
+ async function analyzeDefaultCompilerUnit(input) {
3929
+ const pipeline = defaultAnalyzerPipeline();
3930
+ const contribution = await analyzeCompilerUnit({
3931
+ ...input,
3932
+ analyzers: pipeline.analyzers,
3933
+ trustedInternalAnalyzers: pipeline.methods,
3934
+ // We expose type facts and syntactic diagnostics, not TS JSDoc link diagnostics or prose.
3935
+ // This mode still parses all JavaScript JSDoc, which can define types.
3936
+ semanticResolver: new TypeScriptSemanticResolver(ts8.JSDocParsingMode.ParseForTypeInfo)
3937
+ });
3938
+ return {
3939
+ contribution,
3940
+ files: [...input.preparedByAbsolute.values()].map((source) => source.file)
3941
+ };
3942
+ }
3943
+ async function analyzeCompilerUnit(options) {
3944
+ const { unit } = options;
3945
+ const semantic = await options.semanticResolver.resolve({
3946
+ root: options.root,
3947
+ sourceFiles: unit.sourceFiles,
3948
+ ...unit.record.config ? { tsconfig: unit.record.config } : {},
3949
+ compilerUnit: unit.record.id
3950
+ });
3951
+ const { program, checker } = semantic;
3952
+ const resolveModule = semantic.resolveModule ?? createBoundedModuleResolver(
3953
+ options.root,
3954
+ path9.resolve(options.root, unit.record.root),
3955
+ program.getCompilerOptions()
3956
+ );
3957
+ const diagnostics = [...semantic.diagnostics];
3958
+ const symbols = [];
3959
+ const relations = [];
3960
+ for (const absolutePath of unit.ownedFiles) {
3961
+ const source = options.preparedByAbsolute.get(absolutePath);
3962
+ if (!source) continue;
3963
+ const sourceFile = program.getSourceFile(source.absolutePath);
3964
+ if (!sourceFile) {
3965
+ diagnostics.push({
3966
+ schemaVersion: SCHEMA_VERSION,
3967
+ code: "analysis-error",
3968
+ severity: "error",
3969
+ message: "TypeScript did not include this selected source file in the owning compiler unit.",
3970
+ file: source.relativePath
3971
+ });
3972
+ continue;
3973
+ }
3974
+ const sourceDiagnostics = parseDiagnostics(program, sourceFile, options.root);
3975
+ diagnostics.push(...sourceDiagnostics);
3976
+ const syntax = source.syntax ?? syntaxFromProgram(sourceFile, sourceDiagnostics);
3977
+ source.file.syntax = {
3978
+ parser: sanitizePersistedText(syntax.parser, options.root),
3979
+ factCount: syntax.facts.length,
3980
+ hadErrors: syntax.hadErrors,
3981
+ ...syntax.fallbackFrom ? { fallbackFrom: sanitizePersistedText(syntax.fallbackFrom, options.root) } : {}
3982
+ };
3983
+ const context = {
3984
+ root: options.root,
3985
+ relativePath: source.relativePath,
3986
+ sourceFile,
3987
+ program,
3988
+ checker,
3989
+ sourceHash: source.hash,
3990
+ file: source.file,
3991
+ packageDependencies: options.packageDependencies,
3992
+ syntax,
3993
+ includePrivateSymbols: options.includePrivateSymbols,
3994
+ compilerUnit: unit.record,
3995
+ indexedSourcePaths: options.indexedSourcePaths,
3996
+ resolveModule
3997
+ };
3998
+ const structure = extractFileStructure(context);
3999
+ source.file.imports = structure.imports;
4000
+ source.file.exports = structure.exports;
4001
+ source.file.env = structure.env;
4002
+ source.file.operations = structure.operations;
4003
+ diagnostics.push(...structure.diagnostics);
4004
+ relations.push(...structure.relations);
4005
+ for (const analyzer of options.analyzers) {
4006
+ const trustedSupport = trustedAnalyzerMethods(analyzer, options.trustedInternalAnalyzers);
4007
+ let supported = false;
4008
+ try {
4009
+ supported = trustedSupport ? trustedSupport.supports(context) : analyzer.supports(isolateCustomAnalyzerContext(context));
4010
+ } catch {
4011
+ diagnostics.push({
4012
+ schemaVersion: SCHEMA_VERSION,
4013
+ code: "analysis-error",
4014
+ severity: "warning",
4015
+ message: `Analyzer ${analyzer.name} failed during capability detection; its contribution was skipped.`,
4016
+ file: source.relativePath
4017
+ });
4018
+ continue;
4019
+ }
4020
+ if (!supported || !analyzerEnabled(analyzer, options.selectedFrameworks)) continue;
4021
+ try {
4022
+ const trustedAnalysis = trustedAnalyzerMethods(analyzer, options.trustedInternalAnalyzers);
4023
+ const rawContribution = trustedAnalysis ? await trustedAnalysis.analyze(context) : await analyzer.analyze(isolateCustomAnalyzerContext(context));
4024
+ const contribution = trustedAnalysis ? rawContribution : sanitizeCustomContribution(
4025
+ rawContribution,
4026
+ options.root,
4027
+ analyzer.name,
4028
+ source,
4029
+ symbols
4030
+ );
4031
+ symbols.push(...contribution.symbols ?? []);
4032
+ relations.push(...contribution.relations ?? []);
4033
+ diagnostics.push(...contribution.diagnostics ?? []);
4034
+ source.file.frameworks.push(...contribution.frameworks ?? []);
4035
+ } catch {
4036
+ diagnostics.push({
4037
+ schemaVersion: SCHEMA_VERSION,
4038
+ code: "analysis-error",
4039
+ severity: "warning",
4040
+ message: `Analyzer ${analyzer.name} failed; its contribution was skipped.`,
4041
+ file: source.relativePath
4042
+ });
4043
+ }
4044
+ }
4045
+ source.file.frameworks = [...new Set(source.file.frameworks)].sort();
4046
+ }
4047
+ return {
4048
+ pathAliases: semantic.pathAliases,
4049
+ diagnostics,
4050
+ symbols,
4051
+ relations,
4052
+ semanticInputs: semantic.semanticInputs?.() ?? []
4053
+ };
4054
+ }
4055
+ function isolateCustomAnalyzerContext(context) {
4056
+ return {
4057
+ ...context,
4058
+ file: structuredClone(context.file),
4059
+ syntax: structuredClone(context.syntax),
4060
+ compilerUnit: structuredClone(context.compilerUnit),
4061
+ packageDependencies: new Set(context.packageDependencies),
4062
+ ...context.indexedSourcePaths ? { indexedSourcePaths: new Set(context.indexedSourcePaths) } : {}
4063
+ };
4064
+ }
4065
+ function sanitizeCustomContribution(contribution, root, analyzerName, source, knownSymbols) {
4066
+ const sourceFileId = fileId(source.relativePath);
4067
+ const localSymbolIds = new Set(
4068
+ knownSymbols.filter((symbol) => symbol.file === source.relativePath).map((symbol) => symbol.id)
4069
+ );
4070
+ const localSymbols = new Map(
4071
+ knownSymbols.filter((symbol) => symbol.file === source.relativePath).map((symbol) => [symbol.id, symbol])
4072
+ );
4073
+ const symbolIds = /* @__PURE__ */ new Map();
4074
+ const warnings = [];
4075
+ const pushWarning = (message) => {
4076
+ warnings.push({
4077
+ schemaVersion: SCHEMA_VERSION,
4078
+ code: "analysis-error",
4079
+ severity: "warning",
4080
+ message: sanitizePersistedText(message, root),
4081
+ file: source.relativePath
4082
+ });
4083
+ };
4084
+ const rangeFitsSource = (range) => range.startLine >= 1 && range.endLine >= range.startLine && range.endLine <= source.file.lines && (range.endLine !== range.startLine || range.endColumn >= range.startColumn);
4085
+ const sanitizeEndpoint = (value) => symbolIds.get(value) ?? sanitizeCustomIdentifier(value, root);
4086
+ const symbols = [];
4087
+ for (const raw of contribution.symbols ?? []) {
4088
+ const sanitized = {
4089
+ ...raw,
4090
+ id: sanitizeCustomIdentifier(raw.id, root),
4091
+ name: sanitizePersistedText(raw.name, root),
4092
+ ...raw.signature ? { signature: sanitizePersistedText(raw.signature, root) } : {},
4093
+ ...raw.documentation ? { documentation: sanitizePersistedText(raw.documentation, root) } : {},
4094
+ facts: Object.fromEntries(
4095
+ Object.entries(raw.facts).map(([key, value]) => [
4096
+ sanitizePersistedText(key, root),
4097
+ sanitizeJsonValue(value, root)
4098
+ ])
4099
+ )
4100
+ };
4101
+ const parsed = SymbolRecordSchema.safeParse(sanitized);
4102
+ if (!parsed.success) {
4103
+ pushWarning(`Analyzer ${analyzerName} emitted an invalid custom symbol.`);
4104
+ continue;
4105
+ }
4106
+ if (parsed.data.file !== source.relativePath) {
4107
+ pushWarning(
4108
+ `Analyzer ${analyzerName} emitted a custom symbol for non-local file ${parsed.data.file}.`
4109
+ );
4110
+ continue;
4111
+ }
4112
+ if (parsed.data.sourceHash !== source.hash) {
4113
+ pushWarning(
4114
+ `Analyzer ${analyzerName} emitted a custom symbol with a source hash mismatch for ${parsed.data.id}.`
4115
+ );
4116
+ continue;
4117
+ }
4118
+ if (!rangeFitsSource(parsed.data.range)) {
4119
+ pushWarning(
4120
+ `Analyzer ${analyzerName} emitted a custom symbol with out-of-bounds source range for ${parsed.data.id}.`
4121
+ );
4122
+ continue;
4123
+ }
4124
+ symbols.push(parsed.data);
4125
+ symbolIds.set(raw.id, parsed.data.id);
4126
+ localSymbolIds.add(parsed.data.id);
4127
+ localSymbols.set(parsed.data.id, parsed.data);
4128
+ }
4129
+ const relations = [];
4130
+ for (const raw of contribution.relations ?? []) {
4131
+ const relation2 = {
4132
+ ...raw,
4133
+ from: sanitizeEndpoint(raw.from),
4134
+ to: sanitizeEndpoint(raw.to),
4135
+ origin: {
4136
+ ...raw.origin,
4137
+ explanation: sanitizePersistedText(raw.origin.explanation, root),
4138
+ analyzer: sanitizePersistedText(raw.origin.analyzer, root)
4139
+ }
4140
+ };
4141
+ const parsed = RelationRecordSchema.safeParse(relation2);
4142
+ if (!parsed.success) {
4143
+ pushWarning(
4144
+ `Analyzer ${analyzerName} emitted an invalid custom relation from ${relation2.from} to ${relation2.to}.`
4145
+ );
4146
+ continue;
4147
+ }
4148
+ if (parsed.data.origin.file !== source.relativePath || parsed.data.origin.line !== parsed.data.origin.range.startLine || parsed.data.origin.range.startLine < 1 || parsed.data.origin.range.endLine > source.file.lines) {
4149
+ pushWarning(
4150
+ `Analyzer ${analyzerName} emitted a custom relation with invalid evidence bounds for ${parsed.data.from} -> ${parsed.data.to}.`
4151
+ );
4152
+ continue;
4153
+ }
4154
+ if (parsed.data.type === "imports") {
4155
+ pushWarning(
4156
+ `Analyzer ${analyzerName} emitted a custom imports relation; imports are core-owned and were skipped.`
4157
+ );
4158
+ continue;
4159
+ } else if (parsed.data.type === "contains" || parsed.data.type === "exports") {
4160
+ const targetSymbol = localSymbols.get(parsed.data.to);
4161
+ if (!targetSymbol || !rangesEqual2(parsed.data.origin.range, targetSymbol.range)) {
4162
+ pushWarning(
4163
+ `Analyzer ${analyzerName} emitted an invalid custom structural relation ${parsed.data.type} ${parsed.data.from} -> ${parsed.data.to}.`
4164
+ );
4165
+ continue;
4166
+ }
4167
+ if (parsed.data.type === "exports") {
4168
+ if (parsed.data.from !== sourceFileId || !targetSymbol.exported) {
4169
+ pushWarning(
4170
+ `Analyzer ${analyzerName} emitted an invalid custom export relation ${parsed.data.from} -> ${parsed.data.to}.`
4171
+ );
4172
+ continue;
4173
+ }
4174
+ } else if (parsed.data.from !== sourceFileId) {
4175
+ const parentSymbol = localSymbols.get(parsed.data.from);
4176
+ if (!parentSymbol || !rangeContains2(parentSymbol.range, parsed.data.origin.range)) {
4177
+ pushWarning(
4178
+ `Analyzer ${analyzerName} emitted an invalid custom containment relation ${parsed.data.from} -> ${parsed.data.to}.`
4179
+ );
4180
+ continue;
4181
+ }
4182
+ } else if (!localSymbolIds.has(parsed.data.to)) {
4183
+ pushWarning(
4184
+ `Analyzer ${analyzerName} emitted an invalid custom containment relation from file to non-local symbol ${parsed.data.to}.`
4185
+ );
4186
+ continue;
4187
+ }
4188
+ }
4189
+ relations.push(parsed.data);
4190
+ }
4191
+ const diagnostics = [];
4192
+ for (const raw of contribution.diagnostics ?? []) {
4193
+ const diagnostic = {
4194
+ ...raw,
4195
+ message: sanitizePersistedText(raw.message, root)
4196
+ };
4197
+ const parsed = DiagnosticRecordSchema.safeParse(diagnostic);
4198
+ if (!parsed.success) {
4199
+ pushWarning(`Analyzer ${analyzerName} emitted an invalid custom diagnostic.`);
4200
+ continue;
4201
+ }
4202
+ if (parsed.data.file && parsed.data.file !== source.relativePath) {
4203
+ pushWarning(
4204
+ `Analyzer ${analyzerName} emitted a custom diagnostic for non-local file ${parsed.data.file}.`
4205
+ );
4206
+ continue;
4207
+ }
4208
+ if (parsed.data.file && parsed.data.line && parsed.data.line > source.file.lines) {
4209
+ pushWarning(
4210
+ `Analyzer ${analyzerName} emitted a custom diagnostic with out-of-bounds line ${parsed.data.line}.`
4211
+ );
4212
+ continue;
4213
+ }
4214
+ diagnostics.push(parsed.data);
4215
+ }
4216
+ const frameworks = [];
4217
+ for (const rawFramework of contribution.frameworks ?? []) {
4218
+ const parsed = FrameworkSchema.safeParse(rawFramework);
4219
+ if (!parsed.success) {
4220
+ pushWarning(`Analyzer ${analyzerName} emitted an invalid custom framework ${rawFramework}.`);
4221
+ continue;
4222
+ }
4223
+ frameworks.push(parsed.data);
4224
+ }
4225
+ return {
4226
+ ...symbols.length ? { symbols } : {},
4227
+ ...relations.length ? { relations } : {},
4228
+ ...diagnostics.length || warnings.length ? {
4229
+ diagnostics: [...diagnostics, ...warnings]
4230
+ } : {},
4231
+ ...frameworks.length ? { frameworks } : {}
4232
+ };
4233
+ }
4234
+ function sanitizeCustomIdentifier(value, root) {
4235
+ if (value.startsWith("route:")) {
4236
+ const route = value.slice("route:".length);
4237
+ return looksLikeHostFilePath(route, root) ? "route:<redacted>" : value;
4238
+ }
4239
+ if (value.startsWith("external:")) {
4240
+ const separator = value.lastIndexOf("#");
4241
+ const moduleName = value.slice("external:".length, separator > 0 ? separator : void 0);
4242
+ const exported = separator > 0 ? value.slice(separator + 1) : "default";
4243
+ const safeModule = isAbsolutePath(moduleName) || hasEmbeddedAbsolutePath(moduleName) ? "<absolute-import>" : sanitizePersistedText(moduleName, root);
4244
+ return `external:${safeModule}#${sanitizePersistedText(exported, root)}`;
4245
+ }
4246
+ const prefixEnd = value.indexOf(":");
4247
+ const nameStart = value.lastIndexOf("#");
4248
+ if (prefixEnd >= 0) {
4249
+ const pathEnd = nameStart > prefixEnd ? nameStart : value.length;
4250
+ const candidatePath = value.slice(prefixEnd + 1, pathEnd);
4251
+ if (isAbsolutePath(candidatePath) || hasEmbeddedAbsolutePath(candidatePath)) {
4252
+ const safePath = sanitizePersistedText(candidatePath, root);
4253
+ const suffix = nameStart > prefixEnd ? value.slice(nameStart) : "";
4254
+ return `${value.slice(0, prefixEnd + 1)}${safePath}${sanitizePersistedText(suffix, root)}`;
4255
+ }
4256
+ }
4257
+ return sanitizePersistedText(value, root);
4258
+ }
4259
+ function rangesEqual2(left, right) {
4260
+ return left.startLine === right.startLine && left.startColumn === right.startColumn && left.endLine === right.endLine && left.endColumn === right.endColumn;
4261
+ }
4262
+ function rangeContains2(outer, inner) {
4263
+ if (outer.startLine > inner.startLine || outer.startLine === inner.startLine && outer.startColumn > inner.startColumn)
4264
+ return false;
4265
+ if (outer.endLine < inner.endLine || outer.endLine === inner.endLine && outer.endColumn < inner.endColumn)
4266
+ return false;
4267
+ return true;
4268
+ }
4269
+ function sanitizeJsonValue(value, root) {
4270
+ if (typeof value === "string") return sanitizePersistedText(value, root);
4271
+ if (Array.isArray(value)) return value.map((item) => sanitizeJsonValue(item, root));
4272
+ if (value && typeof value === "object") {
4273
+ return Object.fromEntries(
4274
+ Object.entries(value).map(([key, item]) => [
4275
+ sanitizePersistedText(key, root),
4276
+ sanitizeJsonValue(item, root)
4277
+ ])
4278
+ );
4279
+ }
4280
+ return value;
4281
+ }
4282
+ var canonicalCoreAnalyzerMethods = {
4283
+ supports: CoreAnalyzer.prototype.supports.bind(CoreAnalyzer.prototype),
4284
+ analyze: CoreAnalyzer.prototype.analyze.bind(CoreAnalyzer.prototype)
4285
+ };
4286
+ var canonicalReactAnalyzerMethods = {
4287
+ supports: ReactAnalyzer.prototype.supports.bind(ReactAnalyzer.prototype),
4288
+ analyze: ReactAnalyzer.prototype.analyze.bind(ReactAnalyzer.prototype)
4289
+ };
4290
+ function defaultAnalyzerPipeline() {
4291
+ const core = new CoreAnalyzer();
4292
+ const react = new ReactAnalyzer();
4293
+ return {
4294
+ analyzers: [core, react],
4295
+ methods: /* @__PURE__ */ new Map([
4296
+ [core, canonicalCoreAnalyzerMethods],
4297
+ [react, canonicalReactAnalyzerMethods]
4298
+ ])
4299
+ };
4300
+ }
4301
+ function analyzerEnabled(analyzer, frameworks) {
4302
+ if (analyzer instanceof CoreAnalyzer) return true;
4303
+ const name = analyzer.name.toLowerCase();
4304
+ if (name.includes("react")) return frameworks.includes("react") || frameworks.includes("nextjs");
4305
+ if (name.includes("next")) return frameworks.includes("nextjs");
4306
+ if (name.includes("nest")) return frameworks.includes("nestjs");
4307
+ return true;
4308
+ }
4309
+ function trustedAnalyzerMethods(analyzer, internal) {
4310
+ const internalMethods = internal.get(analyzer);
4311
+ if (internalMethods) return internalMethods;
4312
+ return trustedNextJsAnalyzerMethods(analyzer) ?? trustedNestJsAnalyzerMethods(analyzer);
4313
+ }
4314
+ function parseDiagnostics(program, sourceFile, root) {
4315
+ return program.getSyntacticDiagnostics(sourceFile).map((diagnostic) => compilerDiagnostic(diagnostic, root));
4316
+ }
4317
+ function syntaxFromProgram(sourceFile, diagnostics) {
4318
+ const parser = "typescript-compiler-syntax";
4319
+ const facts = [];
4320
+ for (const statement of sourceFile.statements) {
4321
+ const kind = ts8.isImportDeclaration(statement) ? "import" : ts8.isExportDeclaration(statement) || ts8.isExportAssignment(statement) ? "export" : isTopLevelDeclaration(statement) ? "declaration" : void 0;
4322
+ if (!kind) continue;
4323
+ const name = syntaxStatementName(statement);
4324
+ facts.push({
4325
+ kind,
4326
+ nodeType: ts8.SyntaxKind[statement.kind],
4327
+ ...name ? { name } : {},
4328
+ range: sourceRange(sourceFile, statement),
4329
+ evidence: { parser, exact: true }
4330
+ });
4331
+ }
4332
+ for (const diagnostic of diagnostics) {
4333
+ facts.push({
4334
+ kind: "parse-error",
4335
+ nodeType: "typescript-diagnostic",
4336
+ range: {
4337
+ startLine: diagnostic.line ?? 1,
4338
+ startColumn: diagnostic.column ?? 1,
4339
+ endLine: diagnostic.line ?? 1,
4340
+ endColumn: diagnostic.column ?? 1
4341
+ },
4342
+ evidence: { parser, exact: true }
4343
+ });
4344
+ }
4345
+ return {
4346
+ parser,
4347
+ facts,
4348
+ issues: diagnostics.map((diagnostic) => ({ message: diagnostic.message })),
4349
+ hadErrors: diagnostics.length > 0
4350
+ };
4351
+ }
4352
+ function isTopLevelDeclaration(statement) {
4353
+ return ts8.isFunctionDeclaration(statement) || ts8.isClassDeclaration(statement) || ts8.isInterfaceDeclaration(statement) || ts8.isTypeAliasDeclaration(statement) || ts8.isVariableStatement(statement);
4354
+ }
4355
+ function syntaxStatementName(statement) {
4356
+ if (ts8.isImportDeclaration(statement) && ts8.isStringLiteral(statement.moduleSpecifier)) {
4357
+ return statement.moduleSpecifier.text;
4358
+ }
4359
+ if (ts8.isExportAssignment(statement)) return "default";
4360
+ if (ts8.isExportDeclaration(statement)) {
4361
+ return statement.moduleSpecifier && ts8.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "export";
4362
+ }
4363
+ if (ts8.isVariableStatement(statement)) {
4364
+ return statement.declarationList.declarations.map((declaration) => ts8.isIdentifier(declaration.name) ? declaration.name.text : void 0).filter((name2) => name2 !== void 0).join(",");
4365
+ }
4366
+ const name = statement.name;
4367
+ return name && ts8.isIdentifier(name) ? name.text : void 0;
4368
+ }
4369
+ function sourceRange(sourceFile, node) {
4370
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile, false));
4371
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
4372
+ return {
4373
+ startLine: start.line + 1,
4374
+ startColumn: start.character + 1,
4375
+ endLine: end.line + 1,
4376
+ endColumn: end.character + 1
4377
+ };
4378
+ }
4379
+ function sanitizePersistedText(value, root) {
4380
+ return redactAbsolutePathsInText(value, root) || "<redacted>";
4381
+ }
4382
+
4383
+ // src/compiler-worker.ts
4384
+ if (!parentPort) throw new Error("Compiler worker requires a parent port.");
4385
+ var result = await analyzeDefaultCompilerUnit(workerData);
4386
+ var session = new Session();
4387
+ session.connect();
4388
+ try {
4389
+ await session.post("HeapProfiler.collectGarbage");
4390
+ } finally {
4391
+ session.disconnect();
4392
+ }
4393
+ parentPort.postMessage(result);
4394
+ parentPort.close();
4395
+ //# sourceMappingURL=compiler-worker.js.map