@openpkg-ts/extract 0.11.4 → 0.13.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/dist/bin/tspec.js +1 -1
- package/dist/shared/chunk-ksf9k654.js +1662 -0
- package/dist/src/index.d.ts +40 -21
- package/dist/src/index.js +8 -27
- package/package.json +3 -2
- package/dist/shared/chunk-twaykyxs.js +0 -681
|
@@ -0,0 +1,1662 @@
|
|
|
1
|
+
// src/ast/registry.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
var PRIMITIVES = new Set([
|
|
6
|
+
"string",
|
|
7
|
+
"number",
|
|
8
|
+
"boolean",
|
|
9
|
+
"void",
|
|
10
|
+
"any",
|
|
11
|
+
"undefined",
|
|
12
|
+
"null",
|
|
13
|
+
"never",
|
|
14
|
+
"unknown",
|
|
15
|
+
"object",
|
|
16
|
+
"symbol",
|
|
17
|
+
"bigint"
|
|
18
|
+
]);
|
|
19
|
+
var BUILTINS = new Set([
|
|
20
|
+
"Array",
|
|
21
|
+
"ArrayBuffer",
|
|
22
|
+
"ArrayBufferLike",
|
|
23
|
+
"ArrayLike",
|
|
24
|
+
"Promise",
|
|
25
|
+
"Map",
|
|
26
|
+
"Set",
|
|
27
|
+
"WeakMap",
|
|
28
|
+
"WeakSet",
|
|
29
|
+
"Date",
|
|
30
|
+
"RegExp",
|
|
31
|
+
"Error",
|
|
32
|
+
"Function",
|
|
33
|
+
"Object",
|
|
34
|
+
"String",
|
|
35
|
+
"Number",
|
|
36
|
+
"Boolean",
|
|
37
|
+
"Symbol",
|
|
38
|
+
"BigInt",
|
|
39
|
+
"Uint8Array",
|
|
40
|
+
"Int8Array",
|
|
41
|
+
"Uint16Array",
|
|
42
|
+
"Int16Array",
|
|
43
|
+
"Uint32Array",
|
|
44
|
+
"Int32Array",
|
|
45
|
+
"Float32Array",
|
|
46
|
+
"Float64Array",
|
|
47
|
+
"BigInt64Array",
|
|
48
|
+
"BigUint64Array",
|
|
49
|
+
"DataView",
|
|
50
|
+
"ReadonlyArray",
|
|
51
|
+
"Readonly",
|
|
52
|
+
"Partial",
|
|
53
|
+
"Required",
|
|
54
|
+
"Pick",
|
|
55
|
+
"Omit",
|
|
56
|
+
"Record",
|
|
57
|
+
"Exclude",
|
|
58
|
+
"Extract",
|
|
59
|
+
"NonNullable",
|
|
60
|
+
"Parameters",
|
|
61
|
+
"ReturnType",
|
|
62
|
+
"ConstructorParameters",
|
|
63
|
+
"InstanceType",
|
|
64
|
+
"ThisType",
|
|
65
|
+
"Awaited",
|
|
66
|
+
"PromiseLike",
|
|
67
|
+
"Iterable",
|
|
68
|
+
"Iterator",
|
|
69
|
+
"IterableIterator",
|
|
70
|
+
"Generator",
|
|
71
|
+
"AsyncGenerator",
|
|
72
|
+
"AsyncIterable",
|
|
73
|
+
"AsyncIterator",
|
|
74
|
+
"AsyncIterableIterator",
|
|
75
|
+
"SharedArrayBuffer",
|
|
76
|
+
"Atomics",
|
|
77
|
+
"JSON",
|
|
78
|
+
"Math",
|
|
79
|
+
"console",
|
|
80
|
+
"globalThis"
|
|
81
|
+
]);
|
|
82
|
+
function isGenericTypeParameter(name) {
|
|
83
|
+
if (/^[A-Z]$/.test(name))
|
|
84
|
+
return true;
|
|
85
|
+
if (/^T[A-Z]/.test(name))
|
|
86
|
+
return true;
|
|
87
|
+
if (["Key", "Value", "Item", "Element"].includes(name))
|
|
88
|
+
return true;
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function isExternalType(decl) {
|
|
92
|
+
const sourceFile = decl.getSourceFile();
|
|
93
|
+
if (!sourceFile)
|
|
94
|
+
return false;
|
|
95
|
+
return sourceFile.fileName.includes("node_modules");
|
|
96
|
+
}
|
|
97
|
+
function extractPackageName(filePath) {
|
|
98
|
+
const nmIndex = filePath.lastIndexOf("node_modules");
|
|
99
|
+
if (nmIndex === -1)
|
|
100
|
+
return;
|
|
101
|
+
const afterNm = filePath.slice(nmIndex + "node_modules/".length);
|
|
102
|
+
const parts = afterNm.split("/");
|
|
103
|
+
if (parts[0].startsWith("@") && parts.length >= 2) {
|
|
104
|
+
return `${parts[0]}/${parts[1]}`;
|
|
105
|
+
}
|
|
106
|
+
return parts[0];
|
|
107
|
+
}
|
|
108
|
+
function getPackageVersion(filePath, packageName) {
|
|
109
|
+
const nmIndex = filePath.lastIndexOf("node_modules");
|
|
110
|
+
if (nmIndex === -1)
|
|
111
|
+
return;
|
|
112
|
+
const nmDir = filePath.slice(0, nmIndex + "node_modules".length);
|
|
113
|
+
const pkgJsonPath = path.join(nmDir, packageName, "package.json");
|
|
114
|
+
try {
|
|
115
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
116
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
117
|
+
return pkg.version;
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
function buildExternalSource(decl) {
|
|
123
|
+
const sourceFile = decl.getSourceFile();
|
|
124
|
+
if (!sourceFile)
|
|
125
|
+
return;
|
|
126
|
+
const filePath = sourceFile.fileName;
|
|
127
|
+
if (!filePath.includes("node_modules"))
|
|
128
|
+
return;
|
|
129
|
+
const packageName = extractPackageName(filePath);
|
|
130
|
+
if (!packageName)
|
|
131
|
+
return;
|
|
132
|
+
const version = getPackageVersion(filePath, packageName);
|
|
133
|
+
return {
|
|
134
|
+
file: filePath,
|
|
135
|
+
package: packageName,
|
|
136
|
+
version
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class TypeRegistry {
|
|
141
|
+
types = new Map;
|
|
142
|
+
processing = new Set;
|
|
143
|
+
add(type) {
|
|
144
|
+
this.types.set(type.id, type);
|
|
145
|
+
}
|
|
146
|
+
get(id) {
|
|
147
|
+
return this.types.get(id);
|
|
148
|
+
}
|
|
149
|
+
has(id) {
|
|
150
|
+
return this.types.has(id);
|
|
151
|
+
}
|
|
152
|
+
getAll() {
|
|
153
|
+
return Array.from(this.types.values());
|
|
154
|
+
}
|
|
155
|
+
registerType(type, checker, exportedIds) {
|
|
156
|
+
const symbol = type.getSymbol() || type.aliasSymbol;
|
|
157
|
+
if (!symbol)
|
|
158
|
+
return;
|
|
159
|
+
const name = symbol.getName();
|
|
160
|
+
if (PRIMITIVES.has(name))
|
|
161
|
+
return;
|
|
162
|
+
if (BUILTINS.has(name))
|
|
163
|
+
return;
|
|
164
|
+
if (name.startsWith("__"))
|
|
165
|
+
return;
|
|
166
|
+
if (symbol.flags & ts.SymbolFlags.EnumMember)
|
|
167
|
+
return;
|
|
168
|
+
if (symbol.flags & ts.SymbolFlags.TypeParameter)
|
|
169
|
+
return;
|
|
170
|
+
if (isGenericTypeParameter(name))
|
|
171
|
+
return;
|
|
172
|
+
if (this.has(name))
|
|
173
|
+
return name;
|
|
174
|
+
if (exportedIds.has(name))
|
|
175
|
+
return name;
|
|
176
|
+
if (this.processing.has(name))
|
|
177
|
+
return name;
|
|
178
|
+
this.processing.add(name);
|
|
179
|
+
try {
|
|
180
|
+
const specType = this.buildSpecType(symbol, type, checker);
|
|
181
|
+
if (specType) {
|
|
182
|
+
this.add(specType);
|
|
183
|
+
return specType.id;
|
|
184
|
+
}
|
|
185
|
+
} finally {
|
|
186
|
+
this.processing.delete(name);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
buildSpecType(symbol, type, checker) {
|
|
191
|
+
const name = symbol.getName();
|
|
192
|
+
const decl = symbol.declarations?.[0];
|
|
193
|
+
let kind = "type";
|
|
194
|
+
const external = decl ? isExternalType(decl) : false;
|
|
195
|
+
if (decl) {
|
|
196
|
+
if (ts.isClassDeclaration(decl))
|
|
197
|
+
kind = "class";
|
|
198
|
+
else if (ts.isInterfaceDeclaration(decl))
|
|
199
|
+
kind = "interface";
|
|
200
|
+
else if (ts.isEnumDeclaration(decl))
|
|
201
|
+
kind = "enum";
|
|
202
|
+
}
|
|
203
|
+
if (external) {
|
|
204
|
+
kind = "external";
|
|
205
|
+
}
|
|
206
|
+
const source = decl ? buildExternalSource(decl) : undefined;
|
|
207
|
+
const schema = this.buildShallowSchema(type, checker);
|
|
208
|
+
let members;
|
|
209
|
+
if (decl && (ts.isClassDeclaration(decl) || ts.isInterfaceDeclaration(decl))) {
|
|
210
|
+
members = this.extractShallowMembers(decl, checker);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
id: name,
|
|
214
|
+
name,
|
|
215
|
+
kind,
|
|
216
|
+
schema,
|
|
217
|
+
members: members?.length ? members : undefined,
|
|
218
|
+
...external ? { external: true } : {},
|
|
219
|
+
...source ? { source } : {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
buildShallowSchema(type, checker) {
|
|
223
|
+
if (type.isUnion()) {
|
|
224
|
+
const allEnumLiterals = type.types.every((t) => t.flags & (ts.TypeFlags.EnumLiteral | ts.TypeFlags.NumberLiteral));
|
|
225
|
+
if (allEnumLiterals) {
|
|
226
|
+
const values = type.types.map((t) => {
|
|
227
|
+
if (t.flags & ts.TypeFlags.NumberLiteral) {
|
|
228
|
+
return t.value;
|
|
229
|
+
}
|
|
230
|
+
return checker.typeToString(t);
|
|
231
|
+
}).filter((v) => v !== undefined);
|
|
232
|
+
if (values.every((v) => typeof v === "number")) {
|
|
233
|
+
return { type: "number", enum: values };
|
|
234
|
+
}
|
|
235
|
+
return { type: "string", enum: values };
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
anyOf: type.types.map((t) => {
|
|
239
|
+
if (t.flags & ts.TypeFlags.EnumLiteral) {
|
|
240
|
+
const value = t.value;
|
|
241
|
+
if (typeof value === "number") {
|
|
242
|
+
return { type: "number", enum: [value] };
|
|
243
|
+
}
|
|
244
|
+
return { type: checker.typeToString(t) };
|
|
245
|
+
}
|
|
246
|
+
const sym = t.getSymbol() || t.aliasSymbol;
|
|
247
|
+
if (sym && sym.flags & ts.SymbolFlags.EnumMember) {
|
|
248
|
+
return { type: checker.typeToString(t) };
|
|
249
|
+
}
|
|
250
|
+
if (sym && !sym.getName().startsWith("__")) {
|
|
251
|
+
return { $ref: sym.getName() };
|
|
252
|
+
}
|
|
253
|
+
if (t.flags & ts.TypeFlags.StringLiteral) {
|
|
254
|
+
return { type: "string", enum: [t.value] };
|
|
255
|
+
}
|
|
256
|
+
if (t.flags & ts.TypeFlags.NumberLiteral) {
|
|
257
|
+
return { type: "number", enum: [t.value] };
|
|
258
|
+
}
|
|
259
|
+
return { type: checker.typeToString(t) };
|
|
260
|
+
})
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (type.isIntersection()) {
|
|
264
|
+
return {
|
|
265
|
+
allOf: type.types.map((t) => {
|
|
266
|
+
const sym = t.getSymbol() || t.aliasSymbol;
|
|
267
|
+
if (sym && !sym.getName().startsWith("__")) {
|
|
268
|
+
return { $ref: sym.getName() };
|
|
269
|
+
}
|
|
270
|
+
return { type: checker.typeToString(t) };
|
|
271
|
+
})
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const props = type.getProperties();
|
|
275
|
+
if (props.length > 0) {
|
|
276
|
+
const properties = {};
|
|
277
|
+
const required = [];
|
|
278
|
+
for (const prop of props.slice(0, 30)) {
|
|
279
|
+
const propName = prop.getName();
|
|
280
|
+
if (propName.startsWith("_"))
|
|
281
|
+
continue;
|
|
282
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
283
|
+
const callSigs = propType.getCallSignatures();
|
|
284
|
+
if (callSigs.length > 0) {
|
|
285
|
+
properties[propName] = { type: "function" };
|
|
286
|
+
} else {
|
|
287
|
+
const propSym = propType.getSymbol() || propType.aliasSymbol;
|
|
288
|
+
const symName = propSym?.getName();
|
|
289
|
+
if (propSym && symName && !symName.startsWith("__") && symName !== propName) {
|
|
290
|
+
properties[propName] = { $ref: symName };
|
|
291
|
+
} else {
|
|
292
|
+
properties[propName] = { type: checker.typeToString(propType) };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (!(prop.flags & ts.SymbolFlags.Optional)) {
|
|
296
|
+
required.push(propName);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties,
|
|
302
|
+
...required.length ? { required } : {}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return { type: checker.typeToString(type) };
|
|
306
|
+
}
|
|
307
|
+
extractShallowMembers(decl, checker) {
|
|
308
|
+
const members = [];
|
|
309
|
+
for (const member of decl.members) {
|
|
310
|
+
if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
|
|
311
|
+
const name = member.name?.getText();
|
|
312
|
+
if (!name || name.startsWith("#") || name.startsWith("_"))
|
|
313
|
+
continue;
|
|
314
|
+
const type = checker.getTypeAtLocation(member);
|
|
315
|
+
const sym = type.getSymbol() || type.aliasSymbol;
|
|
316
|
+
members.push({
|
|
317
|
+
name,
|
|
318
|
+
kind: "property",
|
|
319
|
+
schema: sym && !sym.getName().startsWith("__") ? { $ref: sym.getName() } : { type: checker.typeToString(type) }
|
|
320
|
+
});
|
|
321
|
+
} else if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
|
|
322
|
+
const name = member.name?.getText();
|
|
323
|
+
if (!name || name.startsWith("#") || name.startsWith("_"))
|
|
324
|
+
continue;
|
|
325
|
+
members.push({
|
|
326
|
+
name,
|
|
327
|
+
kind: "method"
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return members;
|
|
332
|
+
}
|
|
333
|
+
registerFromSymbol(symbol, checker) {
|
|
334
|
+
const name = symbol.getName();
|
|
335
|
+
if (this.has(name))
|
|
336
|
+
return this.get(name);
|
|
337
|
+
const type = checker.getDeclaredTypeOfSymbol(symbol);
|
|
338
|
+
const specType = this.buildSpecType(symbol, type, checker);
|
|
339
|
+
if (specType) {
|
|
340
|
+
this.add(specType);
|
|
341
|
+
return specType;
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/ast/utils.ts
|
|
348
|
+
import ts2 from "typescript";
|
|
349
|
+
function parseExamplesFromTags(tags) {
|
|
350
|
+
const examples = [];
|
|
351
|
+
for (const tag of tags) {
|
|
352
|
+
if (tag.name !== "example")
|
|
353
|
+
continue;
|
|
354
|
+
const text = tag.text.trim();
|
|
355
|
+
const fenceMatch = text.match(/^```(\w*)\n([\s\S]*?)\n?```$/);
|
|
356
|
+
if (fenceMatch) {
|
|
357
|
+
const lang = fenceMatch[1] || undefined;
|
|
358
|
+
const code = fenceMatch[2].trim();
|
|
359
|
+
const example = { code };
|
|
360
|
+
if (lang && ["ts", "js", "tsx", "jsx", "shell", "json"].includes(lang)) {
|
|
361
|
+
example.language = lang;
|
|
362
|
+
}
|
|
363
|
+
examples.push(example);
|
|
364
|
+
} else if (text) {
|
|
365
|
+
examples.push({ code: text });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return examples;
|
|
369
|
+
}
|
|
370
|
+
function getJSDocComment(node) {
|
|
371
|
+
const jsDocTags = ts2.getJSDocTags(node);
|
|
372
|
+
const tags = jsDocTags.map((tag) => ({
|
|
373
|
+
name: tag.tagName.text,
|
|
374
|
+
text: typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment) ?? ""
|
|
375
|
+
}));
|
|
376
|
+
const jsDocComments = node.jsDoc;
|
|
377
|
+
let description;
|
|
378
|
+
if (jsDocComments && jsDocComments.length > 0) {
|
|
379
|
+
const firstDoc = jsDocComments[0];
|
|
380
|
+
if (firstDoc.comment) {
|
|
381
|
+
description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts2.getTextOfJSDocComment(firstDoc.comment);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const examples = parseExamplesFromTags(tags);
|
|
385
|
+
return { description, tags, examples };
|
|
386
|
+
}
|
|
387
|
+
function getSourceLocation(node, sourceFile) {
|
|
388
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
389
|
+
return {
|
|
390
|
+
file: sourceFile.fileName,
|
|
391
|
+
line: line + 1
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function getParamDescription(propertyName, jsdocTags, inferredAlias) {
|
|
395
|
+
for (const tag of jsdocTags) {
|
|
396
|
+
if (tag.tagName.text !== "param")
|
|
397
|
+
continue;
|
|
398
|
+
const paramTag = tag;
|
|
399
|
+
const tagParamName = paramTag.name?.getText() ?? "";
|
|
400
|
+
const isMatch = tagParamName === propertyName || inferredAlias && tagParamName === `${inferredAlias}.${propertyName}` || tagParamName.endsWith(`.${propertyName}`);
|
|
401
|
+
if (isMatch) {
|
|
402
|
+
const comment = typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment);
|
|
403
|
+
return comment?.trim() || undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
function extractTypeParameters(node, checker) {
|
|
409
|
+
if (!node.typeParameters || node.typeParameters.length === 0) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
return node.typeParameters.map((tp) => {
|
|
413
|
+
const name = tp.name.text;
|
|
414
|
+
let constraint;
|
|
415
|
+
if (tp.constraint) {
|
|
416
|
+
const constraintType = checker.getTypeAtLocation(tp.constraint);
|
|
417
|
+
constraint = checker.typeToString(constraintType);
|
|
418
|
+
}
|
|
419
|
+
let defaultType;
|
|
420
|
+
if (tp.default) {
|
|
421
|
+
const defType = checker.getTypeAtLocation(tp.default);
|
|
422
|
+
defaultType = checker.typeToString(defType);
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
...constraint ? { constraint } : {},
|
|
427
|
+
...defaultType ? { default: defaultType } : {}
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/compiler/program.ts
|
|
433
|
+
import * as path2 from "node:path";
|
|
434
|
+
import ts3 from "typescript";
|
|
435
|
+
var DEFAULT_COMPILER_OPTIONS = {
|
|
436
|
+
target: ts3.ScriptTarget.Latest,
|
|
437
|
+
module: ts3.ModuleKind.CommonJS,
|
|
438
|
+
lib: ["lib.es2021.d.ts"],
|
|
439
|
+
declaration: true,
|
|
440
|
+
moduleResolution: ts3.ModuleResolutionKind.NodeJs
|
|
441
|
+
};
|
|
442
|
+
function createProgram({
|
|
443
|
+
entryFile,
|
|
444
|
+
baseDir = path2.dirname(entryFile),
|
|
445
|
+
content
|
|
446
|
+
}) {
|
|
447
|
+
const configPath = ts3.findConfigFile(baseDir, ts3.sys.fileExists, "tsconfig.json");
|
|
448
|
+
let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
|
|
449
|
+
if (configPath) {
|
|
450
|
+
const configFile = ts3.readConfigFile(configPath, ts3.sys.readFile);
|
|
451
|
+
const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path2.dirname(configPath));
|
|
452
|
+
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
|
453
|
+
}
|
|
454
|
+
const allowJsVal = compilerOptions.allowJs;
|
|
455
|
+
if (typeof allowJsVal === "boolean" && allowJsVal) {
|
|
456
|
+
compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
|
|
457
|
+
}
|
|
458
|
+
const compilerHost = ts3.createCompilerHost(compilerOptions, true);
|
|
459
|
+
let inMemorySource;
|
|
460
|
+
if (content !== undefined) {
|
|
461
|
+
inMemorySource = ts3.createSourceFile(entryFile, content, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
462
|
+
const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
|
|
463
|
+
compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
464
|
+
if (fileName === entryFile) {
|
|
465
|
+
return inMemorySource;
|
|
466
|
+
}
|
|
467
|
+
return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const program = ts3.createProgram([entryFile], compilerOptions, compilerHost);
|
|
471
|
+
const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
|
|
472
|
+
return {
|
|
473
|
+
program,
|
|
474
|
+
compilerHost,
|
|
475
|
+
compilerOptions,
|
|
476
|
+
sourceFile,
|
|
477
|
+
configPath
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/types/schema-builder.ts
|
|
482
|
+
import ts4 from "typescript";
|
|
483
|
+
var PRIMITIVES2 = new Set([
|
|
484
|
+
"string",
|
|
485
|
+
"number",
|
|
486
|
+
"boolean",
|
|
487
|
+
"void",
|
|
488
|
+
"undefined",
|
|
489
|
+
"null",
|
|
490
|
+
"any",
|
|
491
|
+
"unknown",
|
|
492
|
+
"never",
|
|
493
|
+
"object",
|
|
494
|
+
"symbol",
|
|
495
|
+
"bigint"
|
|
496
|
+
]);
|
|
497
|
+
var BUILTIN_GENERICS = new Set([
|
|
498
|
+
"Array",
|
|
499
|
+
"ReadonlyArray",
|
|
500
|
+
"Promise",
|
|
501
|
+
"PromiseLike",
|
|
502
|
+
"Map",
|
|
503
|
+
"Set",
|
|
504
|
+
"WeakMap",
|
|
505
|
+
"WeakSet",
|
|
506
|
+
"Iterable",
|
|
507
|
+
"Iterator",
|
|
508
|
+
"IterableIterator",
|
|
509
|
+
"AsyncIterable",
|
|
510
|
+
"AsyncIterator",
|
|
511
|
+
"AsyncIterableIterator",
|
|
512
|
+
"Generator",
|
|
513
|
+
"AsyncGenerator",
|
|
514
|
+
"Partial",
|
|
515
|
+
"Required",
|
|
516
|
+
"Readonly",
|
|
517
|
+
"Pick",
|
|
518
|
+
"Omit",
|
|
519
|
+
"Record",
|
|
520
|
+
"Exclude",
|
|
521
|
+
"Extract",
|
|
522
|
+
"NonNullable",
|
|
523
|
+
"Parameters",
|
|
524
|
+
"ReturnType",
|
|
525
|
+
"ConstructorParameters",
|
|
526
|
+
"InstanceType",
|
|
527
|
+
"Awaited"
|
|
528
|
+
]);
|
|
529
|
+
var BUILTIN_TYPES = new Set([
|
|
530
|
+
"Date",
|
|
531
|
+
"RegExp",
|
|
532
|
+
"Error",
|
|
533
|
+
"Function",
|
|
534
|
+
"ArrayBuffer",
|
|
535
|
+
"SharedArrayBuffer",
|
|
536
|
+
"DataView",
|
|
537
|
+
"Uint8Array",
|
|
538
|
+
"Int8Array",
|
|
539
|
+
"Uint16Array",
|
|
540
|
+
"Int16Array",
|
|
541
|
+
"Uint32Array",
|
|
542
|
+
"Int32Array",
|
|
543
|
+
"Float32Array",
|
|
544
|
+
"Float64Array",
|
|
545
|
+
"BigInt64Array",
|
|
546
|
+
"BigUint64Array"
|
|
547
|
+
]);
|
|
548
|
+
function isPrimitiveName(name) {
|
|
549
|
+
return PRIMITIVES2.has(name);
|
|
550
|
+
}
|
|
551
|
+
function isBuiltinGeneric(name) {
|
|
552
|
+
return BUILTIN_GENERICS.has(name);
|
|
553
|
+
}
|
|
554
|
+
function isAnonymous(type) {
|
|
555
|
+
const symbol = type.getSymbol() || type.aliasSymbol;
|
|
556
|
+
if (!symbol)
|
|
557
|
+
return true;
|
|
558
|
+
const name = symbol.getName();
|
|
559
|
+
return name.startsWith("__") || name === "";
|
|
560
|
+
}
|
|
561
|
+
function withDepth(ctx, fn) {
|
|
562
|
+
ctx.currentDepth++;
|
|
563
|
+
try {
|
|
564
|
+
return fn();
|
|
565
|
+
} finally {
|
|
566
|
+
ctx.currentDepth--;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function isAtMaxDepth(ctx) {
|
|
570
|
+
if (!ctx)
|
|
571
|
+
return false;
|
|
572
|
+
return ctx.currentDepth >= ctx.maxTypeDepth;
|
|
573
|
+
}
|
|
574
|
+
function buildSchema(type, checker, ctx, _depth = 0) {
|
|
575
|
+
if (isAtMaxDepth(ctx)) {
|
|
576
|
+
return { type: checker.typeToString(type) };
|
|
577
|
+
}
|
|
578
|
+
if (ctx && ctx.visitedTypes.has(type)) {
|
|
579
|
+
const symbol2 = type.getSymbol() || type.aliasSymbol;
|
|
580
|
+
if (symbol2) {
|
|
581
|
+
return { $ref: symbol2.getName() };
|
|
582
|
+
}
|
|
583
|
+
return { type: checker.typeToString(type) };
|
|
584
|
+
}
|
|
585
|
+
if (type.flags & ts4.TypeFlags.String)
|
|
586
|
+
return { type: "string" };
|
|
587
|
+
if (type.flags & ts4.TypeFlags.Number)
|
|
588
|
+
return { type: "number" };
|
|
589
|
+
if (type.flags & ts4.TypeFlags.Boolean)
|
|
590
|
+
return { type: "boolean" };
|
|
591
|
+
if (type.flags & ts4.TypeFlags.Undefined)
|
|
592
|
+
return { type: "undefined" };
|
|
593
|
+
if (type.flags & ts4.TypeFlags.Null)
|
|
594
|
+
return { type: "null" };
|
|
595
|
+
if (type.flags & ts4.TypeFlags.Void)
|
|
596
|
+
return { type: "void" };
|
|
597
|
+
if (type.flags & ts4.TypeFlags.Any)
|
|
598
|
+
return { type: "any" };
|
|
599
|
+
if (type.flags & ts4.TypeFlags.Unknown)
|
|
600
|
+
return { type: "unknown" };
|
|
601
|
+
if (type.flags & ts4.TypeFlags.Never)
|
|
602
|
+
return { type: "never" };
|
|
603
|
+
if (type.flags & ts4.TypeFlags.BigInt)
|
|
604
|
+
return { type: "bigint" };
|
|
605
|
+
if (type.flags & ts4.TypeFlags.ESSymbol)
|
|
606
|
+
return { type: "symbol" };
|
|
607
|
+
if (type.flags & ts4.TypeFlags.StringLiteral) {
|
|
608
|
+
const literal = type.value;
|
|
609
|
+
return { type: "string", enum: [literal] };
|
|
610
|
+
}
|
|
611
|
+
if (type.flags & ts4.TypeFlags.NumberLiteral) {
|
|
612
|
+
const literal = type.value;
|
|
613
|
+
return { type: "number", enum: [literal] };
|
|
614
|
+
}
|
|
615
|
+
if (type.flags & ts4.TypeFlags.BooleanLiteral) {
|
|
616
|
+
const intrinsicName = type.intrinsicName;
|
|
617
|
+
return { type: "boolean", enum: [intrinsicName === "true"] };
|
|
618
|
+
}
|
|
619
|
+
if (type.isUnion()) {
|
|
620
|
+
const types = type.types;
|
|
621
|
+
const allStringLiterals = types.every((t) => t.flags & ts4.TypeFlags.StringLiteral);
|
|
622
|
+
if (allStringLiterals) {
|
|
623
|
+
const enumValues = types.map((t) => t.value);
|
|
624
|
+
return { type: "string", enum: enumValues };
|
|
625
|
+
}
|
|
626
|
+
const allNumberLiterals = types.every((t) => t.flags & ts4.TypeFlags.NumberLiteral);
|
|
627
|
+
if (allNumberLiterals) {
|
|
628
|
+
const enumValues = types.map((t) => t.value);
|
|
629
|
+
return { type: "number", enum: enumValues };
|
|
630
|
+
}
|
|
631
|
+
if (ctx) {
|
|
632
|
+
return withDepth(ctx, () => ({
|
|
633
|
+
anyOf: types.map((t) => buildSchema(t, checker, ctx))
|
|
634
|
+
}));
|
|
635
|
+
}
|
|
636
|
+
return { anyOf: types.map((t) => buildSchema(t, checker, ctx)) };
|
|
637
|
+
}
|
|
638
|
+
if (type.isIntersection()) {
|
|
639
|
+
if (ctx) {
|
|
640
|
+
return withDepth(ctx, () => ({
|
|
641
|
+
allOf: type.types.map((t) => buildSchema(t, checker, ctx))
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
return { allOf: type.types.map((t) => buildSchema(t, checker, ctx)) };
|
|
645
|
+
}
|
|
646
|
+
if (checker.isArrayType(type)) {
|
|
647
|
+
const typeRef2 = type;
|
|
648
|
+
const elementType = typeRef2.typeArguments?.[0];
|
|
649
|
+
if (elementType) {
|
|
650
|
+
if (ctx) {
|
|
651
|
+
return withDepth(ctx, () => ({
|
|
652
|
+
type: "array",
|
|
653
|
+
items: buildSchema(elementType, checker, ctx)
|
|
654
|
+
}));
|
|
655
|
+
}
|
|
656
|
+
return { type: "array", items: buildSchema(elementType, checker, ctx) };
|
|
657
|
+
}
|
|
658
|
+
return { type: "array" };
|
|
659
|
+
}
|
|
660
|
+
if (checker.isTupleType(type)) {
|
|
661
|
+
const typeRef2 = type;
|
|
662
|
+
const elementTypes = typeRef2.typeArguments ?? [];
|
|
663
|
+
if (ctx) {
|
|
664
|
+
return withDepth(ctx, () => ({
|
|
665
|
+
type: "array",
|
|
666
|
+
prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
|
|
667
|
+
minItems: elementTypes.length,
|
|
668
|
+
maxItems: elementTypes.length
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
type: "array",
|
|
673
|
+
prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
|
|
674
|
+
minItems: elementTypes.length,
|
|
675
|
+
maxItems: elementTypes.length
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const typeRef = type;
|
|
679
|
+
if (typeRef.target && typeRef.typeArguments && typeRef.typeArguments.length > 0) {
|
|
680
|
+
const symbol2 = typeRef.target.getSymbol();
|
|
681
|
+
const name = symbol2?.getName();
|
|
682
|
+
if (name && BUILTIN_TYPES.has(name)) {
|
|
683
|
+
return { $ref: name };
|
|
684
|
+
}
|
|
685
|
+
if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
|
|
686
|
+
if (ctx) {
|
|
687
|
+
return withDepth(ctx, () => ({
|
|
688
|
+
$ref: name,
|
|
689
|
+
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
$ref: name,
|
|
694
|
+
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const symbol = type.getSymbol() || type.aliasSymbol;
|
|
699
|
+
if (symbol && !isAnonymous(type)) {
|
|
700
|
+
const name = symbol.getName();
|
|
701
|
+
if (isPrimitiveName(name)) {
|
|
702
|
+
return { type: name };
|
|
703
|
+
}
|
|
704
|
+
if (BUILTIN_TYPES.has(name)) {
|
|
705
|
+
return { $ref: name };
|
|
706
|
+
}
|
|
707
|
+
if (!name.startsWith("__")) {
|
|
708
|
+
return { $ref: name };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (type.flags & ts4.TypeFlags.Object) {
|
|
712
|
+
const objectType = type;
|
|
713
|
+
const callSignatures = type.getCallSignatures();
|
|
714
|
+
if (callSignatures.length > 0) {
|
|
715
|
+
return buildFunctionSchema(callSignatures, checker, ctx);
|
|
716
|
+
}
|
|
717
|
+
const properties = type.getProperties();
|
|
718
|
+
if (properties.length > 0 || objectType.objectFlags & ts4.ObjectFlags.Anonymous) {
|
|
719
|
+
return buildObjectSchema(properties, checker, ctx);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return { type: checker.typeToString(type) };
|
|
723
|
+
}
|
|
724
|
+
function buildFunctionSchema(callSignatures, checker, ctx) {
|
|
725
|
+
const buildSignatures = () => {
|
|
726
|
+
const signatures = callSignatures.map((sig) => {
|
|
727
|
+
const params = sig.getParameters().map((param) => {
|
|
728
|
+
const paramType = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
|
|
729
|
+
return {
|
|
730
|
+
name: param.getName(),
|
|
731
|
+
schema: buildSchema(paramType, checker, ctx),
|
|
732
|
+
required: !(param.flags & ts4.SymbolFlags.Optional)
|
|
733
|
+
};
|
|
734
|
+
});
|
|
735
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
736
|
+
return {
|
|
737
|
+
parameters: params,
|
|
738
|
+
returns: {
|
|
739
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
});
|
|
743
|
+
return signatures;
|
|
744
|
+
};
|
|
745
|
+
if (ctx) {
|
|
746
|
+
return withDepth(ctx, () => ({ type: "function", signatures: buildSignatures() }));
|
|
747
|
+
}
|
|
748
|
+
return { type: "function", signatures: buildSignatures() };
|
|
749
|
+
}
|
|
750
|
+
function buildObjectSchema(properties, checker, ctx) {
|
|
751
|
+
const buildProps = () => {
|
|
752
|
+
const props = {};
|
|
753
|
+
const required = [];
|
|
754
|
+
for (const prop of properties) {
|
|
755
|
+
const propName = prop.getName();
|
|
756
|
+
if (propName.startsWith("_"))
|
|
757
|
+
continue;
|
|
758
|
+
const propType = checker.getTypeOfSymbol(prop);
|
|
759
|
+
props[propName] = buildSchema(propType, checker, ctx);
|
|
760
|
+
if (!(prop.flags & ts4.SymbolFlags.Optional)) {
|
|
761
|
+
required.push(propName);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: props,
|
|
767
|
+
...required.length > 0 ? { required } : {}
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
if (ctx) {
|
|
771
|
+
return withDepth(ctx, buildProps);
|
|
772
|
+
}
|
|
773
|
+
return buildProps();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/types/parameters.ts
|
|
777
|
+
import ts5 from "typescript";
|
|
778
|
+
function extractParameters(signature, ctx) {
|
|
779
|
+
const { typeChecker: checker } = ctx;
|
|
780
|
+
const result = [];
|
|
781
|
+
const signatureDecl = signature.getDeclaration();
|
|
782
|
+
const jsdocTags = signatureDecl ? ts5.getJSDocTags(signatureDecl) : [];
|
|
783
|
+
for (const param of signature.getParameters()) {
|
|
784
|
+
const decl = param.valueDeclaration;
|
|
785
|
+
const type = checker.getTypeOfSymbolAtLocation(param, decl ?? param.valueDeclaration);
|
|
786
|
+
if (decl && ts5.isObjectBindingPattern(decl.name)) {
|
|
787
|
+
const expandedParams = expandBindingPattern(decl, type, jsdocTags, ctx);
|
|
788
|
+
result.push(...expandedParams);
|
|
789
|
+
} else {
|
|
790
|
+
registerReferencedTypes(type, ctx);
|
|
791
|
+
result.push({
|
|
792
|
+
name: param.getName(),
|
|
793
|
+
schema: buildSchema(type, checker, ctx),
|
|
794
|
+
required: !(param.flags & 16777216)
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
function expandBindingPattern(paramDecl, paramType, jsdocTags, ctx) {
|
|
801
|
+
const { typeChecker: checker } = ctx;
|
|
802
|
+
const result = [];
|
|
803
|
+
const bindingPattern = paramDecl.name;
|
|
804
|
+
const allProperties = getEffectiveProperties(paramType, checker);
|
|
805
|
+
const inferredAlias = inferParamAlias(jsdocTags);
|
|
806
|
+
for (const element of bindingPattern.elements) {
|
|
807
|
+
if (!ts5.isBindingElement(element))
|
|
808
|
+
continue;
|
|
809
|
+
const propertyName = element.propertyName ? ts5.isIdentifier(element.propertyName) ? element.propertyName.text : element.propertyName.getText() : ts5.isIdentifier(element.name) ? element.name.text : element.name.getText();
|
|
810
|
+
const propSymbol = allProperties.get(propertyName);
|
|
811
|
+
if (!propSymbol)
|
|
812
|
+
continue;
|
|
813
|
+
const propType = checker.getTypeOfSymbol(propSymbol);
|
|
814
|
+
registerReferencedTypes(propType, ctx);
|
|
815
|
+
const isOptional = !!(propSymbol.flags & ts5.SymbolFlags.Optional) || element.initializer !== undefined;
|
|
816
|
+
const description = getParamDescription(propertyName, jsdocTags, inferredAlias);
|
|
817
|
+
const param = {
|
|
818
|
+
name: propertyName,
|
|
819
|
+
schema: buildSchema(propType, checker, ctx),
|
|
820
|
+
required: !isOptional
|
|
821
|
+
};
|
|
822
|
+
if (description) {
|
|
823
|
+
param.description = description;
|
|
824
|
+
}
|
|
825
|
+
if (element.initializer) {
|
|
826
|
+
param.default = extractDefaultValue(element.initializer);
|
|
827
|
+
}
|
|
828
|
+
result.push(param);
|
|
829
|
+
}
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
function getEffectiveProperties(type, _checker) {
|
|
833
|
+
const properties = new Map;
|
|
834
|
+
if (type.isIntersection()) {
|
|
835
|
+
for (const subType of type.types) {
|
|
836
|
+
for (const prop of subType.getProperties()) {
|
|
837
|
+
properties.set(prop.getName(), prop);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} else {
|
|
841
|
+
for (const prop of type.getProperties()) {
|
|
842
|
+
properties.set(prop.getName(), prop);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return properties;
|
|
846
|
+
}
|
|
847
|
+
function inferParamAlias(jsdocTags) {
|
|
848
|
+
const prefixes = [];
|
|
849
|
+
for (const tag of jsdocTags) {
|
|
850
|
+
if (tag.tagName.text !== "param")
|
|
851
|
+
continue;
|
|
852
|
+
const tagText = typeof tag.comment === "string" ? tag.comment : ts5.getTextOfJSDocComment(tag.comment) ?? "";
|
|
853
|
+
const paramTag = tag;
|
|
854
|
+
const paramName = paramTag.name?.getText() ?? "";
|
|
855
|
+
if (paramName.includes(".")) {
|
|
856
|
+
const prefix = paramName.split(".")[0];
|
|
857
|
+
if (prefix && !prefix.startsWith("__")) {
|
|
858
|
+
prefixes.push(prefix);
|
|
859
|
+
}
|
|
860
|
+
} else if (tagText.includes(".")) {
|
|
861
|
+
const match = tagText.match(/^(\w+)\./);
|
|
862
|
+
if (match && !match[1].startsWith("__")) {
|
|
863
|
+
prefixes.push(match[1]);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (prefixes.length === 0)
|
|
868
|
+
return;
|
|
869
|
+
const counts = new Map;
|
|
870
|
+
for (const p of prefixes)
|
|
871
|
+
counts.set(p, (counts.get(p) ?? 0) + 1);
|
|
872
|
+
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
873
|
+
}
|
|
874
|
+
function extractDefaultValue(initializer) {
|
|
875
|
+
if (ts5.isStringLiteral(initializer)) {
|
|
876
|
+
return initializer.text;
|
|
877
|
+
}
|
|
878
|
+
if (ts5.isNumericLiteral(initializer)) {
|
|
879
|
+
return Number(initializer.text);
|
|
880
|
+
}
|
|
881
|
+
if (initializer.kind === ts5.SyntaxKind.TrueKeyword) {
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
if (initializer.kind === ts5.SyntaxKind.FalseKeyword) {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
if (initializer.kind === ts5.SyntaxKind.NullKeyword) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
return initializer.getText();
|
|
891
|
+
}
|
|
892
|
+
function registerReferencedTypes(type, ctx) {
|
|
893
|
+
if (ctx.visitedTypes.has(type))
|
|
894
|
+
return;
|
|
895
|
+
const isPrimitive = type.flags & (ts5.TypeFlags.String | ts5.TypeFlags.Number | ts5.TypeFlags.Boolean | ts5.TypeFlags.Void | ts5.TypeFlags.Undefined | ts5.TypeFlags.Null | ts5.TypeFlags.Any | ts5.TypeFlags.Unknown | ts5.TypeFlags.Never | ts5.TypeFlags.StringLiteral | ts5.TypeFlags.NumberLiteral | ts5.TypeFlags.BooleanLiteral);
|
|
896
|
+
if (!isPrimitive) {
|
|
897
|
+
ctx.visitedTypes.add(type);
|
|
898
|
+
}
|
|
899
|
+
const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
|
|
900
|
+
typeRegistry.registerType(type, checker, exportedIds);
|
|
901
|
+
const typeArgs = type.typeArguments;
|
|
902
|
+
if (typeArgs) {
|
|
903
|
+
for (const arg of typeArgs) {
|
|
904
|
+
registerReferencedTypes(arg, ctx);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (type.isUnion()) {
|
|
908
|
+
for (const t of type.types) {
|
|
909
|
+
registerReferencedTypes(t, ctx);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (type.isIntersection()) {
|
|
913
|
+
for (const t of type.types) {
|
|
914
|
+
registerReferencedTypes(t, ctx);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/serializers/classes.ts
|
|
920
|
+
import ts6 from "typescript";
|
|
921
|
+
function serializeClass(node, ctx) {
|
|
922
|
+
const { typeChecker: checker } = ctx;
|
|
923
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
924
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
925
|
+
if (!name)
|
|
926
|
+
return null;
|
|
927
|
+
const declSourceFile = node.getSourceFile();
|
|
928
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
929
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
930
|
+
const typeParameters = extractTypeParameters(node, checker);
|
|
931
|
+
const members = [];
|
|
932
|
+
const signatures = [];
|
|
933
|
+
const methodsByName = new Map;
|
|
934
|
+
for (const member of node.members) {
|
|
935
|
+
const memberName = getMemberName(member);
|
|
936
|
+
if (memberName?.startsWith("#"))
|
|
937
|
+
continue;
|
|
938
|
+
if (ts6.isPropertyDeclaration(member)) {
|
|
939
|
+
const propMember = serializeProperty(member, ctx);
|
|
940
|
+
if (propMember)
|
|
941
|
+
members.push(propMember);
|
|
942
|
+
} else if (ts6.isMethodDeclaration(member)) {
|
|
943
|
+
const methodMember = serializeMethod(member, ctx);
|
|
944
|
+
if (methodMember && methodMember.name) {
|
|
945
|
+
if (!methodsByName.has(methodMember.name)) {
|
|
946
|
+
methodsByName.set(methodMember.name, methodMember);
|
|
947
|
+
} else {
|
|
948
|
+
const existing = methodsByName.get(methodMember.name);
|
|
949
|
+
if (!existing.description && methodMember.description) {
|
|
950
|
+
existing.description = methodMember.description;
|
|
951
|
+
}
|
|
952
|
+
if (!existing.tags && methodMember.tags) {
|
|
953
|
+
existing.tags = methodMember.tags;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
} else if (ts6.isConstructorDeclaration(member)) {
|
|
958
|
+
const ctorSig = serializeConstructor(member, ctx);
|
|
959
|
+
if (ctorSig)
|
|
960
|
+
signatures.push(ctorSig);
|
|
961
|
+
} else if (ts6.isGetAccessorDeclaration(member) || ts6.isSetAccessorDeclaration(member)) {
|
|
962
|
+
const accessorMember = serializeAccessor(member, ctx);
|
|
963
|
+
if (accessorMember)
|
|
964
|
+
members.push(accessorMember);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
members.push(...methodsByName.values());
|
|
968
|
+
const extendsClause = getExtendsClause(node, checker);
|
|
969
|
+
const implementsClause = getImplementsClause(node, checker);
|
|
970
|
+
return {
|
|
971
|
+
id: name,
|
|
972
|
+
name,
|
|
973
|
+
kind: "class",
|
|
974
|
+
description,
|
|
975
|
+
tags,
|
|
976
|
+
source,
|
|
977
|
+
typeParameters,
|
|
978
|
+
members: members.length > 0 ? members : undefined,
|
|
979
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
980
|
+
extends: extendsClause,
|
|
981
|
+
implements: implementsClause?.length ? implementsClause : undefined,
|
|
982
|
+
...examples.length > 0 ? { examples } : {}
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function getMemberName(member) {
|
|
986
|
+
if (ts6.isConstructorDeclaration(member))
|
|
987
|
+
return "constructor";
|
|
988
|
+
if (!member.name)
|
|
989
|
+
return;
|
|
990
|
+
if (ts6.isIdentifier(member.name))
|
|
991
|
+
return member.name.text;
|
|
992
|
+
if (ts6.isPrivateIdentifier(member.name))
|
|
993
|
+
return member.name.text;
|
|
994
|
+
return member.name.getText();
|
|
995
|
+
}
|
|
996
|
+
function getVisibility(member) {
|
|
997
|
+
const modifiers = ts6.getModifiers(member);
|
|
998
|
+
if (!modifiers)
|
|
999
|
+
return;
|
|
1000
|
+
for (const mod of modifiers) {
|
|
1001
|
+
if (mod.kind === ts6.SyntaxKind.PrivateKeyword)
|
|
1002
|
+
return "private";
|
|
1003
|
+
if (mod.kind === ts6.SyntaxKind.ProtectedKeyword)
|
|
1004
|
+
return "protected";
|
|
1005
|
+
if (mod.kind === ts6.SyntaxKind.PublicKeyword)
|
|
1006
|
+
return "public";
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
function isStatic(member) {
|
|
1011
|
+
const modifiers = ts6.getModifiers(member);
|
|
1012
|
+
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.StaticKeyword) ?? false;
|
|
1013
|
+
}
|
|
1014
|
+
function isReadonly(member) {
|
|
1015
|
+
const modifiers = ts6.getModifiers(member);
|
|
1016
|
+
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
1017
|
+
}
|
|
1018
|
+
function serializeProperty(node, ctx) {
|
|
1019
|
+
const { typeChecker: checker } = ctx;
|
|
1020
|
+
const name = getMemberName(node);
|
|
1021
|
+
if (!name)
|
|
1022
|
+
return null;
|
|
1023
|
+
const { description, tags } = getJSDocComment(node);
|
|
1024
|
+
const visibility = getVisibility(node);
|
|
1025
|
+
const type = checker.getTypeAtLocation(node);
|
|
1026
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1027
|
+
registerReferencedTypes(type, ctx);
|
|
1028
|
+
const flags = {};
|
|
1029
|
+
if (isStatic(node))
|
|
1030
|
+
flags.static = true;
|
|
1031
|
+
if (isReadonly(node))
|
|
1032
|
+
flags.readonly = true;
|
|
1033
|
+
if (node.questionToken)
|
|
1034
|
+
flags.optional = true;
|
|
1035
|
+
return {
|
|
1036
|
+
name,
|
|
1037
|
+
kind: "property",
|
|
1038
|
+
description,
|
|
1039
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1040
|
+
visibility,
|
|
1041
|
+
schema,
|
|
1042
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function serializeMethod(node, ctx) {
|
|
1046
|
+
const { typeChecker: checker } = ctx;
|
|
1047
|
+
const name = getMemberName(node);
|
|
1048
|
+
if (!name)
|
|
1049
|
+
return null;
|
|
1050
|
+
const { description, tags } = getJSDocComment(node);
|
|
1051
|
+
const visibility = getVisibility(node);
|
|
1052
|
+
const type = checker.getTypeAtLocation(node);
|
|
1053
|
+
const callSignatures = type.getCallSignatures();
|
|
1054
|
+
const signatures = callSignatures.map((sig) => {
|
|
1055
|
+
const params = extractParameters(sig, ctx);
|
|
1056
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1057
|
+
registerReferencedTypes(returnType, ctx);
|
|
1058
|
+
return {
|
|
1059
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1060
|
+
returns: {
|
|
1061
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
});
|
|
1065
|
+
const flags = {};
|
|
1066
|
+
if (isStatic(node))
|
|
1067
|
+
flags.static = true;
|
|
1068
|
+
if (node.asteriskToken)
|
|
1069
|
+
flags.generator = true;
|
|
1070
|
+
const modifiers = ts6.getModifiers(node);
|
|
1071
|
+
if (modifiers?.some((m) => m.kind === ts6.SyntaxKind.AsyncKeyword)) {
|
|
1072
|
+
flags.async = true;
|
|
1073
|
+
}
|
|
1074
|
+
return {
|
|
1075
|
+
name,
|
|
1076
|
+
kind: "method",
|
|
1077
|
+
description,
|
|
1078
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1079
|
+
visibility,
|
|
1080
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
1081
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function serializeConstructor(node, ctx) {
|
|
1085
|
+
const { typeChecker: checker } = ctx;
|
|
1086
|
+
const { description } = getJSDocComment(node);
|
|
1087
|
+
const sig = checker.getSignatureFromDeclaration(node);
|
|
1088
|
+
if (!sig)
|
|
1089
|
+
return null;
|
|
1090
|
+
const params = extractParameters(sig, ctx);
|
|
1091
|
+
return {
|
|
1092
|
+
description,
|
|
1093
|
+
parameters: params.length > 0 ? params : undefined
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function serializeAccessor(node, ctx) {
|
|
1097
|
+
const { typeChecker: checker } = ctx;
|
|
1098
|
+
const name = getMemberName(node);
|
|
1099
|
+
if (!name)
|
|
1100
|
+
return null;
|
|
1101
|
+
const { description, tags } = getJSDocComment(node);
|
|
1102
|
+
const visibility = getVisibility(node);
|
|
1103
|
+
const type = checker.getTypeAtLocation(node);
|
|
1104
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1105
|
+
registerReferencedTypes(type, ctx);
|
|
1106
|
+
const kind = ts6.isGetAccessorDeclaration(node) ? "getter" : "setter";
|
|
1107
|
+
const flags = {};
|
|
1108
|
+
if (isStatic(node))
|
|
1109
|
+
flags.static = true;
|
|
1110
|
+
return {
|
|
1111
|
+
name,
|
|
1112
|
+
kind,
|
|
1113
|
+
description,
|
|
1114
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1115
|
+
visibility,
|
|
1116
|
+
schema,
|
|
1117
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function getExtendsClause(node, checker) {
|
|
1121
|
+
if (!node.heritageClauses)
|
|
1122
|
+
return;
|
|
1123
|
+
for (const clause of node.heritageClauses) {
|
|
1124
|
+
if (clause.token === ts6.SyntaxKind.ExtendsKeyword) {
|
|
1125
|
+
const expr = clause.types[0];
|
|
1126
|
+
if (expr) {
|
|
1127
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1128
|
+
const symbol = type.getSymbol();
|
|
1129
|
+
return symbol?.getName() ?? expr.expression.getText();
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
function getImplementsClause(node, checker) {
|
|
1136
|
+
if (!node.heritageClauses)
|
|
1137
|
+
return;
|
|
1138
|
+
for (const clause of node.heritageClauses) {
|
|
1139
|
+
if (clause.token === ts6.SyntaxKind.ImplementsKeyword) {
|
|
1140
|
+
return clause.types.map((expr) => {
|
|
1141
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1142
|
+
const symbol = type.getSymbol();
|
|
1143
|
+
return symbol?.getName() ?? expr.expression.getText();
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/serializers/enums.ts
|
|
1151
|
+
function serializeEnum(node, ctx) {
|
|
1152
|
+
const { typeChecker: checker } = ctx;
|
|
1153
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
1154
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1155
|
+
if (!name)
|
|
1156
|
+
return null;
|
|
1157
|
+
const declSourceFile = node.getSourceFile();
|
|
1158
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1159
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1160
|
+
const members = node.members.map((member) => {
|
|
1161
|
+
const memberSymbol = checker.getSymbolAtLocation(member.name);
|
|
1162
|
+
const memberName = memberSymbol?.getName() ?? member.name.getText();
|
|
1163
|
+
const constantValue = checker.getConstantValue(member);
|
|
1164
|
+
let schema;
|
|
1165
|
+
if (typeof constantValue === "string") {
|
|
1166
|
+
schema = { type: "string", enum: [constantValue] };
|
|
1167
|
+
} else if (typeof constantValue === "number") {
|
|
1168
|
+
schema = { type: "number", enum: [constantValue] };
|
|
1169
|
+
} else if (member.initializer) {
|
|
1170
|
+
schema = { type: member.initializer.getText() };
|
|
1171
|
+
}
|
|
1172
|
+
const { description: memberDesc } = getJSDocComment(member);
|
|
1173
|
+
return {
|
|
1174
|
+
id: memberName,
|
|
1175
|
+
name: memberName,
|
|
1176
|
+
kind: "enum-member",
|
|
1177
|
+
...schema ? { schema } : {},
|
|
1178
|
+
...memberDesc ? { description: memberDesc } : {}
|
|
1179
|
+
};
|
|
1180
|
+
});
|
|
1181
|
+
return {
|
|
1182
|
+
id: name,
|
|
1183
|
+
name,
|
|
1184
|
+
kind: "enum",
|
|
1185
|
+
description,
|
|
1186
|
+
tags,
|
|
1187
|
+
source,
|
|
1188
|
+
members,
|
|
1189
|
+
...examples.length > 0 ? { examples } : {}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/serializers/functions.ts
|
|
1194
|
+
function serializeFunctionExport(node, ctx) {
|
|
1195
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
1196
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1197
|
+
if (!name)
|
|
1198
|
+
return null;
|
|
1199
|
+
const declSourceFile = node.getSourceFile();
|
|
1200
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1201
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1202
|
+
const typeParameters = extractTypeParameters(node, ctx.typeChecker);
|
|
1203
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
1204
|
+
const callSignatures = type.getCallSignatures();
|
|
1205
|
+
const signatures = callSignatures.map((sig) => {
|
|
1206
|
+
const params = extractParameters(sig, ctx);
|
|
1207
|
+
const returnType = ctx.typeChecker.getReturnTypeOfSignature(sig);
|
|
1208
|
+
registerReferencedTypes(returnType, ctx);
|
|
1209
|
+
return {
|
|
1210
|
+
parameters: params,
|
|
1211
|
+
returns: {
|
|
1212
|
+
schema: buildSchema(returnType, ctx.typeChecker, ctx)
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
});
|
|
1216
|
+
return {
|
|
1217
|
+
id: name,
|
|
1218
|
+
name,
|
|
1219
|
+
kind: "function",
|
|
1220
|
+
description,
|
|
1221
|
+
tags,
|
|
1222
|
+
source,
|
|
1223
|
+
typeParameters,
|
|
1224
|
+
signatures,
|
|
1225
|
+
...examples.length > 0 ? { examples } : {}
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/serializers/interfaces.ts
|
|
1230
|
+
import ts7 from "typescript";
|
|
1231
|
+
function serializeInterface(node, ctx) {
|
|
1232
|
+
const { typeChecker: checker } = ctx;
|
|
1233
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
1234
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1235
|
+
if (!name)
|
|
1236
|
+
return null;
|
|
1237
|
+
const declSourceFile = node.getSourceFile();
|
|
1238
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1239
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1240
|
+
const typeParameters = extractTypeParameters(node, checker);
|
|
1241
|
+
const members = [];
|
|
1242
|
+
const methodsByName = new Map;
|
|
1243
|
+
for (const member of node.members) {
|
|
1244
|
+
if (ts7.isPropertySignature(member)) {
|
|
1245
|
+
const propMember = serializePropertySignature(member, ctx);
|
|
1246
|
+
if (propMember)
|
|
1247
|
+
members.push(propMember);
|
|
1248
|
+
} else if (ts7.isMethodSignature(member)) {
|
|
1249
|
+
const methodMember = serializeMethodSignature(member, ctx);
|
|
1250
|
+
if (methodMember && methodMember.name) {
|
|
1251
|
+
if (!methodsByName.has(methodMember.name)) {
|
|
1252
|
+
methodsByName.set(methodMember.name, methodMember);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
} else if (ts7.isCallSignatureDeclaration(member)) {
|
|
1256
|
+
const callMember = serializeCallSignature(member, ctx);
|
|
1257
|
+
if (callMember)
|
|
1258
|
+
members.push(callMember);
|
|
1259
|
+
} else if (ts7.isIndexSignatureDeclaration(member)) {
|
|
1260
|
+
const indexMember = serializeIndexSignature(member, ctx);
|
|
1261
|
+
if (indexMember)
|
|
1262
|
+
members.push(indexMember);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
members.push(...methodsByName.values());
|
|
1266
|
+
const extendsClause = getInterfaceExtends(node, checker);
|
|
1267
|
+
return {
|
|
1268
|
+
id: name,
|
|
1269
|
+
name,
|
|
1270
|
+
kind: "interface",
|
|
1271
|
+
description,
|
|
1272
|
+
tags,
|
|
1273
|
+
source,
|
|
1274
|
+
typeParameters,
|
|
1275
|
+
members: members.length > 0 ? members : undefined,
|
|
1276
|
+
extends: extendsClause,
|
|
1277
|
+
...examples.length > 0 ? { examples } : {}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function serializePropertySignature(node, ctx) {
|
|
1281
|
+
const { typeChecker: checker } = ctx;
|
|
1282
|
+
const name = node.name.getText();
|
|
1283
|
+
const { description, tags } = getJSDocComment(node);
|
|
1284
|
+
const type = checker.getTypeAtLocation(node);
|
|
1285
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1286
|
+
registerReferencedTypes(type, ctx);
|
|
1287
|
+
const flags = {};
|
|
1288
|
+
if (node.questionToken)
|
|
1289
|
+
flags.optional = true;
|
|
1290
|
+
if (node.modifiers?.some((m) => m.kind === ts7.SyntaxKind.ReadonlyKeyword)) {
|
|
1291
|
+
flags.readonly = true;
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
name,
|
|
1295
|
+
kind: "property",
|
|
1296
|
+
description,
|
|
1297
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1298
|
+
schema,
|
|
1299
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function serializeMethodSignature(node, ctx) {
|
|
1303
|
+
const { typeChecker: checker } = ctx;
|
|
1304
|
+
const name = node.name.getText();
|
|
1305
|
+
const { description, tags } = getJSDocComment(node);
|
|
1306
|
+
const type = checker.getTypeAtLocation(node);
|
|
1307
|
+
const callSignatures = type.getCallSignatures();
|
|
1308
|
+
const signatures = callSignatures.map((sig) => {
|
|
1309
|
+
const params = extractParameters(sig, ctx);
|
|
1310
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1311
|
+
registerReferencedTypes(returnType, ctx);
|
|
1312
|
+
return {
|
|
1313
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1314
|
+
returns: {
|
|
1315
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
});
|
|
1319
|
+
const flags = {};
|
|
1320
|
+
if (node.questionToken)
|
|
1321
|
+
flags.optional = true;
|
|
1322
|
+
return {
|
|
1323
|
+
name,
|
|
1324
|
+
kind: "method",
|
|
1325
|
+
description,
|
|
1326
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1327
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
1328
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function serializeCallSignature(node, ctx) {
|
|
1332
|
+
const { typeChecker: checker } = ctx;
|
|
1333
|
+
const { description, tags } = getJSDocComment(node);
|
|
1334
|
+
const sig = checker.getSignatureFromDeclaration(node);
|
|
1335
|
+
if (!sig)
|
|
1336
|
+
return null;
|
|
1337
|
+
const params = extractParameters(sig, ctx);
|
|
1338
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1339
|
+
registerReferencedTypes(returnType, ctx);
|
|
1340
|
+
return {
|
|
1341
|
+
name: "()",
|
|
1342
|
+
kind: "call-signature",
|
|
1343
|
+
description,
|
|
1344
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1345
|
+
signatures: [
|
|
1346
|
+
{
|
|
1347
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1348
|
+
returns: {
|
|
1349
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
]
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
function serializeIndexSignature(node, ctx) {
|
|
1356
|
+
const { typeChecker: checker } = ctx;
|
|
1357
|
+
const { description, tags } = getJSDocComment(node);
|
|
1358
|
+
const valueType = node.type ? checker.getTypeAtLocation(node.type) : checker.getAnyType();
|
|
1359
|
+
const valueSchema = buildSchema(valueType, checker, ctx);
|
|
1360
|
+
registerReferencedTypes(valueType, ctx);
|
|
1361
|
+
const keyParam = node.parameters[0];
|
|
1362
|
+
const keyType = keyParam?.type ? checker.getTypeAtLocation(keyParam.type) : checker.getStringType();
|
|
1363
|
+
const keyTypeName = checker.typeToString(keyType);
|
|
1364
|
+
return {
|
|
1365
|
+
name: `[${keyTypeName}]`,
|
|
1366
|
+
kind: "index-signature",
|
|
1367
|
+
description,
|
|
1368
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1369
|
+
schema: {
|
|
1370
|
+
type: "object",
|
|
1371
|
+
additionalProperties: valueSchema
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function getInterfaceExtends(node, checker) {
|
|
1376
|
+
if (!node.heritageClauses)
|
|
1377
|
+
return;
|
|
1378
|
+
for (const clause of node.heritageClauses) {
|
|
1379
|
+
if (clause.token === ts7.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
|
|
1380
|
+
const names = clause.types.map((expr) => {
|
|
1381
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1382
|
+
return type.getSymbol()?.getName() ?? expr.expression.getText();
|
|
1383
|
+
});
|
|
1384
|
+
return names.length === 1 ? names[0] : names;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// src/serializers/type-aliases.ts
|
|
1391
|
+
function serializeTypeAlias(node, ctx) {
|
|
1392
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
1393
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1394
|
+
if (!name)
|
|
1395
|
+
return null;
|
|
1396
|
+
const declSourceFile = node.getSourceFile();
|
|
1397
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1398
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1399
|
+
const typeParameters = extractTypeParameters(node, ctx.typeChecker);
|
|
1400
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
1401
|
+
const schema = buildSchema(type, ctx.typeChecker, ctx);
|
|
1402
|
+
registerReferencedTypes(type, ctx);
|
|
1403
|
+
return {
|
|
1404
|
+
id: name,
|
|
1405
|
+
name,
|
|
1406
|
+
kind: "type",
|
|
1407
|
+
description,
|
|
1408
|
+
tags,
|
|
1409
|
+
source,
|
|
1410
|
+
typeParameters,
|
|
1411
|
+
schema,
|
|
1412
|
+
...examples.length > 0 ? { examples } : {}
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/serializers/variables.ts
|
|
1417
|
+
function serializeVariable(node, statement, ctx) {
|
|
1418
|
+
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name);
|
|
1419
|
+
const name = symbol?.getName() ?? node.name.getText();
|
|
1420
|
+
if (!name)
|
|
1421
|
+
return null;
|
|
1422
|
+
const declSourceFile = node.getSourceFile();
|
|
1423
|
+
const { description, tags, examples } = getJSDocComment(statement);
|
|
1424
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1425
|
+
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
1426
|
+
const schema = buildSchema(type, ctx.typeChecker, ctx);
|
|
1427
|
+
registerReferencedTypes(type, ctx);
|
|
1428
|
+
return {
|
|
1429
|
+
id: name,
|
|
1430
|
+
name,
|
|
1431
|
+
kind: "variable",
|
|
1432
|
+
description,
|
|
1433
|
+
tags,
|
|
1434
|
+
source,
|
|
1435
|
+
schema,
|
|
1436
|
+
...examples.length > 0 ? { examples } : {}
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/builder/spec-builder.ts
|
|
1441
|
+
import * as fs2 from "node:fs";
|
|
1442
|
+
import * as path3 from "node:path";
|
|
1443
|
+
import { SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1444
|
+
import ts8 from "typescript";
|
|
1445
|
+
|
|
1446
|
+
// src/serializers/context.ts
|
|
1447
|
+
function createContext(program, sourceFile, options = {}) {
|
|
1448
|
+
return {
|
|
1449
|
+
typeChecker: program.getTypeChecker(),
|
|
1450
|
+
program,
|
|
1451
|
+
sourceFile,
|
|
1452
|
+
maxTypeDepth: options.maxTypeDepth ?? 4,
|
|
1453
|
+
maxExternalTypeDepth: options.maxExternalTypeDepth ?? 2,
|
|
1454
|
+
currentDepth: 0,
|
|
1455
|
+
resolveExternalTypes: options.resolveExternalTypes ?? true,
|
|
1456
|
+
typeRegistry: new TypeRegistry,
|
|
1457
|
+
exportedIds: new Set,
|
|
1458
|
+
visitedTypes: new Set
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/builder/spec-builder.ts
|
|
1463
|
+
async function extract(options) {
|
|
1464
|
+
const { entryFile, baseDir, content, maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes } = options;
|
|
1465
|
+
const diagnostics = [];
|
|
1466
|
+
const exports = [];
|
|
1467
|
+
const result = createProgram({ entryFile, baseDir, content });
|
|
1468
|
+
const { program, sourceFile } = result;
|
|
1469
|
+
if (!sourceFile) {
|
|
1470
|
+
return {
|
|
1471
|
+
spec: createEmptySpec(entryFile),
|
|
1472
|
+
diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
const typeChecker = program.getTypeChecker();
|
|
1476
|
+
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
1477
|
+
if (!moduleSymbol) {
|
|
1478
|
+
return {
|
|
1479
|
+
spec: createEmptySpec(entryFile),
|
|
1480
|
+
diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
|
|
1484
|
+
const exportedIds = new Set;
|
|
1485
|
+
for (const symbol of exportedSymbols) {
|
|
1486
|
+
exportedIds.add(symbol.getName());
|
|
1487
|
+
}
|
|
1488
|
+
const ctx = createContext(program, sourceFile, { maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes });
|
|
1489
|
+
ctx.exportedIds = exportedIds;
|
|
1490
|
+
for (const symbol of exportedSymbols) {
|
|
1491
|
+
try {
|
|
1492
|
+
const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
|
|
1493
|
+
if (!declaration)
|
|
1494
|
+
continue;
|
|
1495
|
+
const exportName = symbol.getName();
|
|
1496
|
+
const exp = serializeDeclaration(declaration, symbol, targetSymbol, exportName, ctx);
|
|
1497
|
+
if (exp)
|
|
1498
|
+
exports.push(exp);
|
|
1499
|
+
} catch (err) {
|
|
1500
|
+
diagnostics.push({
|
|
1501
|
+
message: `Failed to serialize ${symbol.getName()}: ${err}`,
|
|
1502
|
+
severity: "warning"
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
const meta = await getPackageMeta(entryFile, baseDir);
|
|
1507
|
+
const spec = {
|
|
1508
|
+
openpkg: SCHEMA_VERSION,
|
|
1509
|
+
meta,
|
|
1510
|
+
exports,
|
|
1511
|
+
types: ctx.typeRegistry.getAll(),
|
|
1512
|
+
generation: {
|
|
1513
|
+
generator: "@openpkg-ts/extract",
|
|
1514
|
+
timestamp: new Date().toISOString()
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
return { spec, diagnostics };
|
|
1518
|
+
}
|
|
1519
|
+
function resolveExportTarget(symbol, checker) {
|
|
1520
|
+
let targetSymbol = symbol;
|
|
1521
|
+
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
1522
|
+
const aliasTarget = checker.getAliasedSymbol(symbol);
|
|
1523
|
+
if (aliasTarget && aliasTarget !== symbol) {
|
|
1524
|
+
targetSymbol = aliasTarget;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
const declarations = targetSymbol.declarations ?? [];
|
|
1528
|
+
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts8.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
1529
|
+
return { declaration, targetSymbol };
|
|
1530
|
+
}
|
|
1531
|
+
function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
|
|
1532
|
+
let result = null;
|
|
1533
|
+
if (ts8.isFunctionDeclaration(declaration)) {
|
|
1534
|
+
result = serializeFunctionExport(declaration, ctx);
|
|
1535
|
+
} else if (ts8.isClassDeclaration(declaration)) {
|
|
1536
|
+
result = serializeClass(declaration, ctx);
|
|
1537
|
+
} else if (ts8.isInterfaceDeclaration(declaration)) {
|
|
1538
|
+
result = serializeInterface(declaration, ctx);
|
|
1539
|
+
} else if (ts8.isTypeAliasDeclaration(declaration)) {
|
|
1540
|
+
result = serializeTypeAlias(declaration, ctx);
|
|
1541
|
+
} else if (ts8.isEnumDeclaration(declaration)) {
|
|
1542
|
+
result = serializeEnum(declaration, ctx);
|
|
1543
|
+
} else if (ts8.isVariableDeclaration(declaration)) {
|
|
1544
|
+
const varStatement = declaration.parent?.parent;
|
|
1545
|
+
if (varStatement && ts8.isVariableStatement(varStatement)) {
|
|
1546
|
+
result = serializeVariable(declaration, varStatement, ctx);
|
|
1547
|
+
}
|
|
1548
|
+
} else if (ts8.isNamespaceExport(declaration) || ts8.isModuleDeclaration(declaration)) {
|
|
1549
|
+
result = serializeNamespaceExport(exportSymbol, exportName, ctx);
|
|
1550
|
+
} else if (ts8.isSourceFile(declaration)) {
|
|
1551
|
+
result = serializeNamespaceExport(exportSymbol, exportName, ctx);
|
|
1552
|
+
}
|
|
1553
|
+
if (result) {
|
|
1554
|
+
result = withExportName(result, exportName);
|
|
1555
|
+
}
|
|
1556
|
+
return result;
|
|
1557
|
+
}
|
|
1558
|
+
function serializeNamespaceExport(symbol, exportName, ctx) {
|
|
1559
|
+
const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
|
|
1560
|
+
return {
|
|
1561
|
+
id: exportName,
|
|
1562
|
+
name: exportName,
|
|
1563
|
+
kind: "namespace",
|
|
1564
|
+
description,
|
|
1565
|
+
tags,
|
|
1566
|
+
...examples.length > 0 ? { examples } : {}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
function getJSDocFromExportSymbol(symbol) {
|
|
1570
|
+
const tags = [];
|
|
1571
|
+
const examples = [];
|
|
1572
|
+
const decl = symbol.declarations?.[0];
|
|
1573
|
+
if (decl) {
|
|
1574
|
+
const exportDecl = ts8.isNamespaceExport(decl) ? decl.parent : decl;
|
|
1575
|
+
if (exportDecl && ts8.isExportDeclaration(exportDecl)) {
|
|
1576
|
+
const jsDocs = ts8.getJSDocCommentsAndTags(exportDecl);
|
|
1577
|
+
for (const doc of jsDocs) {
|
|
1578
|
+
if (ts8.isJSDoc(doc) && doc.comment) {
|
|
1579
|
+
const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
|
|
1580
|
+
if (commentText) {
|
|
1581
|
+
return {
|
|
1582
|
+
description: commentText,
|
|
1583
|
+
tags: extractJSDocTags(doc),
|
|
1584
|
+
examples: extractExamples(doc)
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
const docComment = symbol.getDocumentationComment(undefined);
|
|
1592
|
+
const description = docComment.map((c) => c.text).join(`
|
|
1593
|
+
`) || undefined;
|
|
1594
|
+
const jsTags = symbol.getJsDocTags();
|
|
1595
|
+
for (const tag of jsTags) {
|
|
1596
|
+
const text = tag.text?.map((t) => t.text).join("") ?? "";
|
|
1597
|
+
if (tag.name === "example") {
|
|
1598
|
+
examples.push(text);
|
|
1599
|
+
} else {
|
|
1600
|
+
tags.push({ name: tag.name, text });
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return { description, tags, examples };
|
|
1604
|
+
}
|
|
1605
|
+
function extractJSDocTags(doc) {
|
|
1606
|
+
const tags = [];
|
|
1607
|
+
for (const tag of doc.tags ?? []) {
|
|
1608
|
+
if (tag.tagName.text !== "example") {
|
|
1609
|
+
const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
|
|
1610
|
+
tags.push({ name: tag.tagName.text, text });
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return tags;
|
|
1614
|
+
}
|
|
1615
|
+
function extractExamples(doc) {
|
|
1616
|
+
const examples = [];
|
|
1617
|
+
for (const tag of doc.tags ?? []) {
|
|
1618
|
+
if (tag.tagName.text === "example") {
|
|
1619
|
+
const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
|
|
1620
|
+
if (text)
|
|
1621
|
+
examples.push(text);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
return examples;
|
|
1625
|
+
}
|
|
1626
|
+
function withExportName(entry, exportName) {
|
|
1627
|
+
if (entry.name === exportName) {
|
|
1628
|
+
return entry;
|
|
1629
|
+
}
|
|
1630
|
+
return {
|
|
1631
|
+
...entry,
|
|
1632
|
+
id: exportName,
|
|
1633
|
+
name: entry.name
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
function createEmptySpec(entryFile) {
|
|
1637
|
+
return {
|
|
1638
|
+
openpkg: SCHEMA_VERSION,
|
|
1639
|
+
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
1640
|
+
exports: [],
|
|
1641
|
+
generation: {
|
|
1642
|
+
generator: "@openpkg-ts/extract",
|
|
1643
|
+
timestamp: new Date().toISOString()
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
async function getPackageMeta(entryFile, baseDir) {
|
|
1648
|
+
const searchDir = baseDir ?? path3.dirname(entryFile);
|
|
1649
|
+
const pkgPath = path3.join(searchDir, "package.json");
|
|
1650
|
+
try {
|
|
1651
|
+
if (fs2.existsSync(pkgPath)) {
|
|
1652
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
1653
|
+
return {
|
|
1654
|
+
name: pkg.name ?? path3.basename(searchDir),
|
|
1655
|
+
version: pkg.version,
|
|
1656
|
+
description: pkg.description
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
} catch {}
|
|
1660
|
+
return { name: path3.basename(searchDir) };
|
|
1661
|
+
}
|
|
1662
|
+
export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|