@openpkg-ts/sdk 0.1.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 +127 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +2140 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2140 @@
|
|
|
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 normalizeOpenPkgOptions(options = {}) {
|
|
25
|
+
return {
|
|
26
|
+
...DEFAULT_OPTIONS,
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/analysis/program.ts
|
|
32
|
+
import * as path from "node:path";
|
|
33
|
+
var DEFAULT_COMPILER_OPTIONS = {
|
|
34
|
+
target: ts.ScriptTarget.Latest,
|
|
35
|
+
module: ts.ModuleKind.CommonJS,
|
|
36
|
+
lib: ["lib.es2021.d.ts"],
|
|
37
|
+
allowJs: true,
|
|
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 compilerHost = ts.createCompilerHost(compilerOptions, true);
|
|
54
|
+
let inMemorySource;
|
|
55
|
+
if (content !== undefined) {
|
|
56
|
+
inMemorySource = ts.createSourceFile(entryFile, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
57
|
+
const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
58
|
+
compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
59
|
+
if (fileName === entryFile) {
|
|
60
|
+
return inMemorySource;
|
|
61
|
+
}
|
|
62
|
+
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const program = ts.createProgram([entryFile], compilerOptions, compilerHost);
|
|
66
|
+
const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
|
|
67
|
+
return {
|
|
68
|
+
program,
|
|
69
|
+
compilerHost,
|
|
70
|
+
compilerOptions,
|
|
71
|
+
sourceFile,
|
|
72
|
+
configPath
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/analysis/context.ts
|
|
77
|
+
function createAnalysisContext({
|
|
78
|
+
entryFile,
|
|
79
|
+
packageDir,
|
|
80
|
+
content,
|
|
81
|
+
options
|
|
82
|
+
}) {
|
|
83
|
+
const baseDir = packageDir ?? path2.dirname(entryFile);
|
|
84
|
+
const normalizedOptions = normalizeOpenPkgOptions(options);
|
|
85
|
+
const programResult = createProgram({ entryFile, baseDir, content });
|
|
86
|
+
if (!programResult.sourceFile) {
|
|
87
|
+
throw new Error(`Could not load ${entryFile}`);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
entryFile,
|
|
91
|
+
baseDir,
|
|
92
|
+
program: programResult.program,
|
|
93
|
+
checker: programResult.program.getTypeChecker(),
|
|
94
|
+
sourceFile: programResult.sourceFile,
|
|
95
|
+
compilerOptions: programResult.compilerOptions,
|
|
96
|
+
compilerHost: programResult.compilerHost,
|
|
97
|
+
options: normalizedOptions,
|
|
98
|
+
configPath: programResult.configPath
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/analysis/spec-builder.ts
|
|
103
|
+
import * as fs from "node:fs";
|
|
104
|
+
import * as path3 from "node:path";
|
|
105
|
+
import { SCHEMA_URL } from "@openpkg-ts/spec";
|
|
106
|
+
|
|
107
|
+
// src/utils/type-utils.ts
|
|
108
|
+
function collectReferencedTypes(type, typeChecker, referencedTypes, visitedTypes = new Set) {
|
|
109
|
+
if (visitedTypes.has(type))
|
|
110
|
+
return;
|
|
111
|
+
visitedTypes.add(type);
|
|
112
|
+
const symbol = type.getSymbol();
|
|
113
|
+
if (symbol) {
|
|
114
|
+
const symbolName = symbol.getName();
|
|
115
|
+
if (!symbolName.startsWith("__") && !isBuiltInType(symbolName)) {
|
|
116
|
+
referencedTypes.add(symbolName);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (type.isIntersection()) {
|
|
120
|
+
for (const intersectionType of type.types) {
|
|
121
|
+
collectReferencedTypes(intersectionType, typeChecker, referencedTypes, visitedTypes);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (type.isUnion()) {
|
|
125
|
+
for (const unionType of type.types) {
|
|
126
|
+
collectReferencedTypes(unionType, typeChecker, referencedTypes, visitedTypes);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (type.flags & ts.TypeFlags.Object) {
|
|
130
|
+
const objectType = type;
|
|
131
|
+
if (objectType.objectFlags & ts.ObjectFlags.Reference) {
|
|
132
|
+
const typeRef = objectType;
|
|
133
|
+
if (typeRef.typeArguments) {
|
|
134
|
+
for (const typeArg of typeRef.typeArguments) {
|
|
135
|
+
collectReferencedTypes(typeArg, typeChecker, referencedTypes, visitedTypes);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
|
|
142
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
143
|
+
const typeNameText = node.typeName.getText();
|
|
144
|
+
const symbol = typeChecker.getSymbolAtLocation(node.typeName);
|
|
145
|
+
const name = symbol?.getName() ?? typeNameText;
|
|
146
|
+
if (!isBuiltInType(name)) {
|
|
147
|
+
referencedTypes.add(name);
|
|
148
|
+
}
|
|
149
|
+
node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (ts.isExpressionWithTypeArguments(node)) {
|
|
153
|
+
const expressionText = node.expression.getText();
|
|
154
|
+
const symbol = typeChecker.getSymbolAtLocation(node.expression);
|
|
155
|
+
const name = symbol?.getName() ?? expressionText;
|
|
156
|
+
if (!isBuiltInType(name)) {
|
|
157
|
+
referencedTypes.add(name);
|
|
158
|
+
}
|
|
159
|
+
node.typeArguments?.forEach((arg) => collectReferencedTypesFromNode(arg, typeChecker, referencedTypes));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node)) {
|
|
163
|
+
node.types.forEach((typeNode) => collectReferencedTypesFromNode(typeNode, typeChecker, referencedTypes));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (ts.isArrayTypeNode(node)) {
|
|
167
|
+
collectReferencedTypesFromNode(node.elementType, typeChecker, referencedTypes);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
171
|
+
collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
175
|
+
node.members.forEach((member) => {
|
|
176
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
177
|
+
collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
|
|
178
|
+
}
|
|
179
|
+
if (ts.isMethodSignature(member)) {
|
|
180
|
+
member.typeParameters?.forEach((param) => {
|
|
181
|
+
param.constraint && collectReferencedTypesFromNode(param.constraint, typeChecker, referencedTypes);
|
|
182
|
+
});
|
|
183
|
+
member.parameters.forEach((param) => {
|
|
184
|
+
if (param.type) {
|
|
185
|
+
collectReferencedTypesFromNode(param.type, typeChecker, referencedTypes);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (member.type) {
|
|
189
|
+
collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (ts.isCallSignatureDeclaration(member) && member.type) {
|
|
193
|
+
collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
|
|
194
|
+
}
|
|
195
|
+
if (ts.isIndexSignatureDeclaration(member) && member.type) {
|
|
196
|
+
collectReferencedTypesFromNode(member.type, typeChecker, referencedTypes);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (ts.isTypeOperatorNode(node)) {
|
|
202
|
+
collectReferencedTypesFromNode(node.type, typeChecker, referencedTypes);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (ts.isIndexedAccessTypeNode(node)) {
|
|
206
|
+
collectReferencedTypesFromNode(node.objectType, typeChecker, referencedTypes);
|
|
207
|
+
collectReferencedTypesFromNode(node.indexType, typeChecker, referencedTypes);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
node.forEachChild((child) => {
|
|
214
|
+
if (ts.isTypeNode(child)) {
|
|
215
|
+
collectReferencedTypesFromNode(child, typeChecker, referencedTypes);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function isBuiltInType(name) {
|
|
220
|
+
const builtIns = [
|
|
221
|
+
"string",
|
|
222
|
+
"number",
|
|
223
|
+
"boolean",
|
|
224
|
+
"bigint",
|
|
225
|
+
"symbol",
|
|
226
|
+
"undefined",
|
|
227
|
+
"null",
|
|
228
|
+
"any",
|
|
229
|
+
"unknown",
|
|
230
|
+
"never",
|
|
231
|
+
"void",
|
|
232
|
+
"object",
|
|
233
|
+
"Array",
|
|
234
|
+
"Promise",
|
|
235
|
+
"Map",
|
|
236
|
+
"Set",
|
|
237
|
+
"WeakMap",
|
|
238
|
+
"WeakSet",
|
|
239
|
+
"Date",
|
|
240
|
+
"RegExp",
|
|
241
|
+
"Error",
|
|
242
|
+
"Function",
|
|
243
|
+
"Object",
|
|
244
|
+
"String",
|
|
245
|
+
"Number",
|
|
246
|
+
"Boolean",
|
|
247
|
+
"BigInt",
|
|
248
|
+
"Symbol",
|
|
249
|
+
"Uint8Array",
|
|
250
|
+
"Int8Array",
|
|
251
|
+
"Uint16Array",
|
|
252
|
+
"Int16Array",
|
|
253
|
+
"Uint32Array",
|
|
254
|
+
"Int32Array",
|
|
255
|
+
"Float32Array",
|
|
256
|
+
"Float64Array",
|
|
257
|
+
"BigInt64Array",
|
|
258
|
+
"BigUint64Array",
|
|
259
|
+
"Uint8ClampedArray",
|
|
260
|
+
"ArrayBuffer",
|
|
261
|
+
"ArrayBufferLike",
|
|
262
|
+
"DataView",
|
|
263
|
+
"Uint8ArrayConstructor",
|
|
264
|
+
"ArrayBufferConstructor",
|
|
265
|
+
"JSON",
|
|
266
|
+
"Math",
|
|
267
|
+
"Reflect",
|
|
268
|
+
"Proxy",
|
|
269
|
+
"Intl",
|
|
270
|
+
"globalThis",
|
|
271
|
+
"__type"
|
|
272
|
+
];
|
|
273
|
+
return builtIns.includes(name);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/utils/parameter-utils.ts
|
|
277
|
+
var BUILTIN_TYPE_SCHEMAS = {
|
|
278
|
+
Date: { type: "string", format: "date-time" },
|
|
279
|
+
RegExp: { type: "object", description: "RegExp" },
|
|
280
|
+
Error: { type: "object" },
|
|
281
|
+
Promise: { type: "object" },
|
|
282
|
+
Map: { type: "object" },
|
|
283
|
+
Set: { type: "object" },
|
|
284
|
+
WeakMap: { type: "object" },
|
|
285
|
+
WeakSet: { type: "object" },
|
|
286
|
+
Function: { type: "object" },
|
|
287
|
+
ArrayBuffer: { type: "string", format: "binary" },
|
|
288
|
+
ArrayBufferLike: { type: "string", format: "binary" },
|
|
289
|
+
DataView: { type: "string", format: "binary" },
|
|
290
|
+
Uint8Array: { type: "string", format: "byte" },
|
|
291
|
+
Uint16Array: { type: "string", format: "byte" },
|
|
292
|
+
Uint32Array: { type: "string", format: "byte" },
|
|
293
|
+
Int8Array: { type: "string", format: "byte" },
|
|
294
|
+
Int16Array: { type: "string", format: "byte" },
|
|
295
|
+
Int32Array: { type: "string", format: "byte" },
|
|
296
|
+
Float32Array: { type: "string", format: "byte" },
|
|
297
|
+
Float64Array: { type: "string", format: "byte" },
|
|
298
|
+
BigInt64Array: { type: "string", format: "byte" },
|
|
299
|
+
BigUint64Array: { type: "string", format: "byte" }
|
|
300
|
+
};
|
|
301
|
+
function isObjectLiteralType(type) {
|
|
302
|
+
if (!(type.getFlags() & ts.TypeFlags.Object)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
const objectFlags = type.objectFlags;
|
|
306
|
+
return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
|
|
307
|
+
}
|
|
308
|
+
function isPureRefSchema(value) {
|
|
309
|
+
return Object.keys(value).length === 1 && "$ref" in value;
|
|
310
|
+
}
|
|
311
|
+
function withDescription(schema, description) {
|
|
312
|
+
if (isPureRefSchema(schema)) {
|
|
313
|
+
return {
|
|
314
|
+
allOf: [schema],
|
|
315
|
+
description
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...schema,
|
|
320
|
+
description
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function propertiesToSchema(properties, description) {
|
|
324
|
+
const schema = {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {}
|
|
327
|
+
};
|
|
328
|
+
const required = [];
|
|
329
|
+
for (const prop of properties) {
|
|
330
|
+
const propType = prop.type;
|
|
331
|
+
let propSchema;
|
|
332
|
+
if (typeof propType === "string") {
|
|
333
|
+
if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
|
|
334
|
+
propSchema = { type: propType === "bigint" ? "string" : propType };
|
|
335
|
+
} else {
|
|
336
|
+
propSchema = { type: propType };
|
|
337
|
+
}
|
|
338
|
+
} else if (propType && typeof propType === "object") {
|
|
339
|
+
propSchema = propType;
|
|
340
|
+
} else {
|
|
341
|
+
propSchema = { type: "any" };
|
|
342
|
+
}
|
|
343
|
+
if (prop.description && typeof propSchema === "object") {
|
|
344
|
+
propSchema = withDescription(propSchema, prop.description);
|
|
345
|
+
}
|
|
346
|
+
schema.properties[prop.name] = propSchema;
|
|
347
|
+
if (!prop.optional) {
|
|
348
|
+
required.push(prop.name);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (required.length > 0) {
|
|
352
|
+
schema.required = required;
|
|
353
|
+
}
|
|
354
|
+
if (description) {
|
|
355
|
+
return withDescription(schema, description);
|
|
356
|
+
}
|
|
357
|
+
return schema;
|
|
358
|
+
}
|
|
359
|
+
function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
|
|
360
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
361
|
+
return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
|
|
362
|
+
}
|
|
363
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
364
|
+
const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
365
|
+
return { allOf: schemas };
|
|
366
|
+
}
|
|
367
|
+
if (ts.isUnionTypeNode(node)) {
|
|
368
|
+
const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
369
|
+
return { anyOf: schemas };
|
|
370
|
+
}
|
|
371
|
+
if (ts.isArrayTypeNode(node)) {
|
|
372
|
+
return {
|
|
373
|
+
type: "array",
|
|
374
|
+
items: buildSchemaFromTypeNode(node.elementType, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
378
|
+
const properties = {};
|
|
379
|
+
const required = [];
|
|
380
|
+
for (const member of node.members) {
|
|
381
|
+
if (!ts.isPropertySignature(member) || !member.name) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const propName = member.name.getText();
|
|
385
|
+
let schema2 = "any";
|
|
386
|
+
if (member.type) {
|
|
387
|
+
const memberType = typeChecker.getTypeFromTypeNode(member.type);
|
|
388
|
+
const formatted = formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes);
|
|
389
|
+
if (typeof formatted === "string") {
|
|
390
|
+
if (formatted === "any") {
|
|
391
|
+
schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
|
|
392
|
+
} else {
|
|
393
|
+
schema2 = { type: formatted };
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
schema2 = formatted;
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
schema2 = { type: "any" };
|
|
400
|
+
}
|
|
401
|
+
const description = getDocDescriptionForProperty(functionDoc, parentParamName, propName);
|
|
402
|
+
if (typeof schema2 === "object" && description) {
|
|
403
|
+
schema2 = withDescription(schema2, description);
|
|
404
|
+
}
|
|
405
|
+
properties[propName] = schema2;
|
|
406
|
+
if (!member.questionToken) {
|
|
407
|
+
required.push(propName);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const schema = {
|
|
411
|
+
type: "object",
|
|
412
|
+
properties
|
|
413
|
+
};
|
|
414
|
+
if (required.length > 0) {
|
|
415
|
+
schema.required = required;
|
|
416
|
+
}
|
|
417
|
+
return schema;
|
|
418
|
+
}
|
|
419
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
420
|
+
const typeName = node.typeName.getText();
|
|
421
|
+
if (typeName === "Array") {
|
|
422
|
+
return { type: "array" };
|
|
423
|
+
}
|
|
424
|
+
const builtInSchema = BUILTIN_TYPE_SCHEMAS[typeName];
|
|
425
|
+
if (builtInSchema) {
|
|
426
|
+
return { ...builtInSchema };
|
|
427
|
+
}
|
|
428
|
+
if (isBuiltInType(typeName)) {
|
|
429
|
+
return { type: "object" };
|
|
430
|
+
}
|
|
431
|
+
if (!typeRefs.has(typeName)) {
|
|
432
|
+
typeRefs.set(typeName, typeName);
|
|
433
|
+
}
|
|
434
|
+
referencedTypes?.add(typeName);
|
|
435
|
+
return { $ref: `#/types/${typeName}` };
|
|
436
|
+
}
|
|
437
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
438
|
+
if (ts.isStringLiteral(node.literal)) {
|
|
439
|
+
return { enum: [node.literal.text] };
|
|
440
|
+
}
|
|
441
|
+
if (ts.isNumericLiteral(node.literal)) {
|
|
442
|
+
return { enum: [Number(node.literal.text)] };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (ts.isIntersectionTypeNode(node)) {
|
|
446
|
+
const schemas = node.types.map((typeNode) => buildSchemaFromTypeNode(typeNode, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
|
|
447
|
+
if (schemas.some((schema) => ("$ref" in schema) && Object.keys(schema).length === 1)) {
|
|
448
|
+
const refs = schemas.filter((schema) => ("$ref" in schema) && Object.keys(schema).length === 1);
|
|
449
|
+
const nonRefs = schemas.filter((schema) => !(("$ref" in schema) && Object.keys(schema).length === 1));
|
|
450
|
+
if (refs.length === schemas.length) {
|
|
451
|
+
return refs[0];
|
|
452
|
+
}
|
|
453
|
+
if (nonRefs.length > 0) {
|
|
454
|
+
const merged = nonRefs.reduce((acc, schema) => ({ ...acc, ...schema }), {});
|
|
455
|
+
return merged;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
allOf: schemas
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return { type: node.getText() };
|
|
463
|
+
}
|
|
464
|
+
function getDocDescriptionForProperty(functionDoc, parentParamName, propName) {
|
|
465
|
+
if (!functionDoc) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
let match = functionDoc.params.find((p) => p.name === `${parentParamName}.${propName}`);
|
|
469
|
+
if (!match) {
|
|
470
|
+
match = functionDoc.params.find((p) => p.name.endsWith(`.${propName}`));
|
|
471
|
+
}
|
|
472
|
+
return match?.description;
|
|
473
|
+
}
|
|
474
|
+
function schemaIsAny(schema) {
|
|
475
|
+
if (typeof schema === "string") {
|
|
476
|
+
return schema === "any";
|
|
477
|
+
}
|
|
478
|
+
if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
function schemasAreEqual(left, right) {
|
|
484
|
+
if (typeof left !== typeof right) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
488
|
+
return left === right;
|
|
489
|
+
}
|
|
490
|
+
if (left == null || right == null) {
|
|
491
|
+
return left === right;
|
|
492
|
+
}
|
|
493
|
+
const normalize = (value) => {
|
|
494
|
+
if (Array.isArray(value)) {
|
|
495
|
+
return value.map((item) => normalize(item));
|
|
496
|
+
}
|
|
497
|
+
if (value && typeof value === "object") {
|
|
498
|
+
const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
|
|
499
|
+
return Object.fromEntries(sortedEntries);
|
|
500
|
+
}
|
|
501
|
+
return value;
|
|
502
|
+
};
|
|
503
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
504
|
+
}
|
|
505
|
+
function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visitedAliases) {
|
|
506
|
+
const visited = visitedAliases ?? new Set;
|
|
507
|
+
const aliasSymbol = type.aliasSymbol;
|
|
508
|
+
let aliasName;
|
|
509
|
+
let aliasAdded = false;
|
|
510
|
+
if (aliasSymbol) {
|
|
511
|
+
aliasName = aliasSymbol.getName();
|
|
512
|
+
if (visited.has(aliasName)) {
|
|
513
|
+
return { $ref: `#/types/${aliasName}` };
|
|
514
|
+
}
|
|
515
|
+
if (typeRefs.has(aliasName)) {
|
|
516
|
+
return { $ref: `#/types/${aliasName}` };
|
|
517
|
+
}
|
|
518
|
+
if (referencedTypes && !isBuiltInType(aliasName)) {
|
|
519
|
+
referencedTypes.add(aliasName);
|
|
520
|
+
return { $ref: `#/types/${aliasName}` };
|
|
521
|
+
}
|
|
522
|
+
visited.add(aliasName);
|
|
523
|
+
aliasAdded = true;
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const typeString = typeChecker.typeToString(type);
|
|
527
|
+
const primitives = [
|
|
528
|
+
"string",
|
|
529
|
+
"number",
|
|
530
|
+
"boolean",
|
|
531
|
+
"bigint",
|
|
532
|
+
"symbol",
|
|
533
|
+
"any",
|
|
534
|
+
"unknown",
|
|
535
|
+
"void",
|
|
536
|
+
"undefined",
|
|
537
|
+
"null",
|
|
538
|
+
"never"
|
|
539
|
+
];
|
|
540
|
+
if (primitives.includes(typeString)) {
|
|
541
|
+
if (typeString === "bigint") {
|
|
542
|
+
return { type: "string", format: "bigint" };
|
|
543
|
+
}
|
|
544
|
+
if (typeString === "undefined" || typeString === "null") {
|
|
545
|
+
return { type: "null" };
|
|
546
|
+
}
|
|
547
|
+
if (typeString === "void" || typeString === "never") {
|
|
548
|
+
return { type: "null" };
|
|
549
|
+
}
|
|
550
|
+
return { type: typeString };
|
|
551
|
+
}
|
|
552
|
+
if (type.isUnion()) {
|
|
553
|
+
const unionType = type;
|
|
554
|
+
const parts = unionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes, visited));
|
|
555
|
+
return {
|
|
556
|
+
anyOf: parts
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (type.isIntersection()) {
|
|
560
|
+
const intersectionType = type;
|
|
561
|
+
const parts = intersectionType.types.map((t) => formatTypeReference(t, typeChecker, typeRefs, referencedTypes, visited));
|
|
562
|
+
const normalized = parts.flatMap((part) => {
|
|
563
|
+
if (typeof part === "string") {
|
|
564
|
+
return [{ type: part }];
|
|
565
|
+
}
|
|
566
|
+
if (part && typeof part === "object" && "allOf" in part) {
|
|
567
|
+
return Array.isArray(part.allOf) ? part.allOf : [part];
|
|
568
|
+
}
|
|
569
|
+
return [part];
|
|
570
|
+
});
|
|
571
|
+
if (normalized.length === 1) {
|
|
572
|
+
return normalized[0];
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
allOf: normalized
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
const symbol = type.getSymbol();
|
|
579
|
+
if (symbol) {
|
|
580
|
+
const symbolName = symbol.getName();
|
|
581
|
+
if (symbolName.startsWith("__")) {
|
|
582
|
+
if (type.getFlags() & ts.TypeFlags.Object) {
|
|
583
|
+
const properties = type.getProperties();
|
|
584
|
+
if (properties.length > 0) {
|
|
585
|
+
const objSchema = {
|
|
586
|
+
type: "object",
|
|
587
|
+
properties: {}
|
|
588
|
+
};
|
|
589
|
+
const required = [];
|
|
590
|
+
for (const prop of properties) {
|
|
591
|
+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
592
|
+
const propName = prop.getName();
|
|
593
|
+
objSchema.properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes, visited);
|
|
594
|
+
if (!(prop.flags & ts.SymbolFlags.Optional)) {
|
|
595
|
+
required.push(propName);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (required.length > 0) {
|
|
599
|
+
objSchema.required = required;
|
|
600
|
+
}
|
|
601
|
+
return objSchema;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return { type: "object" };
|
|
605
|
+
}
|
|
606
|
+
if (typeRefs.has(symbolName)) {
|
|
607
|
+
return { $ref: `#/types/${symbolName}` };
|
|
608
|
+
}
|
|
609
|
+
if (symbolName === "Array") {
|
|
610
|
+
return { type: "array" };
|
|
611
|
+
}
|
|
612
|
+
const builtInSchema = BUILTIN_TYPE_SCHEMAS[symbolName];
|
|
613
|
+
if (builtInSchema) {
|
|
614
|
+
return { ...builtInSchema };
|
|
615
|
+
}
|
|
616
|
+
if (referencedTypes && !isBuiltInType(symbolName)) {
|
|
617
|
+
referencedTypes.add(symbolName);
|
|
618
|
+
return { $ref: `#/types/${symbolName}` };
|
|
619
|
+
}
|
|
620
|
+
if (isBuiltInType(symbolName)) {
|
|
621
|
+
return { type: "object" };
|
|
622
|
+
}
|
|
623
|
+
return { $ref: `#/types/${symbolName}` };
|
|
624
|
+
}
|
|
625
|
+
if (type.isLiteral()) {
|
|
626
|
+
if (typeString.startsWith('"') && typeString.endsWith('"')) {
|
|
627
|
+
const literalValue = typeString.slice(1, -1);
|
|
628
|
+
return { enum: [literalValue] };
|
|
629
|
+
}
|
|
630
|
+
return { enum: [Number(typeString)] };
|
|
631
|
+
}
|
|
632
|
+
const typePattern = /^(\w+)(\s*\|\s*undefined)?$/;
|
|
633
|
+
const match = typeString.match(typePattern);
|
|
634
|
+
if (match) {
|
|
635
|
+
const [, typeName, hasUndefined] = match;
|
|
636
|
+
if (typeRefs.has(typeName) || !isBuiltInType(typeName)) {
|
|
637
|
+
if (hasUndefined) {
|
|
638
|
+
return {
|
|
639
|
+
anyOf: [{ $ref: `#/types/${typeName}` }, { type: "null" }]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return { $ref: `#/types/${typeName}` };
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return { type: typeString };
|
|
646
|
+
} finally {
|
|
647
|
+
if (aliasAdded && aliasName) {
|
|
648
|
+
visited.delete(aliasName);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes) {
|
|
653
|
+
const paramName = param.getName();
|
|
654
|
+
const fallbackName = paramName === "__0" || ts.isObjectBindingPattern(paramDecl.name) || ts.isArrayBindingPattern(paramDecl.name) ? "object" : paramName;
|
|
655
|
+
if (paramType.isIntersection()) {
|
|
656
|
+
const properties = [];
|
|
657
|
+
const intersectionType = paramType;
|
|
658
|
+
for (const subType of intersectionType.types) {
|
|
659
|
+
const symbol2 = subType.getSymbol();
|
|
660
|
+
const _typeString = typeChecker.typeToString(subType);
|
|
661
|
+
const isAnonymousObject = isObjectLiteralType(subType) || symbol2?.getName().startsWith("__");
|
|
662
|
+
if (isAnonymousObject) {
|
|
663
|
+
for (const prop of subType.getProperties()) {
|
|
664
|
+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
665
|
+
let description = "";
|
|
666
|
+
if (functionDoc) {
|
|
667
|
+
let docParam = functionDoc.params.find((p) => p.name === `${paramName}.${prop.getName()}`);
|
|
668
|
+
if (!docParam && paramName === "__0") {
|
|
669
|
+
docParam = functionDoc.params.find((p) => p.name.endsWith(`.${prop.getName()}`));
|
|
670
|
+
}
|
|
671
|
+
if (docParam) {
|
|
672
|
+
description = docParam.description;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
properties.push({
|
|
676
|
+
name: prop.getName(),
|
|
677
|
+
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
|
|
678
|
+
description,
|
|
679
|
+
optional: !!(prop.flags & ts.SymbolFlags.Optional)
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
} else if (symbol2) {
|
|
683
|
+
const _symbolName = symbol2.getName();
|
|
684
|
+
if (!isBuiltInType(_symbolName)) {
|
|
685
|
+
for (const prop of subType.getProperties()) {
|
|
686
|
+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
687
|
+
properties.push({
|
|
688
|
+
name: prop.getName(),
|
|
689
|
+
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
|
|
690
|
+
description: "",
|
|
691
|
+
optional: !!(prop.flags & ts.SymbolFlags.Optional)
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const actualName = fallbackName;
|
|
698
|
+
return {
|
|
699
|
+
name: actualName,
|
|
700
|
+
required: !typeChecker.isOptionalParameter(paramDecl),
|
|
701
|
+
description: paramDoc?.description || "",
|
|
702
|
+
schema: propertiesToSchema(properties)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (paramType.isUnion()) {
|
|
706
|
+
const unionType = paramType;
|
|
707
|
+
const objectOptions = [];
|
|
708
|
+
let hasNonObjectTypes = false;
|
|
709
|
+
for (const subType of unionType.types) {
|
|
710
|
+
const symbol2 = subType.getSymbol();
|
|
711
|
+
if (isObjectLiteralType(subType) || symbol2?.getName().startsWith("__")) {
|
|
712
|
+
const properties = [];
|
|
713
|
+
for (const prop of subType.getProperties()) {
|
|
714
|
+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
715
|
+
properties.push({
|
|
716
|
+
name: prop.getName(),
|
|
717
|
+
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
|
|
718
|
+
description: "",
|
|
719
|
+
optional: !!(prop.flags & ts.SymbolFlags.Optional)
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
if (properties.length > 0) {
|
|
723
|
+
objectOptions.push({ properties });
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
hasNonObjectTypes = true;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (objectOptions.length > 0 && !hasNonObjectTypes) {
|
|
730
|
+
const readableName2 = fallbackName;
|
|
731
|
+
return {
|
|
732
|
+
name: readableName2,
|
|
733
|
+
required: !typeChecker.isOptionalParameter(paramDecl),
|
|
734
|
+
description: paramDoc?.description || "",
|
|
735
|
+
schema: {
|
|
736
|
+
oneOf: objectOptions.map((opt) => propertiesToSchema(opt.properties))
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const symbol = paramType.getSymbol();
|
|
742
|
+
if ((symbol?.getName().startsWith("__") || isObjectLiteralType(paramType)) && paramType.getProperties().length > 0) {
|
|
743
|
+
const properties = [];
|
|
744
|
+
for (const prop of paramType.getProperties()) {
|
|
745
|
+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
746
|
+
properties.push({
|
|
747
|
+
name: prop.getName(),
|
|
748
|
+
type: formatTypeReference(propType, typeChecker, typeRefs, referencedTypes),
|
|
749
|
+
description: "",
|
|
750
|
+
optional: !!(prop.flags & ts.SymbolFlags.Optional)
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
const readableName2 = fallbackName;
|
|
754
|
+
return {
|
|
755
|
+
name: readableName2,
|
|
756
|
+
required: !typeChecker.isOptionalParameter(paramDecl),
|
|
757
|
+
description: paramDoc?.description || "",
|
|
758
|
+
schema: propertiesToSchema(properties)
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
if (paramType.flags & ts.TypeFlags.Any && paramDecl.type && paramDecl.name && ts.isObjectBindingPattern(paramDecl.name)) {
|
|
762
|
+
const actualName = fallbackName;
|
|
763
|
+
const schema2 = buildSchemaFromTypeNode(paramDecl.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, param.getName());
|
|
764
|
+
return {
|
|
765
|
+
name: actualName,
|
|
766
|
+
required: !typeChecker.isOptionalParameter(paramDecl),
|
|
767
|
+
description: paramDoc?.description || "",
|
|
768
|
+
schema: schema2
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
const typeRef = formatTypeReference(paramType, typeChecker, typeRefs, referencedTypes);
|
|
772
|
+
let schema;
|
|
773
|
+
if (typeof typeRef === "string") {
|
|
774
|
+
if ([
|
|
775
|
+
"string",
|
|
776
|
+
"number",
|
|
777
|
+
"boolean",
|
|
778
|
+
"null",
|
|
779
|
+
"undefined",
|
|
780
|
+
"any",
|
|
781
|
+
"unknown",
|
|
782
|
+
"never",
|
|
783
|
+
"void"
|
|
784
|
+
].includes(typeRef)) {
|
|
785
|
+
schema = { type: typeRef };
|
|
786
|
+
} else {
|
|
787
|
+
schema = { type: typeRef };
|
|
788
|
+
}
|
|
789
|
+
} else {
|
|
790
|
+
schema = typeRef;
|
|
791
|
+
}
|
|
792
|
+
if (paramDecl.type) {
|
|
793
|
+
const astSchema = buildSchemaFromTypeNode(paramDecl.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, param.getName());
|
|
794
|
+
if (schemaIsAny(schema)) {
|
|
795
|
+
schema = astSchema;
|
|
796
|
+
} else if (!(("type" in schema) && schema.type === "any") && !(typeof schema === "object" && isPureRefSchema(schema)) && Object.keys(astSchema).length > 0 && !schemasAreEqual(schema, astSchema)) {
|
|
797
|
+
schema = {
|
|
798
|
+
allOf: [schema, astSchema]
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
const readableName = fallbackName;
|
|
803
|
+
return {
|
|
804
|
+
name: readableName,
|
|
805
|
+
required: !typeChecker.isOptionalParameter(paramDecl),
|
|
806
|
+
description: paramDoc?.description || "",
|
|
807
|
+
schema
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/utils/tsdoc-utils.ts
|
|
812
|
+
function parseJSDocComment(symbol, _typeChecker, sourceFileOverride) {
|
|
813
|
+
const node = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
814
|
+
if (!node)
|
|
815
|
+
return null;
|
|
816
|
+
const sourceFile = sourceFileOverride || node.getSourceFile();
|
|
817
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceFile.text, node.pos);
|
|
818
|
+
if (!commentRanges || commentRanges.length === 0) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
const lastComment = commentRanges[commentRanges.length - 1];
|
|
822
|
+
const commentText = sourceFile.text.substring(lastComment.pos, lastComment.end);
|
|
823
|
+
return parseJSDocText(commentText);
|
|
824
|
+
}
|
|
825
|
+
function parseJSDocText(commentText) {
|
|
826
|
+
const tags = [];
|
|
827
|
+
const result = {
|
|
828
|
+
description: "",
|
|
829
|
+
params: [],
|
|
830
|
+
examples: []
|
|
831
|
+
};
|
|
832
|
+
const cleanedText = commentText.replace(/^\/\*\*\s*/, "").replace(/\s*\*\/$/, "").replace(/^\s*\* ?/gm, "");
|
|
833
|
+
const lines = cleanedText.split(/\n/);
|
|
834
|
+
let currentTag = "";
|
|
835
|
+
let currentContent = [];
|
|
836
|
+
const pushDescription = (line) => {
|
|
837
|
+
const processed = replaceInlineLinks(line, tags).trimEnd();
|
|
838
|
+
if (processed.trim()) {
|
|
839
|
+
result.description = result.description ? `${result.description}
|
|
840
|
+
${processed}` : processed;
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
for (const line of lines) {
|
|
844
|
+
const tagMatch = line.match(/^@(\w+)(?:\s+(.*))?$/);
|
|
845
|
+
if (tagMatch) {
|
|
846
|
+
if (currentTag) {
|
|
847
|
+
processTag(result, tags, currentTag, currentContent.join(String.fromCharCode(10)));
|
|
848
|
+
}
|
|
849
|
+
currentTag = tagMatch[1];
|
|
850
|
+
currentContent = tagMatch[2] ? [tagMatch[2]] : [];
|
|
851
|
+
} else if (currentTag) {
|
|
852
|
+
currentContent.push(line);
|
|
853
|
+
} else {
|
|
854
|
+
if (line.trim()) {
|
|
855
|
+
pushDescription(line);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (currentTag) {
|
|
860
|
+
processTag(result, tags, currentTag, currentContent.join(String.fromCharCode(10)));
|
|
861
|
+
}
|
|
862
|
+
if (result.examples && result.examples.length === 0) {
|
|
863
|
+
delete result.examples;
|
|
864
|
+
}
|
|
865
|
+
if (tags.length > 0) {
|
|
866
|
+
result.tags = tags;
|
|
867
|
+
}
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
function processTag(result, tags, tag, content) {
|
|
871
|
+
switch (tag) {
|
|
872
|
+
case "param":
|
|
873
|
+
case "parameter": {
|
|
874
|
+
const paramMatch = content.match(/^(?:\{([^}]+)\}\s+)?(\S+)(?:\s+-\s+)?(.*)$/);
|
|
875
|
+
if (paramMatch) {
|
|
876
|
+
const [, type, name, description] = paramMatch;
|
|
877
|
+
const processedDescription = replaceInlineLinks(description || "", tags);
|
|
878
|
+
result.params.push({
|
|
879
|
+
name: name || "",
|
|
880
|
+
description: processedDescription || "",
|
|
881
|
+
type
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
case "returns":
|
|
887
|
+
case "return": {
|
|
888
|
+
result.returns = replaceInlineLinks(content, tags);
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
case "example": {
|
|
892
|
+
const example = replaceInlineLinks(content.trim(), tags).trim();
|
|
893
|
+
if (example) {
|
|
894
|
+
if (!result.examples) {
|
|
895
|
+
result.examples = [];
|
|
896
|
+
}
|
|
897
|
+
result.examples.push(example);
|
|
898
|
+
}
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case "see": {
|
|
902
|
+
const parts = content.split(",").map((part) => part.trim()).filter(Boolean);
|
|
903
|
+
for (const part of parts) {
|
|
904
|
+
const linkTargets = extractLinkTargets(part);
|
|
905
|
+
if (linkTargets.length > 0) {
|
|
906
|
+
for (const target of linkTargets) {
|
|
907
|
+
tags.push({ name: "link", text: target });
|
|
908
|
+
tags.push({ name: "see", text: target });
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
tags.push({ name: "see", text: part });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
case "link": {
|
|
917
|
+
const { target } = parseLinkBody(content.trim());
|
|
918
|
+
if (target) {
|
|
919
|
+
tags.push({ name: "link", text: target });
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
}
|
|
923
|
+
default: {
|
|
924
|
+
replaceInlineLinks(content, tags);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function replaceInlineLinks(text, tags, tagName = "link") {
|
|
929
|
+
return text.replace(/\{@link\s+([^}]+)\}/g, (_match, body) => {
|
|
930
|
+
const { target, label } = parseLinkBody(body);
|
|
931
|
+
if (target) {
|
|
932
|
+
tags.push({ name: tagName, text: target });
|
|
933
|
+
}
|
|
934
|
+
return label || target || "";
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
function extractLinkTargets(text) {
|
|
938
|
+
const targets = [];
|
|
939
|
+
text.replace(/\{@link\s+([^}]+)\}/g, (_match, body) => {
|
|
940
|
+
const { target } = parseLinkBody(body);
|
|
941
|
+
if (target) {
|
|
942
|
+
targets.push(target);
|
|
943
|
+
}
|
|
944
|
+
return "";
|
|
945
|
+
});
|
|
946
|
+
return targets;
|
|
947
|
+
}
|
|
948
|
+
function parseLinkBody(raw) {
|
|
949
|
+
const trimmed = raw.trim();
|
|
950
|
+
if (!trimmed) {
|
|
951
|
+
return { target: "" };
|
|
952
|
+
}
|
|
953
|
+
const pipeIndex = trimmed.indexOf("|");
|
|
954
|
+
if (pipeIndex >= 0) {
|
|
955
|
+
const target2 = trimmed.slice(0, pipeIndex).trim();
|
|
956
|
+
const label2 = trimmed.slice(pipeIndex + 1).trim();
|
|
957
|
+
return { target: target2, label: label2 };
|
|
958
|
+
}
|
|
959
|
+
const parts = trimmed.split(/\s+/);
|
|
960
|
+
const target = parts.shift() ?? "";
|
|
961
|
+
const label = parts.join(" ").trim();
|
|
962
|
+
return { target, label: label || undefined };
|
|
963
|
+
}
|
|
964
|
+
function extractDestructuredParams(parsedDoc, paramName) {
|
|
965
|
+
const destructuredParams = new Map;
|
|
966
|
+
const paramPrefix = `${paramName}.`;
|
|
967
|
+
for (const param of parsedDoc.params) {
|
|
968
|
+
if (param.name.startsWith(paramPrefix)) {
|
|
969
|
+
const propertyName = param.name.substring(paramPrefix.length);
|
|
970
|
+
destructuredParams.set(propertyName, param.description);
|
|
971
|
+
} else if (param.name.includes(".") && paramName === "__0") {
|
|
972
|
+
const [_prefix, propertyName] = param.name.split(".", 2);
|
|
973
|
+
if (propertyName) {
|
|
974
|
+
destructuredParams.set(propertyName, param.description);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return destructuredParams;
|
|
979
|
+
}
|
|
980
|
+
function getParameterDocumentation(param, paramDecl, typeChecker) {
|
|
981
|
+
const result = {
|
|
982
|
+
description: ""
|
|
983
|
+
};
|
|
984
|
+
const funcNode = paramDecl.parent;
|
|
985
|
+
if (ts.isFunctionDeclaration(funcNode) || ts.isFunctionExpression(funcNode)) {
|
|
986
|
+
const funcSymbol = typeChecker.getSymbolAtLocation(funcNode.name || funcNode);
|
|
987
|
+
if (funcSymbol) {
|
|
988
|
+
const parsedDoc = parseJSDocComment(funcSymbol, typeChecker);
|
|
989
|
+
if (parsedDoc) {
|
|
990
|
+
const paramName = param.getName();
|
|
991
|
+
const paramDoc = parsedDoc.params.find((p) => p.name === paramName || p.name.split(".")[0] === paramName);
|
|
992
|
+
if (paramDoc) {
|
|
993
|
+
result.description = paramDoc.description;
|
|
994
|
+
}
|
|
995
|
+
const destructuredProps = extractDestructuredParams(parsedDoc, paramName);
|
|
996
|
+
if (destructuredProps.size > 0) {
|
|
997
|
+
result.destructuredProperties = Array.from(destructuredProps.entries()).map(([name, description]) => ({
|
|
998
|
+
name,
|
|
999
|
+
description
|
|
1000
|
+
}));
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/analysis/ast-utils.ts
|
|
1009
|
+
function getJSDocComment(symbol, typeChecker) {
|
|
1010
|
+
const comments = symbol.getDocumentationComment(typeChecker);
|
|
1011
|
+
return ts.displayPartsToString(comments);
|
|
1012
|
+
}
|
|
1013
|
+
function getSourceLocation(node) {
|
|
1014
|
+
const sourceFile = node.getSourceFile();
|
|
1015
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
1016
|
+
return {
|
|
1017
|
+
file: sourceFile.fileName,
|
|
1018
|
+
line: line + 1
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/analysis/serializers/classes.ts
|
|
1023
|
+
function serializeClass(declaration, symbol, context) {
|
|
1024
|
+
const { checker, typeRegistry } = context;
|
|
1025
|
+
const typeRefs = typeRegistry.getTypeRefs();
|
|
1026
|
+
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
1027
|
+
const members = serializeClassMembers(declaration, checker, typeRefs, referencedTypes);
|
|
1028
|
+
const parsedDoc = parseJSDocComment(symbol, context.checker);
|
|
1029
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
|
|
1030
|
+
const exportEntry = {
|
|
1031
|
+
id: symbol.getName(),
|
|
1032
|
+
name: symbol.getName(),
|
|
1033
|
+
kind: "class",
|
|
1034
|
+
description,
|
|
1035
|
+
source: getSourceLocation(declaration),
|
|
1036
|
+
members: members.length > 0 ? members : undefined,
|
|
1037
|
+
tags: parsedDoc?.tags
|
|
1038
|
+
};
|
|
1039
|
+
const typeDefinition = {
|
|
1040
|
+
id: symbol.getName(),
|
|
1041
|
+
name: symbol.getName(),
|
|
1042
|
+
kind: "class",
|
|
1043
|
+
description,
|
|
1044
|
+
source: getSourceLocation(declaration),
|
|
1045
|
+
members: members.length > 0 ? members : undefined,
|
|
1046
|
+
tags: parsedDoc?.tags
|
|
1047
|
+
};
|
|
1048
|
+
return {
|
|
1049
|
+
exportEntry,
|
|
1050
|
+
typeDefinition
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
function serializeClassMembers(declaration, checker, typeRefs, referencedTypes) {
|
|
1054
|
+
const members = [];
|
|
1055
|
+
for (const member of declaration.members) {
|
|
1056
|
+
if (!member.name && !ts.isConstructorDeclaration(member)) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
|
|
1060
|
+
const memberName = member.name?.getText();
|
|
1061
|
+
if (!memberName)
|
|
1062
|
+
continue;
|
|
1063
|
+
const memberSymbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined;
|
|
1064
|
+
const memberType = memberSymbol ? checker.getTypeOfSymbolAtLocation(memberSymbol, member) : member.type ? checker.getTypeFromTypeNode(member.type) : checker.getTypeAtLocation(member);
|
|
1065
|
+
collectReferencedTypes(memberType, checker, referencedTypes);
|
|
1066
|
+
const schema = formatTypeReference(memberType, checker, typeRefs, referencedTypes);
|
|
1067
|
+
const flags = {};
|
|
1068
|
+
const isOptionalSymbol = memberSymbol != null && (memberSymbol.flags & ts.SymbolFlags.Optional) !== 0;
|
|
1069
|
+
if (member.questionToken || isOptionalSymbol) {
|
|
1070
|
+
flags.optional = true;
|
|
1071
|
+
}
|
|
1072
|
+
if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword)) {
|
|
1073
|
+
flags.readonly = true;
|
|
1074
|
+
}
|
|
1075
|
+
if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
|
1076
|
+
flags.static = true;
|
|
1077
|
+
}
|
|
1078
|
+
members.push({
|
|
1079
|
+
id: memberName,
|
|
1080
|
+
name: memberName,
|
|
1081
|
+
kind: "property",
|
|
1082
|
+
visibility: getMemberVisibility(member.modifiers),
|
|
1083
|
+
schema,
|
|
1084
|
+
description: memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined,
|
|
1085
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1086
|
+
});
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
if (ts.isMethodDeclaration(member)) {
|
|
1090
|
+
const memberName = member.name?.getText() ?? "method";
|
|
1091
|
+
const memberSymbol = member.name ? checker.getSymbolAtLocation(member.name) : undefined;
|
|
1092
|
+
const methodDoc = memberSymbol ? parseJSDocComment(memberSymbol, checker) : null;
|
|
1093
|
+
const signature = checker.getSignatureFromDeclaration(member);
|
|
1094
|
+
const signatures = signature ? [
|
|
1095
|
+
serializeSignature(signature, checker, typeRefs, referencedTypes, methodDoc, memberSymbol)
|
|
1096
|
+
] : undefined;
|
|
1097
|
+
members.push({
|
|
1098
|
+
id: memberName,
|
|
1099
|
+
name: memberName,
|
|
1100
|
+
kind: "method",
|
|
1101
|
+
visibility: getMemberVisibility(member.modifiers),
|
|
1102
|
+
signatures,
|
|
1103
|
+
description: memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined,
|
|
1104
|
+
flags: getMethodFlags(member)
|
|
1105
|
+
});
|
|
1106
|
+
continue;
|
|
1107
|
+
}
|
|
1108
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
1109
|
+
const ctorSymbol = checker.getSymbolAtLocation(member);
|
|
1110
|
+
const ctorDoc = ctorSymbol ? parseJSDocComment(ctorSymbol, checker) : null;
|
|
1111
|
+
const signature = checker.getSignatureFromDeclaration(member);
|
|
1112
|
+
const signatures = signature ? [serializeSignature(signature, checker, typeRefs, referencedTypes, ctorDoc, ctorSymbol)] : undefined;
|
|
1113
|
+
members.push({
|
|
1114
|
+
id: "constructor",
|
|
1115
|
+
name: "constructor",
|
|
1116
|
+
kind: "constructor",
|
|
1117
|
+
visibility: getMemberVisibility(member.modifiers),
|
|
1118
|
+
signatures,
|
|
1119
|
+
description: ctorSymbol ? getJSDocComment(ctorSymbol, checker) : undefined
|
|
1120
|
+
});
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) {
|
|
1124
|
+
const memberName = member.name?.getText();
|
|
1125
|
+
if (!memberName)
|
|
1126
|
+
continue;
|
|
1127
|
+
const memberSymbol = checker.getSymbolAtLocation(member.name);
|
|
1128
|
+
const accessorType = ts.isGetAccessorDeclaration(member) ? checker.getTypeAtLocation(member) : member.parameters.length > 0 ? checker.getTypeAtLocation(member.parameters[0]) : checker.getTypeAtLocation(member);
|
|
1129
|
+
collectReferencedTypes(accessorType, checker, referencedTypes);
|
|
1130
|
+
const schema = formatTypeReference(accessorType, checker, typeRefs, referencedTypes);
|
|
1131
|
+
members.push({
|
|
1132
|
+
id: memberName,
|
|
1133
|
+
name: memberName,
|
|
1134
|
+
kind: "accessor",
|
|
1135
|
+
visibility: getMemberVisibility(member.modifiers),
|
|
1136
|
+
schema,
|
|
1137
|
+
description: memberSymbol ? getJSDocComment(memberSymbol, checker) : undefined
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return members;
|
|
1142
|
+
}
|
|
1143
|
+
function serializeSignature(signature, checker, typeRefs, referencedTypes, doc, symbol) {
|
|
1144
|
+
return {
|
|
1145
|
+
parameters: signature.getParameters().map((param) => {
|
|
1146
|
+
const paramDecl = param.valueDeclaration;
|
|
1147
|
+
const paramType = paramDecl?.type != null ? checker.getTypeFromTypeNode(paramDecl.type) : checker.getTypeAtLocation(paramDecl);
|
|
1148
|
+
collectReferencedTypes(paramType, checker, referencedTypes);
|
|
1149
|
+
const paramDoc = paramDecl ? getParameterDocumentation(param, paramDecl, checker) : undefined;
|
|
1150
|
+
return structureParameter(param, paramDecl, paramType, checker, typeRefs, doc, paramDoc, referencedTypes);
|
|
1151
|
+
}),
|
|
1152
|
+
returns: {
|
|
1153
|
+
schema: formatTypeReference(signature.getReturnType(), checker, typeRefs, referencedTypes),
|
|
1154
|
+
description: doc?.returns || ""
|
|
1155
|
+
},
|
|
1156
|
+
description: doc?.description || (symbol ? getJSDocComment(symbol, checker) : undefined)
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function getMemberVisibility(modifiers) {
|
|
1160
|
+
if (!modifiers)
|
|
1161
|
+
return;
|
|
1162
|
+
if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)) {
|
|
1163
|
+
return "private";
|
|
1164
|
+
}
|
|
1165
|
+
if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.ProtectedKeyword)) {
|
|
1166
|
+
return "protected";
|
|
1167
|
+
}
|
|
1168
|
+
if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PublicKeyword)) {
|
|
1169
|
+
return "public";
|
|
1170
|
+
}
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
function getMethodFlags(member) {
|
|
1174
|
+
const flags = {};
|
|
1175
|
+
if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
|
|
1176
|
+
flags.static = true;
|
|
1177
|
+
}
|
|
1178
|
+
if (member.asteriskToken) {
|
|
1179
|
+
flags.generator = true;
|
|
1180
|
+
}
|
|
1181
|
+
if (member.questionToken) {
|
|
1182
|
+
flags.optional = true;
|
|
1183
|
+
}
|
|
1184
|
+
return Object.keys(flags).length > 0 ? flags : undefined;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/analysis/serializers/enums.ts
|
|
1188
|
+
function serializeEnum(declaration, symbol, context) {
|
|
1189
|
+
const parsedDoc = parseJSDocComment(symbol, context.checker);
|
|
1190
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
|
|
1191
|
+
const exportEntry = {
|
|
1192
|
+
id: symbol.getName(),
|
|
1193
|
+
name: symbol.getName(),
|
|
1194
|
+
kind: "enum",
|
|
1195
|
+
description,
|
|
1196
|
+
source: getSourceLocation(declaration),
|
|
1197
|
+
tags: parsedDoc?.tags
|
|
1198
|
+
};
|
|
1199
|
+
const typeDefinition = {
|
|
1200
|
+
id: symbol.getName(),
|
|
1201
|
+
name: symbol.getName(),
|
|
1202
|
+
kind: "enum",
|
|
1203
|
+
members: getEnumMembers(declaration),
|
|
1204
|
+
description,
|
|
1205
|
+
source: getSourceLocation(declaration),
|
|
1206
|
+
tags: parsedDoc?.tags
|
|
1207
|
+
};
|
|
1208
|
+
return {
|
|
1209
|
+
exportEntry,
|
|
1210
|
+
typeDefinition
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function getEnumMembers(enumDecl) {
|
|
1214
|
+
return enumDecl.members.map((member) => ({
|
|
1215
|
+
id: member.name?.getText() || "",
|
|
1216
|
+
name: member.name?.getText() || "",
|
|
1217
|
+
value: member.initializer ? member.initializer.getText() : undefined,
|
|
1218
|
+
description: ""
|
|
1219
|
+
}));
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// src/analysis/serializers/functions.ts
|
|
1223
|
+
function serializeCallSignatures(signatures, symbol, context, parsedDoc) {
|
|
1224
|
+
if (signatures.length === 0) {
|
|
1225
|
+
return [];
|
|
1226
|
+
}
|
|
1227
|
+
const { checker, typeRegistry } = context;
|
|
1228
|
+
const typeRefs = typeRegistry.getTypeRefs();
|
|
1229
|
+
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
1230
|
+
const functionDoc = parsedDoc ?? (symbol ? parseJSDocComment(symbol, checker) : null);
|
|
1231
|
+
return signatures.map((signature) => {
|
|
1232
|
+
const parameters = signature.getParameters().map((param) => {
|
|
1233
|
+
const paramDecl = param.declarations?.find(ts.isParameter);
|
|
1234
|
+
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);
|
|
1235
|
+
collectReferencedTypes(paramType, checker, referencedTypes);
|
|
1236
|
+
if (paramDecl?.type) {
|
|
1237
|
+
collectReferencedTypesFromNode(paramDecl.type, checker, referencedTypes);
|
|
1238
|
+
}
|
|
1239
|
+
if (paramDecl && ts.isParameter(paramDecl)) {
|
|
1240
|
+
const paramDoc = getParameterDocumentation(param, paramDecl, checker);
|
|
1241
|
+
return structureParameter(param, paramDecl, paramType, checker, typeRefs, functionDoc, paramDoc, referencedTypes);
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
name: param.getName(),
|
|
1245
|
+
required: !(param.flags & ts.SymbolFlags.Optional),
|
|
1246
|
+
description: "",
|
|
1247
|
+
schema: formatTypeReference(paramType, checker, typeRefs, referencedTypes)
|
|
1248
|
+
};
|
|
1249
|
+
});
|
|
1250
|
+
const returnType = signature.getReturnType();
|
|
1251
|
+
if (returnType) {
|
|
1252
|
+
collectReferencedTypes(returnType, checker, referencedTypes);
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
parameters,
|
|
1256
|
+
returns: {
|
|
1257
|
+
schema: returnType ? formatTypeReference(returnType, checker, typeRefs, referencedTypes) : { type: "void" },
|
|
1258
|
+
description: functionDoc?.returns || ""
|
|
1259
|
+
},
|
|
1260
|
+
description: functionDoc?.description || undefined
|
|
1261
|
+
};
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
function serializeFunctionExport(declaration, symbol, context) {
|
|
1265
|
+
const { checker } = context;
|
|
1266
|
+
const signature = checker.getSignatureFromDeclaration(declaration);
|
|
1267
|
+
const funcSymbol = checker.getSymbolAtLocation(declaration.name || declaration);
|
|
1268
|
+
const parsedDoc = parseJSDocComment(symbol, checker);
|
|
1269
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
|
|
1270
|
+
return {
|
|
1271
|
+
id: symbol.getName(),
|
|
1272
|
+
name: symbol.getName(),
|
|
1273
|
+
kind: "function",
|
|
1274
|
+
signatures: signature ? serializeCallSignatures([signature], funcSymbol ?? symbol, context, parsedDoc) : [],
|
|
1275
|
+
description,
|
|
1276
|
+
source: getSourceLocation(declaration),
|
|
1277
|
+
examples: parsedDoc?.examples,
|
|
1278
|
+
tags: parsedDoc?.tags
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/analysis/serializers/interfaces.ts
|
|
1283
|
+
function serializeInterface(declaration, symbol, context) {
|
|
1284
|
+
const parsedDoc = parseJSDocComment(symbol, context.checker);
|
|
1285
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, context.checker);
|
|
1286
|
+
const exportEntry = {
|
|
1287
|
+
id: symbol.getName(),
|
|
1288
|
+
name: symbol.getName(),
|
|
1289
|
+
kind: "interface",
|
|
1290
|
+
description,
|
|
1291
|
+
source: getSourceLocation(declaration),
|
|
1292
|
+
tags: parsedDoc?.tags
|
|
1293
|
+
};
|
|
1294
|
+
const schema = interfaceToSchema(declaration, context.checker, context.typeRegistry.getTypeRefs(), context.typeRegistry.getReferencedTypes());
|
|
1295
|
+
const typeDefinition = {
|
|
1296
|
+
id: symbol.getName(),
|
|
1297
|
+
name: symbol.getName(),
|
|
1298
|
+
kind: "interface",
|
|
1299
|
+
schema,
|
|
1300
|
+
description,
|
|
1301
|
+
source: getSourceLocation(declaration),
|
|
1302
|
+
tags: parsedDoc?.tags
|
|
1303
|
+
};
|
|
1304
|
+
return {
|
|
1305
|
+
exportEntry,
|
|
1306
|
+
typeDefinition
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function interfaceToSchema(iface, typeChecker, typeRefs, referencedTypes) {
|
|
1310
|
+
const schema = {
|
|
1311
|
+
type: "object",
|
|
1312
|
+
properties: {}
|
|
1313
|
+
};
|
|
1314
|
+
const required = [];
|
|
1315
|
+
for (const prop of iface.members.filter(ts.isPropertySignature)) {
|
|
1316
|
+
const propName = prop.name?.getText() || "";
|
|
1317
|
+
if (prop.type) {
|
|
1318
|
+
const propType = typeChecker.getTypeAtLocation(prop.type);
|
|
1319
|
+
collectReferencedTypes(propType, typeChecker, referencedTypes);
|
|
1320
|
+
}
|
|
1321
|
+
schema.properties[propName] = prop.type ? formatTypeReference(typeChecker.getTypeAtLocation(prop.type), typeChecker, typeRefs, referencedTypes) : { type: "any" };
|
|
1322
|
+
if (!prop.questionToken) {
|
|
1323
|
+
required.push(propName);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (required.length > 0) {
|
|
1327
|
+
schema.required = required;
|
|
1328
|
+
}
|
|
1329
|
+
return schema;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// src/analysis/serializers/type-aliases.ts
|
|
1333
|
+
function serializeTypeAlias(declaration, symbol, context) {
|
|
1334
|
+
const { checker, typeRegistry } = context;
|
|
1335
|
+
const typeRefs = typeRegistry.getTypeRefs();
|
|
1336
|
+
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
1337
|
+
const parsedDoc = parseJSDocComment(symbol, checker);
|
|
1338
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
|
|
1339
|
+
const exportEntry = {
|
|
1340
|
+
id: symbol.getName(),
|
|
1341
|
+
name: symbol.getName(),
|
|
1342
|
+
kind: "type",
|
|
1343
|
+
type: typeToRef(declaration.type, checker, typeRefs, referencedTypes),
|
|
1344
|
+
description,
|
|
1345
|
+
source: getSourceLocation(declaration),
|
|
1346
|
+
tags: parsedDoc?.tags
|
|
1347
|
+
};
|
|
1348
|
+
const aliasType = checker.getTypeAtLocation(declaration.type);
|
|
1349
|
+
const aliasName = symbol.getName();
|
|
1350
|
+
const existingRef = typeRefs.get(aliasName);
|
|
1351
|
+
if (existingRef) {
|
|
1352
|
+
typeRefs.delete(aliasName);
|
|
1353
|
+
}
|
|
1354
|
+
const aliasSchema = formatTypeReference(aliasType, checker, typeRefs, undefined);
|
|
1355
|
+
if (existingRef) {
|
|
1356
|
+
typeRefs.set(aliasName, existingRef);
|
|
1357
|
+
}
|
|
1358
|
+
const typeDefinition = {
|
|
1359
|
+
id: symbol.getName(),
|
|
1360
|
+
name: symbol.getName(),
|
|
1361
|
+
kind: "type",
|
|
1362
|
+
description,
|
|
1363
|
+
source: getSourceLocation(declaration),
|
|
1364
|
+
tags: parsedDoc?.tags
|
|
1365
|
+
};
|
|
1366
|
+
if (typeof aliasSchema === "string") {
|
|
1367
|
+
typeDefinition.type = aliasSchema;
|
|
1368
|
+
} else if (aliasSchema && Object.keys(aliasSchema).length > 0) {
|
|
1369
|
+
typeDefinition.schema = aliasSchema;
|
|
1370
|
+
} else {
|
|
1371
|
+
typeDefinition.type = declaration.type.getText();
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
exportEntry,
|
|
1375
|
+
typeDefinition
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function typeToRef(node, typeChecker, typeRefs, referencedTypes) {
|
|
1379
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
1380
|
+
collectReferencedTypes(type, typeChecker, referencedTypes);
|
|
1381
|
+
return formatTypeReference(type, typeChecker, typeRefs, referencedTypes);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/analysis/serializers/variables.ts
|
|
1385
|
+
function serializeVariable(declaration, symbol, context) {
|
|
1386
|
+
const { checker, typeRegistry } = context;
|
|
1387
|
+
const variableType = checker.getTypeAtLocation(declaration.name ?? declaration);
|
|
1388
|
+
const callSignatures = variableType.getCallSignatures();
|
|
1389
|
+
const parsedDoc = parseJSDocComment(symbol, checker);
|
|
1390
|
+
const description = parsedDoc?.description ?? getJSDocComment(symbol, checker);
|
|
1391
|
+
if (callSignatures.length > 0) {
|
|
1392
|
+
return {
|
|
1393
|
+
id: symbol.getName(),
|
|
1394
|
+
name: symbol.getName(),
|
|
1395
|
+
kind: "function",
|
|
1396
|
+
signatures: serializeCallSignatures(callSignatures, symbol, context, parsedDoc),
|
|
1397
|
+
description,
|
|
1398
|
+
source: getSourceLocation(declaration.initializer ?? declaration),
|
|
1399
|
+
examples: parsedDoc?.examples,
|
|
1400
|
+
tags: parsedDoc?.tags
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const typeRefs = typeRegistry.getTypeRefs();
|
|
1404
|
+
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
1405
|
+
return {
|
|
1406
|
+
id: symbol.getName(),
|
|
1407
|
+
name: symbol.getName(),
|
|
1408
|
+
kind: "variable",
|
|
1409
|
+
type: typeToRef2(declaration, checker, typeRefs, referencedTypes),
|
|
1410
|
+
description,
|
|
1411
|
+
source: getSourceLocation(declaration),
|
|
1412
|
+
tags: parsedDoc?.tags
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
function typeToRef2(node, typeChecker, typeRefs, referencedTypes) {
|
|
1416
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
1417
|
+
collectReferencedTypes(type, typeChecker, referencedTypes);
|
|
1418
|
+
return formatTypeReference(type, typeChecker, typeRefs, referencedTypes);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/analysis/type-registry.ts
|
|
1422
|
+
class TypeRegistry {
|
|
1423
|
+
typeRefs = new Map;
|
|
1424
|
+
typeDefinitions = new Map;
|
|
1425
|
+
referencedTypes = new Set;
|
|
1426
|
+
registerExportedType(name, id = name) {
|
|
1427
|
+
if (!this.typeRefs.has(name)) {
|
|
1428
|
+
this.typeRefs.set(name, id);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
hasType(name) {
|
|
1432
|
+
return this.typeDefinitions.has(name);
|
|
1433
|
+
}
|
|
1434
|
+
registerTypeDefinition(definition) {
|
|
1435
|
+
if (this.typeDefinitions.has(definition.name)) {
|
|
1436
|
+
return false;
|
|
1437
|
+
}
|
|
1438
|
+
this.typeDefinitions.set(definition.name, definition);
|
|
1439
|
+
if (!this.typeRefs.has(definition.name)) {
|
|
1440
|
+
this.typeRefs.set(definition.name, definition.id);
|
|
1441
|
+
}
|
|
1442
|
+
return true;
|
|
1443
|
+
}
|
|
1444
|
+
getTypeRefs() {
|
|
1445
|
+
return this.typeRefs;
|
|
1446
|
+
}
|
|
1447
|
+
getTypeDefinitions() {
|
|
1448
|
+
return Array.from(this.typeDefinitions.values());
|
|
1449
|
+
}
|
|
1450
|
+
getReferencedTypes() {
|
|
1451
|
+
return this.referencedTypes;
|
|
1452
|
+
}
|
|
1453
|
+
isKnownType(name) {
|
|
1454
|
+
if (this.typeDefinitions.has(name)) {
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
const ref = this.typeRefs.get(name);
|
|
1458
|
+
if (ref === undefined) {
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
if (ref !== name) {
|
|
1462
|
+
return this.typeDefinitions.has(ref);
|
|
1463
|
+
}
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// src/analysis/spec-builder.ts
|
|
1469
|
+
function buildOpenPkgSpec(context, resolveExternalTypes) {
|
|
1470
|
+
const { baseDir, checker: typeChecker, sourceFile, program } = context;
|
|
1471
|
+
const packageJsonPath = path3.join(baseDir, "package.json");
|
|
1472
|
+
const packageJson = fs.existsSync(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {};
|
|
1473
|
+
const spec = {
|
|
1474
|
+
$schema: SCHEMA_URL,
|
|
1475
|
+
openpkg: "0.1.0",
|
|
1476
|
+
meta: {
|
|
1477
|
+
name: packageJson.name || "unknown",
|
|
1478
|
+
version: packageJson.version || "1.0.0",
|
|
1479
|
+
description: packageJson.description || "",
|
|
1480
|
+
license: packageJson.license || "",
|
|
1481
|
+
repository: packageJson.repository?.url || packageJson.repository || "",
|
|
1482
|
+
ecosystem: "js/ts"
|
|
1483
|
+
},
|
|
1484
|
+
exports: [],
|
|
1485
|
+
types: []
|
|
1486
|
+
};
|
|
1487
|
+
const typeRegistry = new TypeRegistry;
|
|
1488
|
+
const serializerContext = {
|
|
1489
|
+
checker: typeChecker,
|
|
1490
|
+
typeRegistry
|
|
1491
|
+
};
|
|
1492
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
1493
|
+
if (!moduleSymbol) {
|
|
1494
|
+
return spec;
|
|
1495
|
+
}
|
|
1496
|
+
const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
|
|
1497
|
+
for (const symbol of exportedSymbols) {
|
|
1498
|
+
const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
|
|
1499
|
+
if (!declaration)
|
|
1500
|
+
continue;
|
|
1501
|
+
const exportName = symbol.getName();
|
|
1502
|
+
if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) || ts.isTypeAliasDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
|
|
1503
|
+
typeRegistry.registerExportedType(exportName, targetSymbol.getName());
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
for (const symbol of exportedSymbols) {
|
|
1507
|
+
const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
|
|
1508
|
+
if (!declaration)
|
|
1509
|
+
continue;
|
|
1510
|
+
const exportName = symbol.getName();
|
|
1511
|
+
if (ts.isFunctionDeclaration(declaration)) {
|
|
1512
|
+
const exportEntry = serializeFunctionExport(declaration, targetSymbol, serializerContext);
|
|
1513
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1514
|
+
} else if (ts.isClassDeclaration(declaration)) {
|
|
1515
|
+
const { exportEntry, typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
|
|
1516
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1517
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1518
|
+
spec.types?.push(typeDefinition);
|
|
1519
|
+
}
|
|
1520
|
+
} else if (ts.isInterfaceDeclaration(declaration)) {
|
|
1521
|
+
const { exportEntry, typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
|
|
1522
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1523
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1524
|
+
spec.types?.push(typeDefinition);
|
|
1525
|
+
}
|
|
1526
|
+
} else if (ts.isTypeAliasDeclaration(declaration)) {
|
|
1527
|
+
const { exportEntry, typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
|
|
1528
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1529
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1530
|
+
spec.types?.push(typeDefinition);
|
|
1531
|
+
}
|
|
1532
|
+
} else if (ts.isEnumDeclaration(declaration)) {
|
|
1533
|
+
const { exportEntry, typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
|
|
1534
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1535
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1536
|
+
spec.types?.push(typeDefinition);
|
|
1537
|
+
}
|
|
1538
|
+
} else if (ts.isVariableDeclaration(declaration)) {
|
|
1539
|
+
const exportEntry = serializeVariable(declaration, targetSymbol, serializerContext);
|
|
1540
|
+
spec.exports.push(withExportName(exportEntry, exportName));
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
for (const typeName of typeRegistry.getReferencedTypes()) {
|
|
1544
|
+
if (typeRegistry.isKnownType(typeName)) {
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
const allSourceFiles = program.getSourceFiles();
|
|
1548
|
+
for (const file of allSourceFiles) {
|
|
1549
|
+
if (!resolveExternalTypes && (file.fileName.includes("node_modules") || file.fileName.endsWith(".d.ts") && !file.fileName.startsWith(baseDir))) {
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
const fileSymbol = typeChecker.getSymbolAtLocation(file);
|
|
1553
|
+
if (!fileSymbol) {
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
const exports = typeChecker.getExportsOfModule(fileSymbol);
|
|
1557
|
+
for (const exportSymbol of exports) {
|
|
1558
|
+
if (exportSymbol.getName() !== typeName || typeRegistry.isKnownType(typeName)) {
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
const { declaration, targetSymbol } = resolveExportTarget(exportSymbol, typeChecker);
|
|
1562
|
+
if (!declaration)
|
|
1563
|
+
continue;
|
|
1564
|
+
if (ts.isClassDeclaration(declaration)) {
|
|
1565
|
+
const { typeDefinition } = serializeClass(declaration, targetSymbol, serializerContext);
|
|
1566
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1567
|
+
spec.types?.push(typeDefinition);
|
|
1568
|
+
}
|
|
1569
|
+
} else if (ts.isInterfaceDeclaration(declaration)) {
|
|
1570
|
+
const { typeDefinition } = serializeInterface(declaration, targetSymbol, serializerContext);
|
|
1571
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1572
|
+
spec.types?.push(typeDefinition);
|
|
1573
|
+
}
|
|
1574
|
+
} else if (ts.isTypeAliasDeclaration(declaration)) {
|
|
1575
|
+
const { typeDefinition } = serializeTypeAlias(declaration, targetSymbol, serializerContext);
|
|
1576
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1577
|
+
spec.types?.push(typeDefinition);
|
|
1578
|
+
}
|
|
1579
|
+
} else if (ts.isEnumDeclaration(declaration)) {
|
|
1580
|
+
const { typeDefinition } = serializeEnum(declaration, targetSymbol, serializerContext);
|
|
1581
|
+
if (typeDefinition && typeRegistry.registerTypeDefinition(typeDefinition)) {
|
|
1582
|
+
spec.types?.push(typeDefinition);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return spec;
|
|
1589
|
+
}
|
|
1590
|
+
function resolveExportTarget(symbol, checker) {
|
|
1591
|
+
let targetSymbol = symbol;
|
|
1592
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
1593
|
+
const aliasTarget = checker.getImmediateAliasedSymbol(symbol);
|
|
1594
|
+
if (aliasTarget) {
|
|
1595
|
+
targetSymbol = aliasTarget;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
const declarations = targetSymbol.declarations ?? [];
|
|
1599
|
+
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
1600
|
+
return {
|
|
1601
|
+
declaration,
|
|
1602
|
+
targetSymbol
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function withExportName(entry, exportName) {
|
|
1606
|
+
if (entry.name === exportName) {
|
|
1607
|
+
return entry;
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
...entry,
|
|
1611
|
+
id: exportName,
|
|
1612
|
+
name: exportName
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/analysis/run-analysis.ts
|
|
1617
|
+
function findNearestPackageJson(startDir) {
|
|
1618
|
+
let current = startDir;
|
|
1619
|
+
while (true) {
|
|
1620
|
+
const candidate = path4.join(current, "package.json");
|
|
1621
|
+
if (fs2.existsSync(candidate)) {
|
|
1622
|
+
return candidate;
|
|
1623
|
+
}
|
|
1624
|
+
const parent = path4.dirname(current);
|
|
1625
|
+
if (parent === current) {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
current = parent;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function hasNodeModulesDirectory(directories) {
|
|
1632
|
+
for (const dir of directories) {
|
|
1633
|
+
let current = dir;
|
|
1634
|
+
while (true) {
|
|
1635
|
+
const candidate = path4.join(current, "node_modules");
|
|
1636
|
+
if (fs2.existsSync(candidate)) {
|
|
1637
|
+
return true;
|
|
1638
|
+
}
|
|
1639
|
+
const parent = path4.dirname(current);
|
|
1640
|
+
if (parent === current) {
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
current = parent;
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return false;
|
|
1647
|
+
}
|
|
1648
|
+
function runAnalysis(input) {
|
|
1649
|
+
const context = createAnalysisContext(input);
|
|
1650
|
+
const { baseDir, options } = context;
|
|
1651
|
+
const packageJsonPath = findNearestPackageJson(baseDir);
|
|
1652
|
+
const searchDirs = new Set([baseDir]);
|
|
1653
|
+
if (packageJsonPath) {
|
|
1654
|
+
searchDirs.add(path4.dirname(packageJsonPath));
|
|
1655
|
+
}
|
|
1656
|
+
const hasNodeModules = hasNodeModulesDirectory(searchDirs);
|
|
1657
|
+
const resolveExternalTypes = options.resolveExternalTypes !== undefined ? options.resolveExternalTypes : hasNodeModules;
|
|
1658
|
+
const diagnostics = ts.getPreEmitDiagnostics(context.program);
|
|
1659
|
+
const spec = buildOpenPkgSpec(context, resolveExternalTypes);
|
|
1660
|
+
return {
|
|
1661
|
+
spec,
|
|
1662
|
+
metadata: {
|
|
1663
|
+
baseDir,
|
|
1664
|
+
configPath: context.configPath,
|
|
1665
|
+
packageJsonPath,
|
|
1666
|
+
hasNodeModules,
|
|
1667
|
+
resolveExternalTypes
|
|
1668
|
+
},
|
|
1669
|
+
diagnostics
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// src/extractor.ts
|
|
1674
|
+
async function extractPackageSpec(entryFile, packageDir, content, options) {
|
|
1675
|
+
const result = runAnalysis({
|
|
1676
|
+
entryFile,
|
|
1677
|
+
packageDir,
|
|
1678
|
+
content,
|
|
1679
|
+
options
|
|
1680
|
+
});
|
|
1681
|
+
return result.spec;
|
|
1682
|
+
}
|
|
1683
|
+
// src/openpkg.ts
|
|
1684
|
+
import * as fsSync from "node:fs";
|
|
1685
|
+
import * as fs3 from "node:fs/promises";
|
|
1686
|
+
import * as path5 from "node:path";
|
|
1687
|
+
|
|
1688
|
+
// src/filtering/apply-filters.ts
|
|
1689
|
+
var TYPE_REF_PREFIX = "#/types/";
|
|
1690
|
+
var toLowerKey = (value) => value.trim().toLowerCase();
|
|
1691
|
+
var buildLookupMap = (values) => {
|
|
1692
|
+
const map = new Map;
|
|
1693
|
+
if (!values) {
|
|
1694
|
+
return map;
|
|
1695
|
+
}
|
|
1696
|
+
for (const value of values) {
|
|
1697
|
+
const key = toLowerKey(value);
|
|
1698
|
+
if (!map.has(key)) {
|
|
1699
|
+
map.set(key, value);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return map;
|
|
1703
|
+
};
|
|
1704
|
+
var matches = (candidate, lookup) => {
|
|
1705
|
+
if (!candidate) {
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const keys = [candidate.id, candidate.name];
|
|
1709
|
+
for (const key of keys) {
|
|
1710
|
+
if (!key) {
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
const normalized = toLowerKey(key);
|
|
1714
|
+
if (lookup.has(normalized)) {
|
|
1715
|
+
return normalized;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return;
|
|
1719
|
+
};
|
|
1720
|
+
var collectTypeRefs = (value, refs, seen = new Set) => {
|
|
1721
|
+
if (value === null || value === undefined) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
if (typeof value !== "object") {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (seen.has(value)) {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
seen.add(value);
|
|
1731
|
+
if (Array.isArray(value)) {
|
|
1732
|
+
for (const item of value) {
|
|
1733
|
+
collectTypeRefs(item, refs, seen);
|
|
1734
|
+
}
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const record = value;
|
|
1738
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
1739
|
+
if (key === "$ref" && typeof nested === "string" && nested.startsWith(TYPE_REF_PREFIX)) {
|
|
1740
|
+
const typeId = nested.slice(TYPE_REF_PREFIX.length);
|
|
1741
|
+
if (typeId) {
|
|
1742
|
+
refs.add(typeId);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
collectTypeRefs(nested, refs, seen);
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
var applyFilters = (spec, options) => {
|
|
1749
|
+
const includeLookup = buildLookupMap(options.include);
|
|
1750
|
+
const excludeLookup = buildLookupMap(options.exclude);
|
|
1751
|
+
if (includeLookup.size === 0 && excludeLookup.size === 0) {
|
|
1752
|
+
return { spec, diagnostics: [], changed: false };
|
|
1753
|
+
}
|
|
1754
|
+
const includeMatches = new Set;
|
|
1755
|
+
const diagnostics = [];
|
|
1756
|
+
const exportsList = spec.exports ?? [];
|
|
1757
|
+
const typesList = spec.types ?? [];
|
|
1758
|
+
const keptExports = [];
|
|
1759
|
+
for (const entry of exportsList) {
|
|
1760
|
+
const includeMatch = includeLookup.size === 0 ? undefined : matches(entry, includeLookup);
|
|
1761
|
+
const excludeMatch = matches(entry, excludeLookup);
|
|
1762
|
+
const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
|
|
1763
|
+
const allowedByExclude = !excludeMatch;
|
|
1764
|
+
if (includeMatch) {
|
|
1765
|
+
includeMatches.add(includeMatch);
|
|
1766
|
+
}
|
|
1767
|
+
if (allowedByInclude && allowedByExclude) {
|
|
1768
|
+
keptExports.push(entry);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
const typeMap = new Map(typesList.map((typeEntry) => [typeEntry.id, typeEntry]));
|
|
1772
|
+
const requestedTypeIds = new Set;
|
|
1773
|
+
const excludedTypeIds = new Set;
|
|
1774
|
+
for (const typeEntry of typesList) {
|
|
1775
|
+
const includeMatch = includeLookup.size === 0 ? undefined : matches(typeEntry, includeLookup);
|
|
1776
|
+
if (includeMatch) {
|
|
1777
|
+
includeMatches.add(includeMatch);
|
|
1778
|
+
requestedTypeIds.add(typeEntry.id);
|
|
1779
|
+
}
|
|
1780
|
+
const excludeMatch = matches(typeEntry, excludeLookup);
|
|
1781
|
+
if (excludeMatch) {
|
|
1782
|
+
excludedTypeIds.add(typeEntry.id);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
const referencedTypeIds = new Set;
|
|
1786
|
+
for (const entry of keptExports) {
|
|
1787
|
+
collectTypeRefs(entry, referencedTypeIds);
|
|
1788
|
+
}
|
|
1789
|
+
for (const requestedId of requestedTypeIds) {
|
|
1790
|
+
referencedTypeIds.add(requestedId);
|
|
1791
|
+
}
|
|
1792
|
+
const processedTypeIds = new Set;
|
|
1793
|
+
const finalTypeIds = new Set;
|
|
1794
|
+
const excludedButReferenced = new Set;
|
|
1795
|
+
const queue = Array.from(referencedTypeIds);
|
|
1796
|
+
while (queue.length > 0) {
|
|
1797
|
+
const currentId = queue.pop();
|
|
1798
|
+
if (!currentId || processedTypeIds.has(currentId)) {
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
processedTypeIds.add(currentId);
|
|
1802
|
+
if (excludedTypeIds.has(currentId)) {
|
|
1803
|
+
excludedButReferenced.add(currentId);
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (!typeMap.has(currentId)) {
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
finalTypeIds.add(currentId);
|
|
1810
|
+
const typeEntry = typeMap.get(currentId);
|
|
1811
|
+
if (typeEntry) {
|
|
1812
|
+
const nestedRefs = new Set;
|
|
1813
|
+
collectTypeRefs(typeEntry, nestedRefs);
|
|
1814
|
+
for (const ref of nestedRefs) {
|
|
1815
|
+
if (!processedTypeIds.has(ref)) {
|
|
1816
|
+
queue.push(ref);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (includeLookup.size > 0 && keptExports.length === 0 && finalTypeIds.size === 0) {
|
|
1822
|
+
diagnostics.push({
|
|
1823
|
+
message: "Include filters did not match any exports or types.",
|
|
1824
|
+
severity: "warning"
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
if (excludedButReferenced.size > 0) {
|
|
1828
|
+
const labels = Array.from(excludedButReferenced).map((id) => {
|
|
1829
|
+
const entry = typeMap.get(id);
|
|
1830
|
+
return entry?.name ?? id;
|
|
1831
|
+
});
|
|
1832
|
+
diagnostics.push({
|
|
1833
|
+
message: `Excluded types are still referenced: ${labels.join(", ")}`,
|
|
1834
|
+
severity: "warning",
|
|
1835
|
+
target: "type"
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
const unmatchedIncludes = Array.from(includeLookup.keys()).filter((key) => !includeMatches.has(key));
|
|
1839
|
+
if (unmatchedIncludes.length > 0) {
|
|
1840
|
+
const labels = unmatchedIncludes.map((key) => includeLookup.get(key) ?? key);
|
|
1841
|
+
diagnostics.push({
|
|
1842
|
+
message: `Include filters with no matches: ${labels.join(", ")}`,
|
|
1843
|
+
severity: "warning"
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
const filteredTypes = typesList.filter((typeEntry) => finalTypeIds.has(typeEntry.id));
|
|
1847
|
+
const filteredSpec = {
|
|
1848
|
+
...spec,
|
|
1849
|
+
exports: keptExports,
|
|
1850
|
+
types: filteredTypes.length > 0 ? filteredTypes : spec.types ? [] : undefined
|
|
1851
|
+
};
|
|
1852
|
+
const changed = keptExports.length !== exportsList.length || filteredTypes.length !== typesList.length;
|
|
1853
|
+
return {
|
|
1854
|
+
spec: filteredSpec,
|
|
1855
|
+
diagnostics,
|
|
1856
|
+
changed
|
|
1857
|
+
};
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
// src/openpkg.ts
|
|
1861
|
+
class OpenPkg {
|
|
1862
|
+
options;
|
|
1863
|
+
constructor(options = {}) {
|
|
1864
|
+
this.options = normalizeOpenPkgOptions(options);
|
|
1865
|
+
}
|
|
1866
|
+
async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
|
|
1867
|
+
const resolvedFileName = path5.resolve(fileName);
|
|
1868
|
+
const tempDir = path5.dirname(resolvedFileName);
|
|
1869
|
+
const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
|
|
1870
|
+
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
1871
|
+
}
|
|
1872
|
+
async analyzeFile(filePath, analyzeOptions = {}) {
|
|
1873
|
+
const resolvedPath = path5.resolve(filePath);
|
|
1874
|
+
const content = await fs3.readFile(resolvedPath, "utf-8");
|
|
1875
|
+
const packageDir = resolvePackageDir(resolvedPath);
|
|
1876
|
+
const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
|
|
1877
|
+
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
1878
|
+
}
|
|
1879
|
+
async analyzeProject(entryPath, analyzeOptions = {}) {
|
|
1880
|
+
return this.analyzeFile(entryPath, analyzeOptions);
|
|
1881
|
+
}
|
|
1882
|
+
async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
|
|
1883
|
+
const resolvedFileName = path5.resolve(fileName ?? "temp.ts");
|
|
1884
|
+
const packageDir = resolvePackageDir(resolvedFileName);
|
|
1885
|
+
const analysis = runAnalysis({
|
|
1886
|
+
entryFile: resolvedFileName,
|
|
1887
|
+
packageDir,
|
|
1888
|
+
content: code,
|
|
1889
|
+
options: this.options
|
|
1890
|
+
});
|
|
1891
|
+
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
1892
|
+
return {
|
|
1893
|
+
spec: filterOutcome.spec,
|
|
1894
|
+
diagnostics: [
|
|
1895
|
+
...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
|
|
1896
|
+
...filterOutcome.diagnostics
|
|
1897
|
+
],
|
|
1898
|
+
metadata: this.normalizeMetadata(analysis.metadata)
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
|
|
1902
|
+
const resolvedPath = path5.resolve(filePath);
|
|
1903
|
+
const content = await fs3.readFile(resolvedPath, "utf-8");
|
|
1904
|
+
const packageDir = resolvePackageDir(resolvedPath);
|
|
1905
|
+
const analysis = runAnalysis({
|
|
1906
|
+
entryFile: resolvedPath,
|
|
1907
|
+
packageDir,
|
|
1908
|
+
content,
|
|
1909
|
+
options: this.options
|
|
1910
|
+
});
|
|
1911
|
+
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
1912
|
+
return {
|
|
1913
|
+
spec: filterOutcome.spec,
|
|
1914
|
+
diagnostics: [
|
|
1915
|
+
...analysis.diagnostics.map((diagnostic) => this.normalizeDiagnostic(diagnostic)),
|
|
1916
|
+
...filterOutcome.diagnostics
|
|
1917
|
+
],
|
|
1918
|
+
metadata: this.normalizeMetadata(analysis.metadata)
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
normalizeDiagnostic(tsDiagnostic) {
|
|
1922
|
+
const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
|
|
1923
|
+
`);
|
|
1924
|
+
let location;
|
|
1925
|
+
if (tsDiagnostic.file && typeof tsDiagnostic.start === "number") {
|
|
1926
|
+
const { line, character } = tsDiagnostic.file.getLineAndCharacterOfPosition(tsDiagnostic.start);
|
|
1927
|
+
location = {
|
|
1928
|
+
file: tsDiagnostic.file.fileName,
|
|
1929
|
+
line: line + 1,
|
|
1930
|
+
column: character + 1
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
const severity = this.mapSeverity(tsDiagnostic.category);
|
|
1934
|
+
return {
|
|
1935
|
+
message,
|
|
1936
|
+
severity,
|
|
1937
|
+
location
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
mapSeverity(category) {
|
|
1941
|
+
switch (category) {
|
|
1942
|
+
case ts.DiagnosticCategory.Message:
|
|
1943
|
+
case ts.DiagnosticCategory.Suggestion:
|
|
1944
|
+
return "info";
|
|
1945
|
+
case ts.DiagnosticCategory.Warning:
|
|
1946
|
+
return "warning";
|
|
1947
|
+
default:
|
|
1948
|
+
return "error";
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
normalizeMetadata(metadata) {
|
|
1952
|
+
return {
|
|
1953
|
+
baseDir: metadata.baseDir,
|
|
1954
|
+
configPath: metadata.configPath,
|
|
1955
|
+
packageJsonPath: metadata.packageJsonPath,
|
|
1956
|
+
hasNodeModules: metadata.hasNodeModules,
|
|
1957
|
+
resolveExternalTypes: metadata.resolveExternalTypes
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
applySpecFilters(spec, filters) {
|
|
1961
|
+
if (!filters || !filters.include?.length && !filters.exclude?.length) {
|
|
1962
|
+
return { spec, diagnostics: [] };
|
|
1963
|
+
}
|
|
1964
|
+
const result = applyFilters(spec, filters);
|
|
1965
|
+
return {
|
|
1966
|
+
spec: result.spec,
|
|
1967
|
+
diagnostics: result.diagnostics.map((diagnostic) => ({
|
|
1968
|
+
message: diagnostic.message,
|
|
1969
|
+
severity: diagnostic.severity
|
|
1970
|
+
}))
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
async function analyze(code, options = {}) {
|
|
1975
|
+
return new OpenPkg().analyze(code, "temp.ts", options);
|
|
1976
|
+
}
|
|
1977
|
+
async function analyzeFile(filePath, options = {}) {
|
|
1978
|
+
return new OpenPkg().analyzeFile(filePath, options);
|
|
1979
|
+
}
|
|
1980
|
+
function resolvePackageDir(entryFile) {
|
|
1981
|
+
const fallbackDir = path5.dirname(entryFile);
|
|
1982
|
+
let currentDir = fallbackDir;
|
|
1983
|
+
while (true) {
|
|
1984
|
+
const candidate = path5.join(currentDir, "package.json");
|
|
1985
|
+
if (fsSync.existsSync(candidate)) {
|
|
1986
|
+
return currentDir;
|
|
1987
|
+
}
|
|
1988
|
+
const parentDir = path5.dirname(currentDir);
|
|
1989
|
+
if (parentDir === currentDir) {
|
|
1990
|
+
return fallbackDir;
|
|
1991
|
+
}
|
|
1992
|
+
currentDir = parentDir;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
// src/types/openpkg.ts
|
|
1996
|
+
import { z } from "zod";
|
|
1997
|
+
var schemaSchema = z.lazy(() => z.union([
|
|
1998
|
+
z.object({
|
|
1999
|
+
type: z.enum(["string", "number", "boolean", "integer", "null", "array", "object"])
|
|
2000
|
+
}),
|
|
2001
|
+
z.object({
|
|
2002
|
+
$ref: z.string()
|
|
2003
|
+
}),
|
|
2004
|
+
z.object({
|
|
2005
|
+
type: z.literal("array"),
|
|
2006
|
+
items: schemaSchema.optional(),
|
|
2007
|
+
description: z.string().optional()
|
|
2008
|
+
}),
|
|
2009
|
+
z.object({
|
|
2010
|
+
type: z.literal("object"),
|
|
2011
|
+
properties: z.record(z.string(), schemaSchema).optional(),
|
|
2012
|
+
required: z.array(z.string()).optional(),
|
|
2013
|
+
description: z.string().optional(),
|
|
2014
|
+
additionalProperties: z.union([z.boolean(), schemaSchema]).optional()
|
|
2015
|
+
}),
|
|
2016
|
+
z.object({
|
|
2017
|
+
oneOf: z.array(schemaSchema),
|
|
2018
|
+
description: z.string().optional()
|
|
2019
|
+
}),
|
|
2020
|
+
z.object({
|
|
2021
|
+
anyOf: z.array(schemaSchema),
|
|
2022
|
+
description: z.string().optional()
|
|
2023
|
+
}),
|
|
2024
|
+
z.object({
|
|
2025
|
+
allOf: z.array(schemaSchema),
|
|
2026
|
+
description: z.string().optional()
|
|
2027
|
+
}),
|
|
2028
|
+
z.object({
|
|
2029
|
+
enum: z.array(z.union([z.string(), z.number(), z.null()])),
|
|
2030
|
+
description: z.string().optional()
|
|
2031
|
+
})
|
|
2032
|
+
]));
|
|
2033
|
+
var parameterSchema = z.object({
|
|
2034
|
+
name: z.string(),
|
|
2035
|
+
in: z.literal("query").optional(),
|
|
2036
|
+
required: z.boolean().optional(),
|
|
2037
|
+
description: z.string().optional(),
|
|
2038
|
+
schema: schemaSchema
|
|
2039
|
+
});
|
|
2040
|
+
var returnTypeSchema = z.object({
|
|
2041
|
+
schema: schemaSchema,
|
|
2042
|
+
description: z.string().optional()
|
|
2043
|
+
});
|
|
2044
|
+
var classMemberSchema = z.object({
|
|
2045
|
+
id: z.string(),
|
|
2046
|
+
name: z.string(),
|
|
2047
|
+
kind: z.enum(["method", "property", "constructor", "accessor"]),
|
|
2048
|
+
visibility: z.enum(["public", "private", "protected"]).optional(),
|
|
2049
|
+
signatures: z.array(z.object({
|
|
2050
|
+
parameters: z.array(parameterSchema).optional(),
|
|
2051
|
+
returns: returnTypeSchema.optional(),
|
|
2052
|
+
description: z.string().optional()
|
|
2053
|
+
})).optional(),
|
|
2054
|
+
schema: schemaSchema.optional(),
|
|
2055
|
+
description: z.string().optional(),
|
|
2056
|
+
examples: z.array(z.string()).optional(),
|
|
2057
|
+
flags: z.record(z.string(), z.boolean()).optional()
|
|
2058
|
+
});
|
|
2059
|
+
var enumMemberSchema = z.object({
|
|
2060
|
+
id: z.string(),
|
|
2061
|
+
name: z.string(),
|
|
2062
|
+
value: z.union([z.string(), z.number()]).optional(),
|
|
2063
|
+
description: z.string().optional()
|
|
2064
|
+
});
|
|
2065
|
+
var memberSchema = z.union([classMemberSchema, enumMemberSchema]);
|
|
2066
|
+
var openPkgSchema = z.object({
|
|
2067
|
+
$schema: z.string().optional(),
|
|
2068
|
+
openpkg: z.literal("0.1.0"),
|
|
2069
|
+
meta: z.object({
|
|
2070
|
+
name: z.string(),
|
|
2071
|
+
version: z.string(),
|
|
2072
|
+
description: z.string().optional(),
|
|
2073
|
+
license: z.string().optional(),
|
|
2074
|
+
repository: z.string().optional(),
|
|
2075
|
+
ecosystem: z.string().default("js/ts")
|
|
2076
|
+
}),
|
|
2077
|
+
exports: z.array(z.object({
|
|
2078
|
+
id: z.string(),
|
|
2079
|
+
name: z.string(),
|
|
2080
|
+
kind: z.enum([
|
|
2081
|
+
"function",
|
|
2082
|
+
"class",
|
|
2083
|
+
"variable",
|
|
2084
|
+
"interface",
|
|
2085
|
+
"type",
|
|
2086
|
+
"enum",
|
|
2087
|
+
"module",
|
|
2088
|
+
"namespace",
|
|
2089
|
+
"reference"
|
|
2090
|
+
]),
|
|
2091
|
+
signatures: z.array(z.object({
|
|
2092
|
+
parameters: z.array(parameterSchema).optional(),
|
|
2093
|
+
returns: returnTypeSchema.optional(),
|
|
2094
|
+
description: z.string().optional()
|
|
2095
|
+
})).optional(),
|
|
2096
|
+
members: z.array(memberSchema).optional(),
|
|
2097
|
+
type: z.union([z.string(), schemaSchema]).optional(),
|
|
2098
|
+
schema: schemaSchema.optional(),
|
|
2099
|
+
description: z.string().optional(),
|
|
2100
|
+
examples: z.array(z.string()).optional(),
|
|
2101
|
+
source: z.object({
|
|
2102
|
+
file: z.string().optional(),
|
|
2103
|
+
line: z.number().optional(),
|
|
2104
|
+
url: z.string().optional()
|
|
2105
|
+
}).optional(),
|
|
2106
|
+
flags: z.record(z.string(), z.unknown()).optional(),
|
|
2107
|
+
tags: z.array(z.object({
|
|
2108
|
+
name: z.string(),
|
|
2109
|
+
text: z.string()
|
|
2110
|
+
})).optional()
|
|
2111
|
+
})),
|
|
2112
|
+
types: z.array(z.object({
|
|
2113
|
+
id: z.string(),
|
|
2114
|
+
name: z.string(),
|
|
2115
|
+
kind: z.enum(["class", "interface", "type", "enum"]),
|
|
2116
|
+
description: z.string().optional(),
|
|
2117
|
+
schema: schemaSchema.optional(),
|
|
2118
|
+
type: z.union([z.string(), schemaSchema]).optional(),
|
|
2119
|
+
members: z.array(memberSchema).optional(),
|
|
2120
|
+
source: z.object({
|
|
2121
|
+
file: z.string().optional(),
|
|
2122
|
+
line: z.number().optional(),
|
|
2123
|
+
url: z.string().optional()
|
|
2124
|
+
}).optional(),
|
|
2125
|
+
tags: z.array(z.object({
|
|
2126
|
+
name: z.string(),
|
|
2127
|
+
text: z.string()
|
|
2128
|
+
})).optional(),
|
|
2129
|
+
rawComments: z.string().optional()
|
|
2130
|
+
})).optional(),
|
|
2131
|
+
examples: z.array(z.object({})).optional(),
|
|
2132
|
+
extensions: z.record(z.string(), z.unknown()).optional()
|
|
2133
|
+
});
|
|
2134
|
+
export {
|
|
2135
|
+
openPkgSchema,
|
|
2136
|
+
extractPackageSpec,
|
|
2137
|
+
analyzeFile,
|
|
2138
|
+
analyze,
|
|
2139
|
+
OpenPkg
|
|
2140
|
+
};
|