@doccov/sdk 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -45
- package/dist/index.d.ts +206 -1
- package/dist/index.js +2045 -331
- package/package.json +2 -2
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
|
-
|
|
870
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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 (
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
964
|
-
|
|
965
|
-
|
|
884
|
+
return drifts;
|
|
885
|
+
}
|
|
886
|
+
function detectPropertyTypeDrift(entry) {
|
|
887
|
+
const members = entry.members ?? [];
|
|
888
|
+
if (members.length === 0) {
|
|
889
|
+
return [];
|
|
966
890
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
|
|
973
|
-
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
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() &
|
|
1299
|
+
if (!(type.getFlags() & ts2.TypeFlags.Object)) {
|
|
1065
1300
|
return false;
|
|
1066
1301
|
}
|
|
1067
1302
|
const objectFlags = type.objectFlags;
|
|
1068
|
-
return (objectFlags &
|
|
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 (
|
|
1357
|
+
if (ts2.isParenthesizedTypeNode(node)) {
|
|
1123
1358
|
return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
|
|
1124
1359
|
}
|
|
1125
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
1374
|
+
if (ts2.isTypeLiteralNode(node)) {
|
|
1140
1375
|
const properties = {};
|
|
1141
1376
|
const required = [];
|
|
1142
1377
|
for (const member of node.members) {
|
|
1143
|
-
if (!
|
|
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 (
|
|
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 (
|
|
1200
|
-
if (
|
|
1434
|
+
if (ts2.isLiteralTypeNode(node)) {
|
|
1435
|
+
if (ts2.isStringLiteral(node.literal)) {
|
|
1201
1436
|
return { enum: [node.literal.text] };
|
|
1202
1437
|
}
|
|
1203
|
-
if (
|
|
1438
|
+
if (ts2.isNumericLiteral(node.literal)) {
|
|
1204
1439
|
return { enum: [Number(node.literal.text)] };
|
|
1205
1440
|
}
|
|
1206
1441
|
}
|
|
1207
|
-
if (
|
|
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() &
|
|
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 &
|
|
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" ||
|
|
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
|
-
|
|
1447
|
-
|
|
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 &
|
|
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 &
|
|
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 &
|
|
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 &
|
|
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 &
|
|
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
|
-
|
|
1612
|
-
if (!
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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 &
|
|
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 ===
|
|
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 ===
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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(
|
|
2405
|
+
const memberSymbol = checker.getSymbolAtLocation(primaryMember.name);
|
|
2018
2406
|
const memberDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
|
|
2019
|
-
const accessorType =
|
|
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(
|
|
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 ===
|
|
2450
|
+
if (modifiers.some((mod) => mod.kind === ts2.SyntaxKind.PrivateKeyword)) {
|
|
2057
2451
|
return "private";
|
|
2058
2452
|
}
|
|
2059
|
-
if (modifiers.some((mod) => mod.kind ===
|
|
2453
|
+
if (modifiers.some((mod) => mod.kind === ts2.SyntaxKind.ProtectedKeyword)) {
|
|
2060
2454
|
return "protected";
|
|
2061
2455
|
}
|
|
2062
|
-
if (modifiers.some((mod) => mod.kind ===
|
|
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 ===
|
|
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(
|
|
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 &&
|
|
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 &
|
|
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:
|
|
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
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
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 (
|
|
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 (
|
|
3069
|
+
if (ts2.isFunctionDeclaration(declaration)) {
|
|
2437
3070
|
const exportEntry = serializeFunctionExport(declaration, targetSymbol, serializerContext);
|
|
2438
3071
|
addExport(spec, exportEntry, exportName, baseDir);
|
|
2439
|
-
} else if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
3117
|
+
if (ts2.isClassDeclaration(declaration)) {
|
|
2482
3118
|
const { typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
|
|
2483
3119
|
addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
|
|
2484
|
-
} else if (
|
|
3120
|
+
} else if (ts2.isInterfaceDeclaration(declaration)) {
|
|
2485
3121
|
const { typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
|
|
2486
3122
|
addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
|
|
2487
|
-
} else if (
|
|
3123
|
+
} else if (ts2.isTypeAliasDeclaration(declaration)) {
|
|
2488
3124
|
const { typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
|
|
2489
3125
|
addTypeDefinition(spec, typeRegistry, typeDefinition, baseDir);
|
|
2490
|
-
} else if (
|
|
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
|
-
|
|
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 &
|
|
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 !==
|
|
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:
|
|
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 =
|
|
3327
|
+
const diagnostics = ts2.getPreEmitDiagnostics(context.program).filter((d) => {
|
|
2622
3328
|
if (d.code === 5053)
|
|
2623
3329
|
return false;
|
|
2624
|
-
const msg =
|
|
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
|
|
2655
|
-
import * as
|
|
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 =
|
|
2839
|
-
const tempDir =
|
|
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 =
|
|
2845
|
-
const content = await
|
|
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 =
|
|
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 =
|
|
2874
|
-
const content = await
|
|
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 =
|
|
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
|
|
2914
|
-
case
|
|
4421
|
+
case ts2.DiagnosticCategory.Message:
|
|
4422
|
+
case ts2.DiagnosticCategory.Suggestion:
|
|
2915
4423
|
return "info";
|
|
2916
|
-
case
|
|
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 =
|
|
4461
|
+
const fallbackDir = path6.dirname(entryFile);
|
|
2954
4462
|
let currentDir = fallbackDir;
|
|
2955
4463
|
while (true) {
|
|
2956
|
-
const candidate =
|
|
4464
|
+
const candidate = path6.join(currentDir, "package.json");
|
|
2957
4465
|
if (fsSync.existsSync(candidate)) {
|
|
2958
4466
|
return currentDir;
|
|
2959
4467
|
}
|
|
2960
|
-
const parentDir =
|
|
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,
|