@doccov/sdk 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2973 @@
1
+ // src/analysis/run-analysis.ts
2
+ import * as fs2 from "node:fs";
3
+ import * as path4 from "node:path";
4
+
5
+ // src/ts-module.ts
6
+ import * as tsNamespace from "typescript";
7
+ var resolvedTypeScriptModule = (() => {
8
+ const candidate = tsNamespace;
9
+ if (candidate.ScriptTarget === undefined && typeof candidate.default !== "undefined") {
10
+ return candidate.default;
11
+ }
12
+ return candidate;
13
+ })();
14
+ var ts = resolvedTypeScriptModule;
15
+
16
+ // src/analysis/context.ts
17
+ import * as path2 from "node:path";
18
+
19
+ // src/options.ts
20
+ var DEFAULT_OPTIONS = {
21
+ includePrivate: false,
22
+ followImports: true
23
+ };
24
+ function normalizeDocCovOptions(options = {}) {
25
+ return {
26
+ ...DEFAULT_OPTIONS,
27
+ ...options
28
+ };
29
+ }
30
+ var normalizeOpenPkgOptions = normalizeDocCovOptions;
31
+
32
+ // src/analysis/program.ts
33
+ import * as path from "node:path";
34
+ var DEFAULT_COMPILER_OPTIONS = {
35
+ target: ts.ScriptTarget.Latest,
36
+ module: ts.ModuleKind.CommonJS,
37
+ lib: ["lib.es2021.d.ts"],
38
+ declaration: true,
39
+ moduleResolution: ts.ModuleResolutionKind.NodeJs
40
+ };
41
+ function createProgram({
42
+ entryFile,
43
+ baseDir = path.dirname(entryFile),
44
+ content
45
+ }) {
46
+ const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
47
+ let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
48
+ if (configPath) {
49
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
50
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
51
+ compilerOptions = { ...compilerOptions, ...parsedConfig.options };
52
+ }
53
+ const allowJsVal = compilerOptions.allowJs;
54
+ if (typeof allowJsVal === "boolean" && allowJsVal) {
55
+ compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
56
+ }
57
+ const compilerHost = ts.createCompilerHost(compilerOptions, true);
58
+ let inMemorySource;
59
+ if (content !== undefined) {
60
+ inMemorySource = ts.createSourceFile(entryFile, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
61
+ const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
62
+ compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
63
+ if (fileName === entryFile) {
64
+ return inMemorySource;
65
+ }
66
+ return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
67
+ };
68
+ }
69
+ const program = ts.createProgram([entryFile], compilerOptions, compilerHost);
70
+ const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
71
+ return {
72
+ program,
73
+ compilerHost,
74
+ compilerOptions,
75
+ sourceFile,
76
+ configPath
77
+ };
78
+ }
79
+
80
+ // src/analysis/context.ts
81
+ function createAnalysisContext({
82
+ entryFile,
83
+ packageDir,
84
+ content,
85
+ options
86
+ }) {
87
+ const baseDir = packageDir ?? path2.dirname(entryFile);
88
+ const normalizedOptions = normalizeOpenPkgOptions(options);
89
+ const programResult = createProgram({ entryFile, baseDir, content });
90
+ if (!programResult.sourceFile) {
91
+ throw new Error(`Could not load ${entryFile}`);
92
+ }
93
+ return {
94
+ entryFile,
95
+ baseDir,
96
+ program: programResult.program,
97
+ checker: programResult.program.getTypeChecker(),
98
+ sourceFile: programResult.sourceFile,
99
+ compilerOptions: programResult.compilerOptions,
100
+ compilerHost: programResult.compilerHost,
101
+ options: normalizedOptions,
102
+ configPath: programResult.configPath
103
+ };
104
+ }
105
+
106
+ // src/analysis/spec-builder.ts
107
+ import * as fs from "node:fs";
108
+ import * as path3 from "node:path";
109
+ import { SCHEMA_URL } from "@openpkg-ts/spec";
110
+
111
+ // src/analysis/docs-coverage.ts
112
+ var DOC_SECTIONS = ["description", "params", "returns", "examples"];
113
+ var SECTION_WEIGHT = 100 / DOC_SECTIONS.length;
114
+ function computeDocsCoverage(spec) {
115
+ const coverageByExport = new Map;
116
+ const exportRegistry = new Set;
117
+ for (const entry of spec.exports ?? []) {
118
+ exportRegistry.add(entry.name);
119
+ exportRegistry.add(entry.id);
120
+ }
121
+ for (const type of spec.types ?? []) {
122
+ exportRegistry.add(type.name);
123
+ exportRegistry.add(type.id);
124
+ }
125
+ let aggregateScore = 0;
126
+ let processed = 0;
127
+ for (const entry of spec.exports ?? []) {
128
+ const coverage = evaluateExport(entry, exportRegistry);
129
+ coverageByExport.set(entry.id ?? entry.name, coverage.docs);
130
+ aggregateScore += coverage.docs.coverageScore ?? 0;
131
+ processed += 1;
132
+ }
133
+ const specCoverageScore = processed === 0 ? 100 : Math.round(aggregateScore / processed);
134
+ return {
135
+ spec: { coverageScore: specCoverageScore },
136
+ exports: coverageByExport
137
+ };
138
+ }
139
+ function evaluateExport(entry, exportRegistry) {
140
+ const missing = [];
141
+ const drift = [
142
+ ...detectParamDrift(entry),
143
+ ...detectOptionalityDrift(entry),
144
+ ...detectParamTypeDrift(entry),
145
+ ...detectReturnTypeDrift(entry),
146
+ ...detectGenericConstraintDrift(entry),
147
+ ...detectDeprecatedDrift(entry),
148
+ ...detectVisibilityDrift(entry),
149
+ ...detectExampleDrift(entry, exportRegistry),
150
+ ...detectBrokenLinks(entry, exportRegistry)
151
+ ];
152
+ if (!hasDescription(entry)) {
153
+ missing.push("description");
154
+ }
155
+ if (!paramsDocumented(entry)) {
156
+ missing.push("params");
157
+ }
158
+ if (!returnsDocumented(entry)) {
159
+ missing.push("returns");
160
+ }
161
+ if (!hasExamples(entry)) {
162
+ missing.push("examples");
163
+ }
164
+ const satisfied = DOC_SECTIONS.length - missing.length;
165
+ const coverageScore = Math.max(0, Math.round(satisfied * SECTION_WEIGHT));
166
+ return {
167
+ id: entry.id,
168
+ docs: {
169
+ coverageScore,
170
+ missing: missing.length > 0 ? missing : undefined,
171
+ drift: drift.length > 0 ? drift : undefined
172
+ }
173
+ };
174
+ }
175
+ function hasDescription(entry) {
176
+ return Boolean(entry.description && entry.description.trim().length > 0);
177
+ }
178
+ function paramsDocumented(entry) {
179
+ const parameters = (entry.signatures ?? []).flatMap((signature) => signature.parameters ?? []);
180
+ if (parameters.length === 0) {
181
+ return true;
182
+ }
183
+ return parameters.every((param) => Boolean(param.description && param.description.trim().length > 0));
184
+ }
185
+ function returnsDocumented(entry) {
186
+ const signatures = entry.signatures ?? [];
187
+ if (signatures.length === 0) {
188
+ return true;
189
+ }
190
+ return signatures.every((signature) => {
191
+ const text = signature.returns?.description;
192
+ return Boolean(text && text.trim().length > 0);
193
+ });
194
+ }
195
+ function hasExamples(entry) {
196
+ return Array.isArray(entry.examples) && entry.examples.length > 0;
197
+ }
198
+ function detectParamDrift(entry) {
199
+ const drifts = [];
200
+ const signatures = entry.signatures ?? [];
201
+ if (signatures.length === 0) {
202
+ return drifts;
203
+ }
204
+ const actualParamNames = new Set;
205
+ for (const signature of signatures) {
206
+ for (const param of signature.parameters ?? []) {
207
+ if (param.name) {
208
+ actualParamNames.add(param.name);
209
+ }
210
+ }
211
+ }
212
+ if (actualParamNames.size === 0) {
213
+ return drifts;
214
+ }
215
+ const documentedParamNames = (entry.tags ?? []).filter((tag) => tag.name === "param" && Boolean(tag.text)).map((tag) => extractParamFromTag(tag.text ?? "")?.name).filter((name) => Boolean(name));
216
+ if (documentedParamNames.length === 0) {
217
+ return drifts;
218
+ }
219
+ for (const documentedName of documentedParamNames) {
220
+ if (actualParamNames.has(documentedName)) {
221
+ continue;
222
+ }
223
+ const suggestion = findClosestMatch(documentedName, Array.from(actualParamNames));
224
+ drifts.push({
225
+ type: "param-mismatch",
226
+ target: documentedName,
227
+ issue: `JSDoc documents parameter "${documentedName}" which is not present in the signature.`,
228
+ suggestion: suggestion?.distance !== undefined && suggestion.distance <= 3 ? suggestion.value : undefined
229
+ });
230
+ }
231
+ return drifts;
232
+ }
233
+ function detectOptionalityDrift(entry) {
234
+ const signatures = entry.signatures ?? [];
235
+ if (signatures.length === 0) {
236
+ return [];
237
+ }
238
+ const actualOptionality = new Map;
239
+ for (const signature of signatures) {
240
+ for (const param of signature.parameters ?? []) {
241
+ if (!param.name || actualOptionality.has(param.name)) {
242
+ continue;
243
+ }
244
+ actualOptionality.set(param.name, param.required === false);
245
+ }
246
+ }
247
+ if (actualOptionality.size === 0) {
248
+ return [];
249
+ }
250
+ const documentedParams = (entry.tags ?? []).filter((tag) => tag.name === "param" && Boolean(tag.text)).map((tag) => extractParamFromTag(tag.text ?? "")).filter((parsed) => Boolean(parsed?.name));
251
+ if (documentedParams.length === 0) {
252
+ return [];
253
+ }
254
+ const drifts = [];
255
+ for (const docParam of documentedParams) {
256
+ const actualOptional = actualOptionality.get(docParam.name);
257
+ if (actualOptional === undefined) {
258
+ continue;
259
+ }
260
+ const documentedOptional = Boolean(docParam.isOptional);
261
+ if (actualOptional === documentedOptional) {
262
+ continue;
263
+ }
264
+ const issue = documentedOptional ? `JSDoc marks parameter "${docParam.name}" optional but the signature requires it.` : `JSDoc omits optional brackets for parameter "${docParam.name}" but the signature marks it optional.`;
265
+ const suggestion = documentedOptional ? `Remove brackets around ${docParam.name} or mark the parameter optional in the signature.` : `Document ${docParam.name} as [${docParam.name}] or make it required in the signature.`;
266
+ drifts.push({
267
+ type: "optionality-mismatch",
268
+ target: docParam.name,
269
+ issue,
270
+ suggestion
271
+ });
272
+ }
273
+ return drifts;
274
+ }
275
+ function detectParamTypeDrift(entry) {
276
+ const signatures = entry.signatures ?? [];
277
+ if (signatures.length === 0) {
278
+ return [];
279
+ }
280
+ const documentedParams = (entry.tags ?? []).filter((tag) => tag.name === "param" && Boolean(tag.text)).map((tag) => extractParamFromTag(tag.text ?? "")).filter((parsed) => Boolean(parsed?.name) && Boolean(parsed?.type));
281
+ if (documentedParams.length === 0) {
282
+ return [];
283
+ }
284
+ const declaredParamTypes = new Map;
285
+ for (const signature of signatures) {
286
+ for (const param of signature.parameters ?? []) {
287
+ if (!param.name || declaredParamTypes.has(param.name)) {
288
+ continue;
289
+ }
290
+ const declaredType = extractTypeFromSchema(param.schema);
291
+ if (declaredType) {
292
+ declaredParamTypes.set(param.name, declaredType);
293
+ }
294
+ }
295
+ }
296
+ if (declaredParamTypes.size === 0) {
297
+ return [];
298
+ }
299
+ const drifts = [];
300
+ for (const documentedParam of documentedParams) {
301
+ const declaredType = declaredParamTypes.get(documentedParam.name);
302
+ if (!declaredType || !documentedParam.type) {
303
+ continue;
304
+ }
305
+ const documentedNormalized = normalizeType(documentedParam.type);
306
+ const declaredNormalized = normalizeType(declaredType);
307
+ if (!documentedNormalized || !declaredNormalized) {
308
+ continue;
309
+ }
310
+ if (typesEquivalent(documentedNormalized, declaredNormalized)) {
311
+ continue;
312
+ }
313
+ drifts.push({
314
+ type: "param-type-mismatch",
315
+ target: documentedParam.name,
316
+ issue: buildParamTypeMismatchIssue(documentedParam.name, documentedParam.type, declaredType),
317
+ suggestion: `Update @param {${declaredType}} ${documentedParam.name} to match the signature.`
318
+ });
319
+ }
320
+ return drifts;
321
+ }
322
+ function detectReturnTypeDrift(entry) {
323
+ const returnsTag = entry.tags?.find((tag) => tag.name === "returns" && tag.text?.length);
324
+ if (!returnsTag) {
325
+ return [];
326
+ }
327
+ const documentedType = extractReturnTypeFromTag(returnsTag.text);
328
+ if (!documentedType) {
329
+ return [];
330
+ }
331
+ const signatureWithReturns = entry.signatures?.find((signature) => signature.returns);
332
+ const signatureReturn = signatureWithReturns?.returns;
333
+ if (!signatureReturn) {
334
+ return [];
335
+ }
336
+ const declaredRaw = signatureReturn.tsType ?? extractTypeFromSchema(signatureReturn.schema);
337
+ const declaredType = normalizeType(declaredRaw) ?? undefined;
338
+ if (!declaredType) {
339
+ return [];
340
+ }
341
+ const documentedNormalized = normalizeType(documentedType);
342
+ if (!documentedNormalized) {
343
+ return [];
344
+ }
345
+ if (typesEquivalent(documentedNormalized, declaredType)) {
346
+ return [];
347
+ }
348
+ return [
349
+ {
350
+ type: "return-type-mismatch",
351
+ target: "returns",
352
+ issue: buildReturnTypeMismatchIssue(documentedType, documentedNormalized, declaredType),
353
+ suggestion: `Update @returns to ${declaredType}.`
354
+ }
355
+ ];
356
+ }
357
+ function detectDeprecatedDrift(entry) {
358
+ const codeDeprecated = Boolean(entry.deprecated);
359
+ const docsDeprecated = entry.tags?.some((tag) => tag.name.toLowerCase() === "deprecated") ?? false;
360
+ if (codeDeprecated === docsDeprecated) {
361
+ return [];
362
+ }
363
+ const target = entry.name ?? entry.id;
364
+ if (codeDeprecated && !docsDeprecated) {
365
+ return [
366
+ {
367
+ type: "deprecated-mismatch",
368
+ target,
369
+ issue: `Declaration for "${target}" is marked deprecated but @deprecated is missing from the docs.`,
370
+ suggestion: "Add an @deprecated tag explaining the replacement or removal timeline."
371
+ }
372
+ ];
373
+ }
374
+ return [
375
+ {
376
+ type: "deprecated-mismatch",
377
+ target,
378
+ issue: `JSDoc marks "${target}" as deprecated but the TypeScript declaration is not.`,
379
+ suggestion: "Remove the @deprecated tag or deprecate the declaration."
380
+ }
381
+ ];
382
+ }
383
+ function detectGenericConstraintDrift(entry) {
384
+ const templateTags = entry.tags?.filter((tag) => tag.name === "template" && Boolean(tag.text?.trim())) ?? [];
385
+ if (templateTags.length === 0) {
386
+ return [];
387
+ }
388
+ const documentedTemplates = templateTags.map((tag) => parseTemplateTag(tag.text)).filter((template) => Boolean(template?.name));
389
+ if (documentedTemplates.length === 0) {
390
+ return [];
391
+ }
392
+ const actualConstraints = collectActualTypeParameterConstraints(entry);
393
+ if (actualConstraints.size === 0) {
394
+ return [];
395
+ }
396
+ const drifts = [];
397
+ for (const doc of documentedTemplates) {
398
+ if (!actualConstraints.has(doc.name)) {
399
+ continue;
400
+ }
401
+ const actualConstraint = actualConstraints.get(doc.name);
402
+ const normalizedActual = normalizeType(actualConstraint);
403
+ const normalizedDocumented = normalizeType(doc.constraint);
404
+ if (!normalizedActual && !normalizedDocumented) {
405
+ continue;
406
+ }
407
+ if (normalizedActual === normalizedDocumented) {
408
+ continue;
409
+ }
410
+ drifts.push({
411
+ type: "generic-constraint-mismatch",
412
+ target: doc.name,
413
+ issue: buildGenericConstraintMismatchIssue(doc.name, doc.constraint, actualConstraint),
414
+ suggestion: buildGenericConstraintSuggestion(doc.name, actualConstraint)
415
+ });
416
+ }
417
+ return drifts;
418
+ }
419
+ var VISIBILITY_TAG_MAP = {
420
+ internal: "internal",
421
+ alpha: "internal",
422
+ private: "private",
423
+ protected: "protected",
424
+ public: "public"
425
+ };
426
+ function detectVisibilityDrift(entry) {
427
+ const drifts = [];
428
+ const exportDocVisibility = getDocVisibility(entry.tags);
429
+ const exportActualVisibility = "public";
430
+ if (exportDocVisibility && !visibilityMatches(exportDocVisibility.value, exportActualVisibility)) {
431
+ const target = entry.name ?? entry.id ?? "export";
432
+ drifts.push({
433
+ type: "visibility-mismatch",
434
+ target,
435
+ issue: buildVisibilityIssue(target, exportDocVisibility, exportActualVisibility),
436
+ suggestion: buildVisibilitySuggestion(exportDocVisibility, exportActualVisibility)
437
+ });
438
+ }
439
+ const members = Array.isArray(entry.members) ? entry.members : [];
440
+ for (const member of members) {
441
+ const typedMember = member;
442
+ const memberDocVisibility = getDocVisibility(typedMember.tags);
443
+ if (!memberDocVisibility) {
444
+ continue;
445
+ }
446
+ const memberActualVisibility = typedMember.visibility ?? "public";
447
+ if (visibilityMatches(memberDocVisibility.value, memberActualVisibility)) {
448
+ continue;
449
+ }
450
+ const memberName = typedMember.name ?? typedMember.id ?? typedMember.kind ?? "member";
451
+ const qualifiedTarget = `${entry.name ?? entry.id ?? "export"}#${memberName}`;
452
+ drifts.push({
453
+ type: "visibility-mismatch",
454
+ target: qualifiedTarget,
455
+ issue: buildVisibilityIssue(qualifiedTarget, memberDocVisibility, memberActualVisibility),
456
+ suggestion: buildVisibilitySuggestion(memberDocVisibility, memberActualVisibility)
457
+ });
458
+ }
459
+ return drifts;
460
+ }
461
+ function getDocVisibility(tags) {
462
+ if (!tags) {
463
+ return;
464
+ }
465
+ for (const tag of tags) {
466
+ const normalizedName = tag.name?.toLowerCase();
467
+ if (!normalizedName) {
468
+ continue;
469
+ }
470
+ const mapped = VISIBILITY_TAG_MAP[normalizedName];
471
+ if (mapped) {
472
+ return {
473
+ value: mapped,
474
+ tagName: tag.name
475
+ };
476
+ }
477
+ }
478
+ return;
479
+ }
480
+ function visibilityMatches(docVisibility, actualVisibility) {
481
+ if (docVisibility === "internal") {
482
+ return actualVisibility !== "public";
483
+ }
484
+ if (docVisibility === "public") {
485
+ return actualVisibility === "public";
486
+ }
487
+ return docVisibility === actualVisibility;
488
+ }
489
+ function buildVisibilityIssue(target, docVisibility, actualVisibility) {
490
+ const docLabel = formatDocVisibilityTag(docVisibility.tagName);
491
+ return `JSDoc marks "${target}" as ${docLabel} but the declaration is ${actualVisibility}.`;
492
+ }
493
+ function buildVisibilitySuggestion(docVisibility, actualVisibility) {
494
+ const docLabel = formatDocVisibilityTag(docVisibility.tagName);
495
+ switch (docVisibility.value) {
496
+ case "internal":
497
+ return `Remove ${docLabel} or mark the declaration protected/private.`;
498
+ case "public":
499
+ return `Remove ${docLabel} or mark the declaration public.`;
500
+ case "protected":
501
+ if (actualVisibility === "private") {
502
+ return `Promote the declaration to protected or replace ${docLabel} with @private.`;
503
+ }
504
+ return `Remove ${docLabel} or mark the declaration protected.`;
505
+ case "private":
506
+ if (actualVisibility === "protected") {
507
+ return `Downgrade the declaration to private or replace ${docLabel} with @protected/@internal.`;
508
+ }
509
+ return `Remove ${docLabel} or mark the declaration private.`;
510
+ default:
511
+ return "Align the JSDoc visibility tag with the declaration visibility.";
512
+ }
513
+ }
514
+ function formatDocVisibilityTag(tagName) {
515
+ const trimmed = tagName.trim();
516
+ if (!trimmed) {
517
+ return "@internal";
518
+ }
519
+ return trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
520
+ }
521
+ function extractParamFromTag(text) {
522
+ const trimmed = text.trim();
523
+ if (!trimmed) {
524
+ return;
525
+ }
526
+ const match = trimmed.match(/^(?:\{([^}]+)\}\s+)?(\S+)(?:\s+-\s+)?/);
527
+ if (!match) {
528
+ return;
529
+ }
530
+ const [, type, rawName] = match;
531
+ const isOptional = Boolean(rawName?.startsWith("[") && rawName?.endsWith("]"));
532
+ const name = normalizeParamName(rawName);
533
+ if (!name) {
534
+ return;
535
+ }
536
+ return {
537
+ name,
538
+ type: type?.trim(),
539
+ isOptional
540
+ };
541
+ }
542
+ function normalizeParamName(raw) {
543
+ if (!raw) {
544
+ return;
545
+ }
546
+ let name = raw.trim();
547
+ if (!name) {
548
+ return;
549
+ }
550
+ if (name.startsWith("[") && name.endsWith("]")) {
551
+ name = name.slice(1, -1);
552
+ }
553
+ const equalsIndex = name.indexOf("=");
554
+ if (equalsIndex >= 0) {
555
+ name = name.slice(0, equalsIndex);
556
+ }
557
+ if (name.endsWith(",")) {
558
+ name = name.slice(0, -1);
559
+ }
560
+ return name;
561
+ }
562
+ function extractReturnTypeFromTag(text) {
563
+ const trimmed = text.trim();
564
+ if (!trimmed) {
565
+ return;
566
+ }
567
+ const braceMatch = trimmed.match(/^\{([^}]+)\}/);
568
+ if (braceMatch) {
569
+ return braceMatch[1]?.trim();
570
+ }
571
+ const [first] = trimmed.split(/\s+/);
572
+ return first?.trim();
573
+ }
574
+ function extractTypeFromSchema(schema) {
575
+ if (!schema) {
576
+ return;
577
+ }
578
+ if (typeof schema === "string") {
579
+ return schema;
580
+ }
581
+ if (typeof schema === "object") {
582
+ const record = schema;
583
+ if (typeof record.type === "string") {
584
+ return record.type;
585
+ }
586
+ if (typeof record.$ref === "string") {
587
+ const ref = record.$ref;
588
+ return ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
589
+ }
590
+ }
591
+ return;
592
+ }
593
+ function normalizeType(value) {
594
+ if (!value) {
595
+ return;
596
+ }
597
+ return value.replace(/\s+/g, " ").replace(/\s*<\s*/g, "<").replace(/\s*>\s*/g, ">").trim();
598
+ }
599
+ var VOID_EQUIVALENTS = new Set(["void", "undefined"]);
600
+ function typesEquivalent(a, b) {
601
+ if (a === b) {
602
+ return true;
603
+ }
604
+ const lowerA = a.toLowerCase();
605
+ const lowerB = b.toLowerCase();
606
+ if (VOID_EQUIVALENTS.has(lowerA) && VOID_EQUIVALENTS.has(lowerB)) {
607
+ return true;
608
+ }
609
+ return false;
610
+ }
611
+ function unwrapPromise(type) {
612
+ const match = type.match(/^promise<(.+)>$/i);
613
+ return match ? match[1]?.trim() : undefined;
614
+ }
615
+ function buildReturnTypeMismatchIssue(documentedRaw, documentedNormalized, declaredNormalized) {
616
+ const docPromiseInner = unwrapPromise(documentedNormalized);
617
+ const declaredPromiseInner = unwrapPromise(declaredNormalized);
618
+ if (docPromiseInner && !declaredPromiseInner && docPromiseInner === declaredNormalized) {
619
+ return `JSDoc documents Promise<${docPromiseInner}> but the function returns ${declaredNormalized}.`;
620
+ }
621
+ if (!docPromiseInner && declaredPromiseInner && documentedNormalized === declaredPromiseInner) {
622
+ return `JSDoc documents ${documentedNormalized} but the function returns Promise<${declaredPromiseInner}>.`;
623
+ }
624
+ return `JSDoc documents ${documentedRaw} but the function returns ${declaredNormalized}.`;
625
+ }
626
+ function buildParamTypeMismatchIssue(paramName, documentedRaw, declaredRaw) {
627
+ return `JSDoc documents ${documentedRaw} for parameter "${paramName}" but the signature declares ${declaredRaw}.`;
628
+ }
629
+ function parseTemplateTag(text) {
630
+ const trimmed = text?.trim();
631
+ if (!trimmed) {
632
+ return;
633
+ }
634
+ let remaining = trimmed;
635
+ let constraint;
636
+ const braceMatch = remaining.match(/^\{([^}]+)\}\s+(.+)$/);
637
+ if (braceMatch) {
638
+ constraint = braceMatch[1]?.trim();
639
+ remaining = braceMatch[2]?.trim() ?? "";
640
+ }
641
+ if (!remaining) {
642
+ return;
643
+ }
644
+ const parts = remaining.split(/\s+/).filter(Boolean);
645
+ const rawName = parts.shift();
646
+ if (!rawName) {
647
+ return;
648
+ }
649
+ const name = rawName.replace(/[.,;:]+$/, "");
650
+ if (!constraint && parts.length > 0 && parts[0] === "extends") {
651
+ const constraintTokens = parts.slice(1);
652
+ const dashIndex = constraintTokens.findIndex((token) => token === "-" || token === "–");
653
+ const tokens = dashIndex >= 0 ? constraintTokens.slice(0, dashIndex) : constraintTokens;
654
+ constraint = tokens.join(" ").trim();
655
+ }
656
+ if (!constraint) {
657
+ constraint = undefined;
658
+ }
659
+ return {
660
+ name,
661
+ constraint
662
+ };
663
+ }
664
+ function collectActualTypeParameterConstraints(entry) {
665
+ const constraints = new Map;
666
+ for (const typeParam of entry.typeParameters ?? []) {
667
+ if (!typeParam?.name || constraints.has(typeParam.name)) {
668
+ continue;
669
+ }
670
+ constraints.set(typeParam.name, typeParam.constraint ?? undefined);
671
+ }
672
+ for (const signature of entry.signatures ?? []) {
673
+ for (const typeParam of signature.typeParameters ?? []) {
674
+ if (!typeParam?.name || constraints.has(typeParam.name)) {
675
+ continue;
676
+ }
677
+ constraints.set(typeParam.name, typeParam.constraint ?? undefined);
678
+ }
679
+ }
680
+ return constraints;
681
+ }
682
+ function buildGenericConstraintMismatchIssue(templateName, documentedConstraint, actualConstraint) {
683
+ if (actualConstraint && documentedConstraint) {
684
+ return `JSDoc constrains template "${templateName}" to ${documentedConstraint} but the declaration constrains it to ${actualConstraint}.`;
685
+ }
686
+ if (actualConstraint && !documentedConstraint) {
687
+ return `JSDoc omits the constraint for template "${templateName}" but the declaration constrains it to ${actualConstraint}.`;
688
+ }
689
+ if (!actualConstraint && documentedConstraint) {
690
+ return `JSDoc constrains template "${templateName}" to ${documentedConstraint} but the declaration has no constraint.`;
691
+ }
692
+ return `Template "${templateName}" has inconsistent constraints between JSDoc and the declaration.`;
693
+ }
694
+ function buildGenericConstraintSuggestion(templateName, actualConstraint) {
695
+ if (actualConstraint) {
696
+ return `Update @template to {${actualConstraint}} ${templateName} to reflect the declaration.`;
697
+ }
698
+ return `Remove the constraint from @template ${templateName} to match the declaration.`;
699
+ }
700
+ function findClosestMatch(source, candidates) {
701
+ if (candidates.length === 0) {
702
+ return;
703
+ }
704
+ const normalizedSource = source.toLowerCase();
705
+ const substringCandidate = candidates.find((candidate) => {
706
+ const normalizedCandidate = candidate.toLowerCase();
707
+ return normalizedCandidate.includes(normalizedSource) || normalizedSource.includes(normalizedCandidate);
708
+ });
709
+ if (substringCandidate && substringCandidate !== source) {
710
+ return { value: substringCandidate, distance: 0 };
711
+ }
712
+ let best;
713
+ for (const candidate of candidates) {
714
+ const distance = levenshtein(source, candidate);
715
+ if (!best || distance < best.distance) {
716
+ best = { value: candidate, distance };
717
+ }
718
+ }
719
+ return best;
720
+ }
721
+ function levenshtein(a, b) {
722
+ if (a === b) {
723
+ return 0;
724
+ }
725
+ if (a.length === 0) {
726
+ return b.length;
727
+ }
728
+ if (b.length === 0) {
729
+ return a.length;
730
+ }
731
+ const matrix = [];
732
+ for (let i = 0;i <= b.length; i++) {
733
+ matrix[i] = [i];
734
+ }
735
+ for (let j = 0;j <= a.length; j++) {
736
+ matrix[0][j] = j;
737
+ }
738
+ for (let i = 1;i <= b.length; i++) {
739
+ for (let j = 1;j <= a.length; j++) {
740
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
741
+ matrix[i][j] = matrix[i - 1][j - 1];
742
+ } else {
743
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
744
+ }
745
+ }
746
+ }
747
+ return matrix[b.length][a.length];
748
+ }
749
+ function detectExampleDrift(entry, exportRegistry) {
750
+ if (!exportRegistry || !entry.examples || entry.examples.length === 0) {
751
+ return [];
752
+ }
753
+ const drifts = [];
754
+ const identifierPattern = /\b([A-Z][a-zA-Z0-9]*)\b/g;
755
+ for (const example of entry.examples) {
756
+ if (typeof example !== "string") {
757
+ continue;
758
+ }
759
+ const matches = example.matchAll(identifierPattern);
760
+ const referencedIdentifiers = new Set;
761
+ for (const match of matches) {
762
+ const identifier = match[1];
763
+ if (identifier && !isBuiltInIdentifier(identifier)) {
764
+ referencedIdentifiers.add(identifier);
765
+ }
766
+ }
767
+ for (const identifier of referencedIdentifiers) {
768
+ if (!exportRegistry.has(identifier)) {
769
+ const suggestion = findClosestMatch(identifier, Array.from(exportRegistry));
770
+ if (suggestion && suggestion.distance <= 3) {
771
+ drifts.push({
772
+ type: "example-drift",
773
+ target: identifier,
774
+ issue: `@example references "${identifier}" which does not exist in this package.`,
775
+ suggestion: `Did you mean "${suggestion.value}"?`
776
+ });
777
+ }
778
+ }
779
+ }
780
+ }
781
+ return drifts;
782
+ }
783
+ function isBuiltInIdentifier(identifier) {
784
+ const builtIns = new Set([
785
+ "Array",
786
+ "Object",
787
+ "String",
788
+ "Number",
789
+ "Boolean",
790
+ "Function",
791
+ "Symbol",
792
+ "BigInt",
793
+ "Date",
794
+ "RegExp",
795
+ "Error",
796
+ "TypeError",
797
+ "ReferenceError",
798
+ "SyntaxError",
799
+ "Map",
800
+ "Set",
801
+ "WeakMap",
802
+ "WeakSet",
803
+ "Promise",
804
+ "Proxy",
805
+ "Reflect",
806
+ "JSON",
807
+ "Math",
808
+ "Intl",
809
+ "ArrayBuffer",
810
+ "DataView",
811
+ "URL",
812
+ "Record",
813
+ "Partial",
814
+ "Required",
815
+ "Readonly",
816
+ "Pick",
817
+ "Omit",
818
+ "Exclude",
819
+ "Extract",
820
+ "NonNullable",
821
+ "ReturnType",
822
+ "InstanceType",
823
+ "Parameters",
824
+ "ConstructorParameters",
825
+ "Console",
826
+ "Event",
827
+ "Element",
828
+ "Document",
829
+ "Window",
830
+ "Node",
831
+ "React",
832
+ "Component",
833
+ "Props",
834
+ "State"
835
+ ]);
836
+ return builtIns.has(identifier);
837
+ }
838
+ function detectBrokenLinks(entry, exportRegistry) {
839
+ if (!exportRegistry) {
840
+ return [];
841
+ }
842
+ const drifts = [];
843
+ const linkPattern = /\{@link\s+([^}\s]+)\s*\}/g;
844
+ const allText = [
845
+ entry.description ?? "",
846
+ ...(entry.tags ?? []).map((tag) => tag.text),
847
+ ...entry.examples ?? []
848
+ ].join(" ");
849
+ const matches = allText.matchAll(linkPattern);
850
+ for (const match of matches) {
851
+ const target = match[1];
852
+ if (!target) {
853
+ continue;
854
+ }
855
+ const rootName = target.split(".")[0] ?? target;
856
+ if (!exportRegistry.has(rootName) && !exportRegistry.has(target)) {
857
+ const suggestion = findClosestMatch(rootName, Array.from(exportRegistry));
858
+ drifts.push({
859
+ type: "broken-link",
860
+ target,
861
+ issue: `{@link ${target}} references a symbol that does not exist.`,
862
+ suggestion: suggestion && suggestion.distance <= 3 ? `Did you mean "${suggestion.value}"?` : undefined
863
+ });
864
+ }
865
+ }
866
+ return drifts;
867
+ }
868
+
869
+ // src/utils/type-utils.ts
870
+ function collectReferencedTypes(type, typeChecker, referencedTypes, visitedTypes = new Set) {
871
+ if (visitedTypes.has(type))
872
+ return;
873
+ visitedTypes.add(type);
874
+ const symbol = type.getSymbol();
875
+ if (symbol) {
876
+ const symbolName = symbol.getName();
877
+ if (!symbolName.startsWith("__") && !isBuiltInType(symbolName)) {
878
+ referencedTypes.add(symbolName);
879
+ }
880
+ }
881
+ if (type.isIntersection()) {
882
+ for (const intersectionType of type.types) {
883
+ collectReferencedTypes(intersectionType, typeChecker, referencedTypes, visitedTypes);
884
+ }
885
+ }
886
+ if (type.isUnion()) {
887
+ for (const unionType of type.types) {
888
+ collectReferencedTypes(unionType, typeChecker, referencedTypes, visitedTypes);
889
+ }
890
+ }
891
+ if (type.flags & ts.TypeFlags.Object) {
892
+ const objectType = type;
893
+ if (objectType.objectFlags & ts.ObjectFlags.Reference) {
894
+ const typeRef = objectType;
895
+ if (typeRef.typeArguments) {
896
+ for (const typeArg of typeRef.typeArguments) {
897
+ collectReferencedTypes(typeArg, typeChecker, referencedTypes, visitedTypes);
898
+ }
899
+ }
900
+ }
901
+ }
902
+ }
903
+ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
904
+ if (ts.isTypeReferenceNode(node)) {
905
+ const typeNameText = node.typeName.getText();
906
+ const symbol = typeChecker.getSymbolAtLocation(node.typeName);
907
+ const name = symbol?.getName() ?? typeNameText;
908
+ if (!isBuiltInType(name)) {
909
+ referencedTypes.add(name);
910
+ }
911
+ node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
912
+ return;
913
+ }
914
+ if (ts.isExpressionWithTypeArguments(node)) {
915
+ const expressionText = node.expression.getText();
916
+ const symbol = typeChecker.getSymbolAtLocation(node.expression);
917
+ const name = symbol?.getName() ?? expressionText;
918
+ if (!isBuiltInType(name)) {
919
+ referencedTypes.add(name);
920
+ }
921
+ node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
922
+ return;
923
+ }
924
+ if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) {
925
+ node.types.forEach((typeNode) => collectReferencedTypesFromNode(typeNode, typeChecker, referencedTypes));
926
+ return;
927
+ }
928
+ if (ts.isArrayTypeNode(node)) {
929
+ collectReferencedTypesFromNode(node.elementType, typeChecker, referencedTypes);
930
+ return;
931
+ }
932
+ if (ts.isParenthesizedTypeNode(node)) {
933
+ collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
934
+ return;
935
+ }
936
+ if (ts.isTypeLiteralNode(node)) {
937
+ node.members.forEach((member) => {
938
+ if (ts.isPropertySignature(member) && member.type) {
939
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
940
+ }
941
+ if (ts.isMethodSignature(member)) {
942
+ member.typeParameters?.forEach((param) => {
943
+ param.constraint && collectReferencedTypesFromNode(param.constraint, typeChecker, referencedTypes);
944
+ });
945
+ member.parameters.forEach((param) => {
946
+ if (param.type) {
947
+ collectReferencedTypesFromNode(param.type, typeChecker, referencedTypes);
948
+ }
949
+ });
950
+ if (member.type) {
951
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
952
+ }
953
+ }
954
+ if (ts.isCallSignatureDeclaration(member) && member.type) {
955
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
956
+ }
957
+ if (ts.isIndexSignatureDeclaration(member) && member.type) {
958
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
959
+ }
960
+ });
961
+ return;
962
+ }
963
+ if (ts.isTypeOperatorNode(node)) {
964
+ collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
965
+ return;
966
+ }
967
+ if (ts.isIndexedAccessTypeNode(node)) {
968
+ collectReferencedTypesFromNode(node.objectType, typeChecker, referencedTypes);
969
+ collectReferencedTypesFromNode(node.indexType, typeChecker, referencedTypes);
970
+ return;
971
+ }
972
+ if (ts.isLiteralTypeNode(node)) {
973
+ return;
974
+ }
975
+ node.forEachChild((child) => {
976
+ if (ts.isTypeNode(child)) {
977
+ collectReferencedTypesFromNode(child, typeChecker, referencedTypes);
978
+ }
979
+ });
980
+ }
981
+ function isBuiltInType(name) {
982
+ const builtIns = [
983
+ "string",
984
+ "number",
985
+ "boolean",
986
+ "bigint",
987
+ "symbol",
988
+ "undefined",
989
+ "null",
990
+ "any",
991
+ "unknown",
992
+ "never",
993
+ "void",
994
+ "object",
995
+ "Array",
996
+ "Promise",
997
+ "Map",
998
+ "Set",
999
+ "WeakMap",
1000
+ "WeakSet",
1001
+ "Date",
1002
+ "RegExp",
1003
+ "Error",
1004
+ "Function",
1005
+ "Object",
1006
+ "String",
1007
+ "Number",
1008
+ "Boolean",
1009
+ "BigInt",
1010
+ "Symbol",
1011
+ "Uint8Array",
1012
+ "Int8Array",
1013
+ "Uint16Array",
1014
+ "Int16Array",
1015
+ "Uint32Array",
1016
+ "Int32Array",
1017
+ "Float32Array",
1018
+ "Float64Array",
1019
+ "BigInt64Array",
1020
+ "BigUint64Array",
1021
+ "Uint8ClampedArray",
1022
+ "ArrayBuffer",
1023
+ "ArrayBufferLike",
1024
+ "DataView",
1025
+ "Uint8ArrayConstructor",
1026
+ "ArrayBufferConstructor",
1027
+ "JSON",
1028
+ "Math",
1029
+ "Reflect",
1030
+ "Proxy",
1031
+ "Intl",
1032
+ "globalThis",
1033
+ "__type"
1034
+ ];
1035
+ return builtIns.includes(name);
1036
+ }
1037
+
1038
+ // src/utils/parameter-utils.ts
1039
+ var BUILTIN_TYPE_SCHEMAS = {
1040
+ Date: { type: "string", format: "date-time" },
1041
+ RegExp: { type: "object", description: "RegExp" },
1042
+ Error: { type: "object" },
1043
+ Promise: { type: "object" },
1044
+ Map: { type: "object" },
1045
+ Set: { type: "object" },
1046
+ WeakMap: { type: "object" },
1047
+ WeakSet: { type: "object" },
1048
+ Function: { type: "object" },
1049
+ ArrayBuffer: { type: "string", format: "binary" },
1050
+ ArrayBufferLike: { type: "string", format: "binary" },
1051
+ DataView: { type: "string", format: "binary" },
1052
+ Uint8Array: { type: "string", format: "byte" },
1053
+ Uint16Array: { type: "string", format: "byte" },
1054
+ Uint32Array: { type: "string", format: "byte" },
1055
+ Int8Array: { type: "string", format: "byte" },
1056
+ Int16Array: { type: "string", format: "byte" },
1057
+ Int32Array: { type: "string", format: "byte" },
1058
+ Float32Array: { type: "string", format: "byte" },
1059
+ Float64Array: { type: "string", format: "byte" },
1060
+ BigInt64Array: { type: "string", format: "byte" },
1061
+ BigUint64Array: { type: "string", format: "byte" }
1062
+ };
1063
+ function isObjectLiteralType(type) {
1064
+ if (!(type.getFlags() & ts.TypeFlags.Object)) {
1065
+ return false;
1066
+ }
1067
+ const objectFlags = type.objectFlags;
1068
+ return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
1069
+ }
1070
+ function isPureRefSchema(value) {
1071
+ return Object.keys(value).length === 1 && "$ref" in value;
1072
+ }
1073
+ function withDescription(schema, description) {
1074
+ if (isPureRefSchema(schema)) {
1075
+ return {
1076
+ allOf: [schema],
1077
+ description
1078
+ };
1079
+ }
1080
+ return {
1081
+ ...schema,
1082
+ description
1083
+ };
1084
+ }
1085
+ function propertiesToSchema(properties, description) {
1086
+ const schema = {
1087
+ type: "object",
1088
+ properties: {}
1089
+ };
1090
+ const required = [];
1091
+ for (const prop of properties) {
1092
+ const propType = prop.type;
1093
+ let propSchema;
1094
+ if (typeof propType === "string") {
1095
+ if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
1096
+ propSchema = { type: propType === "bigint" ? "string" : propType };
1097
+ } else {
1098
+ propSchema = { type: propType };
1099
+ }
1100
+ } else if (propType && typeof propType === "object") {
1101
+ propSchema = propType;
1102
+ } else {
1103
+ propSchema = { type: "any" };
1104
+ }
1105
+ if (prop.description && typeof propSchema === "object") {
1106
+ propSchema = withDescription(propSchema, prop.description);
1107
+ }
1108
+ schema.properties[prop.name] = propSchema;
1109
+ if (!prop.optional) {
1110
+ required.push(prop.name);
1111
+ }
1112
+ }
1113
+ if (required.length > 0) {
1114
+ schema.required = required;
1115
+ }
1116
+ if (description) {
1117
+ return withDescription(schema, description);
1118
+ }
1119
+ return schema;
1120
+ }
1121
+ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
1122
+ if (ts.isParenthesizedTypeNode(node)) {
1123
+ return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
1124
+ }
1125
+ if (ts.isIntersectionTypeNode(node)) {
1126
+ const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1127
+ return { allOf: schemas };
1128
+ }
1129
+ if (ts.isUnionTypeNode(node)) {
1130
+ const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1131
+ return { anyOf: schemas };
1132
+ }
1133
+ if (ts.isArrayTypeNode(node)) {
1134
+ return {
1135
+ type: "array",
1136
+ items: buildSchemaFromTypeNode(node.elementType, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName)
1137
+ };
1138
+ }
1139
+ if (ts.isTypeLiteralNode(node)) {
1140
+ const properties = {};
1141
+ const required = [];
1142
+ for (const member of node.members) {
1143
+ if (!ts.isPropertySignature(member) || !member.name) {
1144
+ continue;
1145
+ }
1146
+ const propName = member.name.getText();
1147
+ let schema2 = "any";
1148
+ if (member.type) {
1149
+ const memberType = typeChecker.getTypeFromTypeNode(member.type);
1150
+ const formatted = formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes);
1151
+ if (typeof formatted === "string") {
1152
+ if (formatted === "any") {
1153
+ schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
1154
+ } else {
1155
+ schema2 = { type: formatted };
1156
+ }
1157
+ } else {
1158
+ schema2 = formatted;
1159
+ }
1160
+ } else {
1161
+ schema2 = { type: "any" };
1162
+ }
1163
+ const description = getDocDescriptionForProperty(functionDoc, parentParamName, propName);
1164
+ if (typeof schema2 === "object" && description) {
1165
+ schema2 = withDescription(schema2, description);
1166
+ }
1167
+ properties[propName] = schema2;
1168
+ if (!member.questionToken) {
1169
+ required.push(propName);
1170
+ }
1171
+ }
1172
+ const schema = {
1173
+ type: "object",
1174
+ properties
1175
+ };
1176
+ if (required.length > 0) {
1177
+ schema.required = required;
1178
+ }
1179
+ return schema;
1180
+ }
1181
+ if (ts.isTypeReferenceNode(node)) {
1182
+ const typeName = node.typeName.getText();
1183
+ if (typeName === "Array") {
1184
+ return { type: "array" };
1185
+ }
1186
+ const builtInSchema = BUILTIN_TYPE_SCHEMAS[typeName];
1187
+ if (builtInSchema) {
1188
+ return { ...builtInSchema };
1189
+ }
1190
+ if (isBuiltInType(typeName)) {
1191
+ return { type: "object" };
1192
+ }
1193
+ if (!typeRefs.has(typeName)) {
1194
+ typeRefs.set(typeName, typeName);
1195
+ }
1196
+ referencedTypes?.add(typeName);
1197
+ return { $ref: `#/types/${typeName}` };
1198
+ }
1199
+ if (ts.isLiteralTypeNode(node)) {
1200
+ if (ts.isStringLiteral(node.literal)) {
1201
+ return { enum: [node.literal.text] };
1202
+ }
1203
+ if (ts.isNumericLiteral(node.literal)) {
1204
+ return { enum: [Number(node.literal.text)] };
1205
+ }
1206
+ }
1207
+ if (ts.isIntersectionTypeNode(node)) {
1208
+ const schemas = node.types.map((typeNode) => buildSchemaFromTypeNode(typeNode, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1209
+ if (schemas.some((schema) => ("$ref" in schema) && Object.keys(schema).length === 1)) {
1210
+ const refs = schemas.filter((schema) => ("$ref" in schema) && Object.keys(schema).length === 1);
1211
+ const nonRefs = schemas.filter((schema) => !(("$ref" in schema) && Object.keys(schema).length === 1));
1212
+ if (refs.length === schemas.length) {
1213
+ return refs[0];
1214
+ }
1215
+ if (nonRefs.length > 0) {
1216
+ const merged = {};
1217
+ for (const obj of nonRefs) {
1218
+ for (const [k, v] of Object.entries(obj)) {
1219
+ merged[k] = v;
1220
+ }
1221
+ }
1222
+ return merged;
1223
+ }
1224
+ }
1225
+ return {
1226
+ allOf: schemas
1227
+ };
1228
+ }
1229
+ return { type: node.getText() };
1230
+ }
1231
+ function getDocDescriptionForProperty(functionDoc, parentParamName, propName) {
1232
+ if (!functionDoc) {
1233
+ return;
1234
+ }
1235
+ let match = functionDoc.params.find((p) => p.name === `${parentParamName}.${propName}`);
1236
+ if (!match) {
1237
+ match = functionDoc.params.find((p) => p.name.endsWith(`.${propName}`));
1238
+ }
1239
+ return match?.description;
1240
+ }
1241
+ function schemaIsAny(schema) {
1242
+ if (typeof schema === "string") {
1243
+ return schema === "any";
1244
+ }
1245
+ if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
1246
+ return true;
1247
+ }
1248
+ return false;
1249
+ }
1250
+ function schemasAreEqual(left, right) {
1251
+ if (typeof left !== typeof right) {
1252
+ return false;
1253
+ }
1254
+ if (typeof left === "string" && typeof right === "string") {
1255
+ return left === right;
1256
+ }
1257
+ if (left == null || right == null) {
1258
+ return left === right;
1259
+ }
1260
+ const normalize = (value) => {
1261
+ if (Array.isArray(value)) {
1262
+ return value.map((item) => normalize(item));
1263
+ }
1264
+ if (value && typeof value === "object") {
1265
+ const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
1266
+ return Object.fromEntries(sortedEntries);
1267
+ }
1268
+ return value;
1269
+ };
1270
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
1271
+ }
1272
+ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visitedAliases) {
1273
+ const visited = visitedAliases ?? new Set;
1274
+ const aliasSymbol = type.aliasSymbol;
1275
+ let aliasName;
1276
+ let aliasAdded = false;
1277
+ if (aliasSymbol) {
1278
+ aliasName = aliasSymbol.getName();
1279
+ if (visited.has(aliasName)) {
1280
+ return { $ref: `#/types/${aliasName}` };
1281
+ }
1282
+ if (typeRefs.has(aliasName)) {
1283
+ return { $ref: `#/types/${aliasName}` };
1284
+ }
1285
+ if (referencedTypes && !isBuiltInType(aliasName)) {
1286
+ referencedTypes.add(aliasName);
1287
+ return { $ref: `#/types/${aliasName}` };
1288
+ }
1289
+ visited.add(aliasName);
1290
+ aliasAdded = true;
1291
+ }
1292
+ try {
1293
+ const typeString = typeChecker.typeToString(type);
1294
+ const primitives = [
1295
+ "string",
1296
+ "number",
1297
+ "boolean",
1298
+ "bigint",
1299
+ "symbol",
1300
+ "any",
1301
+ "unknown",
1302
+ "void",
1303
+ "undefined",
1304
+ "null",
1305
+ "never"
1306
+ ];
1307
+ if (primitives.includes(typeString)) {
1308
+ if (typeString === "bigint") {
1309
+ return { type: "string", format: "bigint" };
1310
+ }
1311
+ if (typeString === "undefined" || typeString === "null") {
1312
+ return { type: "null" };
1313
+ }
1314
+ if (typeString === "void" || typeString === "never") {
1315
+ return { type: "null" };
1316
+ }
1317
+ return { type: typeString };
1318
+ }
1319
+ if (type.isUnion()) {
1320
+ const unionType = type;
1321
+ const parts = unionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes, visited));
1322
+ return {
1323
+ anyOf: parts
1324
+ };
1325
+ }
1326
+ if (type.isIntersection()) {
1327
+ const intersectionType = type;
1328
+ const parts = intersectionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes, visited));
1329
+ const normalized = parts.flatMap((part) => {
1330
+ if (typeof part === "string") {
1331
+ return [{ type: part }];
1332
+ }
1333
+ if (part && typeof part === "object" && "allOf" in part) {
1334
+ return Array.isArray(part.allOf) ? part.allOf : [part];
1335
+ }
1336
+ return [part];
1337
+ });
1338
+ if (normalized.length === 1) {
1339
+ return normalized[0];
1340
+ }
1341
+ return {
1342
+ allOf: normalized
1343
+ };
1344
+ }
1345
+ const symbol = type.getSymbol();
1346
+ if (symbol) {
1347
+ const symbolName = symbol.getName();
1348
+ if (symbolName.startsWith("__")) {
1349
+ if (type.getFlags() & ts.TypeFlags.Object) {
1350
+ const properties = type.getProperties();
1351
+ if (properties.length > 0) {
1352
+ const objSchema = {
1353
+ type: "object",
1354
+ properties: {}
1355
+ };
1356
+ const required = [];
1357
+ for (const prop of properties) {
1358
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1359
+ const propName = prop.getName();
1360
+ objSchema.properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes, visited);
1361
+ if (!(prop.flags & ts.SymbolFlags.Optional)) {
1362
+ required.push(propName);
1363
+ }
1364
+ }
1365
+ if (required.length > 0) {
1366
+ objSchema.required = required;
1367
+ }
1368
+ return objSchema;
1369
+ }
1370
+ }
1371
+ return { type: "object" };
1372
+ }
1373
+ if (typeRefs.has(symbolName)) {
1374
+ return { $ref: `#/types/${symbolName}` };
1375
+ }
1376
+ if (symbolName === "Array") {
1377
+ return { type: "array" };
1378
+ }
1379
+ const builtInSchema = BUILTIN_TYPE_SCHEMAS[symbolName];
1380
+ if (builtInSchema) {
1381
+ return { ...builtInSchema };
1382
+ }
1383
+ if (referencedTypes && !isBuiltInType(symbolName)) {
1384
+ referencedTypes.add(symbolName);
1385
+ return { $ref: `#/types/${symbolName}` };
1386
+ }
1387
+ if (isBuiltInType(symbolName)) {
1388
+ return { type: "object" };
1389
+ }
1390
+ return { $ref: `#/types/${symbolName}` };
1391
+ }
1392
+ if (type.isLiteral()) {
1393
+ if (typeString.startsWith('"') && typeString.endsWith('"')) {
1394
+ const literalValue = typeString.slice(1, -1);
1395
+ return { enum: [literalValue] };
1396
+ }
1397
+ return { enum: [Number(typeString)] };
1398
+ }
1399
+ const typePattern = /^(\w+)(\s*\|\s*undefined)?$/;
1400
+ const match = typeString.match(typePattern);
1401
+ if (match) {
1402
+ const [, typeName, hasUndefined] = match;
1403
+ if (typeRefs.has(typeName) || !isBuiltInType(typeName)) {
1404
+ if (hasUndefined) {
1405
+ return {
1406
+ anyOf: [{ $ref: `#/types/${typeName}` }, { type: "null" }]
1407
+ };
1408
+ }
1409
+ return { $ref: `#/types/${typeName}` };
1410
+ }
1411
+ }
1412
+ return { type: typeString };
1413
+ } finally {
1414
+ if (aliasAdded && aliasName) {
1415
+ visited.delete(aliasName);
1416
+ }
1417
+ }
1418
+ }
1419
+ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes) {
1420
+ const paramName = param.getName();
1421
+ const isDestructured = paramName === "__0" || ts.isObjectBindingPattern(paramDecl.name) || ts.isArrayBindingPattern(paramDecl.name);
1422
+ let inferredAlias;
1423
+ if (isDestructured && functionDoc && Array.isArray(functionDoc.params)) {
1424
+ const prefixes = functionDoc.params.map((p) => p?.name).filter((n) => typeof n === "string" && n.includes(".")).map((n) => n.split(".", 2)[0]).filter(Boolean);
1425
+ if (prefixes.length > 0) {
1426
+ const counts = new Map;
1427
+ for (const px of prefixes)
1428
+ counts.set(px, (counts.get(px) ?? 0) + 1);
1429
+ inferredAlias = Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
1430
+ }
1431
+ }
1432
+ const fallbackName = isDestructured ? inferredAlias ?? "options" : paramName;
1433
+ const docDescription = paramDoc?.description?.trim();
1434
+ if (paramType.isIntersection()) {
1435
+ const properties = [];
1436
+ const intersectionType = paramType;
1437
+ for (const subType of intersectionType.types) {
1438
+ const symbol2 = subType.getSymbol();
1439
+ const _typeString = typeChecker.typeToString(subType);
1440
+ const isAnonymousObject = isObjectLiteralType(subType) || symbol2?.getName().startsWith("__");
1441
+ if (isAnonymousObject) {
1442
+ for (const prop of subType.getProperties()) {
1443
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1444
+ let description = "";
1445
+ if (functionDoc) {
1446
+ let docParam = functionDoc.params.find((p) => p.name === `${paramName}.${prop.getName()}`);
1447
+ if (!docParam && paramName === "__0") {
1448
+ docParam = functionDoc.params.find((p) => p.name.endsWith(`.${prop.getName()}`));
1449
+ }
1450
+ if (docParam) {
1451
+ description = docParam.description;
1452
+ }
1453
+ }
1454
+ properties.push({
1455
+ name: prop.getName(),
1456
+ type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1457
+ description,
1458
+ optional: !!(prop.flags & ts.SymbolFlags.Optional)
1459
+ });
1460
+ }
1461
+ } else if (symbol2) {
1462
+ const _symbolName = symbol2.getName();
1463
+ if (!isBuiltInType(_symbolName)) {
1464
+ for (const prop of subType.getProperties()) {
1465
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1466
+ properties.push({
1467
+ name: prop.getName(),
1468
+ type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1469
+ description: "",
1470
+ optional: !!(prop.flags & ts.SymbolFlags.Optional)
1471
+ });
1472
+ }
1473
+ }
1474
+ }
1475
+ }
1476
+ const actualName = fallbackName;
1477
+ const out2 = {
1478
+ name: actualName,
1479
+ required: !typeChecker.isOptionalParameter(paramDecl),
1480
+ schema: propertiesToSchema(properties)
1481
+ };
1482
+ if (docDescription) {
1483
+ out2.description = docDescription;
1484
+ }
1485
+ return out2;
1486
+ }
1487
+ if (paramType.isUnion()) {
1488
+ const unionType = paramType;
1489
+ const objectOptions = [];
1490
+ let hasNonObjectTypes = false;
1491
+ for (const subType of unionType.types) {
1492
+ const symbol2 = subType.getSymbol();
1493
+ if (isObjectLiteralType(subType) || symbol2?.getName().startsWith("__")) {
1494
+ const properties = [];
1495
+ for (const prop of subType.getProperties()) {
1496
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1497
+ properties.push({
1498
+ name: prop.getName(),
1499
+ type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1500
+ description: "",
1501
+ optional: !!(prop.flags & ts.SymbolFlags.Optional)
1502
+ });
1503
+ }
1504
+ if (properties.length > 0) {
1505
+ objectOptions.push({ properties });
1506
+ }
1507
+ } else {
1508
+ hasNonObjectTypes = true;
1509
+ }
1510
+ }
1511
+ if (objectOptions.length > 0 && !hasNonObjectTypes) {
1512
+ const readableName2 = fallbackName;
1513
+ const out2 = {
1514
+ name: readableName2,
1515
+ required: !typeChecker.isOptionalParameter(paramDecl),
1516
+ schema: {
1517
+ oneOf: objectOptions.map((opt) => propertiesToSchema(opt.properties))
1518
+ }
1519
+ };
1520
+ if (docDescription) {
1521
+ out2.description = docDescription;
1522
+ }
1523
+ return out2;
1524
+ }
1525
+ }
1526
+ const symbol = paramType.getSymbol();
1527
+ if ((symbol?.getName().startsWith("__") || isObjectLiteralType(paramType)) && paramType.getProperties().length > 0) {
1528
+ const properties = [];
1529
+ for (const prop of paramType.getProperties()) {
1530
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1531
+ properties.push({
1532
+ name: prop.getName(),
1533
+ type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1534
+ description: "",
1535
+ optional: !!(prop.flags & ts.SymbolFlags.Optional)
1536
+ });
1537
+ }
1538
+ const readableName2 = fallbackName;
1539
+ const out2 = {
1540
+ name: readableName2,
1541
+ required: !typeChecker.isOptionalParameter(paramDecl),
1542
+ schema: propertiesToSchema(properties)
1543
+ };
1544
+ if (docDescription) {
1545
+ out2.description = docDescription;
1546
+ }
1547
+ return out2;
1548
+ }
1549
+ if (paramType.flags & ts.TypeFlags.Any && paramDecl.type && paramDecl.name && ts.isObjectBindingPattern(paramDecl.name)) {
1550
+ const actualName = fallbackName;
1551
+ const schema2 = buildSchemaFromTypeNode(paramDecl.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, param.getName());
1552
+ const out2 = {
1553
+ name: actualName,
1554
+ required: !typeChecker.isOptionalParameter(paramDecl),
1555
+ schema: schema2
1556
+ };
1557
+ if (docDescription) {
1558
+ out2.description = docDescription;
1559
+ }
1560
+ return out2;
1561
+ }
1562
+ const typeRef = formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes);
1563
+ let schema;
1564
+ if (typeof typeRef === "string") {
1565
+ if ([
1566
+ "string",
1567
+ "number",
1568
+ "boolean",
1569
+ "null",
1570
+ "undefined",
1571
+ "any",
1572
+ "unknown",
1573
+ "never",
1574
+ "void"
1575
+ ].includes(typeRef)) {
1576
+ schema = { type: typeRef };
1577
+ } else {
1578
+ schema = { type: typeRef };
1579
+ }
1580
+ } else {
1581
+ schema = typeRef;
1582
+ }
1583
+ if (paramDecl.type) {
1584
+ const astSchema = buildSchemaFromTypeNode(paramDecl.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, param.getName());
1585
+ if (schemaIsAny(schema)) {
1586
+ schema = astSchema;
1587
+ } else if (!(("type" in schema) && schema.type === "any") && !(typeof schema === "object" && isPureRefSchema(schema)) && Object.keys(astSchema).length > 0 && !schemasAreEqual(schema, astSchema)) {
1588
+ schema = {
1589
+ allOf: [schema, astSchema]
1590
+ };
1591
+ }
1592
+ }
1593
+ const readableName = fallbackName;
1594
+ const out = {
1595
+ name: readableName,
1596
+ required: !typeChecker.isOptionalParameter(paramDecl),
1597
+ schema
1598
+ };
1599
+ if (docDescription) {
1600
+ out.description = docDescription;
1601
+ }
1602
+ return out;
1603
+ }
1604
+
1605
+ // src/utils/tsdoc-utils.ts
1606
+ function parseJSDocComment(symbol, _typeChecker, sourceFileOverride) {
1607
+ const node = symbol.valueDeclaration || symbol.declarations?.[0];
1608
+ if (!node)
1609
+ return null;
1610
+ const sourceFile = sourceFileOverride || node.getSourceFile();
1611
+ const commentRanges = ts.getLeadingCommentRanges(sourceFile.text, node.pos);
1612
+ if (!commentRanges || commentRanges.length === 0) {
1613
+ return null;
1614
+ }
1615
+ const lastComment = commentRanges[commentRanges.length - 1];
1616
+ const commentText = sourceFile.text.substring(lastComment.pos, lastComment.end);
1617
+ return parseJSDocText(commentText);
1618
+ }
1619
+ function parseJSDocText(commentText) {
1620
+ const tags = [];
1621
+ const result = {
1622
+ description: "",
1623
+ params: [],
1624
+ examples: [],
1625
+ rawParamNames: []
1626
+ };
1627
+ const cleanedText = commentText.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/$/, "").replace(/^\s*\* ?/gm, "").replace(/\n\/\s*$/, "");
1628
+ const lines = cleanedText.split(/\n/);
1629
+ let currentTag = "";
1630
+ let currentContent = [];
1631
+ const pushDescription = (line) => {
1632
+ const processed = replaceInlineLinks(line, tags).trimEnd();
1633
+ if (processed.trim()) {
1634
+ result.description = result.description ? `${result.description}
1635
+ ${processed}` : processed;
1636
+ }
1637
+ };
1638
+ for (const line of lines) {
1639
+ const tagMatch = line.match(/^@(\w+)(?:\s+(.*))?$/);
1640
+ if (tagMatch) {
1641
+ if (currentTag) {
1642
+ processTag(result, tags, currentTag, currentContent.join(String.fromCharCode(10)));
1643
+ }
1644
+ currentTag = tagMatch[1];
1645
+ currentContent = tagMatch[2] ? [tagMatch[2]] : [];
1646
+ } else if (currentTag) {
1647
+ currentContent.push(line);
1648
+ } else {
1649
+ if (line.trim()) {
1650
+ pushDescription(line);
1651
+ }
1652
+ }
1653
+ }
1654
+ if (currentTag) {
1655
+ processTag(result, tags, currentTag, currentContent.join(String.fromCharCode(10)));
1656
+ }
1657
+ if (result.examples && result.examples.length === 0) {
1658
+ delete result.examples;
1659
+ }
1660
+ if (tags.length > 0) {
1661
+ result.tags = tags;
1662
+ }
1663
+ return result;
1664
+ }
1665
+ function processTag(result, tags, tag, content) {
1666
+ switch (tag) {
1667
+ case "param":
1668
+ case "parameter": {
1669
+ const paramMatch = content.match(/^(?:\{([^}]+)\}\s+)?(\S+)(?:\s+-\s+)?(.*)$/);
1670
+ if (paramMatch) {
1671
+ const [, type, name, description] = paramMatch;
1672
+ const processedDescription = replaceInlineLinks(description || "", tags);
1673
+ const normalizedName = name || "";
1674
+ result.rawParamNames?.push(normalizedName);
1675
+ result.params.push({
1676
+ name: normalizedName,
1677
+ description: processedDescription || "",
1678
+ type
1679
+ });
1680
+ }
1681
+ tags.push({ name: "param", text: content });
1682
+ break;
1683
+ }
1684
+ case "returns":
1685
+ case "return": {
1686
+ const returnMatch = content.match(/^(?:\{([^}]+)\}\s*)?(.*)$/s);
1687
+ const typeText = returnMatch?.[1]?.trim();
1688
+ const descriptionText = replaceInlineLinks(returnMatch?.[2] ?? "", tags).trim();
1689
+ if (typeText) {
1690
+ result.returnsType = typeText;
1691
+ }
1692
+ if (descriptionText) {
1693
+ result.returns = descriptionText;
1694
+ } else if (!result.returns) {
1695
+ result.returns = "";
1696
+ }
1697
+ tags.push({ name: "returns", text: content });
1698
+ break;
1699
+ }
1700
+ case "example": {
1701
+ const example = replaceInlineLinks(content.trim(), tags).trim();
1702
+ if (example) {
1703
+ if (!result.examples) {
1704
+ result.examples = [];
1705
+ }
1706
+ result.examples.push(example);
1707
+ }
1708
+ break;
1709
+ }
1710
+ case "see": {
1711
+ const parts = content.split(",").map((part) => part.trim()).filter(Boolean);
1712
+ for (const part of parts) {
1713
+ const linkTargets = extractLinkTargets(part);
1714
+ if (linkTargets.length > 0) {
1715
+ for (const target of linkTargets) {
1716
+ tags.push({ name: "link", text: target });
1717
+ tags.push({ name: "see", text: target });
1718
+ }
1719
+ } else {
1720
+ tags.push({ name: "see", text: part });
1721
+ }
1722
+ }
1723
+ break;
1724
+ }
1725
+ case "link": {
1726
+ const { target } = parseLinkBody(content.trim());
1727
+ if (target) {
1728
+ tags.push({ name: "link", text: target });
1729
+ }
1730
+ break;
1731
+ }
1732
+ default: {
1733
+ const text = replaceInlineLinks(content, tags).trim();
1734
+ if (text) {
1735
+ tags.push({ name: tag, text });
1736
+ }
1737
+ }
1738
+ }
1739
+ }
1740
+ function replaceInlineLinks(text, tags, tagName = "link") {
1741
+ return text.replace(/\{@link\s+([^}]+)\}/g, (_match, body) => {
1742
+ const { target, label } = parseLinkBody(body);
1743
+ if (target) {
1744
+ tags.push({ name: tagName, text: target });
1745
+ }
1746
+ return label || target || "";
1747
+ });
1748
+ }
1749
+ function extractLinkTargets(text) {
1750
+ const targets = [];
1751
+ text.replace(/\{@link\s+([^}]+)\}/g, (_match, body) => {
1752
+ const { target } = parseLinkBody(body);
1753
+ if (target) {
1754
+ targets.push(target);
1755
+ }
1756
+ return "";
1757
+ });
1758
+ return targets;
1759
+ }
1760
+ function parseLinkBody(raw) {
1761
+ const trimmed = raw.trim();
1762
+ if (!trimmed) {
1763
+ return { target: "" };
1764
+ }
1765
+ const pipeIndex = trimmed.indexOf("|");
1766
+ if (pipeIndex >= 0) {
1767
+ const target2 = trimmed.slice(0, pipeIndex).trim();
1768
+ const label2 = trimmed.slice(pipeIndex + 1).trim();
1769
+ return { target: target2, label: label2 };
1770
+ }
1771
+ const parts = trimmed.split(/\s+/);
1772
+ const target = parts.shift() ?? "";
1773
+ const label = parts.join(" ").trim();
1774
+ return { target, label: label || undefined };
1775
+ }
1776
+ function extractDestructuredParams(parsedDoc, paramName) {
1777
+ const destructuredParams = new Map;
1778
+ const paramPrefix = `${paramName}.`;
1779
+ for (const param of parsedDoc.params) {
1780
+ if (param.name.startsWith(paramPrefix)) {
1781
+ const propertyName = param.name.substring(paramPrefix.length);
1782
+ destructuredParams.set(propertyName, param.description);
1783
+ } else if (param.name.includes(".") && paramName === "__0") {
1784
+ const [_prefix, propertyName] = param.name.split(".", 2);
1785
+ if (propertyName) {
1786
+ destructuredParams.set(propertyName, param.description);
1787
+ }
1788
+ }
1789
+ }
1790
+ return destructuredParams;
1791
+ }
1792
+ function getParameterDocumentation(param, paramDecl, typeChecker) {
1793
+ const result = {
1794
+ description: ""
1795
+ };
1796
+ const funcNode = paramDecl.parent;
1797
+ if (ts.isFunctionDeclaration(funcNode) || ts.isFunctionExpression(funcNode)) {
1798
+ const funcSymbol = typeChecker.getSymbolAtLocation(funcNode.name || funcNode);
1799
+ if (funcSymbol) {
1800
+ const parsedDoc = parseJSDocComment(funcSymbol, typeChecker);
1801
+ if (parsedDoc) {
1802
+ const paramName = param.getName();
1803
+ const paramDoc = parsedDoc.params.find((p) => p.name === paramName || p.name.split(".")[0] === paramName);
1804
+ if (paramDoc) {
1805
+ result.description = paramDoc.description;
1806
+ }
1807
+ const destructuredProps = extractDestructuredParams(parsedDoc, paramName);
1808
+ if (destructuredProps.size > 0) {
1809
+ result.destructuredProperties = Array.from(destructuredProps.entries()).map(([name, description]) => ({
1810
+ name,
1811
+ description
1812
+ }));
1813
+ }
1814
+ }
1815
+ }
1816
+ }
1817
+ return result;
1818
+ }
1819
+
1820
+ // src/utils/type-parameter-utils.ts
1821
+ function serializeTypeParameterDeclarations(typeParameters, checker, referencedTypes) {
1822
+ if (!typeParameters || typeParameters.length === 0) {
1823
+ return;
1824
+ }
1825
+ const serialized = [];
1826
+ for (const typeParam of typeParameters) {
1827
+ const name = typeParam.name?.getText().trim();
1828
+ if (!name) {
1829
+ continue;
1830
+ }
1831
+ let constraint;
1832
+ if (typeParam.constraint) {
1833
+ collectReferencedTypesFromNode(typeParam.constraint, checker, referencedTypes);
1834
+ constraint = typeParam.constraint.getText().trim();
1835
+ }
1836
+ let defaultType;
1837
+ if (typeParam.default) {
1838
+ collectReferencedTypesFromNode(typeParam.default, checker, referencedTypes);
1839
+ defaultType = typeParam.default.getText().trim();
1840
+ }
1841
+ serialized.push({
1842
+ name,
1843
+ ...constraint ? { constraint } : {},
1844
+ ...defaultType ? { default: defaultType } : {}
1845
+ });
1846
+ }
1847
+ return serialized.length > 0 ? serialized : undefined;
1848
+ }
1849
+
1850
+ // src/analysis/ast-utils.ts
1851
+ function getJSDocComment(symbol, typeChecker) {
1852
+ const comments = symbol.getDocumentationComment(typeChecker);
1853
+ return ts.displayPartsToString(comments);
1854
+ }
1855
+ function getSourceLocation(node) {
1856
+ const sourceFile = node.getSourceFile();
1857
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1858
+ return {
1859
+ file: sourceFile.fileName,
1860
+ line: line + 1
1861
+ };
1862
+ }
1863
+ function isSymbolDeprecated(symbol) {
1864
+ if (!symbol) {
1865
+ return false;
1866
+ }
1867
+ const jsDocTags = symbol.getJsDocTags();
1868
+ if (jsDocTags.some((tag) => tag.name.toLowerCase() === "deprecated")) {
1869
+ return true;
1870
+ }
1871
+ for (const declaration of symbol.getDeclarations() ?? []) {
1872
+ if (ts.getJSDocDeprecatedTag(declaration)) {
1873
+ return true;
1874
+ }
1875
+ }
1876
+ return false;
1877
+ }
1878
+
1879
+ // src/analysis/serializers/presentation.ts
1880
+ function extractPresentationMetadata(doc) {
1881
+ if (!doc?.tags || doc.tags.length === 0) {
1882
+ return {};
1883
+ }
1884
+ const findTag = (...names) => {
1885
+ for (const name of names) {
1886
+ const match = doc.tags?.find((tag) => tag.name.toLowerCase() === name.toLowerCase());
1887
+ const text = match?.text.trim();
1888
+ if (text) {
1889
+ return text;
1890
+ }
1891
+ }
1892
+ return;
1893
+ };
1894
+ return {
1895
+ slug: findTag("slug"),
1896
+ displayName: findTag("displayname", "display-name", "title"),
1897
+ category: findTag("category", "group", "module"),
1898
+ importPath: findTag("importpath", "import-path", "import")
1899
+ };
1900
+ }
1901
+
1902
+ // src/analysis/serializers/classes.ts
1903
+ function serializeClass(declaration, symbol, context) {
1904
+ const { checker, typeRegistry } = context;
1905
+ const typeRefs = typeRegistry.getTypeRefs();
1906
+ const referencedTypes = typeRegistry.getReferencedTypes();
1907
+ const members = serializeClassMembers(declaration, checker, typeRefs, referencedTypes);
1908
+ const typeParameters = serializeTypeParameterDeclarations(declaration.typeParameters, checker, referencedTypes);
1909
+ const parsedDoc = parseJSDocComment(symbol, context.checker);
1910
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
1911
+ const metadata = extractPresentationMetadata(parsedDoc);
1912
+ const exportEntry = {
1913
+ id: symbol.getName(),
1914
+ name: symbol.getName(),
1915
+ ...metadata,
1916
+ kind: "class",
1917
+ deprecated: isSymbolDeprecated(symbol),
1918
+ description,
1919
+ source: getSourceLocation(declaration),
1920
+ members: members.length > 0 ? members : undefined,
1921
+ typeParameters,
1922
+ tags: parsedDoc?.tags
1923
+ };
1924
+ const typeDefinition = {
1925
+ id: symbol.getName(),
1926
+ name: symbol.getName(),
1927
+ ...metadata,
1928
+ kind: "class",
1929
+ description,
1930
+ source: getSourceLocation(declaration),
1931
+ members: members.length > 0 ? members : undefined,
1932
+ tags: parsedDoc?.tags
1933
+ };
1934
+ return {
1935
+ exportEntry,
1936
+ typeDefinition
1937
+ };
1938
+ }
1939
+ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes) {
1940
+ const members = [];
1941
+ for (const member of declaration.members) {
1942
+ if (!member.name && !ts.isConstructorDeclaration(member)) {
1943
+ continue;
1944
+ }
1945
+ if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
1946
+ const memberName = member.name?.getText();
1947
+ if (!memberName)
1948
+ continue;
1949
+ const memberSymbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined;
1950
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
1951
+ const memberType = memberSymbol ? checker.getTypeOfSymbolAtLocation(memberSymbol, member) : member.type ? checker.getTypeFromTypeNode(member.type) : checker.getTypeAtLocation(member);
1952
+ collectReferencedTypes(memberType, checker, referencedTypes);
1953
+ const schema = formatTypeReference(memberType, checker, typeRefs, referencedTypes);
1954
+ const flags = {};
1955
+ const isOptionalSymbol = memberSymbol != null && (memberSymbol.flags & ts.SymbolFlags.Optional) !== 0;
1956
+ if (member.questionToken || isOptionalSymbol) {
1957
+ flags.optional = true;
1958
+ }
1959
+ if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword)) {
1960
+ flags.readonly = true;
1961
+ }
1962
+ if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
1963
+ flags.static = true;
1964
+ }
1965
+ members.push({
1966
+ id: memberName,
1967
+ name: memberName,
1968
+ kind: "property",
1969
+ visibility: getMemberVisibility(member.modifiers),
1970
+ schema,
1971
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined),
1972
+ flags: Object.keys(flags).length > 0 ? flags : undefined,
1973
+ tags: memberDoc?.tags
1974
+ });
1975
+ continue;
1976
+ }
1977
+ if (ts.isMethodDeclaration(member)) {
1978
+ const memberName = member.name?.getText() ?? "method";
1979
+ const memberSymbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined;
1980
+ const methodDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
1981
+ const signature = checker.getSignatureFromDeclaration(member);
1982
+ const signatures = signature ? [
1983
+ serializeSignature(signature, checker, typeRefs, referencedTypes, methodDoc, memberSymbol)
1984
+ ] : undefined;
1985
+ members.push({
1986
+ id: memberName,
1987
+ name: memberName,
1988
+ kind: "method",
1989
+ visibility: getMemberVisibility(member.modifiers),
1990
+ signatures,
1991
+ description: methodDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined),
1992
+ flags: getMethodFlags(member),
1993
+ tags: methodDoc?.tags
1994
+ });
1995
+ continue;
1996
+ }
1997
+ if (ts.isConstructorDeclaration(member)) {
1998
+ const ctorSymbol = checker.getSymbolAtLocation(member);
1999
+ const ctorDoc = ctorSymbol ? parseJSDocComment(ctorSymbol, checker) : null;
2000
+ const signature = checker.getSignatureFromDeclaration(member);
2001
+ const signatures = signature ? [serializeSignature(signature, checker, typeRefs, referencedTypes, ctorDoc, ctorSymbol)] : undefined;
2002
+ members.push({
2003
+ id: "constructor",
2004
+ name: "constructor",
2005
+ kind: "constructor",
2006
+ visibility: getMemberVisibility(member.modifiers),
2007
+ signatures,
2008
+ description: ctorDoc?.description ?? (ctorSymbol ? getJSDocComment(ctorSymbol, checker) : undefined),
2009
+ tags: ctorDoc?.tags
2010
+ });
2011
+ continue;
2012
+ }
2013
+ if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
2014
+ const memberName = member.name?.getText();
2015
+ if (!memberName)
2016
+ continue;
2017
+ const memberSymbol = checker.getSymbolAtLocation(member.name);
2018
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2019
+ const accessorType = ts.isGetAccessorDeclaration(member) ? checker.getTypeAtLocation(member) : member.parameters.length > 0 ? checker.getTypeAtLocation(member.parameters[0]) : checker.getTypeAtLocation(member);
2020
+ collectReferencedTypes(accessorType, checker, referencedTypes);
2021
+ const schema = formatTypeReference(accessorType, checker, typeRefs, referencedTypes);
2022
+ members.push({
2023
+ id: memberName,
2024
+ name: memberName,
2025
+ kind: "accessor",
2026
+ visibility: getMemberVisibility(member.modifiers),
2027
+ schema,
2028
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined),
2029
+ tags: memberDoc?.tags
2030
+ });
2031
+ }
2032
+ }
2033
+ return members;
2034
+ }
2035
+ function serializeSignature(signature, checker, typeRefs, referencedTypes, doc, symbol) {
2036
+ const typeParameters = serializeTypeParameterDeclarations(signature.declaration?.typeParameters, checker, referencedTypes);
2037
+ return {
2038
+ parameters: signature.getParameters().map((param) => {
2039
+ const paramDecl = param.valueDeclaration;
2040
+ const paramType = paramDecl?.type != null ? checker.getTypeFromTypeNode(paramDecl.type) : checker.getTypeAtLocation(paramDecl);
2041
+ collectReferencedTypes(paramType, checker, referencedTypes);
2042
+ const paramDoc = paramDecl ? getParameterDocumentation(param, paramDecl, checker) : undefined;
2043
+ return structureParameter(param, paramDecl, paramType, checker, typeRefs, doc, paramDoc, referencedTypes);
2044
+ }),
2045
+ returns: {
2046
+ schema: formatTypeReference(signature.getReturnType(), checker, typeRefs, referencedTypes),
2047
+ description: doc?.returns || ""
2048
+ },
2049
+ description: doc?.description || (symbol ? getJSDocComment(symbol, checker) : undefined),
2050
+ typeParameters
2051
+ };
2052
+ }
2053
+ function getMemberVisibility(modifiers) {
2054
+ if (!modifiers)
2055
+ return;
2056
+ if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)) {
2057
+ return "private";
2058
+ }
2059
+ if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.ProtectedKeyword)) {
2060
+ return "protected";
2061
+ }
2062
+ if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PublicKeyword)) {
2063
+ return "public";
2064
+ }
2065
+ return;
2066
+ }
2067
+ function getMethodFlags(member) {
2068
+ const flags = {};
2069
+ if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
2070
+ flags.static = true;
2071
+ }
2072
+ if (member.asteriskToken) {
2073
+ flags.generator = true;
2074
+ }
2075
+ if (member.questionToken) {
2076
+ flags.optional = true;
2077
+ }
2078
+ return Object.keys(flags).length > 0 ? flags : undefined;
2079
+ }
2080
+
2081
+ // src/analysis/serializers/enums.ts
2082
+ function serializeEnum(declaration, symbol, context) {
2083
+ const parsedDoc = parseJSDocComment(symbol, context.checker);
2084
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
2085
+ const metadata = extractPresentationMetadata(parsedDoc);
2086
+ const exportEntry = {
2087
+ id: symbol.getName(),
2088
+ name: symbol.getName(),
2089
+ ...metadata,
2090
+ kind: "enum",
2091
+ deprecated: isSymbolDeprecated(symbol),
2092
+ description,
2093
+ source: getSourceLocation(declaration),
2094
+ tags: parsedDoc?.tags
2095
+ };
2096
+ const typeDefinition = {
2097
+ id: symbol.getName(),
2098
+ name: symbol.getName(),
2099
+ ...metadata,
2100
+ kind: "enum",
2101
+ members: getEnumMembers(declaration),
2102
+ description,
2103
+ source: getSourceLocation(declaration),
2104
+ tags: parsedDoc?.tags
2105
+ };
2106
+ return {
2107
+ exportEntry,
2108
+ typeDefinition
2109
+ };
2110
+ }
2111
+ function getEnumMembers(enumDecl) {
2112
+ return enumDecl.members.map((member) => ({
2113
+ id: member.name?.getText() || "",
2114
+ name: member.name?.getText() || "",
2115
+ value: member.initializer ? member.initializer.getText() : undefined,
2116
+ description: ""
2117
+ }));
2118
+ }
2119
+
2120
+ // src/analysis/serializers/functions.ts
2121
+ function serializeCallSignatures(signatures, symbol, context, parsedDoc) {
2122
+ if (signatures.length === 0) {
2123
+ return [];
2124
+ }
2125
+ const { checker, typeRegistry } = context;
2126
+ const typeRefs = typeRegistry.getTypeRefs();
2127
+ const referencedTypes = typeRegistry.getReferencedTypes();
2128
+ const functionDoc = parsedDoc ?? (symbol ? parseJSDocComment(symbol, checker) : null);
2129
+ return signatures.map((signature) => {
2130
+ const parameters = signature.getParameters().map((param) => {
2131
+ const paramDecl = param.declarations?.find(ts.isParameter);
2132
+ const paramType = paramDecl ? paramDecl.type != null ? checker.getTypeFromTypeNode(paramDecl.type) : checker.getTypeAtLocation(paramDecl) : checker.getTypeOfSymbolAtLocation(param, symbol?.declarations?.[0] ?? signature.declaration ?? param.declarations?.[0] ?? param.valueDeclaration);
2133
+ collectReferencedTypes(paramType, checker, referencedTypes);
2134
+ if (paramDecl?.type) {
2135
+ collectReferencedTypesFromNode(paramDecl.type, checker, referencedTypes);
2136
+ }
2137
+ if (paramDecl && ts.isParameter(paramDecl)) {
2138
+ const paramDoc = getParameterDocumentation(param, paramDecl, checker);
2139
+ return structureParameter(param, paramDecl, paramType, checker, typeRefs, functionDoc, paramDoc, referencedTypes);
2140
+ }
2141
+ return {
2142
+ name: param.getName(),
2143
+ required: !(param.flags & ts.SymbolFlags.Optional),
2144
+ description: "",
2145
+ schema: formatTypeReference(paramType, checker, typeRefs, referencedTypes)
2146
+ };
2147
+ });
2148
+ const returnType = signature.getReturnType();
2149
+ const returnTypeText = returnType ? checker.typeToString(returnType) : undefined;
2150
+ if (returnType) {
2151
+ collectReferencedTypes(returnType, checker, referencedTypes);
2152
+ }
2153
+ const typeParameters = serializeTypeParameterDeclarations(signature.declaration?.typeParameters, checker, referencedTypes);
2154
+ return {
2155
+ parameters,
2156
+ returns: {
2157
+ schema: returnType ? formatTypeReference(returnType, checker, typeRefs, referencedTypes) : { type: "void" },
2158
+ description: functionDoc?.returns || "",
2159
+ tsType: returnTypeText
2160
+ },
2161
+ description: functionDoc?.description || undefined,
2162
+ typeParameters
2163
+ };
2164
+ });
2165
+ }
2166
+ function serializeFunctionExport(declaration, symbol, context) {
2167
+ const { checker } = context;
2168
+ const signature = checker.getSignatureFromDeclaration(declaration);
2169
+ const funcSymbol = checker.getSymbolAtLocation(declaration.name || declaration);
2170
+ const parsedDoc = parseJSDocComment(symbol, checker);
2171
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2172
+ const metadata = extractPresentationMetadata(parsedDoc);
2173
+ return {
2174
+ id: symbol.getName(),
2175
+ name: symbol.getName(),
2176
+ ...metadata,
2177
+ kind: "function",
2178
+ deprecated: isSymbolDeprecated(symbol),
2179
+ signatures: signature ? serializeCallSignatures([signature], funcSymbol ?? symbol, context, parsedDoc) : [],
2180
+ description,
2181
+ source: getSourceLocation(declaration),
2182
+ examples: parsedDoc?.examples,
2183
+ tags: parsedDoc?.tags
2184
+ };
2185
+ }
2186
+
2187
+ // src/analysis/serializers/interfaces.ts
2188
+ function serializeInterface(declaration, symbol, context) {
2189
+ const { checker, typeRegistry } = context;
2190
+ const parsedDoc = parseJSDocComment(symbol, checker);
2191
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2192
+ const metadata = extractPresentationMetadata(parsedDoc);
2193
+ const referencedTypes = typeRegistry.getReferencedTypes();
2194
+ const typeRefs = typeRegistry.getTypeRefs();
2195
+ const typeParameters = serializeTypeParameterDeclarations(declaration.typeParameters, checker, referencedTypes);
2196
+ const exportEntry = {
2197
+ id: symbol.getName(),
2198
+ name: symbol.getName(),
2199
+ ...metadata,
2200
+ kind: "interface",
2201
+ deprecated: isSymbolDeprecated(symbol),
2202
+ description,
2203
+ source: getSourceLocation(declaration),
2204
+ typeParameters,
2205
+ tags: parsedDoc?.tags
2206
+ };
2207
+ const schema = interfaceToSchema(declaration, checker, typeRefs, referencedTypes);
2208
+ const typeDefinition = {
2209
+ id: symbol.getName(),
2210
+ name: symbol.getName(),
2211
+ ...metadata,
2212
+ kind: "interface",
2213
+ schema,
2214
+ description,
2215
+ source: getSourceLocation(declaration),
2216
+ tags: parsedDoc?.tags
2217
+ };
2218
+ return {
2219
+ exportEntry,
2220
+ typeDefinition
2221
+ };
2222
+ }
2223
+ function interfaceToSchema(iface, typeChecker, typeRefs, referencedTypes) {
2224
+ const schema = {
2225
+ type: "object",
2226
+ properties: {}
2227
+ };
2228
+ const required = [];
2229
+ for (const prop of iface.members.filter(ts.isPropertySignature)) {
2230
+ const propName = prop.name?.getText() || "";
2231
+ if (prop.type) {
2232
+ const propType = typeChecker.getTypeAtLocation(prop.type);
2233
+ collectReferencedTypes(propType, typeChecker, referencedTypes);
2234
+ }
2235
+ schema.properties[propName] = prop.type ? formatTypeReference(typeChecker.getTypeAtLocation(prop.type), typeChecker, typeRefs, referencedTypes) : { type: "any" };
2236
+ if (!prop.questionToken) {
2237
+ required.push(propName);
2238
+ }
2239
+ }
2240
+ if (required.length > 0) {
2241
+ schema.required = required;
2242
+ }
2243
+ return schema;
2244
+ }
2245
+
2246
+ // src/analysis/serializers/type-aliases.ts
2247
+ function serializeTypeAlias(declaration, symbol, context) {
2248
+ const { checker, typeRegistry } = context;
2249
+ const typeRefs = typeRegistry.getTypeRefs();
2250
+ const referencedTypes = typeRegistry.getReferencedTypes();
2251
+ const parsedDoc = parseJSDocComment(symbol, checker);
2252
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2253
+ const metadata = extractPresentationMetadata(parsedDoc);
2254
+ const typeParameters = serializeTypeParameterDeclarations(declaration.typeParameters, checker, referencedTypes);
2255
+ const exportEntry = {
2256
+ id: symbol.getName(),
2257
+ name: symbol.getName(),
2258
+ ...metadata,
2259
+ kind: "type",
2260
+ deprecated: isSymbolDeprecated(symbol),
2261
+ type: typeToRef(declaration.type, checker, typeRefs, referencedTypes),
2262
+ description,
2263
+ source: getSourceLocation(declaration),
2264
+ typeParameters,
2265
+ tags: parsedDoc?.tags
2266
+ };
2267
+ const aliasType = checker.getTypeAtLocation(declaration.type);
2268
+ const aliasName = symbol.getName();
2269
+ const existingRef = typeRefs.get(aliasName);
2270
+ if (existingRef) {
2271
+ typeRefs.delete(aliasName);
2272
+ }
2273
+ const aliasSchema = formatTypeReference(aliasType, checker, typeRefs, undefined);
2274
+ if (existingRef) {
2275
+ typeRefs.set(aliasName, existingRef);
2276
+ }
2277
+ const typeDefinition = {
2278
+ id: symbol.getName(),
2279
+ name: symbol.getName(),
2280
+ ...metadata,
2281
+ kind: "type",
2282
+ description,
2283
+ source: getSourceLocation(declaration),
2284
+ tags: parsedDoc?.tags
2285
+ };
2286
+ if (typeof aliasSchema === "string") {
2287
+ typeDefinition.type = aliasSchema;
2288
+ } else if (aliasSchema && Object.keys(aliasSchema).length > 0) {
2289
+ typeDefinition.schema = aliasSchema;
2290
+ } else {
2291
+ typeDefinition.type = declaration.type.getText();
2292
+ }
2293
+ return {
2294
+ exportEntry,
2295
+ typeDefinition
2296
+ };
2297
+ }
2298
+ function typeToRef(node, typeChecker, typeRefs, referencedTypes) {
2299
+ const type = typeChecker.getTypeAtLocation(node);
2300
+ collectReferencedTypes(type, typeChecker, referencedTypes);
2301
+ return formatTypeReference(type, typeChecker, typeRefs, referencedTypes);
2302
+ }
2303
+
2304
+ // src/analysis/serializers/variables.ts
2305
+ function serializeVariable(declaration, symbol, context) {
2306
+ const { checker, typeRegistry } = context;
2307
+ const variableType = checker.getTypeAtLocation(declaration.name ?? declaration);
2308
+ const callSignatures = variableType.getCallSignatures();
2309
+ const parsedDoc = parseJSDocComment(symbol, checker);
2310
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2311
+ const metadata = extractPresentationMetadata(parsedDoc);
2312
+ if (callSignatures.length > 0) {
2313
+ return {
2314
+ id: symbol.getName(),
2315
+ name: symbol.getName(),
2316
+ ...metadata,
2317
+ kind: "function",
2318
+ deprecated: isSymbolDeprecated(symbol),
2319
+ signatures: serializeCallSignatures(callSignatures, symbol, context, parsedDoc),
2320
+ description,
2321
+ source: getSourceLocation(declaration.initializer ?? declaration),
2322
+ examples: parsedDoc?.examples,
2323
+ tags: parsedDoc?.tags
2324
+ };
2325
+ }
2326
+ const typeRefs = typeRegistry.getTypeRefs();
2327
+ const referencedTypes = typeRegistry.getReferencedTypes();
2328
+ return {
2329
+ id: symbol.getName(),
2330
+ name: symbol.getName(),
2331
+ ...metadata,
2332
+ kind: "variable",
2333
+ deprecated: isSymbolDeprecated(symbol),
2334
+ type: typeToRef2(declaration, checker, typeRefs, referencedTypes),
2335
+ description,
2336
+ source: getSourceLocation(declaration),
2337
+ tags: parsedDoc?.tags
2338
+ };
2339
+ }
2340
+ function typeToRef2(node, typeChecker, typeRefs, referencedTypes) {
2341
+ const type = typeChecker.getTypeAtLocation(node);
2342
+ collectReferencedTypes(type, typeChecker, referencedTypes);
2343
+ return formatTypeReference(type, typeChecker, typeRefs, referencedTypes);
2344
+ }
2345
+
2346
+ // src/analysis/type-registry.ts
2347
+ class TypeRegistry {
2348
+ typeRefs = new Map;
2349
+ typeDefinitions = new Map;
2350
+ referencedTypes = new Set;
2351
+ registerExportedType(name, id = name) {
2352
+ if (!this.typeRefs.has(name)) {
2353
+ this.typeRefs.set(name, id);
2354
+ }
2355
+ }
2356
+ hasType(name) {
2357
+ return this.typeDefinitions.has(name);
2358
+ }
2359
+ registerTypeDefinition(definition) {
2360
+ if (this.typeDefinitions.has(definition.name)) {
2361
+ return false;
2362
+ }
2363
+ this.typeDefinitions.set(definition.name, definition);
2364
+ if (!this.typeRefs.has(definition.name)) {
2365
+ this.typeRefs.set(definition.name, definition.id);
2366
+ }
2367
+ return true;
2368
+ }
2369
+ getTypeRefs() {
2370
+ return this.typeRefs;
2371
+ }
2372
+ getTypeDefinitions() {
2373
+ return Array.from(this.typeDefinitions.values());
2374
+ }
2375
+ getReferencedTypes() {
2376
+ return this.referencedTypes;
2377
+ }
2378
+ isKnownType(name) {
2379
+ if (this.typeDefinitions.has(name)) {
2380
+ return true;
2381
+ }
2382
+ const ref = this.typeRefs.get(name);
2383
+ if (ref === undefined) {
2384
+ return false;
2385
+ }
2386
+ if (ref !== name) {
2387
+ return this.typeDefinitions.has(ref);
2388
+ }
2389
+ return false;
2390
+ }
2391
+ }
2392
+
2393
+ // src/analysis/spec-builder.ts
2394
+ function buildOpenPkgSpec(context, resolveExternalTypes) {
2395
+ const { baseDir, checker: typeChecker, sourceFile, program } = context;
2396
+ const packageJsonPath = path3.join(baseDir, "package.json");
2397
+ const packageJson = fs.existsSync(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {};
2398
+ const spec = {
2399
+ $schema: SCHEMA_URL,
2400
+ openpkg: "0.2.0",
2401
+ meta: {
2402
+ name: packageJson.name || "unknown",
2403
+ version: packageJson.version || "1.0.0",
2404
+ description: packageJson.description || "",
2405
+ license: packageJson.license || "",
2406
+ repository: packageJson.repository?.url || packageJson.repository || "",
2407
+ ecosystem: "js/ts"
2408
+ },
2409
+ exports: [],
2410
+ types: []
2411
+ };
2412
+ const typeRegistry = new TypeRegistry;
2413
+ const serializerContext = {
2414
+ checker: typeChecker,
2415
+ typeRegistry
2416
+ };
2417
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
2418
+ if (!moduleSymbol) {
2419
+ return spec;
2420
+ }
2421
+ const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
2422
+ for (const symbol of exportedSymbols) {
2423
+ const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
2424
+ if (!declaration)
2425
+ continue;
2426
+ const exportName = symbol.getName();
2427
+ if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
2428
+ typeRegistry.registerExportedType(exportName, targetSymbol.getName());
2429
+ }
2430
+ }
2431
+ for (const symbol of exportedSymbols) {
2432
+ const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
2433
+ if (!declaration)
2434
+ continue;
2435
+ const exportName = symbol.getName();
2436
+ if (ts.isFunctionDeclaration(declaration)) {
2437
+ const exportEntry = serializeFunctionExport(declaration, targetSymbol, serializerContext);
2438
+ addExport(spec, exportEntry, exportName, baseDir);
2439
+ } else if (ts.isClassDeclaration(declaration)) {
2440
+ const { exportEntry, typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
2441
+ addExport(spec, exportEntry, exportName, baseDir);
2442
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2443
+ } else if (ts.isInterfaceDeclaration(declaration)) {
2444
+ const { exportEntry, typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
2445
+ addExport(spec, exportEntry, exportName, baseDir);
2446
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2447
+ } else if (ts.isTypeAliasDeclaration(declaration)) {
2448
+ const { exportEntry, typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
2449
+ addExport(spec, exportEntry, exportName, baseDir);
2450
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2451
+ } else if (ts.isEnumDeclaration(declaration)) {
2452
+ const { exportEntry, typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
2453
+ addExport(spec, exportEntry, exportName, baseDir);
2454
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2455
+ } else if (ts.isVariableDeclaration(declaration)) {
2456
+ const exportEntry = serializeVariable(declaration, targetSymbol, serializerContext);
2457
+ addExport(spec, exportEntry, exportName, baseDir);
2458
+ }
2459
+ }
2460
+ for (const typeName of typeRegistry.getReferencedTypes()) {
2461
+ if (typeRegistry.isKnownType(typeName)) {
2462
+ continue;
2463
+ }
2464
+ const allSourceFiles = program.getSourceFiles();
2465
+ for (const file of allSourceFiles) {
2466
+ if (!resolveExternalTypes && (file.fileName.includes("node_modules") || file.fileName.endsWith(".d.ts") && !file.fileName.startsWith(baseDir))) {
2467
+ continue;
2468
+ }
2469
+ const fileSymbol = typeChecker.getSymbolAtLocation(file);
2470
+ if (!fileSymbol) {
2471
+ continue;
2472
+ }
2473
+ const exports = typeChecker.getExportsOfModule(fileSymbol);
2474
+ for (const exportSymbol of exports) {
2475
+ if (exportSymbol.getName() !== typeName || typeRegistry.isKnownType(typeName)) {
2476
+ continue;
2477
+ }
2478
+ const { declaration, targetSymbol } = resolveExportTarget(exportSymbol, typeChecker);
2479
+ if (!declaration)
2480
+ continue;
2481
+ if (ts.isClassDeclaration(declaration)) {
2482
+ const { typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
2483
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2484
+ } else if (ts.isInterfaceDeclaration(declaration)) {
2485
+ const { typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
2486
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2487
+ } else if (ts.isTypeAliasDeclaration(declaration)) {
2488
+ const { typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
2489
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2490
+ } else if (ts.isEnumDeclaration(declaration)) {
2491
+ const { typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
2492
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2493
+ }
2494
+ }
2495
+ }
2496
+ }
2497
+ const coverage = computeDocsCoverage(spec);
2498
+ spec.docs = coverage.spec;
2499
+ spec.exports.forEach((entry) => {
2500
+ const exportCoverage = coverage.exports.get(entry.id);
2501
+ if (exportCoverage) {
2502
+ entry.docs = exportCoverage;
2503
+ }
2504
+ });
2505
+ return spec;
2506
+ }
2507
+ function addExport(spec, entry, exportName, baseDir) {
2508
+ const named = withExportName(entry, exportName);
2509
+ spec.exports.push(applyPresentationDefaults(named, baseDir));
2510
+ }
2511
+ function addTypeDefinition(spec, typeRegistry, definition, baseDir) {
2512
+ if (!definition) {
2513
+ return;
2514
+ }
2515
+ const enriched = applyPresentationDefaults(definition, baseDir);
2516
+ if (typeRegistry.registerTypeDefinition(enriched)) {
2517
+ spec.types?.push(enriched);
2518
+ }
2519
+ }
2520
+ function applyPresentationDefaults(entry, baseDir) {
2521
+ const slug = entry.slug ?? createSlug(entry.name);
2522
+ const displayName = entry.displayName ?? entry.name;
2523
+ const category = entry.category ?? entry.kind;
2524
+ const importPath = entry.importPath ?? deriveImportPath(entry.source?.file, baseDir);
2525
+ return {
2526
+ ...entry,
2527
+ ...slug ? { slug } : {},
2528
+ ...displayName ? { displayName } : {},
2529
+ ...category ? { category } : {},
2530
+ ...importPath ? { importPath } : {}
2531
+ };
2532
+ }
2533
+ function createSlug(name) {
2534
+ const normalized = name.replace(/([a-z\d])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/--+/g, "-").toLowerCase();
2535
+ return normalized || name.toLowerCase();
2536
+ }
2537
+ function deriveImportPath(sourceFile, baseDir) {
2538
+ if (!sourceFile) {
2539
+ return;
2540
+ }
2541
+ const relative2 = path3.relative(baseDir, sourceFile);
2542
+ if (!relative2 || relative2.startsWith("..")) {
2543
+ return;
2544
+ }
2545
+ const normalized = relative2.replace(/\\/g, "/");
2546
+ const withoutExt = normalized.replace(/\.[^.]+$/, "");
2547
+ if (!withoutExt) {
2548
+ return;
2549
+ }
2550
+ const prefixed = withoutExt.startsWith(".") ? withoutExt : `./${withoutExt}`;
2551
+ return prefixed.replace(/\/\/+/, "/");
2552
+ }
2553
+ function resolveExportTarget(symbol, checker) {
2554
+ let targetSymbol = symbol;
2555
+ if (symbol.flags & ts.SymbolFlags.Alias) {
2556
+ const aliasTarget = checker.getImmediateAliasedSymbol(symbol);
2557
+ if (aliasTarget) {
2558
+ targetSymbol = aliasTarget;
2559
+ }
2560
+ }
2561
+ const declarations = targetSymbol.declarations ?? [];
2562
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts.SyntaxKind.ExportSpecifier) || declarations[0];
2563
+ return {
2564
+ declaration,
2565
+ targetSymbol
2566
+ };
2567
+ }
2568
+ function withExportName(entry, exportName) {
2569
+ if (entry.name === exportName) {
2570
+ return entry;
2571
+ }
2572
+ return {
2573
+ ...entry,
2574
+ id: exportName,
2575
+ name: exportName
2576
+ };
2577
+ }
2578
+
2579
+ // src/analysis/run-analysis.ts
2580
+ function findNearestPackageJson(startDir) {
2581
+ let current = startDir;
2582
+ while (true) {
2583
+ const candidate = path4.join(current, "package.json");
2584
+ if (fs2.existsSync(candidate)) {
2585
+ return candidate;
2586
+ }
2587
+ const parent = path4.dirname(current);
2588
+ if (parent === current) {
2589
+ return;
2590
+ }
2591
+ current = parent;
2592
+ }
2593
+ }
2594
+ function hasNodeModulesDirectory(directories) {
2595
+ for (const dir of directories) {
2596
+ let current = dir;
2597
+ while (true) {
2598
+ const candidate = path4.join(current, "node_modules");
2599
+ if (fs2.existsSync(candidate)) {
2600
+ return true;
2601
+ }
2602
+ const parent = path4.dirname(current);
2603
+ if (parent === current) {
2604
+ break;
2605
+ }
2606
+ current = parent;
2607
+ }
2608
+ }
2609
+ return false;
2610
+ }
2611
+ function runAnalysis(input) {
2612
+ const context = createAnalysisContext(input);
2613
+ const { baseDir, options } = context;
2614
+ const packageJsonPath = findNearestPackageJson(baseDir);
2615
+ const searchDirs = new Set([baseDir]);
2616
+ if (packageJsonPath) {
2617
+ searchDirs.add(path4.dirname(packageJsonPath));
2618
+ }
2619
+ const hasNodeModules = hasNodeModulesDirectory(searchDirs);
2620
+ const resolveExternalTypes = options.resolveExternalTypes !== undefined ? options.resolveExternalTypes : hasNodeModules;
2621
+ const diagnostics = ts.getPreEmitDiagnostics(context.program).filter((d) => {
2622
+ if (d.code === 5053)
2623
+ return false;
2624
+ const msg = ts.flattenDiagnosticMessageText(d.messageText, `
2625
+ `);
2626
+ return !/allowJs/i.test(msg);
2627
+ });
2628
+ const spec = buildOpenPkgSpec(context, resolveExternalTypes);
2629
+ return {
2630
+ spec,
2631
+ metadata: {
2632
+ baseDir,
2633
+ configPath: context.configPath,
2634
+ packageJsonPath,
2635
+ hasNodeModules,
2636
+ resolveExternalTypes
2637
+ },
2638
+ diagnostics
2639
+ };
2640
+ }
2641
+
2642
+ // src/extractor.ts
2643
+ async function extractPackageSpec(entryFile, packageDir, content, options) {
2644
+ const result = runAnalysis({
2645
+ entryFile,
2646
+ packageDir,
2647
+ content,
2648
+ options
2649
+ });
2650
+ return result.spec;
2651
+ }
2652
+ // src/openpkg.ts
2653
+ import * as fsSync from "node:fs";
2654
+ import * as fs3 from "node:fs/promises";
2655
+ import * as path5 from "node:path";
2656
+
2657
+ // src/filtering/apply-filters.ts
2658
+ var TYPE_REF_PREFIX = "#/types/";
2659
+ var toLowerKey = (value) => value.trim().toLowerCase();
2660
+ var buildLookupMap = (values) => {
2661
+ const map = new Map;
2662
+ if (!values) {
2663
+ return map;
2664
+ }
2665
+ for (const value of values) {
2666
+ const key = toLowerKey(value);
2667
+ if (!map.has(key)) {
2668
+ map.set(key, value);
2669
+ }
2670
+ }
2671
+ return map;
2672
+ };
2673
+ var matches = (candidate, lookup) => {
2674
+ if (!candidate) {
2675
+ return;
2676
+ }
2677
+ const keys = [candidate.id, candidate.name];
2678
+ for (const key of keys) {
2679
+ if (!key) {
2680
+ continue;
2681
+ }
2682
+ const normalized = toLowerKey(key);
2683
+ if (lookup.has(normalized)) {
2684
+ return normalized;
2685
+ }
2686
+ }
2687
+ return;
2688
+ };
2689
+ var collectTypeRefs = (value, refs, seen = new Set) => {
2690
+ if (value === null || value === undefined) {
2691
+ return;
2692
+ }
2693
+ if (typeof value !== "object") {
2694
+ return;
2695
+ }
2696
+ if (seen.has(value)) {
2697
+ return;
2698
+ }
2699
+ seen.add(value);
2700
+ if (Array.isArray(value)) {
2701
+ for (const item of value) {
2702
+ collectTypeRefs(item, refs, seen);
2703
+ }
2704
+ return;
2705
+ }
2706
+ const record = value;
2707
+ for (const [key, nested] of Object.entries(record)) {
2708
+ if (key === "$ref" && typeof nested === "string" && nested.startsWith(TYPE_REF_PREFIX)) {
2709
+ const typeId = nested.slice(TYPE_REF_PREFIX.length);
2710
+ if (typeId) {
2711
+ refs.add(typeId);
2712
+ }
2713
+ }
2714
+ collectTypeRefs(nested, refs, seen);
2715
+ }
2716
+ };
2717
+ var applyFilters = (spec, options) => {
2718
+ const includeLookup = buildLookupMap(options.include);
2719
+ const excludeLookup = buildLookupMap(options.exclude);
2720
+ if (includeLookup.size === 0 && excludeLookup.size === 0) {
2721
+ return { spec, diagnostics: [], changed: false };
2722
+ }
2723
+ const includeMatches = new Set;
2724
+ const diagnostics = [];
2725
+ const exportsList = spec.exports ?? [];
2726
+ const typesList = spec.types ?? [];
2727
+ const keptExports = [];
2728
+ for (const entry of exportsList) {
2729
+ const includeMatch = includeLookup.size === 0 ? undefined : matches(entry, includeLookup);
2730
+ const excludeMatch = matches(entry, excludeLookup);
2731
+ const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
2732
+ const allowedByExclude = !excludeMatch;
2733
+ if (includeMatch) {
2734
+ includeMatches.add(includeMatch);
2735
+ }
2736
+ if (allowedByInclude && allowedByExclude) {
2737
+ keptExports.push(entry);
2738
+ }
2739
+ }
2740
+ const typeMap = new Map(typesList.map((typeEntry) => [typeEntry.id, typeEntry]));
2741
+ const requestedTypeIds = new Set;
2742
+ const excludedTypeIds = new Set;
2743
+ for (const typeEntry of typesList) {
2744
+ const includeMatch = includeLookup.size === 0 ? undefined : matches(typeEntry, includeLookup);
2745
+ if (includeMatch) {
2746
+ includeMatches.add(includeMatch);
2747
+ requestedTypeIds.add(typeEntry.id);
2748
+ }
2749
+ const excludeMatch = matches(typeEntry, excludeLookup);
2750
+ if (excludeMatch) {
2751
+ excludedTypeIds.add(typeEntry.id);
2752
+ }
2753
+ }
2754
+ const referencedTypeIds = new Set;
2755
+ for (const entry of keptExports) {
2756
+ collectTypeRefs(entry, referencedTypeIds);
2757
+ }
2758
+ for (const requestedId of requestedTypeIds) {
2759
+ referencedTypeIds.add(requestedId);
2760
+ }
2761
+ const processedTypeIds = new Set;
2762
+ const finalTypeIds = new Set;
2763
+ const excludedButReferenced = new Set;
2764
+ const queue = Array.from(referencedTypeIds);
2765
+ while (queue.length > 0) {
2766
+ const currentId = queue.pop();
2767
+ if (!currentId || processedTypeIds.has(currentId)) {
2768
+ continue;
2769
+ }
2770
+ processedTypeIds.add(currentId);
2771
+ if (excludedTypeIds.has(currentId)) {
2772
+ excludedButReferenced.add(currentId);
2773
+ continue;
2774
+ }
2775
+ if (!typeMap.has(currentId)) {
2776
+ continue;
2777
+ }
2778
+ finalTypeIds.add(currentId);
2779
+ const typeEntry = typeMap.get(currentId);
2780
+ if (typeEntry) {
2781
+ const nestedRefs = new Set;
2782
+ collectTypeRefs(typeEntry, nestedRefs);
2783
+ for (const ref of nestedRefs) {
2784
+ if (!processedTypeIds.has(ref)) {
2785
+ queue.push(ref);
2786
+ }
2787
+ }
2788
+ }
2789
+ }
2790
+ if (includeLookup.size > 0 && keptExports.length === 0 && finalTypeIds.size === 0) {
2791
+ diagnostics.push({
2792
+ message: "Include filters did not match any exports or types.",
2793
+ severity: "warning"
2794
+ });
2795
+ }
2796
+ if (excludedButReferenced.size > 0) {
2797
+ const labels = Array.from(excludedButReferenced).map((id) => {
2798
+ const entry = typeMap.get(id);
2799
+ return entry?.name ?? id;
2800
+ });
2801
+ diagnostics.push({
2802
+ message: `Excluded types are still referenced: ${labels.join(", ")}`,
2803
+ severity: "warning",
2804
+ target: "type"
2805
+ });
2806
+ }
2807
+ const unmatchedIncludes = Array.from(includeLookup.keys()).filter((key) => !includeMatches.has(key));
2808
+ if (unmatchedIncludes.length > 0) {
2809
+ const labels = unmatchedIncludes.map((key) => includeLookup.get(key) ?? key);
2810
+ diagnostics.push({
2811
+ message: `Include filters with no matches: ${labels.join(", ")}`,
2812
+ severity: "warning",
2813
+ target: "type"
2814
+ });
2815
+ }
2816
+ const filteredTypes = typesList.filter((typeEntry) => finalTypeIds.has(typeEntry.id));
2817
+ const baseSpec = {
2818
+ ...spec,
2819
+ exports: keptExports,
2820
+ types: filteredTypes.length > 0 ? filteredTypes : spec.types ? [] : undefined
2821
+ };
2822
+ const filteredSpec = spec.docs ? { ...baseSpec, docs: computeDocsCoverage(baseSpec).spec } : baseSpec;
2823
+ const changed = keptExports.length !== exportsList.length || filteredTypes.length !== typesList.length;
2824
+ return {
2825
+ spec: filteredSpec,
2826
+ diagnostics,
2827
+ changed
2828
+ };
2829
+ };
2830
+
2831
+ // src/openpkg.ts
2832
+ class DocCov {
2833
+ options;
2834
+ constructor(options = {}) {
2835
+ this.options = normalizeDocCovOptions(options);
2836
+ }
2837
+ async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
2838
+ const resolvedFileName = path5.resolve(fileName);
2839
+ const tempDir = path5.dirname(resolvedFileName);
2840
+ const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
2841
+ return this.applySpecFilters(spec, analyzeOptions.filters).spec;
2842
+ }
2843
+ async analyzeFile(filePath, analyzeOptions = {}) {
2844
+ const resolvedPath = path5.resolve(filePath);
2845
+ const content = await fs3.readFile(resolvedPath, "utf-8");
2846
+ const packageDir = resolvePackageDir(resolvedPath);
2847
+ const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
2848
+ return this.applySpecFilters(spec, analyzeOptions.filters).spec;
2849
+ }
2850
+ async analyzeProject(entryPath, analyzeOptions = {}) {
2851
+ return this.analyzeFile(entryPath, analyzeOptions);
2852
+ }
2853
+ async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
2854
+ const resolvedFileName = path5.resolve(fileName ?? "temp.ts");
2855
+ const packageDir = resolvePackageDir(resolvedFileName);
2856
+ const analysis = runAnalysis({
2857
+ entryFile: resolvedFileName,
2858
+ packageDir,
2859
+ content: code,
2860
+ options: this.options
2861
+ });
2862
+ const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
2863
+ return {
2864
+ spec: filterOutcome.spec,
2865
+ diagnostics: [
2866
+ ...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
2867
+ ...filterOutcome.diagnostics
2868
+ ],
2869
+ metadata: this.normalizeMetadata(analysis.metadata)
2870
+ };
2871
+ }
2872
+ async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
2873
+ const resolvedPath = path5.resolve(filePath);
2874
+ const content = await fs3.readFile(resolvedPath, "utf-8");
2875
+ const packageDir = resolvePackageDir(resolvedPath);
2876
+ const analysis = runAnalysis({
2877
+ entryFile: resolvedPath,
2878
+ packageDir,
2879
+ content,
2880
+ options: this.options
2881
+ });
2882
+ const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
2883
+ return {
2884
+ spec: filterOutcome.spec,
2885
+ diagnostics: [
2886
+ ...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
2887
+ ...filterOutcome.diagnostics
2888
+ ],
2889
+ metadata: this.normalizeMetadata(analysis.metadata)
2890
+ };
2891
+ }
2892
+ normalizeDiagnostic(tsDiagnostic) {
2893
+ const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
2894
+ `);
2895
+ let location;
2896
+ if (tsDiagnostic.file && typeof tsDiagnostic.start === "number") {
2897
+ const { line, character } = tsDiagnostic.file.getLineAndCharacterOfPosition(tsDiagnostic.start);
2898
+ location = {
2899
+ file: tsDiagnostic.file.fileName,
2900
+ line: line + 1,
2901
+ column: character + 1
2902
+ };
2903
+ }
2904
+ const severity = this.mapSeverity(tsDiagnostic.category);
2905
+ return {
2906
+ message,
2907
+ severity,
2908
+ location
2909
+ };
2910
+ }
2911
+ mapSeverity(category) {
2912
+ switch (category) {
2913
+ case ts.DiagnosticCategory.Message:
2914
+ case ts.DiagnosticCategory.Suggestion:
2915
+ return "info";
2916
+ case ts.DiagnosticCategory.Warning:
2917
+ return "warning";
2918
+ default:
2919
+ return "error";
2920
+ }
2921
+ }
2922
+ normalizeMetadata(metadata) {
2923
+ return {
2924
+ baseDir: metadata.baseDir,
2925
+ configPath: metadata.configPath,
2926
+ packageJsonPath: metadata.packageJsonPath,
2927
+ hasNodeModules: metadata.hasNodeModules,
2928
+ resolveExternalTypes: metadata.resolveExternalTypes
2929
+ };
2930
+ }
2931
+ applySpecFilters(spec, filters) {
2932
+ if (!filters || !filters.include?.length && !filters.exclude?.length) {
2933
+ return { spec, diagnostics: [] };
2934
+ }
2935
+ const result = applyFilters(spec, filters);
2936
+ return {
2937
+ spec: result.spec,
2938
+ diagnostics: result.diagnostics.map((diagnostic) => ({
2939
+ message: diagnostic.message,
2940
+ severity: diagnostic.severity
2941
+ }))
2942
+ };
2943
+ }
2944
+ }
2945
+ async function analyze(code, options = {}) {
2946
+ return new DocCov().analyze(code, "temp.ts", options);
2947
+ }
2948
+ async function analyzeFile(filePath, options = {}) {
2949
+ return new DocCov().analyzeFile(filePath, options);
2950
+ }
2951
+ var OpenPkg = DocCov;
2952
+ function resolvePackageDir(entryFile) {
2953
+ const fallbackDir = path5.dirname(entryFile);
2954
+ let currentDir = fallbackDir;
2955
+ while (true) {
2956
+ const candidate = path5.join(currentDir, "package.json");
2957
+ if (fsSync.existsSync(candidate)) {
2958
+ return currentDir;
2959
+ }
2960
+ const parentDir = path5.dirname(currentDir);
2961
+ if (parentDir === currentDir) {
2962
+ return fallbackDir;
2963
+ }
2964
+ currentDir = parentDir;
2965
+ }
2966
+ }
2967
+ export {
2968
+ extractPackageSpec,
2969
+ analyzeFile,
2970
+ analyze,
2971
+ OpenPkg,
2972
+ DocCov
2973
+ };