@doccov/sdk 0.2.2 → 0.3.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 CHANGED
@@ -1,114 +1,5 @@
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
1
  // src/analysis/docs-coverage.ts
2
+ import ts from "typescript";
112
3
  var DOC_SECTIONS = ["description", "params", "returns", "examples"];
113
4
  var SECTION_WEIGHT = 100 / DOC_SECTIONS.length;
114
5
  function computeDocsCoverage(spec) {
@@ -147,7 +38,10 @@ function evaluateExport(entry, exportRegistry) {
147
38
  ...detectDeprecatedDrift(entry),
148
39
  ...detectVisibilityDrift(entry),
149
40
  ...detectExampleDrift(entry, exportRegistry),
150
- ...detectBrokenLinks(entry, exportRegistry)
41
+ ...detectBrokenLinks(entry, exportRegistry),
42
+ ...detectExampleSyntaxErrors(entry),
43
+ ...detectAsyncMismatch(entry),
44
+ ...detectPropertyTypeDrift(entry)
151
45
  ];
152
46
  if (!hasDescription(entry)) {
153
47
  missing.push("description");
@@ -202,10 +96,16 @@ function detectParamDrift(entry) {
202
96
  return drifts;
203
97
  }
204
98
  const actualParamNames = new Set;
99
+ const paramProperties = new Map;
205
100
  for (const signature of signatures) {
206
101
  for (const param of signature.parameters ?? []) {
207
102
  if (param.name) {
208
103
  actualParamNames.add(param.name);
104
+ const schema = param.schema;
105
+ if (schema?.properties && typeof schema.properties === "object") {
106
+ const propNames = new Set(Object.keys(schema.properties));
107
+ paramProperties.set(param.name, propNames);
108
+ }
209
109
  }
210
110
  }
211
111
  }
@@ -220,6 +120,28 @@ function detectParamDrift(entry) {
220
120
  if (actualParamNames.has(documentedName)) {
221
121
  continue;
222
122
  }
123
+ if (documentedName.includes(".")) {
124
+ const [prefix, ...rest] = documentedName.split(".");
125
+ const propertyPath = rest.join(".");
126
+ if (actualParamNames.has(prefix)) {
127
+ const properties = paramProperties.get(prefix);
128
+ if (properties) {
129
+ const firstProperty = rest[0];
130
+ if (properties.has(firstProperty)) {
131
+ continue;
132
+ }
133
+ const suggestion2 = findClosestMatch(firstProperty, Array.from(properties));
134
+ drifts.push({
135
+ type: "param-mismatch",
136
+ target: documentedName,
137
+ issue: `JSDoc documents property "${propertyPath}" on parameter "${prefix}" which does not exist.`,
138
+ suggestion: suggestion2?.distance !== undefined && suggestion2.distance <= 3 ? `${prefix}.${suggestion2.value}` : undefined
139
+ });
140
+ continue;
141
+ }
142
+ continue;
143
+ }
144
+ }
223
145
  const suggestion = findClosestMatch(documentedName, Array.from(actualParamNames));
224
146
  drifts.push({
225
147
  type: "param-mismatch",
@@ -865,131 +787,444 @@ function detectBrokenLinks(entry, exportRegistry) {
865
787
  }
866
788
  return drifts;
867
789
  }
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
- }
790
+ function detectExampleSyntaxErrors(entry) {
791
+ if (!entry.examples || entry.examples.length === 0) {
792
+ return [];
880
793
  }
881
- if (type.isIntersection()) {
882
- for (const intersectionType of type.types) {
883
- collectReferencedTypes(intersectionType, typeChecker, referencedTypes, visitedTypes);
794
+ const drifts = [];
795
+ for (let i = 0;i < entry.examples.length; i++) {
796
+ const example = entry.examples[i];
797
+ if (typeof example !== "string")
798
+ continue;
799
+ const codeContent = example.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
800
+ if (!codeContent)
801
+ continue;
802
+ const sourceFile = ts.createSourceFile(`example-${i}.ts`, codeContent, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
803
+ const parseDiagnostics = sourceFile.parseDiagnostics;
804
+ if (parseDiagnostics && parseDiagnostics.length > 0) {
805
+ const firstError = parseDiagnostics[0];
806
+ const message = ts.flattenDiagnosticMessageText(firstError.messageText, `
807
+ `);
808
+ drifts.push({
809
+ type: "example-syntax-error",
810
+ target: `example[${i}]`,
811
+ issue: `@example contains invalid syntax: ${message}`,
812
+ suggestion: "Check for missing brackets, semicolons, or typos."
813
+ });
884
814
  }
885
815
  }
886
- if (type.isUnion()) {
887
- for (const unionType of type.types) {
888
- collectReferencedTypes(unionType, typeChecker, referencedTypes, visitedTypes);
889
- }
816
+ return drifts;
817
+ }
818
+ function detectExampleRuntimeErrors(entry, runtimeResults) {
819
+ if (!entry.examples || entry.examples.length === 0 || runtimeResults.size === 0) {
820
+ return [];
890
821
  }
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
- }
822
+ const drifts = [];
823
+ for (let i = 0;i < entry.examples.length; i++) {
824
+ const result = runtimeResults.get(i);
825
+ if (!result || result.success) {
826
+ continue;
900
827
  }
828
+ const errorMessage = extractErrorMessage(result.stderr);
829
+ const isTimeout = result.stderr.includes("timed out");
830
+ drifts.push({
831
+ type: "example-runtime-error",
832
+ target: `example[${i}]`,
833
+ issue: isTimeout ? `@example timed out after ${result.duration}ms.` : `@example throws at runtime: ${errorMessage}`,
834
+ suggestion: isTimeout ? "Check for infinite loops or long-running operations." : "Fix the example code or update it to match the current API."
835
+ });
901
836
  }
837
+ return drifts;
902
838
  }
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;
839
+ function extractErrorMessage(stderr) {
840
+ const lines = stderr.split(`
841
+ `).filter((line) => line.trim());
842
+ if (lines.length === 0) {
843
+ return "Unknown error";
913
844
  }
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);
845
+ for (const line of lines) {
846
+ const errorMatch = line.match(/^(?:Error|TypeError|ReferenceError|SyntaxError):\s*(.+)/);
847
+ if (errorMatch) {
848
+ return errorMatch[0];
920
849
  }
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
850
  }
928
- if (ts.isArrayTypeNode(node)) {
929
- collectReferencedTypesFromNode(node.elementType, typeChecker, referencedTypes);
930
- return;
851
+ const firstLine = lines[0] ?? "Unknown error";
852
+ return firstLine.length > 100 ? `${firstLine.slice(0, 100)}...` : firstLine;
853
+ }
854
+ function detectAsyncMismatch(entry) {
855
+ const signatures = entry.signatures ?? [];
856
+ if (signatures.length === 0) {
857
+ return [];
931
858
  }
932
- if (ts.isParenthesizedTypeNode(node)) {
933
- collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
934
- return;
859
+ const drifts = [];
860
+ const returnsPromise = signatures.some((sig) => {
861
+ const returnType = sig.returns?.tsType ?? extractTypeFromSchema(sig.returns?.schema) ?? "";
862
+ return returnType.startsWith("Promise<") || returnType === "Promise";
863
+ });
864
+ const returnsTag = entry.tags?.find((tag) => tag.name === "returns" || tag.name === "return");
865
+ const documentedAsPromise = returnsTag?.text?.includes("Promise") ?? false;
866
+ const hasAsyncTag = entry.tags?.some((tag) => tag.name === "async");
867
+ const isAsyncFunction = entry.flags?.async === true;
868
+ if (returnsPromise && !documentedAsPromise && !hasAsyncTag) {
869
+ drifts.push({
870
+ type: "async-mismatch",
871
+ target: "returns",
872
+ issue: "Function returns Promise but documentation does not indicate async behavior.",
873
+ suggestion: "Add @async tag or document @returns {Promise<...>}."
874
+ });
935
875
  }
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
- }
876
+ if (!returnsPromise && (documentedAsPromise || hasAsyncTag) && !isAsyncFunction) {
877
+ drifts.push({
878
+ type: "async-mismatch",
879
+ target: "returns",
880
+ issue: "Documentation indicates async but function does not return Promise.",
881
+ suggestion: "Remove @async tag or update @returns type."
960
882
  });
961
- return;
962
883
  }
963
- if (ts.isTypeOperatorNode(node)) {
964
- collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
965
- return;
884
+ return drifts;
885
+ }
886
+ function detectPropertyTypeDrift(entry) {
887
+ const members = entry.members ?? [];
888
+ if (members.length === 0) {
889
+ return [];
966
890
  }
967
- if (ts.isIndexedAccessTypeNode(node)) {
968
- collectReferencedTypesFromNode(node.objectType, typeChecker, referencedTypes);
969
- collectReferencedTypesFromNode(node.indexType, typeChecker, referencedTypes);
970
- return;
891
+ const drifts = [];
892
+ for (const member of members) {
893
+ const typedMember = member;
894
+ if (typedMember.kind !== "property")
895
+ continue;
896
+ const typeTag = typedMember.tags?.find((tag) => tag.name === "type");
897
+ if (!typeTag?.text)
898
+ continue;
899
+ const documentedType = extractTypeFromBraces(typeTag.text);
900
+ if (!documentedType)
901
+ continue;
902
+ const actualType = extractTypeFromSchema(typedMember.schema);
903
+ if (!actualType)
904
+ continue;
905
+ const normalizedDoc = normalizeType(documentedType);
906
+ const normalizedActual = normalizeType(actualType);
907
+ if (!normalizedDoc || !normalizedActual)
908
+ continue;
909
+ if (!typesEquivalent(normalizedDoc, normalizedActual)) {
910
+ const memberName = typedMember.name ?? typedMember.id ?? "property";
911
+ drifts.push({
912
+ type: "property-type-drift",
913
+ target: memberName,
914
+ issue: `Property "${memberName}" documented as {${documentedType}} but actual type is ${actualType}.`,
915
+ suggestion: `Update @type {${actualType}} to match the declaration.`
916
+ });
917
+ }
971
918
  }
972
- if (ts.isLiteralTypeNode(node)) {
973
- return;
919
+ return drifts;
920
+ }
921
+ function extractTypeFromBraces(text) {
922
+ const match = text.match(/^\{([^}]+)\}/);
923
+ return match?.[1]?.trim();
924
+ }
925
+ function parseAssertions(code) {
926
+ const assertions = [];
927
+ const cleanCode = code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
928
+ const lines = cleanCode.split(`
929
+ `);
930
+ const assertionPattern = /\/\/\s*=>\s*(.+?)\s*$/;
931
+ for (let i = 0;i < lines.length; i++) {
932
+ const match = lines[i].match(assertionPattern);
933
+ if (match?.[1]) {
934
+ assertions.push({
935
+ lineNumber: i + 1,
936
+ expected: match[1].trim()
937
+ });
938
+ }
974
939
  }
975
- node.forEachChild((child) => {
976
- if (ts.isTypeNode(child)) {
977
- collectReferencedTypesFromNode(child, typeChecker, referencedTypes);
940
+ return assertions;
941
+ }
942
+ function hasNonAssertionComments(code) {
943
+ return /\/\/(?!\s*=>)/.test(code);
944
+ }
945
+ function detectExampleAssertionFailures(entry, runtimeResults) {
946
+ if (!entry.examples || entry.examples.length === 0 || runtimeResults.size === 0) {
947
+ return [];
948
+ }
949
+ const drifts = [];
950
+ for (let i = 0;i < entry.examples.length; i++) {
951
+ const example = entry.examples[i];
952
+ const result = runtimeResults.get(i);
953
+ if (!result || !result.success || typeof example !== "string") {
954
+ continue;
978
955
  }
979
- });
956
+ const assertions = parseAssertions(example);
957
+ if (assertions.length === 0) {
958
+ continue;
959
+ }
960
+ const stdoutLines = result.stdout.split(`
961
+ `).map((line) => line.trim()).filter((line) => line.length > 0);
962
+ for (let j = 0;j < assertions.length; j++) {
963
+ const assertion = assertions[j];
964
+ const actual = stdoutLines[j];
965
+ if (actual === undefined) {
966
+ drifts.push({
967
+ type: "example-assertion-failed",
968
+ target: `example[${i}]:line${assertion.lineNumber}`,
969
+ issue: `Assertion expected "${assertion.expected}" but no output was produced`,
970
+ suggestion: "Ensure the example produces output for each assertion"
971
+ });
972
+ continue;
973
+ }
974
+ if (assertion.expected.trim() !== actual.trim()) {
975
+ drifts.push({
976
+ type: "example-assertion-failed",
977
+ target: `example[${i}]:line${assertion.lineNumber}`,
978
+ issue: `Assertion failed: expected "${assertion.expected}" but got "${actual}"`,
979
+ suggestion: `Update assertion to: // => ${actual}`
980
+ });
981
+ }
982
+ }
983
+ }
984
+ return drifts;
980
985
  }
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",
986
+ // src/analysis/run-analysis.ts
987
+ import * as fs2 from "node:fs";
988
+ import * as path4 from "node:path";
989
+
990
+ // src/ts-module.ts
991
+ import * as tsNamespace from "typescript";
992
+ var resolvedTypeScriptModule = (() => {
993
+ const candidate = tsNamespace;
994
+ if (candidate.ScriptTarget === undefined && typeof candidate.default !== "undefined") {
995
+ return candidate.default;
996
+ }
997
+ return candidate;
998
+ })();
999
+ var ts2 = resolvedTypeScriptModule;
1000
+
1001
+ // src/analysis/context.ts
1002
+ import * as path2 from "node:path";
1003
+
1004
+ // src/options.ts
1005
+ var DEFAULT_OPTIONS = {
1006
+ includePrivate: false,
1007
+ followImports: true
1008
+ };
1009
+ function normalizeDocCovOptions(options = {}) {
1010
+ return {
1011
+ ...DEFAULT_OPTIONS,
1012
+ ...options
1013
+ };
1014
+ }
1015
+ var normalizeOpenPkgOptions = normalizeDocCovOptions;
1016
+
1017
+ // src/analysis/program.ts
1018
+ import * as path from "node:path";
1019
+ var DEFAULT_COMPILER_OPTIONS = {
1020
+ target: ts2.ScriptTarget.Latest,
1021
+ module: ts2.ModuleKind.CommonJS,
1022
+ lib: ["lib.es2021.d.ts"],
1023
+ declaration: true,
1024
+ moduleResolution: ts2.ModuleResolutionKind.NodeJs
1025
+ };
1026
+ function createProgram({
1027
+ entryFile,
1028
+ baseDir = path.dirname(entryFile),
1029
+ content
1030
+ }) {
1031
+ const configPath = ts2.findConfigFile(baseDir, ts2.sys.fileExists, "tsconfig.json");
1032
+ let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
1033
+ if (configPath) {
1034
+ const configFile = ts2.readConfigFile(configPath, ts2.sys.readFile);
1035
+ const parsedConfig = ts2.parseJsonConfigFileContent(configFile.config, ts2.sys, path.dirname(configPath));
1036
+ compilerOptions = { ...compilerOptions, ...parsedConfig.options };
1037
+ }
1038
+ const allowJsVal = compilerOptions.allowJs;
1039
+ if (typeof allowJsVal === "boolean" && allowJsVal) {
1040
+ compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
1041
+ }
1042
+ const compilerHost = ts2.createCompilerHost(compilerOptions, true);
1043
+ let inMemorySource;
1044
+ if (content !== undefined) {
1045
+ inMemorySource = ts2.createSourceFile(entryFile, content, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
1046
+ const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
1047
+ compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
1048
+ if (fileName === entryFile) {
1049
+ return inMemorySource;
1050
+ }
1051
+ return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
1052
+ };
1053
+ }
1054
+ const program = ts2.createProgram([entryFile], compilerOptions, compilerHost);
1055
+ const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
1056
+ return {
1057
+ program,
1058
+ compilerHost,
1059
+ compilerOptions,
1060
+ sourceFile,
1061
+ configPath
1062
+ };
1063
+ }
1064
+
1065
+ // src/analysis/context.ts
1066
+ function createAnalysisContext({
1067
+ entryFile,
1068
+ packageDir,
1069
+ content,
1070
+ options
1071
+ }) {
1072
+ const baseDir = packageDir ?? path2.dirname(entryFile);
1073
+ const normalizedOptions = normalizeOpenPkgOptions(options);
1074
+ const programResult = createProgram({ entryFile, baseDir, content });
1075
+ if (!programResult.sourceFile) {
1076
+ throw new Error(`Could not load ${entryFile}`);
1077
+ }
1078
+ return {
1079
+ entryFile,
1080
+ baseDir,
1081
+ program: programResult.program,
1082
+ checker: programResult.program.getTypeChecker(),
1083
+ sourceFile: programResult.sourceFile,
1084
+ compilerOptions: programResult.compilerOptions,
1085
+ compilerHost: programResult.compilerHost,
1086
+ options: normalizedOptions,
1087
+ configPath: programResult.configPath
1088
+ };
1089
+ }
1090
+
1091
+ // src/analysis/spec-builder.ts
1092
+ import * as fs from "node:fs";
1093
+ import * as path3 from "node:path";
1094
+ import { SCHEMA_URL } from "@openpkg-ts/spec";
1095
+
1096
+ // src/utils/type-utils.ts
1097
+ function getTypeId(type, typeChecker) {
1098
+ const internalId = type.id;
1099
+ if (internalId !== undefined) {
1100
+ return `id:${internalId}`;
1101
+ }
1102
+ return `str:${typeChecker.typeToString(type)}`;
1103
+ }
1104
+ function collectReferencedTypes(type, typeChecker, referencedTypes, visitedTypeIds = new Set) {
1105
+ const typeId = getTypeId(type, typeChecker);
1106
+ if (visitedTypeIds.has(typeId))
1107
+ return;
1108
+ visitedTypeIds.add(typeId);
1109
+ const symbol = type.getSymbol();
1110
+ if (symbol) {
1111
+ const symbolName = symbol.getName();
1112
+ if (!symbolName.startsWith("__") && !isBuiltInType(symbolName)) {
1113
+ referencedTypes.add(symbolName);
1114
+ }
1115
+ }
1116
+ if (type.isIntersection()) {
1117
+ for (const intersectionType of type.types) {
1118
+ collectReferencedTypes(intersectionType, typeChecker, referencedTypes, visitedTypeIds);
1119
+ }
1120
+ }
1121
+ if (type.isUnion()) {
1122
+ for (const unionType of type.types) {
1123
+ collectReferencedTypes(unionType, typeChecker, referencedTypes, visitedTypeIds);
1124
+ }
1125
+ }
1126
+ if (type.flags & ts2.TypeFlags.Object) {
1127
+ const objectType = type;
1128
+ if (objectType.objectFlags & ts2.ObjectFlags.Reference) {
1129
+ const typeRef = objectType;
1130
+ if (typeRef.typeArguments) {
1131
+ for (const typeArg of typeRef.typeArguments) {
1132
+ collectReferencedTypes(typeArg, typeChecker, referencedTypes, visitedTypeIds);
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ }
1138
+ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
1139
+ if (ts2.isTypeReferenceNode(node)) {
1140
+ const typeNameText = node.typeName.getText();
1141
+ const symbol = typeChecker.getSymbolAtLocation(node.typeName);
1142
+ const name = symbol?.getName() ?? typeNameText;
1143
+ if (!isBuiltInType(name)) {
1144
+ referencedTypes.add(name);
1145
+ }
1146
+ node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
1147
+ return;
1148
+ }
1149
+ if (ts2.isExpressionWithTypeArguments(node)) {
1150
+ const expressionText = node.expression.getText();
1151
+ const symbol = typeChecker.getSymbolAtLocation(node.expression);
1152
+ const name = symbol?.getName() ?? expressionText;
1153
+ if (!isBuiltInType(name)) {
1154
+ referencedTypes.add(name);
1155
+ }
1156
+ node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
1157
+ return;
1158
+ }
1159
+ if (ts2.isUnionTypeNode(node) || ts2.isIntersectionTypeNode(node)) {
1160
+ node.types.forEach((typeNode) => collectReferencedTypesFromNode(typeNode, typeChecker, referencedTypes));
1161
+ return;
1162
+ }
1163
+ if (ts2.isArrayTypeNode(node)) {
1164
+ collectReferencedTypesFromNode(node.elementType, typeChecker, referencedTypes);
1165
+ return;
1166
+ }
1167
+ if (ts2.isParenthesizedTypeNode(node)) {
1168
+ collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
1169
+ return;
1170
+ }
1171
+ if (ts2.isTypeLiteralNode(node)) {
1172
+ node.members.forEach((member) => {
1173
+ if (ts2.isPropertySignature(member) && member.type) {
1174
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
1175
+ }
1176
+ if (ts2.isMethodSignature(member)) {
1177
+ member.typeParameters?.forEach((param) => {
1178
+ param.constraint && collectReferencedTypesFromNode(param.constraint, typeChecker, referencedTypes);
1179
+ });
1180
+ member.parameters.forEach((param) => {
1181
+ if (param.type) {
1182
+ collectReferencedTypesFromNode(param.type, typeChecker, referencedTypes);
1183
+ }
1184
+ });
1185
+ if (member.type) {
1186
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
1187
+ }
1188
+ }
1189
+ if (ts2.isCallSignatureDeclaration(member) && member.type) {
1190
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
1191
+ }
1192
+ if (ts2.isIndexSignatureDeclaration(member) && member.type) {
1193
+ collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
1194
+ }
1195
+ });
1196
+ return;
1197
+ }
1198
+ if (ts2.isTypeOperatorNode(node)) {
1199
+ collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
1200
+ return;
1201
+ }
1202
+ if (ts2.isIndexedAccessTypeNode(node)) {
1203
+ collectReferencedTypesFromNode(node.objectType, typeChecker, referencedTypes);
1204
+ collectReferencedTypesFromNode(node.indexType, typeChecker, referencedTypes);
1205
+ return;
1206
+ }
1207
+ if (ts2.isLiteralTypeNode(node)) {
1208
+ return;
1209
+ }
1210
+ node.forEachChild((child) => {
1211
+ if (ts2.isTypeNode(child)) {
1212
+ collectReferencedTypesFromNode(child, typeChecker, referencedTypes);
1213
+ }
1214
+ });
1215
+ }
1216
+ function isBuiltInType(name) {
1217
+ const builtIns = [
1218
+ "string",
1219
+ "number",
1220
+ "boolean",
1221
+ "bigint",
1222
+ "symbol",
1223
+ "undefined",
1224
+ "null",
1225
+ "any",
1226
+ "unknown",
1227
+ "never",
993
1228
  "void",
994
1229
  "object",
995
1230
  "Array",
@@ -1061,11 +1296,11 @@ var BUILTIN_TYPE_SCHEMAS = {
1061
1296
  BigUint64Array: { type: "string", format: "byte" }
1062
1297
  };
1063
1298
  function isObjectLiteralType(type) {
1064
- if (!(type.getFlags() & ts.TypeFlags.Object)) {
1299
+ if (!(type.getFlags() & ts2.TypeFlags.Object)) {
1065
1300
  return false;
1066
1301
  }
1067
1302
  const objectFlags = type.objectFlags;
1068
- return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
1303
+ return (objectFlags & ts2.ObjectFlags.ObjectLiteral) !== 0;
1069
1304
  }
1070
1305
  function isPureRefSchema(value) {
1071
1306
  return Object.keys(value).length === 1 && "$ref" in value;
@@ -1119,28 +1354,28 @@ function propertiesToSchema(properties, description) {
1119
1354
  return schema;
1120
1355
  }
1121
1356
  function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
1122
- if (ts.isParenthesizedTypeNode(node)) {
1357
+ if (ts2.isParenthesizedTypeNode(node)) {
1123
1358
  return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
1124
1359
  }
1125
- if (ts.isIntersectionTypeNode(node)) {
1360
+ if (ts2.isIntersectionTypeNode(node)) {
1126
1361
  const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1127
1362
  return { allOf: schemas };
1128
1363
  }
1129
- if (ts.isUnionTypeNode(node)) {
1364
+ if (ts2.isUnionTypeNode(node)) {
1130
1365
  const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1131
1366
  return { anyOf: schemas };
1132
1367
  }
1133
- if (ts.isArrayTypeNode(node)) {
1368
+ if (ts2.isArrayTypeNode(node)) {
1134
1369
  return {
1135
1370
  type: "array",
1136
1371
  items: buildSchemaFromTypeNode(node.elementType, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName)
1137
1372
  };
1138
1373
  }
1139
- if (ts.isTypeLiteralNode(node)) {
1374
+ if (ts2.isTypeLiteralNode(node)) {
1140
1375
  const properties = {};
1141
1376
  const required = [];
1142
1377
  for (const member of node.members) {
1143
- if (!ts.isPropertySignature(member) || !member.name) {
1378
+ if (!ts2.isPropertySignature(member) || !member.name) {
1144
1379
  continue;
1145
1380
  }
1146
1381
  const propName = member.name.getText();
@@ -1178,7 +1413,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
1178
1413
  }
1179
1414
  return schema;
1180
1415
  }
1181
- if (ts.isTypeReferenceNode(node)) {
1416
+ if (ts2.isTypeReferenceNode(node)) {
1182
1417
  const typeName = node.typeName.getText();
1183
1418
  if (typeName === "Array") {
1184
1419
  return { type: "array" };
@@ -1196,15 +1431,15 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
1196
1431
  referencedTypes?.add(typeName);
1197
1432
  return { $ref: `#/types/${typeName}` };
1198
1433
  }
1199
- if (ts.isLiteralTypeNode(node)) {
1200
- if (ts.isStringLiteral(node.literal)) {
1434
+ if (ts2.isLiteralTypeNode(node)) {
1435
+ if (ts2.isStringLiteral(node.literal)) {
1201
1436
  return { enum: [node.literal.text] };
1202
1437
  }
1203
- if (ts.isNumericLiteral(node.literal)) {
1438
+ if (ts2.isNumericLiteral(node.literal)) {
1204
1439
  return { enum: [Number(node.literal.text)] };
1205
1440
  }
1206
1441
  }
1207
- if (ts.isIntersectionTypeNode(node)) {
1442
+ if (ts2.isIntersectionTypeNode(node)) {
1208
1443
  const schemas = node.types.map((typeNode) => buildSchemaFromTypeNode(typeNode, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
1209
1444
  if (schemas.some((schema) => ("$ref" in schema) && Object.keys(schema).length === 1)) {
1210
1445
  const refs = schemas.filter((schema) => ("$ref" in schema) && Object.keys(schema).length === 1);
@@ -1228,16 +1463,67 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
1228
1463
  }
1229
1464
  return { type: node.getText() };
1230
1465
  }
1231
- function getDocDescriptionForProperty(functionDoc, parentParamName, propName) {
1466
+ function getDocDescriptionForProperty(functionDoc, parentParamName, propName, inferredAlias) {
1232
1467
  if (!functionDoc) {
1233
1468
  return;
1234
1469
  }
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
- }
1470
+ let match = functionDoc.params.find((p) => p.name === `${parentParamName}.${propName}` || inferredAlias && p.name === `${inferredAlias}.${propName}` || parentParamName.match(/^__\d+$/) && p.name.endsWith(`.${propName}`));
1239
1471
  return match?.description;
1240
1472
  }
1473
+ function findDiscriminatorProperty(unionTypes, typeChecker) {
1474
+ const memberProps = [];
1475
+ for (const t of unionTypes) {
1476
+ if (t.flags & (ts2.TypeFlags.Null | ts2.TypeFlags.Undefined)) {
1477
+ continue;
1478
+ }
1479
+ const props = t.getProperties();
1480
+ if (!props || props.length === 0) {
1481
+ return;
1482
+ }
1483
+ const propValues = new Map;
1484
+ for (const prop of props) {
1485
+ const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1486
+ if (!declaration) {
1487
+ continue;
1488
+ }
1489
+ try {
1490
+ const propType = typeChecker.getTypeOfSymbolAtLocation(prop, declaration);
1491
+ if (propType.isStringLiteral()) {
1492
+ propValues.set(prop.getName(), propType.value);
1493
+ } else if (propType.isNumberLiteral()) {
1494
+ propValues.set(prop.getName(), propType.value);
1495
+ }
1496
+ } catch {
1497
+ continue;
1498
+ }
1499
+ }
1500
+ memberProps.push(propValues);
1501
+ }
1502
+ if (memberProps.length < 2) {
1503
+ return;
1504
+ }
1505
+ const firstMember = memberProps[0];
1506
+ for (const [propName, firstValue] of firstMember) {
1507
+ const values = new Set([firstValue]);
1508
+ let isDiscriminator = true;
1509
+ for (let i = 1;i < memberProps.length; i++) {
1510
+ const value = memberProps[i].get(propName);
1511
+ if (value === undefined) {
1512
+ isDiscriminator = false;
1513
+ break;
1514
+ }
1515
+ if (values.has(value)) {
1516
+ isDiscriminator = false;
1517
+ break;
1518
+ }
1519
+ values.add(value);
1520
+ }
1521
+ if (isDiscriminator) {
1522
+ return propName;
1523
+ }
1524
+ }
1525
+ return;
1526
+ }
1241
1527
  function schemaIsAny(schema) {
1242
1528
  if (typeof schema === "string") {
1243
1529
  return schema === "any";
@@ -1316,9 +1602,25 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
1316
1602
  }
1317
1603
  return { type: typeString };
1318
1604
  }
1605
+ if (type.getFlags() & ts2.TypeFlags.Object) {
1606
+ const objectType = type;
1607
+ if (objectType.objectFlags & ts2.ObjectFlags.Mapped) {
1608
+ return { type: "object", tsType: typeString };
1609
+ }
1610
+ }
1611
+ if (type.flags & ts2.TypeFlags.Conditional) {
1612
+ return { type: "object", tsType: typeString };
1613
+ }
1319
1614
  if (type.isUnion()) {
1320
1615
  const unionType = type;
1321
1616
  const parts = unionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes, visited));
1617
+ const discriminatorProp = findDiscriminatorProperty(unionType.types, typeChecker);
1618
+ if (discriminatorProp) {
1619
+ return {
1620
+ anyOf: parts,
1621
+ discriminator: { propertyName: discriminatorProp }
1622
+ };
1623
+ }
1322
1624
  return {
1323
1625
  anyOf: parts
1324
1626
  };
@@ -1346,7 +1648,7 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
1346
1648
  if (symbol) {
1347
1649
  const symbolName = symbol.getName();
1348
1650
  if (symbolName.startsWith("__")) {
1349
- if (type.getFlags() & ts.TypeFlags.Object) {
1651
+ if (type.getFlags() & ts2.TypeFlags.Object) {
1350
1652
  const properties = type.getProperties();
1351
1653
  if (properties.length > 0) {
1352
1654
  const objSchema = {
@@ -1358,7 +1660,7 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
1358
1660
  const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1359
1661
  const propName = prop.getName();
1360
1662
  objSchema.properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes, visited);
1361
- if (!(prop.flags & ts.SymbolFlags.Optional)) {
1663
+ if (!(prop.flags & ts2.SymbolFlags.Optional)) {
1362
1664
  required.push(propName);
1363
1665
  }
1364
1666
  }
@@ -1418,7 +1720,7 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
1418
1720
  }
1419
1721
  function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes) {
1420
1722
  const paramName = param.getName();
1421
- const isDestructured = paramName === "__0" || ts.isObjectBindingPattern(paramDecl.name) || ts.isArrayBindingPattern(paramDecl.name);
1723
+ const isDestructured = paramName === "__0" || ts2.isObjectBindingPattern(paramDecl.name) || ts2.isArrayBindingPattern(paramDecl.name);
1422
1724
  let inferredAlias;
1423
1725
  if (isDestructured && functionDoc && Array.isArray(functionDoc.params)) {
1424
1726
  const prefixes = functionDoc.params.map((p) => p?.name).filter((n) => typeof n === "string" && n.includes(".")).map((n) => n.split(".", 2)[0]).filter(Boolean);
@@ -1443,10 +1745,8 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1443
1745
  const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
1444
1746
  let description = "";
1445
1747
  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
- }
1748
+ const propName = prop.getName();
1749
+ let docParam = functionDoc.params.find((p) => p.name === `${paramName}.${propName}` || inferredAlias && p.name === `${inferredAlias}.${propName}` || paramName.match(/^__\d+$/) && p.name.endsWith(`.${propName}`));
1450
1750
  if (docParam) {
1451
1751
  description = docParam.description;
1452
1752
  }
@@ -1455,7 +1755,7 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1455
1755
  name: prop.getName(),
1456
1756
  type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1457
1757
  description,
1458
- optional: !!(prop.flags & ts.SymbolFlags.Optional)
1758
+ optional: !!(prop.flags & ts2.SymbolFlags.Optional)
1459
1759
  });
1460
1760
  }
1461
1761
  } else if (symbol2) {
@@ -1467,7 +1767,7 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1467
1767
  name: prop.getName(),
1468
1768
  type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1469
1769
  description: "",
1470
- optional: !!(prop.flags & ts.SymbolFlags.Optional)
1770
+ optional: !!(prop.flags & ts2.SymbolFlags.Optional)
1471
1771
  });
1472
1772
  }
1473
1773
  }
@@ -1498,7 +1798,7 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1498
1798
  name: prop.getName(),
1499
1799
  type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1500
1800
  description: "",
1501
- optional: !!(prop.flags & ts.SymbolFlags.Optional)
1801
+ optional: !!(prop.flags & ts2.SymbolFlags.Optional)
1502
1802
  });
1503
1803
  }
1504
1804
  if (properties.length > 0) {
@@ -1532,7 +1832,7 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1532
1832
  name: prop.getName(),
1533
1833
  type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
1534
1834
  description: "",
1535
- optional: !!(prop.flags & ts.SymbolFlags.Optional)
1835
+ optional: !!(prop.flags & ts2.SymbolFlags.Optional)
1536
1836
  });
1537
1837
  }
1538
1838
  const readableName2 = fallbackName;
@@ -1546,7 +1846,7 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1546
1846
  }
1547
1847
  return out2;
1548
1848
  }
1549
- if (paramType.flags & ts.TypeFlags.Any && paramDecl.type && paramDecl.name && ts.isObjectBindingPattern(paramDecl.name)) {
1849
+ if (paramType.flags & ts2.TypeFlags.Any && paramDecl.type && paramDecl.name && ts2.isObjectBindingPattern(paramDecl.name)) {
1550
1850
  const actualName = fallbackName;
1551
1851
  const schema2 = buildSchemaFromTypeNode(paramDecl.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, param.getName());
1552
1852
  const out2 = {
@@ -1599,21 +1899,56 @@ function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs,
1599
1899
  if (docDescription) {
1600
1900
  out.description = docDescription;
1601
1901
  }
1902
+ if (paramDecl.initializer) {
1903
+ const defaultText = paramDecl.initializer.getText();
1904
+ if (ts2.isStringLiteral(paramDecl.initializer)) {
1905
+ out.default = paramDecl.initializer.text;
1906
+ } else if (ts2.isNumericLiteral(paramDecl.initializer)) {
1907
+ out.default = Number(paramDecl.initializer.text);
1908
+ } else if (paramDecl.initializer.kind === ts2.SyntaxKind.TrueKeyword || paramDecl.initializer.kind === ts2.SyntaxKind.FalseKeyword) {
1909
+ out.default = paramDecl.initializer.kind === ts2.SyntaxKind.TrueKeyword;
1910
+ } else if (paramDecl.initializer.kind === ts2.SyntaxKind.NullKeyword) {
1911
+ out.default = null;
1912
+ } else {
1913
+ out.default = defaultText;
1914
+ }
1915
+ }
1916
+ if (paramDecl.dotDotDotToken) {
1917
+ out.rest = true;
1918
+ }
1602
1919
  return out;
1603
1920
  }
1604
1921
 
1605
1922
  // src/utils/tsdoc-utils.ts
1923
+ function findJSDocComment(commentRanges, sourceText) {
1924
+ if (!commentRanges || commentRanges.length === 0) {
1925
+ return;
1926
+ }
1927
+ for (let i = commentRanges.length - 1;i >= 0; i--) {
1928
+ const range = commentRanges[i];
1929
+ const text = sourceText.substring(range.pos, range.end);
1930
+ if (text.startsWith("/**")) {
1931
+ return range;
1932
+ }
1933
+ }
1934
+ return;
1935
+ }
1606
1936
  function parseJSDocComment(symbol, _typeChecker, sourceFileOverride) {
1607
1937
  const node = symbol.valueDeclaration || symbol.declarations?.[0];
1608
1938
  if (!node)
1609
1939
  return null;
1610
1940
  const sourceFile = sourceFileOverride || node.getSourceFile();
1611
- const commentRanges = ts.getLeadingCommentRanges(sourceFile.text, node.pos);
1612
- if (!commentRanges || commentRanges.length === 0) {
1941
+ let jsdocComment = findJSDocComment(ts2.getLeadingCommentRanges(sourceFile.text, node.pos), sourceFile.text);
1942
+ if (!jsdocComment && ts2.isVariableDeclaration(node) && node.parent?.parent) {
1943
+ const statement = node.parent.parent;
1944
+ if (ts2.isVariableStatement(statement)) {
1945
+ jsdocComment = findJSDocComment(ts2.getLeadingCommentRanges(sourceFile.text, statement.pos), sourceFile.text);
1946
+ }
1947
+ }
1948
+ if (!jsdocComment) {
1613
1949
  return null;
1614
1950
  }
1615
- const lastComment = commentRanges[commentRanges.length - 1];
1616
- const commentText = sourceFile.text.substring(lastComment.pos, lastComment.end);
1951
+ const commentText = sourceFile.text.substring(jsdocComment.pos, jsdocComment.end);
1617
1952
  return parseJSDocText(commentText);
1618
1953
  }
1619
1954
  function parseJSDocText(commentText) {
@@ -1794,7 +2129,7 @@ function getParameterDocumentation(param, paramDecl, typeChecker) {
1794
2129
  description: ""
1795
2130
  };
1796
2131
  const funcNode = paramDecl.parent;
1797
- if (ts.isFunctionDeclaration(funcNode) || ts.isFunctionExpression(funcNode)) {
2132
+ if (ts2.isFunctionDeclaration(funcNode) || ts2.isFunctionExpression(funcNode)) {
1798
2133
  const funcSymbol = typeChecker.getSymbolAtLocation(funcNode.name || funcNode);
1799
2134
  if (funcSymbol) {
1800
2135
  const parsedDoc = parseJSDocComment(funcSymbol, typeChecker);
@@ -1850,7 +2185,7 @@ function serializeTypeParameterDeclarations(typeParameters, checker, referencedT
1850
2185
  // src/analysis/ast-utils.ts
1851
2186
  function getJSDocComment(symbol, typeChecker) {
1852
2187
  const comments = symbol.getDocumentationComment(typeChecker);
1853
- return ts.displayPartsToString(comments);
2188
+ return ts2.displayPartsToString(comments);
1854
2189
  }
1855
2190
  function getSourceLocation(node) {
1856
2191
  const sourceFile = node.getSourceFile();
@@ -1869,7 +2204,7 @@ function isSymbolDeprecated(symbol) {
1869
2204
  return true;
1870
2205
  }
1871
2206
  for (const declaration of symbol.getDeclarations() ?? []) {
1872
- if (ts.getJSDocDeprecatedTag(declaration)) {
2207
+ if (ts2.getJSDocDeprecatedTag(declaration)) {
1873
2208
  return true;
1874
2209
  }
1875
2210
  }
@@ -1906,6 +2241,7 @@ function serializeClass(declaration, symbol, context) {
1906
2241
  const referencedTypes = typeRegistry.getReferencedTypes();
1907
2242
  const members = serializeClassMembers(declaration, checker, typeRefs, referencedTypes);
1908
2243
  const typeParameters = serializeTypeParameterDeclarations(declaration.typeParameters, checker, referencedTypes);
2244
+ const heritage = extractHeritageInfo(declaration, checker, referencedTypes);
1909
2245
  const parsedDoc = parseJSDocComment(symbol, context.checker);
1910
2246
  const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
1911
2247
  const metadata = extractPresentationMetadata(parsedDoc);
@@ -1919,7 +2255,10 @@ function serializeClass(declaration, symbol, context) {
1919
2255
  source: getSourceLocation(declaration),
1920
2256
  members: members.length > 0 ? members : undefined,
1921
2257
  typeParameters,
1922
- tags: parsedDoc?.tags
2258
+ ...heritage.extends ? { extends: heritage.extends } : {},
2259
+ ...heritage.implements && heritage.implements.length > 0 ? { implements: heritage.implements } : {},
2260
+ tags: parsedDoc?.tags,
2261
+ examples: parsedDoc?.examples
1923
2262
  };
1924
2263
  const typeDefinition = {
1925
2264
  id: symbol.getName(),
@@ -1929,6 +2268,8 @@ function serializeClass(declaration, symbol, context) {
1929
2268
  description,
1930
2269
  source: getSourceLocation(declaration),
1931
2270
  members: members.length > 0 ? members : undefined,
2271
+ ...heritage.extends ? { extends: heritage.extends } : {},
2272
+ ...heritage.implements && heritage.implements.length > 0 ? { implements: heritage.implements } : {},
1932
2273
  tags: parsedDoc?.tags
1933
2274
  };
1934
2275
  return {
@@ -1936,13 +2277,53 @@ function serializeClass(declaration, symbol, context) {
1936
2277
  typeDefinition
1937
2278
  };
1938
2279
  }
2280
+ function extractHeritageInfo(declaration, checker, referencedTypes) {
2281
+ const result = {};
2282
+ if (!declaration.heritageClauses) {
2283
+ return result;
2284
+ }
2285
+ for (const clause of declaration.heritageClauses) {
2286
+ if (clause.token === ts2.SyntaxKind.ExtendsKeyword) {
2287
+ const baseType = clause.types[0];
2288
+ if (baseType) {
2289
+ result.extends = baseType.expression.getText();
2290
+ collectReferencedTypesFromNode(baseType, checker, referencedTypes);
2291
+ }
2292
+ } else if (clause.token === ts2.SyntaxKind.ImplementsKeyword) {
2293
+ result.implements = clause.types.map((type) => {
2294
+ collectReferencedTypesFromNode(type, checker, referencedTypes);
2295
+ return type.expression.getText();
2296
+ });
2297
+ }
2298
+ }
2299
+ return result;
2300
+ }
1939
2301
  function serializeClassMembers(declaration, checker, typeRefs, referencedTypes) {
1940
2302
  const members = [];
2303
+ const accessorMap = new Map;
1941
2304
  for (const member of declaration.members) {
1942
- if (!member.name && !ts.isConstructorDeclaration(member)) {
1943
- continue;
1944
- }
1945
- if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
2305
+ if (ts2.isGetAccessorDeclaration(member)) {
2306
+ const name = member.name?.getText();
2307
+ if (name) {
2308
+ const existing = accessorMap.get(name) || {};
2309
+ existing.get = member;
2310
+ accessorMap.set(name, existing);
2311
+ }
2312
+ } else if (ts2.isSetAccessorDeclaration(member)) {
2313
+ const name = member.name?.getText();
2314
+ if (name) {
2315
+ const existing = accessorMap.get(name) || {};
2316
+ existing.set = member;
2317
+ accessorMap.set(name, existing);
2318
+ }
2319
+ }
2320
+ }
2321
+ const processedAccessors = new Set;
2322
+ for (const member of declaration.members) {
2323
+ if (!member.name && !ts2.isConstructorDeclaration(member)) {
2324
+ continue;
2325
+ }
2326
+ if (ts2.isPropertyDeclaration(member) || ts2.isPropertySignature(member)) {
1946
2327
  const memberName = member.name?.getText();
1947
2328
  if (!memberName)
1948
2329
  continue;
@@ -1952,14 +2333,14 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
1952
2333
  collectReferencedTypes(memberType, checker, referencedTypes);
1953
2334
  const schema = formatTypeReference(memberType, checker, typeRefs, referencedTypes);
1954
2335
  const flags = {};
1955
- const isOptionalSymbol = memberSymbol != null && (memberSymbol.flags & ts.SymbolFlags.Optional) !== 0;
2336
+ const isOptionalSymbol = memberSymbol != null && (memberSymbol.flags & ts2.SymbolFlags.Optional) !== 0;
1956
2337
  if (member.questionToken || isOptionalSymbol) {
1957
2338
  flags.optional = true;
1958
2339
  }
1959
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword)) {
2340
+ if (member.modifiers?.some((mod) => mod.kind === ts2.SyntaxKind.ReadonlyKeyword)) {
1960
2341
  flags.readonly = true;
1961
2342
  }
1962
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
2343
+ if (member.modifiers?.some((mod) => mod.kind === ts2.SyntaxKind.StaticKeyword)) {
1963
2344
  flags.static = true;
1964
2345
  }
1965
2346
  members.push({
@@ -1974,7 +2355,7 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
1974
2355
  });
1975
2356
  continue;
1976
2357
  }
1977
- if (ts.isMethodDeclaration(member)) {
2358
+ if (ts2.isMethodDeclaration(member)) {
1978
2359
  const memberName = member.name?.getText() ?? "method";
1979
2360
  const memberSymbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined;
1980
2361
  const methodDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
@@ -1994,7 +2375,7 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
1994
2375
  });
1995
2376
  continue;
1996
2377
  }
1997
- if (ts.isConstructorDeclaration(member)) {
2378
+ if (ts2.isConstructorDeclaration(member)) {
1998
2379
  const ctorSymbol = checker.getSymbolAtLocation(member);
1999
2380
  const ctorDoc = ctorSymbol ? parseJSDocComment(ctorSymbol, checker) : null;
2000
2381
  const signature = checker.getSignatureFromDeclaration(member);
@@ -2010,22 +2391,35 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
2010
2391
  });
2011
2392
  continue;
2012
2393
  }
2013
- if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
2394
+ if (ts2.isGetAccessorDeclaration(member) || ts2.isSetAccessorDeclaration(member)) {
2014
2395
  const memberName = member.name?.getText();
2015
- if (!memberName)
2396
+ if (!memberName || processedAccessors.has(memberName))
2397
+ continue;
2398
+ processedAccessors.add(memberName);
2399
+ const accessorPair = accessorMap.get(memberName);
2400
+ const hasGetter = !!accessorPair?.get;
2401
+ const hasSetter = !!accessorPair?.set;
2402
+ const primaryMember = accessorPair?.get || accessorPair?.set;
2403
+ if (!primaryMember)
2016
2404
  continue;
2017
- const memberSymbol = checker.getSymbolAtLocation(member.name);
2405
+ const memberSymbol = checker.getSymbolAtLocation(primaryMember.name);
2018
2406
  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);
2407
+ const accessorType = accessorPair?.get ? checker.getTypeAtLocation(accessorPair.get) : accessorPair?.set && accessorPair.set.parameters.length > 0 ? checker.getTypeAtLocation(accessorPair.set.parameters[0]) : checker.getTypeAtLocation(primaryMember);
2020
2408
  collectReferencedTypes(accessorType, checker, referencedTypes);
2021
2409
  const schema = formatTypeReference(accessorType, checker, typeRefs, referencedTypes);
2410
+ const flags = {};
2411
+ if (hasGetter)
2412
+ flags.readable = true;
2413
+ if (hasSetter)
2414
+ flags.writable = true;
2022
2415
  members.push({
2023
2416
  id: memberName,
2024
2417
  name: memberName,
2025
2418
  kind: "accessor",
2026
- visibility: getMemberVisibility(member.modifiers),
2419
+ visibility: getMemberVisibility(primaryMember.modifiers),
2027
2420
  schema,
2028
2421
  description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined),
2422
+ flags: Object.keys(flags).length > 0 ? flags : undefined,
2029
2423
  tags: memberDoc?.tags
2030
2424
  });
2031
2425
  }
@@ -2053,20 +2447,20 @@ function serializeSignature(signature, checker, typeRefs, referencedTypes, doc,
2053
2447
  function getMemberVisibility(modifiers) {
2054
2448
  if (!modifiers)
2055
2449
  return;
2056
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)) {
2450
+ if (modifiers.some((mod) => mod.kind === ts2.SyntaxKind.PrivateKeyword)) {
2057
2451
  return "private";
2058
2452
  }
2059
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.ProtectedKeyword)) {
2453
+ if (modifiers.some((mod) => mod.kind === ts2.SyntaxKind.ProtectedKeyword)) {
2060
2454
  return "protected";
2061
2455
  }
2062
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PublicKeyword)) {
2456
+ if (modifiers.some((mod) => mod.kind === ts2.SyntaxKind.PublicKeyword)) {
2063
2457
  return "public";
2064
2458
  }
2065
2459
  return;
2066
2460
  }
2067
2461
  function getMethodFlags(member) {
2068
2462
  const flags = {};
2069
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
2463
+ if (member.modifiers?.some((mod) => mod.kind === ts2.SyntaxKind.StaticKeyword)) {
2070
2464
  flags.static = true;
2071
2465
  }
2072
2466
  if (member.asteriskToken) {
@@ -2091,7 +2485,8 @@ function serializeEnum(declaration, symbol, context) {
2091
2485
  deprecated: isSymbolDeprecated(symbol),
2092
2486
  description,
2093
2487
  source: getSourceLocation(declaration),
2094
- tags: parsedDoc?.tags
2488
+ tags: parsedDoc?.tags,
2489
+ examples: parsedDoc?.examples
2095
2490
  };
2096
2491
  const typeDefinition = {
2097
2492
  id: symbol.getName(),
@@ -2128,19 +2523,19 @@ function serializeCallSignatures(signatures, symbol, context, parsedDoc) {
2128
2523
  const functionDoc = parsedDoc ?? (symbol ? parseJSDocComment(symbol, checker) : null);
2129
2524
  return signatures.map((signature) => {
2130
2525
  const parameters = signature.getParameters().map((param) => {
2131
- const paramDecl = param.declarations?.find(ts.isParameter);
2526
+ const paramDecl = param.declarations?.find(ts2.isParameter);
2132
2527
  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
2528
  collectReferencedTypes(paramType, checker, referencedTypes);
2134
2529
  if (paramDecl?.type) {
2135
2530
  collectReferencedTypesFromNode(paramDecl.type, checker, referencedTypes);
2136
2531
  }
2137
- if (paramDecl && ts.isParameter(paramDecl)) {
2532
+ if (paramDecl && ts2.isParameter(paramDecl)) {
2138
2533
  const paramDoc = getParameterDocumentation(param, paramDecl, checker);
2139
2534
  return structureParameter(param, paramDecl, paramType, checker, typeRefs, functionDoc, paramDoc, referencedTypes);
2140
2535
  }
2141
2536
  return {
2142
2537
  name: param.getName(),
2143
- required: !(param.flags & ts.SymbolFlags.Optional),
2538
+ required: !(param.flags & ts2.SymbolFlags.Optional),
2144
2539
  description: "",
2145
2540
  schema: formatTypeReference(paramType, checker, typeRefs, referencedTypes)
2146
2541
  };
@@ -2151,12 +2546,25 @@ function serializeCallSignatures(signatures, symbol, context, parsedDoc) {
2151
2546
  collectReferencedTypes(returnType, checker, referencedTypes);
2152
2547
  }
2153
2548
  const typeParameters = serializeTypeParameterDeclarations(signature.declaration?.typeParameters, checker, referencedTypes);
2549
+ const typePredicate = checker.getTypePredicateOfSignature(signature);
2550
+ let typePredicateInfo;
2551
+ if (typePredicate) {
2552
+ const paramName = typePredicate.kind === ts2.TypePredicateKind.This || typePredicate.kind === ts2.TypePredicateKind.AssertsThis ? "this" : typePredicate.parameterName ?? "";
2553
+ const predicateType = typePredicate.type ? checker.typeToString(typePredicate.type) : undefined;
2554
+ const isAsserts = typePredicate.kind === ts2.TypePredicateKind.AssertsThis || typePredicate.kind === ts2.TypePredicateKind.AssertsIdentifier;
2555
+ typePredicateInfo = {
2556
+ parameterName: paramName,
2557
+ type: predicateType ?? "unknown",
2558
+ ...isAsserts ? { asserts: true } : {}
2559
+ };
2560
+ }
2154
2561
  return {
2155
2562
  parameters,
2156
2563
  returns: {
2157
2564
  schema: returnType ? formatTypeReference(returnType, checker, typeRefs, referencedTypes) : { type: "void" },
2158
2565
  description: functionDoc?.returns || "",
2159
- tsType: returnTypeText
2566
+ tsType: returnTypeText,
2567
+ ...typePredicateInfo ? { typePredicate: typePredicateInfo } : {}
2160
2568
  },
2161
2569
  description: functionDoc?.description || undefined,
2162
2570
  typeParameters
@@ -2165,18 +2573,19 @@ function serializeCallSignatures(signatures, symbol, context, parsedDoc) {
2165
2573
  }
2166
2574
  function serializeFunctionExport(declaration, symbol, context) {
2167
2575
  const { checker } = context;
2168
- const signature = checker.getSignatureFromDeclaration(declaration);
2169
2576
  const funcSymbol = checker.getSymbolAtLocation(declaration.name || declaration);
2170
2577
  const parsedDoc = parseJSDocComment(symbol, checker);
2171
2578
  const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2172
2579
  const metadata = extractPresentationMetadata(parsedDoc);
2580
+ const type = checker.getTypeAtLocation(declaration.name || declaration);
2581
+ const callSignatures = type.getCallSignatures();
2173
2582
  return {
2174
2583
  id: symbol.getName(),
2175
2584
  name: symbol.getName(),
2176
2585
  ...metadata,
2177
2586
  kind: "function",
2178
2587
  deprecated: isSymbolDeprecated(symbol),
2179
- signatures: signature ? serializeCallSignatures([signature], funcSymbol ?? symbol, context, parsedDoc) : [],
2588
+ signatures: callSignatures.length > 0 ? serializeCallSignatures(callSignatures, funcSymbol ?? symbol, context, parsedDoc) : [],
2180
2589
  description,
2181
2590
  source: getSourceLocation(declaration),
2182
2591
  examples: parsedDoc?.examples,
@@ -2202,7 +2611,8 @@ function serializeInterface(declaration, symbol, context) {
2202
2611
  description,
2203
2612
  source: getSourceLocation(declaration),
2204
2613
  typeParameters,
2205
- tags: parsedDoc?.tags
2614
+ tags: parsedDoc?.tags,
2615
+ examples: parsedDoc?.examples
2206
2616
  };
2207
2617
  const schema = interfaceToSchema(declaration, checker, typeRefs, referencedTypes);
2208
2618
  const typeDefinition = {
@@ -2226,23 +2636,244 @@ function interfaceToSchema(iface, typeChecker, typeRefs, referencedTypes) {
2226
2636
  properties: {}
2227
2637
  };
2228
2638
  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);
2639
+ const callSignatures = [];
2640
+ const constructSignatures = [];
2641
+ for (const member of iface.members) {
2642
+ if (ts2.isIndexSignatureDeclaration(member)) {
2643
+ const indexParam = member.parameters[0];
2644
+ if (indexParam && member.type) {
2645
+ const indexType = typeChecker.getTypeAtLocation(indexParam);
2646
+ const valueType = typeChecker.getTypeAtLocation(member.type);
2647
+ collectReferencedTypes(valueType, typeChecker, referencedTypes);
2648
+ const valueSchema = formatTypeReference(valueType, typeChecker, typeRefs, referencedTypes);
2649
+ const indexTypeString = typeChecker.typeToString(indexType);
2650
+ if (indexTypeString === "string") {
2651
+ schema.additionalProperties = valueSchema;
2652
+ } else if (indexTypeString === "number") {
2653
+ schema.items = valueSchema;
2654
+ }
2655
+ }
2656
+ } else if (ts2.isPropertySignature(member)) {
2657
+ const propName = member.name?.getText() || "";
2658
+ if (member.type) {
2659
+ const propType = typeChecker.getTypeAtLocation(member.type);
2660
+ collectReferencedTypes(propType, typeChecker, referencedTypes);
2661
+ }
2662
+ schema.properties[propName] = member.type ? formatTypeReference(typeChecker.getTypeAtLocation(member.type), typeChecker, typeRefs, referencedTypes) : { type: "any" };
2663
+ if (!member.questionToken) {
2664
+ required.push(propName);
2665
+ }
2666
+ } else if (ts2.isMethodSignature(member)) {
2667
+ const methodName = member.name?.getText() || "";
2668
+ const signature = typeChecker.getSignatureFromDeclaration(member);
2669
+ if (signature) {
2670
+ const parameters = signature.getParameters().map((param) => {
2671
+ const paramDecl = param.declarations?.find(ts2.isParameter);
2672
+ const paramType = paramDecl ? typeChecker.getTypeAtLocation(paramDecl) : typeChecker.getTypeOfSymbolAtLocation(param, member);
2673
+ collectReferencedTypes(paramType, typeChecker, referencedTypes);
2674
+ if (paramDecl) {
2675
+ const paramDoc = getParameterDocumentation(param, paramDecl, typeChecker);
2676
+ return structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, null, paramDoc, referencedTypes);
2677
+ }
2678
+ return {
2679
+ name: param.getName(),
2680
+ required: !(param.flags & ts2.SymbolFlags.Optional),
2681
+ schema: formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes)
2682
+ };
2683
+ });
2684
+ const returnType = signature.getReturnType();
2685
+ if (returnType) {
2686
+ collectReferencedTypes(returnType, typeChecker, referencedTypes);
2687
+ }
2688
+ schema.properties[methodName] = {
2689
+ type: "function",
2690
+ parameters,
2691
+ returns: {
2692
+ schema: returnType ? formatTypeReference(returnType, typeChecker, typeRefs, referencedTypes) : { type: "void" }
2693
+ }
2694
+ };
2695
+ if (!member.questionToken) {
2696
+ required.push(methodName);
2697
+ }
2698
+ }
2699
+ } else if (ts2.isCallSignatureDeclaration(member)) {
2700
+ const signature = typeChecker.getSignatureFromDeclaration(member);
2701
+ if (signature) {
2702
+ const parameters = signature.getParameters().map((param) => {
2703
+ const paramDecl = param.declarations?.find(ts2.isParameter);
2704
+ const paramType = paramDecl ? typeChecker.getTypeAtLocation(paramDecl) : typeChecker.getTypeOfSymbolAtLocation(param, member);
2705
+ collectReferencedTypes(paramType, typeChecker, referencedTypes);
2706
+ if (paramDecl) {
2707
+ const paramDoc = getParameterDocumentation(param, paramDecl, typeChecker);
2708
+ return structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, null, paramDoc, referencedTypes);
2709
+ }
2710
+ return {
2711
+ name: param.getName(),
2712
+ required: !(param.flags & ts2.SymbolFlags.Optional),
2713
+ schema: formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes)
2714
+ };
2715
+ });
2716
+ const returnType = signature.getReturnType();
2717
+ if (returnType) {
2718
+ collectReferencedTypes(returnType, typeChecker, referencedTypes);
2719
+ }
2720
+ callSignatures.push({
2721
+ parameters,
2722
+ returns: {
2723
+ schema: returnType ? formatTypeReference(returnType, typeChecker, typeRefs, referencedTypes) : { type: "void" }
2724
+ }
2725
+ });
2726
+ }
2727
+ } else if (ts2.isConstructSignatureDeclaration(member)) {
2728
+ const signature = typeChecker.getSignatureFromDeclaration(member);
2729
+ if (signature) {
2730
+ const parameters = signature.getParameters().map((param) => {
2731
+ const paramDecl = param.declarations?.find(ts2.isParameter);
2732
+ const paramType = paramDecl ? typeChecker.getTypeAtLocation(paramDecl) : typeChecker.getTypeOfSymbolAtLocation(param, member);
2733
+ collectReferencedTypes(paramType, typeChecker, referencedTypes);
2734
+ if (paramDecl) {
2735
+ const paramDoc = getParameterDocumentation(param, paramDecl, typeChecker);
2736
+ return structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, null, paramDoc, referencedTypes);
2737
+ }
2738
+ return {
2739
+ name: param.getName(),
2740
+ required: !(param.flags & ts2.SymbolFlags.Optional),
2741
+ schema: formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes)
2742
+ };
2743
+ });
2744
+ const returnType = signature.getReturnType();
2745
+ if (returnType) {
2746
+ collectReferencedTypes(returnType, typeChecker, referencedTypes);
2747
+ }
2748
+ constructSignatures.push({
2749
+ parameters,
2750
+ returns: {
2751
+ schema: returnType ? formatTypeReference(returnType, typeChecker, typeRefs, referencedTypes) : { type: "object" }
2752
+ }
2753
+ });
2754
+ }
2238
2755
  }
2239
2756
  }
2240
2757
  if (required.length > 0) {
2241
2758
  schema.required = required;
2242
2759
  }
2760
+ if (callSignatures.length > 0) {
2761
+ schema.callSignatures = callSignatures;
2762
+ }
2763
+ if (constructSignatures.length > 0) {
2764
+ schema.constructSignatures = constructSignatures;
2765
+ }
2243
2766
  return schema;
2244
2767
  }
2245
2768
 
2769
+ // src/analysis/serializers/namespaces.ts
2770
+ function serializeNamespace(declaration, symbol, context) {
2771
+ const { checker } = context;
2772
+ const parsedDoc = parseJSDocComment(symbol, checker);
2773
+ const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
2774
+ const metadata = extractPresentationMetadata(parsedDoc);
2775
+ const members = extractNamespaceMembers(declaration, checker);
2776
+ return {
2777
+ id: symbol.getName(),
2778
+ name: symbol.getName(),
2779
+ ...metadata,
2780
+ kind: "namespace",
2781
+ deprecated: isSymbolDeprecated(symbol),
2782
+ description,
2783
+ source: getSourceLocation(declaration),
2784
+ members: members.length > 0 ? members : undefined,
2785
+ tags: parsedDoc?.tags,
2786
+ examples: parsedDoc?.examples
2787
+ };
2788
+ }
2789
+ function extractNamespaceMembers(declaration, checker) {
2790
+ const members = [];
2791
+ let body = declaration.body;
2792
+ while (body && ts2.isModuleDeclaration(body)) {
2793
+ body = body.body;
2794
+ }
2795
+ if (!body || !ts2.isModuleBlock(body)) {
2796
+ return members;
2797
+ }
2798
+ for (const statement of body.statements) {
2799
+ const hasExportModifier = ts2.canHaveModifiers(statement) && ts2.getModifiers(statement)?.some((mod) => mod.kind === ts2.SyntaxKind.ExportKeyword);
2800
+ if (!hasExportModifier) {
2801
+ continue;
2802
+ }
2803
+ if (ts2.isFunctionDeclaration(statement) && statement.name) {
2804
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2805
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2806
+ members.push({
2807
+ id: statement.name.getText(),
2808
+ name: statement.name.getText(),
2809
+ kind: "function",
2810
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2811
+ });
2812
+ } else if (ts2.isVariableStatement(statement)) {
2813
+ for (const decl of statement.declarationList.declarations) {
2814
+ if (ts2.isIdentifier(decl.name)) {
2815
+ const memberSymbol = checker.getSymbolAtLocation(decl.name);
2816
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2817
+ members.push({
2818
+ id: decl.name.getText(),
2819
+ name: decl.name.getText(),
2820
+ kind: "variable",
2821
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2822
+ });
2823
+ }
2824
+ }
2825
+ } else if (ts2.isInterfaceDeclaration(statement) && statement.name) {
2826
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2827
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2828
+ members.push({
2829
+ id: statement.name.getText(),
2830
+ name: statement.name.getText(),
2831
+ kind: "interface",
2832
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2833
+ });
2834
+ } else if (ts2.isTypeAliasDeclaration(statement) && statement.name) {
2835
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2836
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2837
+ members.push({
2838
+ id: statement.name.getText(),
2839
+ name: statement.name.getText(),
2840
+ kind: "type",
2841
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2842
+ });
2843
+ } else if (ts2.isEnumDeclaration(statement) && statement.name) {
2844
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2845
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2846
+ members.push({
2847
+ id: statement.name.getText(),
2848
+ name: statement.name.getText(),
2849
+ kind: "enum",
2850
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2851
+ });
2852
+ } else if (ts2.isClassDeclaration(statement) && statement.name) {
2853
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2854
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2855
+ members.push({
2856
+ id: statement.name.getText(),
2857
+ name: statement.name.getText(),
2858
+ kind: "class",
2859
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined)
2860
+ });
2861
+ } else if (ts2.isModuleDeclaration(statement) && statement.name) {
2862
+ const memberSymbol = checker.getSymbolAtLocation(statement.name);
2863
+ const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
2864
+ const nestedMembers = extractNamespaceMembers(statement, checker);
2865
+ members.push({
2866
+ id: ts2.isIdentifier(statement.name) ? statement.name.getText() : statement.name.text,
2867
+ name: ts2.isIdentifier(statement.name) ? statement.name.getText() : statement.name.text,
2868
+ kind: "namespace",
2869
+ description: memberDoc?.description ?? (memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined),
2870
+ members: nestedMembers.length > 0 ? nestedMembers : undefined
2871
+ });
2872
+ }
2873
+ }
2874
+ return members;
2875
+ }
2876
+
2246
2877
  // src/analysis/serializers/type-aliases.ts
2247
2878
  function serializeTypeAlias(declaration, symbol, context) {
2248
2879
  const { checker, typeRegistry } = context;
@@ -2262,7 +2893,8 @@ function serializeTypeAlias(declaration, symbol, context) {
2262
2893
  description,
2263
2894
  source: getSourceLocation(declaration),
2264
2895
  typeParameters,
2265
- tags: parsedDoc?.tags
2896
+ tags: parsedDoc?.tags,
2897
+ examples: parsedDoc?.examples
2266
2898
  };
2267
2899
  const aliasType = checker.getTypeAtLocation(declaration.type);
2268
2900
  const aliasName = symbol.getName();
@@ -2334,7 +2966,8 @@ function serializeVariable(declaration, symbol, context) {
2334
2966
  type: typeToRef2(declaration, checker, typeRefs, referencedTypes),
2335
2967
  description,
2336
2968
  source: getSourceLocation(declaration),
2337
- tags: parsedDoc?.tags
2969
+ tags: parsedDoc?.tags,
2970
+ examples: parsedDoc?.examples
2338
2971
  };
2339
2972
  }
2340
2973
  function typeToRef2(node, typeChecker, typeRefs, referencedTypes) {
@@ -2424,7 +3057,7 @@ function buildOpenPkgSpec(context, resolveExternalTypes) {
2424
3057
  if (!declaration)
2425
3058
  continue;
2426
3059
  const exportName = symbol.getName();
2427
- if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
3060
+ if (ts2.isClassDeclaration(declaration) || ts2.isInterfaceDeclaration(declaration) || ts2.isTypeAliasDeclaration(declaration) || ts2.isEnumDeclaration(declaration)) {
2428
3061
  typeRegistry.registerExportedType(exportName, targetSymbol.getName());
2429
3062
  }
2430
3063
  }
@@ -2433,28 +3066,31 @@ function buildOpenPkgSpec(context, resolveExternalTypes) {
2433
3066
  if (!declaration)
2434
3067
  continue;
2435
3068
  const exportName = symbol.getName();
2436
- if (ts.isFunctionDeclaration(declaration)) {
3069
+ if (ts2.isFunctionDeclaration(declaration)) {
2437
3070
  const exportEntry = serializeFunctionExport(declaration, targetSymbol, serializerContext);
2438
3071
  addExport(spec, exportEntry, exportName, baseDir);
2439
- } else if (ts.isClassDeclaration(declaration)) {
3072
+ } else if (ts2.isClassDeclaration(declaration)) {
2440
3073
  const { exportEntry, typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
2441
3074
  addExport(spec, exportEntry, exportName, baseDir);
2442
- addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2443
- } else if (ts.isInterfaceDeclaration(declaration)) {
3075
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir, exportName);
3076
+ } else if (ts2.isInterfaceDeclaration(declaration)) {
2444
3077
  const { exportEntry, typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
2445
3078
  addExport(spec, exportEntry, exportName, baseDir);
2446
- addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2447
- } else if (ts.isTypeAliasDeclaration(declaration)) {
3079
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir, exportName);
3080
+ } else if (ts2.isTypeAliasDeclaration(declaration)) {
2448
3081
  const { exportEntry, typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
2449
3082
  addExport(spec, exportEntry, exportName, baseDir);
2450
- addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2451
- } else if (ts.isEnumDeclaration(declaration)) {
3083
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir, exportName);
3084
+ } else if (ts2.isEnumDeclaration(declaration)) {
2452
3085
  const { exportEntry, typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
2453
3086
  addExport(spec, exportEntry, exportName, baseDir);
2454
- addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2455
- } else if (ts.isVariableDeclaration(declaration)) {
3087
+ addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir, exportName);
3088
+ } else if (ts2.isVariableDeclaration(declaration)) {
2456
3089
  const exportEntry = serializeVariable(declaration, targetSymbol, serializerContext);
2457
3090
  addExport(spec, exportEntry, exportName, baseDir);
3091
+ } else if (ts2.isModuleDeclaration(declaration)) {
3092
+ const exportEntry = serializeNamespace(declaration, targetSymbol, serializerContext);
3093
+ addExport(spec, exportEntry, exportName, baseDir);
2458
3094
  }
2459
3095
  }
2460
3096
  for (const typeName of typeRegistry.getReferencedTypes()) {
@@ -2478,22 +3114,36 @@ function buildOpenPkgSpec(context, resolveExternalTypes) {
2478
3114
  const { declaration, targetSymbol } = resolveExportTarget(exportSymbol, typeChecker);
2479
3115
  if (!declaration)
2480
3116
  continue;
2481
- if (ts.isClassDeclaration(declaration)) {
3117
+ if (ts2.isClassDeclaration(declaration)) {
2482
3118
  const { typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
2483
3119
  addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2484
- } else if (ts.isInterfaceDeclaration(declaration)) {
3120
+ } else if (ts2.isInterfaceDeclaration(declaration)) {
2485
3121
  const { typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
2486
3122
  addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2487
- } else if (ts.isTypeAliasDeclaration(declaration)) {
3123
+ } else if (ts2.isTypeAliasDeclaration(declaration)) {
2488
3124
  const { typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
2489
3125
  addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2490
- } else if (ts.isEnumDeclaration(declaration)) {
3126
+ } else if (ts2.isEnumDeclaration(declaration)) {
2491
3127
  const { typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
2492
3128
  addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
2493
3129
  }
2494
3130
  }
2495
3131
  }
2496
3132
  }
3133
+ for (const typeName of typeRegistry.getReferencedTypes()) {
3134
+ if (typeRegistry.isKnownType(typeName)) {
3135
+ continue;
3136
+ }
3137
+ const stubDefinition = {
3138
+ id: typeName,
3139
+ name: typeName,
3140
+ kind: "external",
3141
+ description: `External type (not resolved)`
3142
+ };
3143
+ if (typeRegistry.registerTypeDefinition(stubDefinition)) {
3144
+ spec.types?.push(stubDefinition);
3145
+ }
3146
+ }
2497
3147
  const coverage = computeDocsCoverage(spec);
2498
3148
  spec.docs = coverage.spec;
2499
3149
  spec.exports.forEach((entry) => {
@@ -2508,11 +3158,20 @@ function addExport(spec, entry, exportName, baseDir) {
2508
3158
  const named = withExportName(entry, exportName);
2509
3159
  spec.exports.push(applyPresentationDefaults(named, baseDir));
2510
3160
  }
2511
- function addTypeDefinition(spec, typeRegistry, definition, baseDir) {
3161
+ function addTypeDefinition(spec, typeRegistry, definition, baseDir, exportAlias) {
2512
3162
  if (!definition) {
2513
3163
  return;
2514
3164
  }
2515
- const enriched = applyPresentationDefaults(definition, baseDir);
3165
+ let finalDefinition = definition;
3166
+ if (exportAlias && exportAlias !== definition.name) {
3167
+ finalDefinition = {
3168
+ ...definition,
3169
+ id: exportAlias,
3170
+ name: definition.name,
3171
+ alias: exportAlias
3172
+ };
3173
+ }
3174
+ const enriched = applyPresentationDefaults(finalDefinition, baseDir);
2516
3175
  if (typeRegistry.registerTypeDefinition(enriched)) {
2517
3176
  spec.types?.push(enriched);
2518
3177
  }
@@ -2552,14 +3211,14 @@ function deriveImportPath(sourceFile, baseDir) {
2552
3211
  }
2553
3212
  function resolveExportTarget(symbol, checker) {
2554
3213
  let targetSymbol = symbol;
2555
- if (symbol.flags & ts.SymbolFlags.Alias) {
3214
+ if (symbol.flags & ts2.SymbolFlags.Alias) {
2556
3215
  const aliasTarget = checker.getImmediateAliasedSymbol(symbol);
2557
3216
  if (aliasTarget) {
2558
3217
  targetSymbol = aliasTarget;
2559
3218
  }
2560
3219
  }
2561
3220
  const declarations = targetSymbol.declarations ?? [];
2562
- const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts.SyntaxKind.ExportSpecifier) || declarations[0];
3221
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts2.SyntaxKind.ExportSpecifier) || declarations[0];
2563
3222
  return {
2564
3223
  declaration,
2565
3224
  targetSymbol
@@ -2572,7 +3231,8 @@ function withExportName(entry, exportName) {
2572
3231
  return {
2573
3232
  ...entry,
2574
3233
  id: exportName,
2575
- name: exportName
3234
+ name: entry.name,
3235
+ alias: exportName
2576
3236
  };
2577
3237
  }
2578
3238
 
@@ -2608,6 +3268,52 @@ function hasNodeModulesDirectory(directories) {
2608
3268
  }
2609
3269
  return false;
2610
3270
  }
3271
+ function collectAllRefs(obj, refs) {
3272
+ if (obj === null || obj === undefined)
3273
+ return;
3274
+ if (Array.isArray(obj)) {
3275
+ for (const item of obj) {
3276
+ collectAllRefs(item, refs);
3277
+ }
3278
+ return;
3279
+ }
3280
+ if (typeof obj === "object") {
3281
+ const record = obj;
3282
+ if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
3283
+ refs.add(record.$ref.slice("#/types/".length));
3284
+ }
3285
+ for (const value of Object.values(record)) {
3286
+ collectAllRefs(value, refs);
3287
+ }
3288
+ }
3289
+ }
3290
+ function collectDanglingRefs(spec) {
3291
+ const definedTypes = new Set(spec.types?.map((t) => t.id) ?? []);
3292
+ const referencedTypes = new Set;
3293
+ collectAllRefs(spec.exports, referencedTypes);
3294
+ collectAllRefs(spec.types, referencedTypes);
3295
+ return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref));
3296
+ }
3297
+ function collectExternalTypes(spec) {
3298
+ return (spec.types ?? []).filter((t) => t.kind === "external").map((t) => t.id);
3299
+ }
3300
+ function hasExternalImports(sourceFile) {
3301
+ let found = false;
3302
+ ts2.forEachChild(sourceFile, (node) => {
3303
+ if (found)
3304
+ return;
3305
+ if (ts2.isImportDeclaration(node) && node.moduleSpecifier) {
3306
+ const specifier = node.moduleSpecifier;
3307
+ if (ts2.isStringLiteral(specifier)) {
3308
+ const modulePath = specifier.text;
3309
+ if (!modulePath.startsWith(".") && !modulePath.startsWith("/")) {
3310
+ found = true;
3311
+ }
3312
+ }
3313
+ }
3314
+ });
3315
+ return found;
3316
+ }
2611
3317
  function runAnalysis(input) {
2612
3318
  const context = createAnalysisContext(input);
2613
3319
  const { baseDir, options } = context;
@@ -2618,14 +3324,38 @@ function runAnalysis(input) {
2618
3324
  }
2619
3325
  const hasNodeModules = hasNodeModulesDirectory(searchDirs);
2620
3326
  const resolveExternalTypes = options.resolveExternalTypes !== undefined ? options.resolveExternalTypes : hasNodeModules;
2621
- const diagnostics = ts.getPreEmitDiagnostics(context.program).filter((d) => {
3327
+ const diagnostics = ts2.getPreEmitDiagnostics(context.program).filter((d) => {
2622
3328
  if (d.code === 5053)
2623
3329
  return false;
2624
- const msg = ts.flattenDiagnosticMessageText(d.messageText, `
3330
+ const msg = ts2.flattenDiagnosticMessageText(d.messageText, `
2625
3331
  `);
2626
3332
  return !/allowJs/i.test(msg);
2627
3333
  });
2628
3334
  const spec = buildOpenPkgSpec(context, resolveExternalTypes);
3335
+ const specDiagnostics = [];
3336
+ if (!hasNodeModules && hasExternalImports(context.sourceFile)) {
3337
+ specDiagnostics.push({
3338
+ message: "External imports detected but node_modules not found.",
3339
+ severity: "info",
3340
+ suggestion: "Run npm install or bun install for complete type resolution."
3341
+ });
3342
+ }
3343
+ const danglingRefs = collectDanglingRefs(spec);
3344
+ for (const ref of danglingRefs) {
3345
+ specDiagnostics.push({
3346
+ message: `Type '${ref}' is referenced but not defined in types[].`,
3347
+ severity: "warning",
3348
+ suggestion: hasNodeModules ? "The type may be from an external package. Check import paths." : "Run npm/bun install to resolve external types."
3349
+ });
3350
+ }
3351
+ const externalTypes = collectExternalTypes(spec);
3352
+ if (externalTypes.length > 0) {
3353
+ specDiagnostics.push({
3354
+ message: `${externalTypes.length} external type(s) could not be fully resolved: ${externalTypes.slice(0, 5).join(", ")}${externalTypes.length > 5 ? "..." : ""}`,
3355
+ severity: "warning",
3356
+ suggestion: hasNodeModules ? "Types are from external packages. Full resolution requires type declarations." : "Run npm/bun install to resolve external type definitions."
3357
+ });
3358
+ }
2629
3359
  return {
2630
3360
  spec,
2631
3361
  metadata: {
@@ -2635,7 +3365,8 @@ function runAnalysis(input) {
2635
3365
  hasNodeModules,
2636
3366
  resolveExternalTypes
2637
3367
  },
2638
- diagnostics
3368
+ diagnostics,
3369
+ specDiagnostics
2639
3370
  };
2640
3371
  }
2641
3372
 
@@ -2649,10 +3380,785 @@ async function extractPackageSpec(entryFile, packageDir, content, options) {
2649
3380
  });
2650
3381
  return result.spec;
2651
3382
  }
3383
+ // src/fix/deterministic-fixes.ts
3384
+ var FIXABLE_DRIFT_TYPES = new Set([
3385
+ "param-mismatch",
3386
+ "param-type-mismatch",
3387
+ "optionality-mismatch",
3388
+ "return-type-mismatch",
3389
+ "generic-constraint-mismatch",
3390
+ "example-assertion-failed",
3391
+ "deprecated-mismatch",
3392
+ "async-mismatch",
3393
+ "property-type-drift"
3394
+ ]);
3395
+ function isFixableDrift(drift) {
3396
+ return FIXABLE_DRIFT_TYPES.has(drift.type);
3397
+ }
3398
+ function generateFix(drift, exportEntry, existingPatch) {
3399
+ switch (drift.type) {
3400
+ case "param-mismatch":
3401
+ return generateParamMismatchFix(drift, exportEntry, existingPatch);
3402
+ case "param-type-mismatch":
3403
+ return generateParamTypeFix(drift, exportEntry, existingPatch);
3404
+ case "optionality-mismatch":
3405
+ return generateOptionalityFix(drift, exportEntry, existingPatch);
3406
+ case "return-type-mismatch":
3407
+ return generateReturnTypeFix(drift, exportEntry, existingPatch);
3408
+ case "generic-constraint-mismatch":
3409
+ return generateGenericConstraintFix(drift, exportEntry, existingPatch);
3410
+ case "example-assertion-failed":
3411
+ return generateAssertionFix(drift, exportEntry);
3412
+ case "deprecated-mismatch":
3413
+ return generateDeprecatedFix(drift, exportEntry, existingPatch);
3414
+ case "async-mismatch":
3415
+ return generateAsyncFix(drift, exportEntry, existingPatch);
3416
+ case "property-type-drift":
3417
+ return generatePropertyTypeFix(drift, exportEntry, existingPatch);
3418
+ default:
3419
+ return null;
3420
+ }
3421
+ }
3422
+ function generateFixesForExport(exportEntry, existingPatch) {
3423
+ const fixes = [];
3424
+ const driftList = exportEntry.docs?.drift ?? [];
3425
+ for (const drift of driftList) {
3426
+ const fix = generateFix(drift, exportEntry, existingPatch);
3427
+ if (fix) {
3428
+ fixes.push(fix);
3429
+ }
3430
+ }
3431
+ return fixes;
3432
+ }
3433
+ function mergeFixes(fixes, basePatch) {
3434
+ let result = basePatch ? { ...basePatch } : {};
3435
+ for (const fix of fixes) {
3436
+ result = mergePatches(result, fix.patch);
3437
+ }
3438
+ return result;
3439
+ }
3440
+ function mergePatches(base, update) {
3441
+ const result = { ...base };
3442
+ if (update.description !== undefined) {
3443
+ result.description = update.description;
3444
+ }
3445
+ if (update.params !== undefined) {
3446
+ const isRemoval = result.params && update.params.length < result.params.length;
3447
+ if (isRemoval) {
3448
+ const existingByName = new Map(result.params?.map((p) => [p.name, p]) ?? []);
3449
+ result.params = update.params.map((updatedParam) => {
3450
+ const existing = existingByName.get(updatedParam.name);
3451
+ return {
3452
+ ...existing,
3453
+ ...updatedParam,
3454
+ description: updatedParam.description ?? existing?.description
3455
+ };
3456
+ });
3457
+ } else {
3458
+ if (!result.params) {
3459
+ result.params = [];
3460
+ }
3461
+ const resultByName = new Map(result.params.map((p) => [p.name, p]));
3462
+ for (const updatedParam of update.params) {
3463
+ const existing = resultByName.get(updatedParam.name);
3464
+ if (existing) {
3465
+ Object.assign(existing, updatedParam);
3466
+ } else {
3467
+ result.params.push(updatedParam);
3468
+ }
3469
+ }
3470
+ }
3471
+ }
3472
+ if (update.returns !== undefined) {
3473
+ result.returns = { ...result.returns, ...update.returns };
3474
+ }
3475
+ if (update.typeParams !== undefined) {
3476
+ result.typeParams = update.typeParams;
3477
+ }
3478
+ if (update.examples !== undefined) {
3479
+ result.examples = update.examples;
3480
+ }
3481
+ if (update.deprecated !== undefined) {
3482
+ result.deprecated = update.deprecated;
3483
+ }
3484
+ if (update.async !== undefined) {
3485
+ result.async = update.async;
3486
+ }
3487
+ if (update.type !== undefined) {
3488
+ result.type = update.type;
3489
+ }
3490
+ return result;
3491
+ }
3492
+ function generateParamMismatchFix(drift, exportEntry, existingPatch) {
3493
+ const issue = drift.issue.toLowerCase();
3494
+ const target = drift.target ?? "";
3495
+ const signature = exportEntry.signatures?.[0];
3496
+ if (!signature)
3497
+ return null;
3498
+ const actualParams = signature.parameters ?? [];
3499
+ const existingParams = existingPatch?.params ?? [];
3500
+ if (issue.includes("missing") || issue.includes("undocumented")) {
3501
+ const paramName = extractParamName(target, drift.issue);
3502
+ if (!paramName)
3503
+ return null;
3504
+ const actualParam = actualParams.find((p) => p.name === paramName);
3505
+ const paramType = actualParam?.schema ? stringifySchema(actualParam.schema) : undefined;
3506
+ const newParam = {
3507
+ name: paramName,
3508
+ type: paramType,
3509
+ optional: actualParam?.required === false
3510
+ };
3511
+ const updatedParams = [...existingParams];
3512
+ const existingIndex = updatedParams.findIndex((p) => p.name === paramName);
3513
+ if (existingIndex >= 0) {
3514
+ updatedParams[existingIndex] = { ...updatedParams[existingIndex], ...newParam };
3515
+ } else {
3516
+ updatedParams.push(newParam);
3517
+ }
3518
+ return {
3519
+ type: "add-param",
3520
+ driftType: drift.type,
3521
+ target: paramName,
3522
+ description: `Add missing @param ${paramName}`,
3523
+ patch: { params: updatedParams }
3524
+ };
3525
+ }
3526
+ if (issue.includes("extra") || issue.includes("removed") || issue.includes("no longer") || issue.includes("not present") || issue.includes("does not exist")) {
3527
+ const paramName = target || extractParamName(target, drift.issue);
3528
+ if (!paramName)
3529
+ return null;
3530
+ const updatedParams = existingParams.filter((p) => p.name !== paramName);
3531
+ return {
3532
+ type: "remove-param",
3533
+ driftType: drift.type,
3534
+ target: paramName,
3535
+ description: `Remove stale @param ${paramName}`,
3536
+ patch: { params: updatedParams }
3537
+ };
3538
+ }
3539
+ if (drift.suggestion && issue.includes("rename")) {
3540
+ const oldName = extractParamName(target, drift.issue);
3541
+ const newNameMatch = drift.suggestion.match(/`(\w+)`/);
3542
+ const newName = newNameMatch?.[1];
3543
+ if (oldName && newName) {
3544
+ const updatedParams = existingParams.map((p) => p.name === oldName ? { ...p, name: newName } : p);
3545
+ return {
3546
+ type: "add-param",
3547
+ driftType: drift.type,
3548
+ target: newName,
3549
+ description: `Rename @param ${oldName} to ${newName}`,
3550
+ patch: { params: updatedParams }
3551
+ };
3552
+ }
3553
+ }
3554
+ return null;
3555
+ }
3556
+ function generateParamTypeFix(drift, exportEntry, existingPatch) {
3557
+ const target = drift.target ?? "";
3558
+ const paramName = extractParamName(target, drift.issue);
3559
+ if (!paramName)
3560
+ return null;
3561
+ const signature = exportEntry.signatures?.[0];
3562
+ const actualParam = signature?.parameters?.find((p) => p.name === paramName);
3563
+ if (!actualParam)
3564
+ return null;
3565
+ const correctType = stringifySchema(actualParam.schema);
3566
+ const existingParams = existingPatch?.params ?? [];
3567
+ const updatedParams = existingParams.map((p) => p.name === paramName ? { ...p, type: correctType } : p);
3568
+ if (!existingParams.some((p) => p.name === paramName)) {
3569
+ updatedParams.push({
3570
+ name: paramName,
3571
+ type: correctType,
3572
+ optional: actualParam.required === false
3573
+ });
3574
+ }
3575
+ return {
3576
+ type: "update-param-type",
3577
+ driftType: drift.type,
3578
+ target: paramName,
3579
+ description: `Update @param ${paramName} type to {${correctType}}`,
3580
+ patch: { params: updatedParams }
3581
+ };
3582
+ }
3583
+ function generateOptionalityFix(drift, exportEntry, existingPatch) {
3584
+ const target = drift.target ?? "";
3585
+ const paramName = extractParamName(target, drift.issue);
3586
+ if (!paramName)
3587
+ return null;
3588
+ const signature = exportEntry.signatures?.[0];
3589
+ const actualParam = signature?.parameters?.find((p) => p.name === paramName);
3590
+ if (!actualParam)
3591
+ return null;
3592
+ const isOptional = actualParam.required === false;
3593
+ const existingParams = existingPatch?.params ?? [];
3594
+ const updatedParams = existingParams.map((p) => p.name === paramName ? { ...p, optional: isOptional } : p);
3595
+ if (!existingParams.some((p) => p.name === paramName)) {
3596
+ updatedParams.push({
3597
+ name: paramName,
3598
+ optional: isOptional
3599
+ });
3600
+ }
3601
+ const optionalityText = isOptional ? "optional" : "required";
3602
+ return {
3603
+ type: "update-param-optionality",
3604
+ driftType: drift.type,
3605
+ target: paramName,
3606
+ description: `Mark @param ${paramName} as ${optionalityText}`,
3607
+ patch: { params: updatedParams }
3608
+ };
3609
+ }
3610
+ function generateReturnTypeFix(drift, exportEntry, existingPatch) {
3611
+ const signature = exportEntry.signatures?.[0];
3612
+ const actualReturn = signature?.returns;
3613
+ if (!actualReturn)
3614
+ return null;
3615
+ const correctType = actualReturn.tsType ?? stringifySchema(actualReturn.schema);
3616
+ const updatedReturn = {
3617
+ ...existingPatch?.returns,
3618
+ type: correctType
3619
+ };
3620
+ return {
3621
+ type: "update-return-type",
3622
+ driftType: drift.type,
3623
+ target: "returns",
3624
+ description: `Update @returns type to {${correctType}}`,
3625
+ patch: { returns: updatedReturn }
3626
+ };
3627
+ }
3628
+ function generateGenericConstraintFix(drift, exportEntry, _existingPatch) {
3629
+ const target = drift.target ?? "";
3630
+ const typeParamName = target || extractTypeParamName(drift.issue);
3631
+ if (!typeParamName)
3632
+ return null;
3633
+ const typeParam = exportEntry.typeParameters?.find((tp) => tp.name === typeParamName);
3634
+ if (!typeParam)
3635
+ return null;
3636
+ const typeParams = exportEntry.typeParameters?.map((tp) => ({
3637
+ name: tp.name,
3638
+ constraint: tp.constraint
3639
+ })) ?? [];
3640
+ return {
3641
+ type: "update-template-constraint",
3642
+ driftType: drift.type,
3643
+ target: typeParamName,
3644
+ description: `Update @template ${typeParamName} constraint to ${typeParam.constraint ?? "none"}`,
3645
+ patch: { typeParams }
3646
+ };
3647
+ }
3648
+ function generateAssertionFix(drift, exportEntry) {
3649
+ if (!drift.suggestion)
3650
+ return null;
3651
+ const targetMatch = drift.target?.match(/example\[(\d+)\](?::line(\d+))?/);
3652
+ if (!targetMatch)
3653
+ return null;
3654
+ const exampleIndex = parseInt(targetMatch[1], 10);
3655
+ const examples = exportEntry.examples ?? [];
3656
+ if (exampleIndex >= examples.length)
3657
+ return null;
3658
+ const newValueMatch = drift.suggestion.match(/\/\/\s*=>\s*(.+)$/);
3659
+ if (!newValueMatch)
3660
+ return null;
3661
+ const newValue = newValueMatch[1].trim();
3662
+ const oldExample = examples[exampleIndex];
3663
+ const oldValueMatch = drift.issue.match(/expected\s+"([^"]+)"/i);
3664
+ const oldValue = oldValueMatch?.[1];
3665
+ if (!oldValue)
3666
+ return null;
3667
+ const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex(oldValue)}`, "g"), `// => ${newValue}`);
3668
+ const updatedExamples = [...examples];
3669
+ updatedExamples[exampleIndex] = updatedExample;
3670
+ return {
3671
+ type: "update-assertion",
3672
+ driftType: drift.type,
3673
+ target: `example[${exampleIndex}]`,
3674
+ description: `Update assertion from "${oldValue}" to "${newValue}"`,
3675
+ patch: { examples: updatedExamples }
3676
+ };
3677
+ }
3678
+ function generateDeprecatedFix(drift, exportEntry, _existingPatch) {
3679
+ const issue = drift.issue.toLowerCase();
3680
+ const target = drift.target ?? exportEntry.name ?? "";
3681
+ if (issue.includes("missing") || issue.includes("@deprecated is missing")) {
3682
+ const deprecationReason = exportEntry.deprecated ? typeof exportEntry.deprecated === "string" ? exportEntry.deprecated : "This API is deprecated." : "This API is deprecated.";
3683
+ return {
3684
+ type: "add-deprecated",
3685
+ driftType: drift.type,
3686
+ target,
3687
+ description: `Add @deprecated tag`,
3688
+ patch: { deprecated: deprecationReason }
3689
+ };
3690
+ }
3691
+ if (issue.includes("not") && issue.includes("deprecated")) {
3692
+ return {
3693
+ type: "remove-deprecated",
3694
+ driftType: drift.type,
3695
+ target,
3696
+ description: `Remove @deprecated tag`,
3697
+ patch: { deprecated: false }
3698
+ };
3699
+ }
3700
+ return null;
3701
+ }
3702
+ function generateAsyncFix(drift, _exportEntry, _existingPatch) {
3703
+ const issue = drift.issue.toLowerCase();
3704
+ if (issue.includes("returns promise") && issue.includes("does not indicate")) {
3705
+ return {
3706
+ type: "add-async",
3707
+ driftType: drift.type,
3708
+ target: "returns",
3709
+ description: `Add @async tag`,
3710
+ patch: { async: true }
3711
+ };
3712
+ }
3713
+ if (issue.includes("does not return promise") || issue.includes("doesn't return promise")) {
3714
+ return {
3715
+ type: "remove-async",
3716
+ driftType: drift.type,
3717
+ target: "returns",
3718
+ description: `Remove @async tag`,
3719
+ patch: { async: false }
3720
+ };
3721
+ }
3722
+ return null;
3723
+ }
3724
+ function generatePropertyTypeFix(drift, _exportEntry, _existingPatch) {
3725
+ const target = drift.target ?? "";
3726
+ let actualType = null;
3727
+ if (drift.suggestion) {
3728
+ const suggestionMatch = drift.suggestion.match(/\{([^}]+)\}/);
3729
+ if (suggestionMatch) {
3730
+ actualType = suggestionMatch[1];
3731
+ }
3732
+ }
3733
+ if (!actualType && drift.issue) {
3734
+ const issueMatch = drift.issue.match(/actual type is\s+(\S+)/i);
3735
+ if (issueMatch) {
3736
+ actualType = issueMatch[1].replace(/\.$/, "");
3737
+ }
3738
+ }
3739
+ if (!actualType)
3740
+ return null;
3741
+ return {
3742
+ type: "update-property-type",
3743
+ driftType: drift.type,
3744
+ target,
3745
+ description: `Update @type to {${actualType}}`,
3746
+ patch: { type: actualType }
3747
+ };
3748
+ }
3749
+ function extractParamName(target, issue) {
3750
+ if (target && !target.includes("[") && !target.includes(":")) {
3751
+ return target;
3752
+ }
3753
+ const patterns = [
3754
+ /[Pp]arameter\s+[`'"]?(\w+)[`'"]?/,
3755
+ /@param\s+(?:\{[^}]+\}\s+)?[`'"]?(\w+)[`'"]?/,
3756
+ /[`'"](\w+)[`'"]\s+(?:is|was|has)/
3757
+ ];
3758
+ for (const pattern of patterns) {
3759
+ const match = issue.match(pattern);
3760
+ if (match?.[1]) {
3761
+ return match[1];
3762
+ }
3763
+ }
3764
+ return null;
3765
+ }
3766
+ function extractTypeParamName(issue) {
3767
+ const match = issue.match(/[Tt]ype\s+parameter\s+[`'"]?(\w+)[`'"]?/);
3768
+ return match?.[1] ?? null;
3769
+ }
3770
+ function stringifySchema(schema) {
3771
+ if (typeof schema === "string") {
3772
+ return schema;
3773
+ }
3774
+ if (schema && typeof schema === "object") {
3775
+ if ("type" in schema && typeof schema.type === "string") {
3776
+ return schema.type;
3777
+ }
3778
+ if ("$ref" in schema && typeof schema.$ref === "string") {
3779
+ const ref = schema.$ref;
3780
+ return ref.split("/").pop() ?? ref;
3781
+ }
3782
+ }
3783
+ return "unknown";
3784
+ }
3785
+ function escapeRegex(str) {
3786
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3787
+ }
3788
+ function categorizeDrifts(drifts) {
3789
+ const fixable = [];
3790
+ const nonFixable = [];
3791
+ for (const drift of drifts) {
3792
+ if (isFixableDrift(drift)) {
3793
+ fixable.push(drift);
3794
+ } else {
3795
+ nonFixable.push(drift);
3796
+ }
3797
+ }
3798
+ return { fixable, nonFixable };
3799
+ }
3800
+ // src/fix/jsdoc-writer.ts
3801
+ import * as fs3 from "node:fs";
3802
+ import * as path5 from "node:path";
3803
+ function parseJSDocToPatch(jsDocText) {
3804
+ const patch = {};
3805
+ const cleanedText = jsDocText.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/$/, "").replace(/^\s*\* ?/gm, "").trim();
3806
+ const lines = cleanedText.split(`
3807
+ `);
3808
+ let currentTag = "";
3809
+ let currentContent = [];
3810
+ const descriptionLines = [];
3811
+ const processCurrentTag = () => {
3812
+ if (!currentTag)
3813
+ return;
3814
+ const content = currentContent.join(`
3815
+ `).trim();
3816
+ switch (currentTag) {
3817
+ case "param":
3818
+ case "parameter": {
3819
+ const paramMatch = content.match(/^(?:\{([^}]+)\}\s+)?(\[?\w+(?:\.\w+)*\]?)(?:\s+-\s+|\s+)?(.*)$/s);
3820
+ if (paramMatch) {
3821
+ const [, type, rawName, description] = paramMatch;
3822
+ const optional = rawName.startsWith("[") && rawName.endsWith("]");
3823
+ const name = optional ? rawName.slice(1, -1) : rawName;
3824
+ if (!patch.params)
3825
+ patch.params = [];
3826
+ patch.params.push({
3827
+ name,
3828
+ type: type?.trim(),
3829
+ description: description?.trim(),
3830
+ optional
3831
+ });
3832
+ }
3833
+ break;
3834
+ }
3835
+ case "returns":
3836
+ case "return": {
3837
+ const returnMatch = content.match(/^(?:\{([^}]+)\}\s*)?(.*)$/s);
3838
+ if (returnMatch) {
3839
+ patch.returns = {
3840
+ type: returnMatch[1]?.trim(),
3841
+ description: returnMatch[2]?.trim()
3842
+ };
3843
+ }
3844
+ break;
3845
+ }
3846
+ case "example": {
3847
+ if (!patch.examples)
3848
+ patch.examples = [];
3849
+ patch.examples.push(content);
3850
+ break;
3851
+ }
3852
+ case "deprecated": {
3853
+ patch.deprecated = content || "Deprecated";
3854
+ break;
3855
+ }
3856
+ case "async": {
3857
+ patch.async = true;
3858
+ break;
3859
+ }
3860
+ case "type": {
3861
+ const typeMatch = content.match(/^\{([^}]+)\}$/);
3862
+ if (typeMatch) {
3863
+ patch.type = typeMatch[1].trim();
3864
+ } else if (content.trim()) {
3865
+ patch.type = content.trim();
3866
+ }
3867
+ break;
3868
+ }
3869
+ case "template": {
3870
+ const templateMatch = content.match(/^(\w+)(?:\s+extends\s+(\S+))?(?:\s+-?\s*(.*))?$/);
3871
+ if (templateMatch) {
3872
+ if (!patch.typeParams)
3873
+ patch.typeParams = [];
3874
+ patch.typeParams.push({
3875
+ name: templateMatch[1],
3876
+ constraint: templateMatch[2],
3877
+ description: templateMatch[3]?.trim()
3878
+ });
3879
+ }
3880
+ break;
3881
+ }
3882
+ default: {
3883
+ if (!patch.otherTags)
3884
+ patch.otherTags = [];
3885
+ patch.otherTags.push({ name: currentTag, text: content });
3886
+ }
3887
+ }
3888
+ };
3889
+ for (const line of lines) {
3890
+ const tagMatch = line.match(/^@(\w+)(?:\s+(.*))?$/);
3891
+ if (tagMatch) {
3892
+ processCurrentTag();
3893
+ currentTag = tagMatch[1];
3894
+ currentContent = tagMatch[2] ? [tagMatch[2]] : [];
3895
+ } else if (currentTag) {
3896
+ currentContent.push(line);
3897
+ } else if (line.trim()) {
3898
+ descriptionLines.push(line);
3899
+ }
3900
+ }
3901
+ processCurrentTag();
3902
+ if (descriptionLines.length > 0) {
3903
+ patch.description = descriptionLines.join(`
3904
+ `).trim();
3905
+ }
3906
+ return patch;
3907
+ }
3908
+ function applyPatchToJSDoc(existing, updates) {
3909
+ const result = { ...existing };
3910
+ if (updates.description !== undefined) {
3911
+ result.description = updates.description;
3912
+ }
3913
+ if (updates.params !== undefined) {
3914
+ const existingByName = new Map(existing.params?.map((p) => [p.name, p]) ?? []);
3915
+ result.params = updates.params.map((updatedParam) => {
3916
+ const existingParam = existingByName.get(updatedParam.name);
3917
+ return {
3918
+ ...existingParam,
3919
+ ...updatedParam,
3920
+ description: updatedParam.description ?? existingParam?.description
3921
+ };
3922
+ });
3923
+ }
3924
+ if (updates.returns !== undefined) {
3925
+ result.returns = {
3926
+ ...existing.returns,
3927
+ ...updates.returns
3928
+ };
3929
+ }
3930
+ if (updates.examples !== undefined) {
3931
+ result.examples = updates.examples;
3932
+ }
3933
+ if (updates.deprecated !== undefined) {
3934
+ result.deprecated = updates.deprecated;
3935
+ }
3936
+ if (updates.typeParams !== undefined) {
3937
+ result.typeParams = updates.typeParams;
3938
+ }
3939
+ if (updates.otherTags !== undefined) {
3940
+ result.otherTags = updates.otherTags;
3941
+ }
3942
+ return result;
3943
+ }
3944
+ function serializeJSDoc(patch, indent = "") {
3945
+ const lines = [];
3946
+ if (patch.description) {
3947
+ const descLines = patch.description.split(`
3948
+ `);
3949
+ for (const line of descLines) {
3950
+ lines.push(line);
3951
+ }
3952
+ }
3953
+ const hasTags = patch.params?.length || patch.returns || patch.examples?.length || patch.deprecated || patch.async || patch.type || patch.typeParams?.length || patch.otherTags?.length;
3954
+ if (patch.description && hasTags) {
3955
+ lines.push("");
3956
+ }
3957
+ if (patch.async) {
3958
+ lines.push("@async");
3959
+ }
3960
+ if (patch.type) {
3961
+ lines.push(`@type {${patch.type}}`);
3962
+ }
3963
+ if (patch.typeParams) {
3964
+ for (const tp of patch.typeParams) {
3965
+ let tagLine = `@template ${tp.name}`;
3966
+ if (tp.constraint) {
3967
+ tagLine += ` extends ${tp.constraint}`;
3968
+ }
3969
+ if (tp.description) {
3970
+ tagLine += ` - ${tp.description}`;
3971
+ }
3972
+ lines.push(tagLine);
3973
+ }
3974
+ }
3975
+ if (patch.params) {
3976
+ for (const param of patch.params) {
3977
+ let tagLine = "@param";
3978
+ if (param.type) {
3979
+ tagLine += ` {${param.type}}`;
3980
+ }
3981
+ const paramName = param.optional ? `[${param.name}]` : param.name;
3982
+ tagLine += ` ${paramName}`;
3983
+ if (param.description) {
3984
+ tagLine += ` - ${param.description}`;
3985
+ }
3986
+ lines.push(tagLine);
3987
+ }
3988
+ }
3989
+ if (patch.returns && (patch.returns.type || patch.returns.description)) {
3990
+ let tagLine = "@returns";
3991
+ if (patch.returns.type) {
3992
+ tagLine += ` {${patch.returns.type}}`;
3993
+ }
3994
+ if (patch.returns.description) {
3995
+ tagLine += ` ${patch.returns.description}`;
3996
+ }
3997
+ lines.push(tagLine);
3998
+ }
3999
+ if (patch.deprecated && patch.deprecated !== false) {
4000
+ lines.push(`@deprecated ${patch.deprecated}`);
4001
+ }
4002
+ if (patch.examples) {
4003
+ for (const example of patch.examples) {
4004
+ lines.push("@example");
4005
+ const exampleLines = example.split(`
4006
+ `);
4007
+ for (const line of exampleLines) {
4008
+ lines.push(line);
4009
+ }
4010
+ }
4011
+ }
4012
+ if (patch.otherTags) {
4013
+ for (const tag of patch.otherTags) {
4014
+ if (tag.text) {
4015
+ lines.push(`@${tag.name} ${tag.text}`);
4016
+ } else {
4017
+ lines.push(`@${tag.name}`);
4018
+ }
4019
+ }
4020
+ }
4021
+ if (lines.length === 0) {
4022
+ return `${indent}/** */`;
4023
+ }
4024
+ if (lines.length === 1 && lines[0].length < 60) {
4025
+ return `${indent}/** ${lines[0]} */`;
4026
+ }
4027
+ const formattedLines = lines.map((line) => `${indent} * ${line}`);
4028
+ return `${indent}/**
4029
+ ${formattedLines.join(`
4030
+ `)}
4031
+ ${indent} */`;
4032
+ }
4033
+ function findJSDocLocation(sourceFile, symbolName, approximateLine) {
4034
+ let result = null;
4035
+ let closestDistance = Number.POSITIVE_INFINITY;
4036
+ function getIndent(node) {
4037
+ const pos = node.getStart(sourceFile);
4038
+ const { character } = sourceFile.getLineAndCharacterOfPosition(pos);
4039
+ return " ".repeat(character);
4040
+ }
4041
+ function processNode(node, name) {
4042
+ if (name !== symbolName)
4043
+ return;
4044
+ const { line: nodeLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
4045
+ if (approximateLine !== undefined) {
4046
+ const distance = Math.abs(nodeLine - approximateLine);
4047
+ if (distance >= closestDistance)
4048
+ return;
4049
+ closestDistance = distance;
4050
+ }
4051
+ const indent = getIndent(node);
4052
+ const commentRanges = ts2.getLeadingCommentRanges(sourceFile.text, node.pos);
4053
+ let jsDocRange;
4054
+ if (commentRanges) {
4055
+ for (let i = commentRanges.length - 1;i >= 0; i--) {
4056
+ const range = commentRanges[i];
4057
+ const text = sourceFile.text.substring(range.pos, range.end);
4058
+ if (text.startsWith("/**")) {
4059
+ jsDocRange = range;
4060
+ break;
4061
+ }
4062
+ }
4063
+ }
4064
+ if (jsDocRange) {
4065
+ const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(jsDocRange.pos);
4066
+ const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(jsDocRange.end);
4067
+ const existingJSDoc = sourceFile.text.substring(jsDocRange.pos, jsDocRange.end);
4068
+ result = {
4069
+ startLine,
4070
+ endLine,
4071
+ declarationLine: nodeLine,
4072
+ hasExisting: true,
4073
+ existingJSDoc,
4074
+ indent
4075
+ };
4076
+ } else {
4077
+ result = {
4078
+ startLine: nodeLine,
4079
+ endLine: nodeLine,
4080
+ declarationLine: nodeLine,
4081
+ hasExisting: false,
4082
+ indent
4083
+ };
4084
+ }
4085
+ }
4086
+ function visit(node) {
4087
+ if (ts2.isFunctionDeclaration(node) && node.name) {
4088
+ processNode(node, node.name.getText(sourceFile));
4089
+ }
4090
+ if (ts2.isVariableStatement(node)) {
4091
+ for (const decl of node.declarationList.declarations) {
4092
+ if (ts2.isIdentifier(decl.name)) {
4093
+ processNode(node, decl.name.getText(sourceFile));
4094
+ }
4095
+ }
4096
+ }
4097
+ if (ts2.isClassDeclaration(node) && node.name) {
4098
+ processNode(node, node.name.getText(sourceFile));
4099
+ }
4100
+ if (ts2.isInterfaceDeclaration(node)) {
4101
+ processNode(node, node.name.getText(sourceFile));
4102
+ }
4103
+ if (ts2.isTypeAliasDeclaration(node)) {
4104
+ processNode(node, node.name.getText(sourceFile));
4105
+ }
4106
+ if (ts2.isMethodDeclaration(node) && node.name) {
4107
+ processNode(node, node.name.getText(sourceFile));
4108
+ }
4109
+ ts2.forEachChild(node, visit);
4110
+ }
4111
+ visit(sourceFile);
4112
+ return result;
4113
+ }
4114
+ async function applyEdits(edits) {
4115
+ const result = {
4116
+ filesModified: 0,
4117
+ editsApplied: 0,
4118
+ errors: []
4119
+ };
4120
+ const editsByFile = new Map;
4121
+ for (const edit of edits) {
4122
+ const existing = editsByFile.get(edit.filePath) ?? [];
4123
+ existing.push(edit);
4124
+ editsByFile.set(edit.filePath, existing);
4125
+ }
4126
+ for (const [filePath, fileEdits] of editsByFile) {
4127
+ try {
4128
+ const content = fs3.readFileSync(filePath, "utf-8");
4129
+ const lines = content.split(`
4130
+ `);
4131
+ const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
4132
+ for (const edit of sortedEdits) {
4133
+ const newJSDocLines = edit.newJSDoc.split(`
4134
+ `);
4135
+ if (edit.hasExisting) {
4136
+ lines.splice(edit.startLine, edit.endLine - edit.startLine + 1, ...newJSDocLines);
4137
+ } else {
4138
+ lines.splice(edit.startLine, 0, ...newJSDocLines);
4139
+ }
4140
+ result.editsApplied++;
4141
+ }
4142
+ fs3.writeFileSync(filePath, lines.join(`
4143
+ `));
4144
+ result.filesModified++;
4145
+ } catch (error) {
4146
+ result.errors.push({
4147
+ file: filePath,
4148
+ error: error instanceof Error ? error.message : String(error)
4149
+ });
4150
+ }
4151
+ }
4152
+ return result;
4153
+ }
4154
+ function createSourceFile(filePath) {
4155
+ const content = fs3.readFileSync(filePath, "utf-8");
4156
+ return ts2.createSourceFile(path5.basename(filePath), content, ts2.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
4157
+ }
2652
4158
  // src/openpkg.ts
2653
4159
  import * as fsSync from "node:fs";
2654
- import * as fs3 from "node:fs/promises";
2655
- import * as path5 from "node:path";
4160
+ import * as fs4 from "node:fs/promises";
4161
+ import * as path6 from "node:path";
2656
4162
 
2657
4163
  // src/filtering/apply-filters.ts
2658
4164
  var TYPE_REF_PREFIX = "#/types/";
@@ -2835,14 +4341,14 @@ class DocCov {
2835
4341
  this.options = normalizeDocCovOptions(options);
2836
4342
  }
2837
4343
  async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
2838
- const resolvedFileName = path5.resolve(fileName);
2839
- const tempDir = path5.dirname(resolvedFileName);
4344
+ const resolvedFileName = path6.resolve(fileName);
4345
+ const tempDir = path6.dirname(resolvedFileName);
2840
4346
  const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
2841
4347
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
2842
4348
  }
2843
4349
  async analyzeFile(filePath, analyzeOptions = {}) {
2844
- const resolvedPath = path5.resolve(filePath);
2845
- const content = await fs3.readFile(resolvedPath, "utf-8");
4350
+ const resolvedPath = path6.resolve(filePath);
4351
+ const content = await fs4.readFile(resolvedPath, "utf-8");
2846
4352
  const packageDir = resolvePackageDir(resolvedPath);
2847
4353
  const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
2848
4354
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
@@ -2851,7 +4357,7 @@ class DocCov {
2851
4357
  return this.analyzeFile(entryPath, analyzeOptions);
2852
4358
  }
2853
4359
  async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
2854
- const resolvedFileName = path5.resolve(fileName ?? "temp.ts");
4360
+ const resolvedFileName = path6.resolve(fileName ?? "temp.ts");
2855
4361
  const packageDir = resolvePackageDir(resolvedFileName);
2856
4362
  const analysis = runAnalysis({
2857
4363
  entryFile: resolvedFileName,
@@ -2864,14 +4370,15 @@ class DocCov {
2864
4370
  spec: filterOutcome.spec,
2865
4371
  diagnostics: [
2866
4372
  ...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
4373
+ ...analysis.specDiagnostics,
2867
4374
  ...filterOutcome.diagnostics
2868
4375
  ],
2869
4376
  metadata: this.normalizeMetadata(analysis.metadata)
2870
4377
  };
2871
4378
  }
2872
4379
  async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
2873
- const resolvedPath = path5.resolve(filePath);
2874
- const content = await fs3.readFile(resolvedPath, "utf-8");
4380
+ const resolvedPath = path6.resolve(filePath);
4381
+ const content = await fs4.readFile(resolvedPath, "utf-8");
2875
4382
  const packageDir = resolvePackageDir(resolvedPath);
2876
4383
  const analysis = runAnalysis({
2877
4384
  entryFile: resolvedPath,
@@ -2884,13 +4391,14 @@ class DocCov {
2884
4391
  spec: filterOutcome.spec,
2885
4392
  diagnostics: [
2886
4393
  ...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
4394
+ ...analysis.specDiagnostics,
2887
4395
  ...filterOutcome.diagnostics
2888
4396
  ],
2889
4397
  metadata: this.normalizeMetadata(analysis.metadata)
2890
4398
  };
2891
4399
  }
2892
4400
  normalizeDiagnostic(tsDiagnostic) {
2893
- const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
4401
+ const message = ts2.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
2894
4402
  `);
2895
4403
  let location;
2896
4404
  if (tsDiagnostic.file && typeof tsDiagnostic.start === "number") {
@@ -2910,10 +4418,10 @@ class DocCov {
2910
4418
  }
2911
4419
  mapSeverity(category) {
2912
4420
  switch (category) {
2913
- case ts.DiagnosticCategory.Message:
2914
- case ts.DiagnosticCategory.Suggestion:
4421
+ case ts2.DiagnosticCategory.Message:
4422
+ case ts2.DiagnosticCategory.Suggestion:
2915
4423
  return "info";
2916
- case ts.DiagnosticCategory.Warning:
4424
+ case ts2.DiagnosticCategory.Warning:
2917
4425
  return "warning";
2918
4426
  default:
2919
4427
  return "error";
@@ -2950,22 +4458,228 @@ async function analyzeFile(filePath, options = {}) {
2950
4458
  }
2951
4459
  var OpenPkg = DocCov;
2952
4460
  function resolvePackageDir(entryFile) {
2953
- const fallbackDir = path5.dirname(entryFile);
4461
+ const fallbackDir = path6.dirname(entryFile);
2954
4462
  let currentDir = fallbackDir;
2955
4463
  while (true) {
2956
- const candidate = path5.join(currentDir, "package.json");
4464
+ const candidate = path6.join(currentDir, "package.json");
2957
4465
  if (fsSync.existsSync(candidate)) {
2958
4466
  return currentDir;
2959
4467
  }
2960
- const parentDir = path5.dirname(currentDir);
4468
+ const parentDir = path6.dirname(currentDir);
2961
4469
  if (parentDir === currentDir) {
2962
4470
  return fallbackDir;
2963
4471
  }
2964
4472
  currentDir = parentDir;
2965
4473
  }
2966
4474
  }
4475
+ // src/utils/example-runner.ts
4476
+ import { spawn } from "node:child_process";
4477
+ import * as fs5 from "node:fs";
4478
+ import * as os from "node:os";
4479
+ import * as path7 from "node:path";
4480
+ function stripCodeBlockMarkers(code) {
4481
+ return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
4482
+ }
4483
+ async function runExample(code, options = {}) {
4484
+ const { timeout = 5000, cwd = process.cwd() } = options;
4485
+ const cleanCode = stripCodeBlockMarkers(code);
4486
+ const tmpFile = path7.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
4487
+ try {
4488
+ fs5.writeFileSync(tmpFile, cleanCode, "utf-8");
4489
+ const startTime = Date.now();
4490
+ return await new Promise((resolve3) => {
4491
+ let stdout = "";
4492
+ let stderr = "";
4493
+ let killed = false;
4494
+ const proc = spawn("node", ["--experimental-strip-types", tmpFile], {
4495
+ cwd,
4496
+ timeout,
4497
+ stdio: ["ignore", "pipe", "pipe"]
4498
+ });
4499
+ proc.stdout?.on("data", (data) => {
4500
+ stdout += data.toString();
4501
+ });
4502
+ proc.stderr?.on("data", (data) => {
4503
+ stderr += data.toString();
4504
+ });
4505
+ const timeoutId = setTimeout(() => {
4506
+ killed = true;
4507
+ proc.kill("SIGKILL");
4508
+ }, timeout);
4509
+ proc.on("close", (exitCode) => {
4510
+ clearTimeout(timeoutId);
4511
+ const duration = Date.now() - startTime;
4512
+ if (killed) {
4513
+ resolve3({
4514
+ success: false,
4515
+ stdout,
4516
+ stderr: stderr || `Example timed out after ${timeout}ms`,
4517
+ exitCode: exitCode ?? 1,
4518
+ duration
4519
+ });
4520
+ } else {
4521
+ resolve3({
4522
+ success: exitCode === 0,
4523
+ stdout,
4524
+ stderr,
4525
+ exitCode: exitCode ?? 1,
4526
+ duration
4527
+ });
4528
+ }
4529
+ });
4530
+ proc.on("error", (error) => {
4531
+ clearTimeout(timeoutId);
4532
+ resolve3({
4533
+ success: false,
4534
+ stdout,
4535
+ stderr: error.message,
4536
+ exitCode: 1,
4537
+ duration: Date.now() - startTime
4538
+ });
4539
+ });
4540
+ });
4541
+ } finally {
4542
+ try {
4543
+ fs5.unlinkSync(tmpFile);
4544
+ } catch {}
4545
+ }
4546
+ }
4547
+ async function runExamples(examples, options = {}) {
4548
+ const results = new Map;
4549
+ for (let i = 0;i < examples.length; i++) {
4550
+ const example = examples[i];
4551
+ if (typeof example === "string" && example.trim()) {
4552
+ results.set(i, await runExample(example, options));
4553
+ }
4554
+ }
4555
+ return results;
4556
+ }
4557
+ function detectPackageManager(cwd) {
4558
+ if (fs5.existsSync(path7.join(cwd, "bun.lockb")))
4559
+ return "bun";
4560
+ if (fs5.existsSync(path7.join(cwd, "pnpm-lock.yaml")))
4561
+ return "pnpm";
4562
+ return "npm";
4563
+ }
4564
+ function getInstallCommand(pm, packagePath) {
4565
+ switch (pm) {
4566
+ case "bun":
4567
+ return { cmd: "bun", args: ["add", packagePath] };
4568
+ case "pnpm":
4569
+ return { cmd: "pnpm", args: ["add", packagePath] };
4570
+ default:
4571
+ return { cmd: "npm", args: ["install", packagePath, "--legacy-peer-deps"] };
4572
+ }
4573
+ }
4574
+ async function runCommand(cmd, args, options) {
4575
+ return new Promise((resolve3) => {
4576
+ let stdout = "";
4577
+ let stderr = "";
4578
+ let killed = false;
4579
+ const proc = spawn(cmd, args, {
4580
+ cwd: options.cwd,
4581
+ stdio: ["ignore", "pipe", "pipe"]
4582
+ });
4583
+ proc.stdout?.on("data", (data) => {
4584
+ stdout += data.toString();
4585
+ });
4586
+ proc.stderr?.on("data", (data) => {
4587
+ stderr += data.toString();
4588
+ });
4589
+ const timeoutId = setTimeout(() => {
4590
+ killed = true;
4591
+ proc.kill("SIGKILL");
4592
+ }, options.timeout);
4593
+ proc.on("close", (exitCode) => {
4594
+ clearTimeout(timeoutId);
4595
+ if (killed) {
4596
+ resolve3({
4597
+ success: false,
4598
+ stdout,
4599
+ stderr: stderr || `Command timed out after ${options.timeout}ms`,
4600
+ exitCode: exitCode ?? 1
4601
+ });
4602
+ } else {
4603
+ resolve3({
4604
+ success: exitCode === 0,
4605
+ stdout,
4606
+ stderr,
4607
+ exitCode: exitCode ?? 1
4608
+ });
4609
+ }
4610
+ });
4611
+ proc.on("error", (error) => {
4612
+ clearTimeout(timeoutId);
4613
+ resolve3({
4614
+ success: false,
4615
+ stdout,
4616
+ stderr: error.message,
4617
+ exitCode: 1
4618
+ });
4619
+ });
4620
+ });
4621
+ }
4622
+ async function runExamplesWithPackage(examples, options) {
4623
+ const { packagePath, packageManager, installTimeout = 60000, timeout = 5000 } = options;
4624
+ const startTime = Date.now();
4625
+ const results = new Map;
4626
+ const absolutePackagePath = path7.resolve(packagePath);
4627
+ const workDir = path7.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
4628
+ try {
4629
+ fs5.mkdirSync(workDir, { recursive: true });
4630
+ const pkgJson = { name: "doccov-example-runner", type: "module" };
4631
+ fs5.writeFileSync(path7.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
4632
+ const pm = packageManager ?? detectPackageManager(options.cwd ?? process.cwd());
4633
+ const { cmd, args } = getInstallCommand(pm, absolutePackagePath);
4634
+ const installResult = await runCommand(cmd, args, {
4635
+ cwd: workDir,
4636
+ timeout: installTimeout
4637
+ });
4638
+ if (!installResult.success) {
4639
+ return {
4640
+ results,
4641
+ installSuccess: false,
4642
+ installError: installResult.stderr || `${pm} install failed with exit code ${installResult.exitCode}`,
4643
+ totalDuration: Date.now() - startTime
4644
+ };
4645
+ }
4646
+ for (let i = 0;i < examples.length; i++) {
4647
+ const example = examples[i];
4648
+ if (typeof example === "string" && example.trim()) {
4649
+ results.set(i, await runExample(example, { timeout, cwd: workDir }));
4650
+ }
4651
+ }
4652
+ return {
4653
+ results,
4654
+ installSuccess: true,
4655
+ totalDuration: Date.now() - startTime
4656
+ };
4657
+ } finally {
4658
+ try {
4659
+ fs5.rmSync(workDir, { recursive: true, force: true });
4660
+ } catch {}
4661
+ }
4662
+ }
2967
4663
  export {
4664
+ serializeJSDoc,
4665
+ runExamplesWithPackage,
4666
+ runExamples,
4667
+ runExample,
4668
+ parseJSDocToPatch,
4669
+ parseAssertions,
4670
+ mergeFixes,
4671
+ isFixableDrift,
4672
+ hasNonAssertionComments,
4673
+ generateFixesForExport,
4674
+ generateFix,
4675
+ findJSDocLocation,
2968
4676
  extractPackageSpec,
4677
+ detectExampleRuntimeErrors,
4678
+ detectExampleAssertionFailures,
4679
+ createSourceFile,
4680
+ categorizeDrifts,
4681
+ applyPatchToJSDoc,
4682
+ applyEdits,
2969
4683
  analyzeFile,
2970
4684
  analyze,
2971
4685
  OpenPkg,