@openpkg-ts/extract 0.12.0 → 0.14.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-wddga8ye.js → chunk-v4cnenxs.js} +841 -96
- package/dist/src/index.d.ts +208 -47
- package/dist/src/index.js +361 -43
- package/package.json +2 -1
|
@@ -203,17 +203,133 @@ class TypeRegistry {
|
|
|
203
203
|
if (external) {
|
|
204
204
|
kind = "external";
|
|
205
205
|
}
|
|
206
|
-
const typeString = checker.typeToString(type);
|
|
207
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
|
+
}
|
|
208
212
|
return {
|
|
209
213
|
id: name,
|
|
210
214
|
name,
|
|
211
215
|
kind,
|
|
212
|
-
|
|
216
|
+
schema,
|
|
217
|
+
members: members?.length ? members : undefined,
|
|
213
218
|
...external ? { external: true } : {},
|
|
214
219
|
...source ? { source } : {}
|
|
215
220
|
};
|
|
216
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: `#/types/${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: `#/types/${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: `#/types/${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: `#/types/${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
|
+
}
|
|
217
333
|
registerFromSymbol(symbol, checker) {
|
|
218
334
|
const name = symbol.getName();
|
|
219
335
|
if (this.has(name))
|
|
@@ -289,6 +405,44 @@ function getParamDescription(propertyName, jsdocTags, inferredAlias) {
|
|
|
289
405
|
}
|
|
290
406
|
return;
|
|
291
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
|
+
function isSymbolDeprecated(symbol) {
|
|
432
|
+
if (!symbol) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const jsDocTags = symbol.getJsDocTags();
|
|
436
|
+
if (jsDocTags.some((tag) => tag.name.toLowerCase() === "deprecated")) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
for (const declaration of symbol.getDeclarations() ?? []) {
|
|
440
|
+
if (ts2.getJSDocDeprecatedTag(declaration)) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
292
446
|
|
|
293
447
|
// src/compiler/program.ts
|
|
294
448
|
import * as path2 from "node:path";
|
|
@@ -339,59 +493,32 @@ function createProgram({
|
|
|
339
493
|
};
|
|
340
494
|
}
|
|
341
495
|
|
|
342
|
-
// src/serializers/classes.ts
|
|
343
|
-
function serializeClass(node, ctx) {
|
|
344
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
345
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
346
|
-
if (!name)
|
|
347
|
-
return null;
|
|
348
|
-
const declSourceFile = node.getSourceFile();
|
|
349
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
350
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
351
|
-
return {
|
|
352
|
-
id: name,
|
|
353
|
-
name,
|
|
354
|
-
kind: "class",
|
|
355
|
-
description,
|
|
356
|
-
tags,
|
|
357
|
-
source,
|
|
358
|
-
members: [],
|
|
359
|
-
...examples.length > 0 ? { examples } : {}
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// src/serializers/enums.ts
|
|
364
|
-
function serializeEnum(node, ctx) {
|
|
365
|
-
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
366
|
-
const name = symbol?.getName() ?? node.name?.getText();
|
|
367
|
-
if (!name)
|
|
368
|
-
return null;
|
|
369
|
-
const declSourceFile = node.getSourceFile();
|
|
370
|
-
const { description, tags, examples } = getJSDocComment(node);
|
|
371
|
-
const source = getSourceLocation(node, declSourceFile);
|
|
372
|
-
const members = node.members.map((member) => {
|
|
373
|
-
const memberSymbol = ctx.typeChecker.getSymbolAtLocation(member.name);
|
|
374
|
-
const memberName = memberSymbol?.getName() ?? member.name.getText();
|
|
375
|
-
return {
|
|
376
|
-
id: memberName,
|
|
377
|
-
name: memberName,
|
|
378
|
-
kind: "enum-member"
|
|
379
|
-
};
|
|
380
|
-
});
|
|
381
|
-
return {
|
|
382
|
-
id: name,
|
|
383
|
-
name,
|
|
384
|
-
kind: "enum",
|
|
385
|
-
description,
|
|
386
|
-
tags,
|
|
387
|
-
source,
|
|
388
|
-
members,
|
|
389
|
-
...examples.length > 0 ? { examples } : {}
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
|
|
393
496
|
// src/types/schema-builder.ts
|
|
394
497
|
import ts4 from "typescript";
|
|
498
|
+
var BUILTIN_TYPE_SCHEMAS = {
|
|
499
|
+
Date: { type: "string", format: "date-time" },
|
|
500
|
+
RegExp: { type: "object", description: "RegExp" },
|
|
501
|
+
Error: { type: "object" },
|
|
502
|
+
Promise: { type: "object" },
|
|
503
|
+
Map: { type: "object" },
|
|
504
|
+
Set: { type: "object" },
|
|
505
|
+
WeakMap: { type: "object" },
|
|
506
|
+
WeakSet: { type: "object" },
|
|
507
|
+
Function: { type: "object" },
|
|
508
|
+
ArrayBuffer: { type: "string", format: "binary" },
|
|
509
|
+
ArrayBufferLike: { type: "string", format: "binary" },
|
|
510
|
+
DataView: { type: "string", format: "binary" },
|
|
511
|
+
Uint8Array: { type: "string", format: "byte" },
|
|
512
|
+
Uint16Array: { type: "string", format: "byte" },
|
|
513
|
+
Uint32Array: { type: "string", format: "byte" },
|
|
514
|
+
Int8Array: { type: "string", format: "byte" },
|
|
515
|
+
Int16Array: { type: "string", format: "byte" },
|
|
516
|
+
Int32Array: { type: "string", format: "byte" },
|
|
517
|
+
Float32Array: { type: "string", format: "byte" },
|
|
518
|
+
Float64Array: { type: "string", format: "byte" },
|
|
519
|
+
BigInt64Array: { type: "string", format: "byte" },
|
|
520
|
+
BigUint64Array: { type: "string", format: "byte" }
|
|
521
|
+
};
|
|
395
522
|
var PRIMITIVES2 = new Set([
|
|
396
523
|
"string",
|
|
397
524
|
"number",
|
|
@@ -487,10 +614,10 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
487
614
|
if (isAtMaxDepth(ctx)) {
|
|
488
615
|
return { type: checker.typeToString(type) };
|
|
489
616
|
}
|
|
490
|
-
if (ctx
|
|
617
|
+
if (ctx?.visitedTypes.has(type)) {
|
|
491
618
|
const symbol2 = type.getSymbol() || type.aliasSymbol;
|
|
492
619
|
if (symbol2) {
|
|
493
|
-
return { $ref: symbol2.getName() };
|
|
620
|
+
return { $ref: `#/types/${symbol2.getName()}` };
|
|
494
621
|
}
|
|
495
622
|
return { type: checker.typeToString(type) };
|
|
496
623
|
}
|
|
@@ -574,15 +701,15 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
574
701
|
const elementTypes = typeRef2.typeArguments ?? [];
|
|
575
702
|
if (ctx) {
|
|
576
703
|
return withDepth(ctx, () => ({
|
|
577
|
-
type: "
|
|
578
|
-
|
|
704
|
+
type: "array",
|
|
705
|
+
prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
|
|
579
706
|
minItems: elementTypes.length,
|
|
580
707
|
maxItems: elementTypes.length
|
|
581
708
|
}));
|
|
582
709
|
}
|
|
583
710
|
return {
|
|
584
|
-
type: "
|
|
585
|
-
|
|
711
|
+
type: "array",
|
|
712
|
+
prefixedItems: elementTypes.map((t) => buildSchema(t, checker, ctx)),
|
|
586
713
|
minItems: elementTypes.length,
|
|
587
714
|
maxItems: elementTypes.length
|
|
588
715
|
};
|
|
@@ -591,15 +718,18 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
591
718
|
if (typeRef.target && typeRef.typeArguments && typeRef.typeArguments.length > 0) {
|
|
592
719
|
const symbol2 = typeRef.target.getSymbol();
|
|
593
720
|
const name = symbol2?.getName();
|
|
721
|
+
if (name && BUILTIN_TYPES.has(name)) {
|
|
722
|
+
return { $ref: `#/types/${name}` };
|
|
723
|
+
}
|
|
594
724
|
if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
|
|
595
725
|
if (ctx) {
|
|
596
726
|
return withDepth(ctx, () => ({
|
|
597
|
-
$ref: name
|
|
727
|
+
$ref: `#/types/${name}`,
|
|
598
728
|
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
599
729
|
}));
|
|
600
730
|
}
|
|
601
731
|
return {
|
|
602
|
-
$ref: name
|
|
732
|
+
$ref: `#/types/${name}`,
|
|
603
733
|
typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
|
|
604
734
|
};
|
|
605
735
|
}
|
|
@@ -611,10 +741,10 @@ function buildSchema(type, checker, ctx, _depth = 0) {
|
|
|
611
741
|
return { type: name };
|
|
612
742
|
}
|
|
613
743
|
if (BUILTIN_TYPES.has(name)) {
|
|
614
|
-
return { $ref: name };
|
|
744
|
+
return { $ref: `#/types/${name}` };
|
|
615
745
|
}
|
|
616
746
|
if (!name.startsWith("__")) {
|
|
617
|
-
return { $ref: name };
|
|
747
|
+
return { $ref: `#/types/${name}` };
|
|
618
748
|
}
|
|
619
749
|
}
|
|
620
750
|
if (type.flags & ts4.TypeFlags.Object) {
|
|
@@ -681,6 +811,110 @@ function buildObjectSchema(properties, checker, ctx) {
|
|
|
681
811
|
}
|
|
682
812
|
return buildProps();
|
|
683
813
|
}
|
|
814
|
+
function isPureRefSchema(schema) {
|
|
815
|
+
return typeof schema === "object" && Object.keys(schema).length === 1 && "$ref" in schema;
|
|
816
|
+
}
|
|
817
|
+
function withDescription(schema, description) {
|
|
818
|
+
if (isPureRefSchema(schema)) {
|
|
819
|
+
return {
|
|
820
|
+
allOf: [schema],
|
|
821
|
+
description
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
return { ...schema, description };
|
|
825
|
+
}
|
|
826
|
+
function schemaIsAny(schema) {
|
|
827
|
+
if (typeof schema === "string") {
|
|
828
|
+
return schema === "any";
|
|
829
|
+
}
|
|
830
|
+
if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
function schemasAreEqual(left, right) {
|
|
836
|
+
if (typeof left !== typeof right) {
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
840
|
+
return left === right;
|
|
841
|
+
}
|
|
842
|
+
if (left == null || right == null) {
|
|
843
|
+
return left === right;
|
|
844
|
+
}
|
|
845
|
+
const normalize = (value) => {
|
|
846
|
+
if (Array.isArray(value)) {
|
|
847
|
+
return value.map((item) => normalize(item));
|
|
848
|
+
}
|
|
849
|
+
if (value && typeof value === "object") {
|
|
850
|
+
const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
|
|
851
|
+
return Object.fromEntries(sortedEntries);
|
|
852
|
+
}
|
|
853
|
+
return value;
|
|
854
|
+
};
|
|
855
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
856
|
+
}
|
|
857
|
+
function deduplicateSchemas(schemas) {
|
|
858
|
+
const result = [];
|
|
859
|
+
for (const schema of schemas) {
|
|
860
|
+
const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
|
|
861
|
+
if (!isDuplicate) {
|
|
862
|
+
result.push(schema);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
function findDiscriminatorProperty(unionTypes, checker) {
|
|
868
|
+
const memberProps = [];
|
|
869
|
+
for (const t of unionTypes) {
|
|
870
|
+
if (t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined)) {
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
const props = t.getProperties();
|
|
874
|
+
if (!props || props.length === 0) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const propValues = new Map;
|
|
878
|
+
for (const prop of props) {
|
|
879
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
880
|
+
if (!declaration)
|
|
881
|
+
continue;
|
|
882
|
+
try {
|
|
883
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
884
|
+
if (propType.isStringLiteral()) {
|
|
885
|
+
propValues.set(prop.getName(), propType.value);
|
|
886
|
+
} else if (propType.isNumberLiteral()) {
|
|
887
|
+
propValues.set(prop.getName(), propType.value);
|
|
888
|
+
}
|
|
889
|
+
} catch {}
|
|
890
|
+
}
|
|
891
|
+
memberProps.push(propValues);
|
|
892
|
+
}
|
|
893
|
+
if (memberProps.length < 2) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const firstMember = memberProps[0];
|
|
897
|
+
for (const [propName, firstValue] of firstMember) {
|
|
898
|
+
const values = new Set([firstValue]);
|
|
899
|
+
let isDiscriminator = true;
|
|
900
|
+
for (let i = 1;i < memberProps.length; i++) {
|
|
901
|
+
const value = memberProps[i].get(propName);
|
|
902
|
+
if (value === undefined) {
|
|
903
|
+
isDiscriminator = false;
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
if (values.has(value)) {
|
|
907
|
+
isDiscriminator = false;
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
values.add(value);
|
|
911
|
+
}
|
|
912
|
+
if (isDiscriminator) {
|
|
913
|
+
return propName;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
684
918
|
|
|
685
919
|
// src/types/parameters.ts
|
|
686
920
|
import ts5 from "typescript";
|
|
@@ -801,7 +1035,10 @@ function extractDefaultValue(initializer) {
|
|
|
801
1035
|
function registerReferencedTypes(type, ctx) {
|
|
802
1036
|
if (ctx.visitedTypes.has(type))
|
|
803
1037
|
return;
|
|
804
|
-
|
|
1038
|
+
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);
|
|
1039
|
+
if (!isPrimitive) {
|
|
1040
|
+
ctx.visitedTypes.add(type);
|
|
1041
|
+
}
|
|
805
1042
|
const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
|
|
806
1043
|
typeRegistry.registerType(type, checker, exportedIds);
|
|
807
1044
|
const typeArgs = type.typeArguments;
|
|
@@ -822,6 +1059,280 @@ function registerReferencedTypes(type, ctx) {
|
|
|
822
1059
|
}
|
|
823
1060
|
}
|
|
824
1061
|
|
|
1062
|
+
// src/serializers/classes.ts
|
|
1063
|
+
import ts6 from "typescript";
|
|
1064
|
+
function serializeClass(node, ctx) {
|
|
1065
|
+
const { typeChecker: checker } = ctx;
|
|
1066
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
1067
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1068
|
+
if (!name)
|
|
1069
|
+
return null;
|
|
1070
|
+
const declSourceFile = node.getSourceFile();
|
|
1071
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1072
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1073
|
+
const typeParameters = extractTypeParameters(node, checker);
|
|
1074
|
+
const members = [];
|
|
1075
|
+
const signatures = [];
|
|
1076
|
+
const methodsByName = new Map;
|
|
1077
|
+
for (const member of node.members) {
|
|
1078
|
+
const memberName = getMemberName(member);
|
|
1079
|
+
if (memberName?.startsWith("#"))
|
|
1080
|
+
continue;
|
|
1081
|
+
if (ts6.isPropertyDeclaration(member)) {
|
|
1082
|
+
const propMember = serializeProperty(member, ctx);
|
|
1083
|
+
if (propMember)
|
|
1084
|
+
members.push(propMember);
|
|
1085
|
+
} else if (ts6.isMethodDeclaration(member)) {
|
|
1086
|
+
const methodMember = serializeMethod(member, ctx);
|
|
1087
|
+
if (methodMember?.name) {
|
|
1088
|
+
if (!methodsByName.has(methodMember.name)) {
|
|
1089
|
+
methodsByName.set(methodMember.name, methodMember);
|
|
1090
|
+
} else {
|
|
1091
|
+
const existing = methodsByName.get(methodMember.name);
|
|
1092
|
+
if (!existing.description && methodMember.description) {
|
|
1093
|
+
existing.description = methodMember.description;
|
|
1094
|
+
}
|
|
1095
|
+
if (!existing.tags && methodMember.tags) {
|
|
1096
|
+
existing.tags = methodMember.tags;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
} else if (ts6.isConstructorDeclaration(member)) {
|
|
1101
|
+
const ctorSig = serializeConstructor(member, ctx);
|
|
1102
|
+
if (ctorSig)
|
|
1103
|
+
signatures.push(ctorSig);
|
|
1104
|
+
} else if (ts6.isGetAccessorDeclaration(member) || ts6.isSetAccessorDeclaration(member)) {
|
|
1105
|
+
const accessorMember = serializeAccessor(member, ctx);
|
|
1106
|
+
if (accessorMember)
|
|
1107
|
+
members.push(accessorMember);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
members.push(...methodsByName.values());
|
|
1111
|
+
const extendsClause = getExtendsClause(node, checker);
|
|
1112
|
+
const implementsClause = getImplementsClause(node, checker);
|
|
1113
|
+
return {
|
|
1114
|
+
id: name,
|
|
1115
|
+
name,
|
|
1116
|
+
kind: "class",
|
|
1117
|
+
description,
|
|
1118
|
+
tags,
|
|
1119
|
+
source,
|
|
1120
|
+
typeParameters,
|
|
1121
|
+
members: members.length > 0 ? members : undefined,
|
|
1122
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
1123
|
+
extends: extendsClause,
|
|
1124
|
+
implements: implementsClause?.length ? implementsClause : undefined,
|
|
1125
|
+
...examples.length > 0 ? { examples } : {}
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function getMemberName(member) {
|
|
1129
|
+
if (ts6.isConstructorDeclaration(member))
|
|
1130
|
+
return "constructor";
|
|
1131
|
+
if (!member.name)
|
|
1132
|
+
return;
|
|
1133
|
+
if (ts6.isIdentifier(member.name))
|
|
1134
|
+
return member.name.text;
|
|
1135
|
+
if (ts6.isPrivateIdentifier(member.name))
|
|
1136
|
+
return member.name.text;
|
|
1137
|
+
return member.name.getText();
|
|
1138
|
+
}
|
|
1139
|
+
function getVisibility(member) {
|
|
1140
|
+
const modifiers = ts6.getModifiers(member);
|
|
1141
|
+
if (!modifiers)
|
|
1142
|
+
return;
|
|
1143
|
+
for (const mod of modifiers) {
|
|
1144
|
+
if (mod.kind === ts6.SyntaxKind.PrivateKeyword)
|
|
1145
|
+
return "private";
|
|
1146
|
+
if (mod.kind === ts6.SyntaxKind.ProtectedKeyword)
|
|
1147
|
+
return "protected";
|
|
1148
|
+
if (mod.kind === ts6.SyntaxKind.PublicKeyword)
|
|
1149
|
+
return "public";
|
|
1150
|
+
}
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
function isStatic(member) {
|
|
1154
|
+
const modifiers = ts6.getModifiers(member);
|
|
1155
|
+
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.StaticKeyword) ?? false;
|
|
1156
|
+
}
|
|
1157
|
+
function isReadonly(member) {
|
|
1158
|
+
const modifiers = ts6.getModifiers(member);
|
|
1159
|
+
return modifiers?.some((m) => m.kind === ts6.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
1160
|
+
}
|
|
1161
|
+
function serializeProperty(node, ctx) {
|
|
1162
|
+
const { typeChecker: checker } = ctx;
|
|
1163
|
+
const name = getMemberName(node);
|
|
1164
|
+
if (!name)
|
|
1165
|
+
return null;
|
|
1166
|
+
const { description, tags } = getJSDocComment(node);
|
|
1167
|
+
const visibility = getVisibility(node);
|
|
1168
|
+
const type = checker.getTypeAtLocation(node);
|
|
1169
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1170
|
+
registerReferencedTypes(type, ctx);
|
|
1171
|
+
const flags = {};
|
|
1172
|
+
if (isStatic(node))
|
|
1173
|
+
flags.static = true;
|
|
1174
|
+
if (isReadonly(node))
|
|
1175
|
+
flags.readonly = true;
|
|
1176
|
+
if (node.questionToken)
|
|
1177
|
+
flags.optional = true;
|
|
1178
|
+
return {
|
|
1179
|
+
name,
|
|
1180
|
+
kind: "property",
|
|
1181
|
+
description,
|
|
1182
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1183
|
+
visibility,
|
|
1184
|
+
schema,
|
|
1185
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
function serializeMethod(node, ctx) {
|
|
1189
|
+
const { typeChecker: checker } = ctx;
|
|
1190
|
+
const name = getMemberName(node);
|
|
1191
|
+
if (!name)
|
|
1192
|
+
return null;
|
|
1193
|
+
const { description, tags } = getJSDocComment(node);
|
|
1194
|
+
const visibility = getVisibility(node);
|
|
1195
|
+
const type = checker.getTypeAtLocation(node);
|
|
1196
|
+
const callSignatures = type.getCallSignatures();
|
|
1197
|
+
const signatures = callSignatures.map((sig) => {
|
|
1198
|
+
const params = extractParameters(sig, ctx);
|
|
1199
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1200
|
+
registerReferencedTypes(returnType, ctx);
|
|
1201
|
+
return {
|
|
1202
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1203
|
+
returns: {
|
|
1204
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
});
|
|
1208
|
+
const flags = {};
|
|
1209
|
+
if (isStatic(node))
|
|
1210
|
+
flags.static = true;
|
|
1211
|
+
if (node.asteriskToken)
|
|
1212
|
+
flags.generator = true;
|
|
1213
|
+
const modifiers = ts6.getModifiers(node);
|
|
1214
|
+
if (modifiers?.some((m) => m.kind === ts6.SyntaxKind.AsyncKeyword)) {
|
|
1215
|
+
flags.async = true;
|
|
1216
|
+
}
|
|
1217
|
+
return {
|
|
1218
|
+
name,
|
|
1219
|
+
kind: "method",
|
|
1220
|
+
description,
|
|
1221
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1222
|
+
visibility,
|
|
1223
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
1224
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function serializeConstructor(node, ctx) {
|
|
1228
|
+
const { typeChecker: checker } = ctx;
|
|
1229
|
+
const { description } = getJSDocComment(node);
|
|
1230
|
+
const sig = checker.getSignatureFromDeclaration(node);
|
|
1231
|
+
if (!sig)
|
|
1232
|
+
return null;
|
|
1233
|
+
const params = extractParameters(sig, ctx);
|
|
1234
|
+
return {
|
|
1235
|
+
description,
|
|
1236
|
+
parameters: params.length > 0 ? params : undefined
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
function serializeAccessor(node, ctx) {
|
|
1240
|
+
const { typeChecker: checker } = ctx;
|
|
1241
|
+
const name = getMemberName(node);
|
|
1242
|
+
if (!name)
|
|
1243
|
+
return null;
|
|
1244
|
+
const { description, tags } = getJSDocComment(node);
|
|
1245
|
+
const visibility = getVisibility(node);
|
|
1246
|
+
const type = checker.getTypeAtLocation(node);
|
|
1247
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1248
|
+
registerReferencedTypes(type, ctx);
|
|
1249
|
+
const kind = ts6.isGetAccessorDeclaration(node) ? "getter" : "setter";
|
|
1250
|
+
const flags = {};
|
|
1251
|
+
if (isStatic(node))
|
|
1252
|
+
flags.static = true;
|
|
1253
|
+
return {
|
|
1254
|
+
name,
|
|
1255
|
+
kind,
|
|
1256
|
+
description,
|
|
1257
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1258
|
+
visibility,
|
|
1259
|
+
schema,
|
|
1260
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function getExtendsClause(node, checker) {
|
|
1264
|
+
if (!node.heritageClauses)
|
|
1265
|
+
return;
|
|
1266
|
+
for (const clause of node.heritageClauses) {
|
|
1267
|
+
if (clause.token === ts6.SyntaxKind.ExtendsKeyword) {
|
|
1268
|
+
const expr = clause.types[0];
|
|
1269
|
+
if (expr) {
|
|
1270
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1271
|
+
const symbol = type.getSymbol();
|
|
1272
|
+
return symbol?.getName() ?? expr.expression.getText();
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
function getImplementsClause(node, checker) {
|
|
1279
|
+
if (!node.heritageClauses)
|
|
1280
|
+
return;
|
|
1281
|
+
for (const clause of node.heritageClauses) {
|
|
1282
|
+
if (clause.token === ts6.SyntaxKind.ImplementsKeyword) {
|
|
1283
|
+
return clause.types.map((expr) => {
|
|
1284
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1285
|
+
const symbol = type.getSymbol();
|
|
1286
|
+
return symbol?.getName() ?? expr.expression.getText();
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/serializers/enums.ts
|
|
1294
|
+
function serializeEnum(node, ctx) {
|
|
1295
|
+
const { typeChecker: checker } = ctx;
|
|
1296
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
1297
|
+
const name = symbol?.getName() ?? node.name?.getText();
|
|
1298
|
+
if (!name)
|
|
1299
|
+
return null;
|
|
1300
|
+
const declSourceFile = node.getSourceFile();
|
|
1301
|
+
const { description, tags, examples } = getJSDocComment(node);
|
|
1302
|
+
const source = getSourceLocation(node, declSourceFile);
|
|
1303
|
+
const members = node.members.map((member) => {
|
|
1304
|
+
const memberSymbol = checker.getSymbolAtLocation(member.name);
|
|
1305
|
+
const memberName = memberSymbol?.getName() ?? member.name.getText();
|
|
1306
|
+
const constantValue = checker.getConstantValue(member);
|
|
1307
|
+
let schema;
|
|
1308
|
+
if (typeof constantValue === "string") {
|
|
1309
|
+
schema = { type: "string", enum: [constantValue] };
|
|
1310
|
+
} else if (typeof constantValue === "number") {
|
|
1311
|
+
schema = { type: "number", enum: [constantValue] };
|
|
1312
|
+
} else if (member.initializer) {
|
|
1313
|
+
schema = { type: member.initializer.getText() };
|
|
1314
|
+
}
|
|
1315
|
+
const { description: memberDesc } = getJSDocComment(member);
|
|
1316
|
+
return {
|
|
1317
|
+
id: memberName,
|
|
1318
|
+
name: memberName,
|
|
1319
|
+
kind: "enum-member",
|
|
1320
|
+
...schema ? { schema } : {},
|
|
1321
|
+
...memberDesc ? { description: memberDesc } : {}
|
|
1322
|
+
};
|
|
1323
|
+
});
|
|
1324
|
+
return {
|
|
1325
|
+
id: name,
|
|
1326
|
+
name,
|
|
1327
|
+
kind: "enum",
|
|
1328
|
+
description,
|
|
1329
|
+
tags,
|
|
1330
|
+
source,
|
|
1331
|
+
members,
|
|
1332
|
+
...examples.length > 0 ? { examples } : {}
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
825
1336
|
// src/serializers/functions.ts
|
|
826
1337
|
function serializeFunctionExport(node, ctx) {
|
|
827
1338
|
const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
|
|
@@ -831,6 +1342,7 @@ function serializeFunctionExport(node, ctx) {
|
|
|
831
1342
|
const declSourceFile = node.getSourceFile();
|
|
832
1343
|
const { description, tags, examples } = getJSDocComment(node);
|
|
833
1344
|
const source = getSourceLocation(node, declSourceFile);
|
|
1345
|
+
const typeParameters = extractTypeParameters(node, ctx.typeChecker);
|
|
834
1346
|
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
835
1347
|
const callSignatures = type.getCallSignatures();
|
|
836
1348
|
const signatures = callSignatures.map((sig) => {
|
|
@@ -851,20 +1363,50 @@ function serializeFunctionExport(node, ctx) {
|
|
|
851
1363
|
description,
|
|
852
1364
|
tags,
|
|
853
1365
|
source,
|
|
1366
|
+
typeParameters,
|
|
854
1367
|
signatures,
|
|
855
1368
|
...examples.length > 0 ? { examples } : {}
|
|
856
1369
|
};
|
|
857
1370
|
}
|
|
858
1371
|
|
|
859
1372
|
// src/serializers/interfaces.ts
|
|
1373
|
+
import ts7 from "typescript";
|
|
860
1374
|
function serializeInterface(node, ctx) {
|
|
861
|
-
const
|
|
1375
|
+
const { typeChecker: checker } = ctx;
|
|
1376
|
+
const symbol = checker.getSymbolAtLocation(node.name ?? node);
|
|
862
1377
|
const name = symbol?.getName() ?? node.name?.getText();
|
|
863
1378
|
if (!name)
|
|
864
1379
|
return null;
|
|
865
1380
|
const declSourceFile = node.getSourceFile();
|
|
866
1381
|
const { description, tags, examples } = getJSDocComment(node);
|
|
867
1382
|
const source = getSourceLocation(node, declSourceFile);
|
|
1383
|
+
const typeParameters = extractTypeParameters(node, checker);
|
|
1384
|
+
const members = [];
|
|
1385
|
+
const methodsByName = new Map;
|
|
1386
|
+
for (const member of node.members) {
|
|
1387
|
+
if (ts7.isPropertySignature(member)) {
|
|
1388
|
+
const propMember = serializePropertySignature(member, ctx);
|
|
1389
|
+
if (propMember)
|
|
1390
|
+
members.push(propMember);
|
|
1391
|
+
} else if (ts7.isMethodSignature(member)) {
|
|
1392
|
+
const methodMember = serializeMethodSignature(member, ctx);
|
|
1393
|
+
if (methodMember?.name) {
|
|
1394
|
+
if (!methodsByName.has(methodMember.name)) {
|
|
1395
|
+
methodsByName.set(methodMember.name, methodMember);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
} else if (ts7.isCallSignatureDeclaration(member)) {
|
|
1399
|
+
const callMember = serializeCallSignature(member, ctx);
|
|
1400
|
+
if (callMember)
|
|
1401
|
+
members.push(callMember);
|
|
1402
|
+
} else if (ts7.isIndexSignatureDeclaration(member)) {
|
|
1403
|
+
const indexMember = serializeIndexSignature(member, ctx);
|
|
1404
|
+
if (indexMember)
|
|
1405
|
+
members.push(indexMember);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
members.push(...methodsByName.values());
|
|
1409
|
+
const extendsClause = getInterfaceExtends(node, checker);
|
|
868
1410
|
return {
|
|
869
1411
|
id: name,
|
|
870
1412
|
name,
|
|
@@ -872,10 +1414,121 @@ function serializeInterface(node, ctx) {
|
|
|
872
1414
|
description,
|
|
873
1415
|
tags,
|
|
874
1416
|
source,
|
|
875
|
-
|
|
1417
|
+
typeParameters,
|
|
1418
|
+
members: members.length > 0 ? members : undefined,
|
|
1419
|
+
extends: extendsClause,
|
|
876
1420
|
...examples.length > 0 ? { examples } : {}
|
|
877
1421
|
};
|
|
878
1422
|
}
|
|
1423
|
+
function serializePropertySignature(node, ctx) {
|
|
1424
|
+
const { typeChecker: checker } = ctx;
|
|
1425
|
+
const name = node.name.getText();
|
|
1426
|
+
const { description, tags } = getJSDocComment(node);
|
|
1427
|
+
const type = checker.getTypeAtLocation(node);
|
|
1428
|
+
const schema = buildSchema(type, checker, ctx);
|
|
1429
|
+
registerReferencedTypes(type, ctx);
|
|
1430
|
+
const flags = {};
|
|
1431
|
+
if (node.questionToken)
|
|
1432
|
+
flags.optional = true;
|
|
1433
|
+
if (node.modifiers?.some((m) => m.kind === ts7.SyntaxKind.ReadonlyKeyword)) {
|
|
1434
|
+
flags.readonly = true;
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
name,
|
|
1438
|
+
kind: "property",
|
|
1439
|
+
description,
|
|
1440
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1441
|
+
schema,
|
|
1442
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
function serializeMethodSignature(node, ctx) {
|
|
1446
|
+
const { typeChecker: checker } = ctx;
|
|
1447
|
+
const name = node.name.getText();
|
|
1448
|
+
const { description, tags } = getJSDocComment(node);
|
|
1449
|
+
const type = checker.getTypeAtLocation(node);
|
|
1450
|
+
const callSignatures = type.getCallSignatures();
|
|
1451
|
+
const signatures = callSignatures.map((sig) => {
|
|
1452
|
+
const params = extractParameters(sig, ctx);
|
|
1453
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1454
|
+
registerReferencedTypes(returnType, ctx);
|
|
1455
|
+
return {
|
|
1456
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1457
|
+
returns: {
|
|
1458
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
});
|
|
1462
|
+
const flags = {};
|
|
1463
|
+
if (node.questionToken)
|
|
1464
|
+
flags.optional = true;
|
|
1465
|
+
return {
|
|
1466
|
+
name,
|
|
1467
|
+
kind: "method",
|
|
1468
|
+
description,
|
|
1469
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1470
|
+
signatures: signatures.length > 0 ? signatures : undefined,
|
|
1471
|
+
flags: Object.keys(flags).length > 0 ? flags : undefined
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function serializeCallSignature(node, ctx) {
|
|
1475
|
+
const { typeChecker: checker } = ctx;
|
|
1476
|
+
const { description, tags } = getJSDocComment(node);
|
|
1477
|
+
const sig = checker.getSignatureFromDeclaration(node);
|
|
1478
|
+
if (!sig)
|
|
1479
|
+
return null;
|
|
1480
|
+
const params = extractParameters(sig, ctx);
|
|
1481
|
+
const returnType = checker.getReturnTypeOfSignature(sig);
|
|
1482
|
+
registerReferencedTypes(returnType, ctx);
|
|
1483
|
+
return {
|
|
1484
|
+
name: "()",
|
|
1485
|
+
kind: "call-signature",
|
|
1486
|
+
description,
|
|
1487
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1488
|
+
signatures: [
|
|
1489
|
+
{
|
|
1490
|
+
parameters: params.length > 0 ? params : undefined,
|
|
1491
|
+
returns: {
|
|
1492
|
+
schema: buildSchema(returnType, checker, ctx)
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
]
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
function serializeIndexSignature(node, ctx) {
|
|
1499
|
+
const { typeChecker: checker } = ctx;
|
|
1500
|
+
const { description, tags } = getJSDocComment(node);
|
|
1501
|
+
const valueType = node.type ? checker.getTypeAtLocation(node.type) : checker.getAnyType();
|
|
1502
|
+
const valueSchema = buildSchema(valueType, checker, ctx);
|
|
1503
|
+
registerReferencedTypes(valueType, ctx);
|
|
1504
|
+
const keyParam = node.parameters[0];
|
|
1505
|
+
const keyType = keyParam?.type ? checker.getTypeAtLocation(keyParam.type) : checker.getStringType();
|
|
1506
|
+
const keyTypeName = checker.typeToString(keyType);
|
|
1507
|
+
return {
|
|
1508
|
+
name: `[${keyTypeName}]`,
|
|
1509
|
+
kind: "index-signature",
|
|
1510
|
+
description,
|
|
1511
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
1512
|
+
schema: {
|
|
1513
|
+
type: "object",
|
|
1514
|
+
additionalProperties: valueSchema
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
function getInterfaceExtends(node, checker) {
|
|
1519
|
+
if (!node.heritageClauses)
|
|
1520
|
+
return;
|
|
1521
|
+
for (const clause of node.heritageClauses) {
|
|
1522
|
+
if (clause.token === ts7.SyntaxKind.ExtendsKeyword && clause.types.length > 0) {
|
|
1523
|
+
const names = clause.types.map((expr) => {
|
|
1524
|
+
const type = checker.getTypeAtLocation(expr);
|
|
1525
|
+
return type.getSymbol()?.getName() ?? expr.expression.getText();
|
|
1526
|
+
});
|
|
1527
|
+
return names.length === 1 ? names[0] : names;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
879
1532
|
|
|
880
1533
|
// src/serializers/type-aliases.ts
|
|
881
1534
|
function serializeTypeAlias(node, ctx) {
|
|
@@ -886,7 +1539,9 @@ function serializeTypeAlias(node, ctx) {
|
|
|
886
1539
|
const declSourceFile = node.getSourceFile();
|
|
887
1540
|
const { description, tags, examples } = getJSDocComment(node);
|
|
888
1541
|
const source = getSourceLocation(node, declSourceFile);
|
|
1542
|
+
const typeParameters = extractTypeParameters(node, ctx.typeChecker);
|
|
889
1543
|
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
1544
|
+
const schema = buildSchema(type, ctx.typeChecker, ctx);
|
|
890
1545
|
registerReferencedTypes(type, ctx);
|
|
891
1546
|
return {
|
|
892
1547
|
id: name,
|
|
@@ -895,7 +1550,8 @@ function serializeTypeAlias(node, ctx) {
|
|
|
895
1550
|
description,
|
|
896
1551
|
tags,
|
|
897
1552
|
source,
|
|
898
|
-
|
|
1553
|
+
typeParameters,
|
|
1554
|
+
schema,
|
|
899
1555
|
...examples.length > 0 ? { examples } : {}
|
|
900
1556
|
};
|
|
901
1557
|
}
|
|
@@ -910,6 +1566,7 @@ function serializeVariable(node, statement, ctx) {
|
|
|
910
1566
|
const { description, tags, examples } = getJSDocComment(statement);
|
|
911
1567
|
const source = getSourceLocation(node, declSourceFile);
|
|
912
1568
|
const type = ctx.typeChecker.getTypeAtLocation(node);
|
|
1569
|
+
const schema = buildSchema(type, ctx.typeChecker, ctx);
|
|
913
1570
|
registerReferencedTypes(type, ctx);
|
|
914
1571
|
return {
|
|
915
1572
|
id: name,
|
|
@@ -918,7 +1575,7 @@ function serializeVariable(node, statement, ctx) {
|
|
|
918
1575
|
description,
|
|
919
1576
|
tags,
|
|
920
1577
|
source,
|
|
921
|
-
schema
|
|
1578
|
+
schema,
|
|
922
1579
|
...examples.length > 0 ? { examples } : {}
|
|
923
1580
|
};
|
|
924
1581
|
}
|
|
@@ -926,8 +1583,8 @@ function serializeVariable(node, statement, ctx) {
|
|
|
926
1583
|
// src/builder/spec-builder.ts
|
|
927
1584
|
import * as fs2 from "node:fs";
|
|
928
1585
|
import * as path3 from "node:path";
|
|
929
|
-
import { SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
930
|
-
import
|
|
1586
|
+
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
1587
|
+
import ts8 from "typescript";
|
|
931
1588
|
|
|
932
1589
|
// src/serializers/context.ts
|
|
933
1590
|
function createContext(program, sourceFile, options = {}) {
|
|
@@ -946,15 +1603,52 @@ function createContext(program, sourceFile, options = {}) {
|
|
|
946
1603
|
}
|
|
947
1604
|
|
|
948
1605
|
// src/builder/spec-builder.ts
|
|
1606
|
+
var BUILTIN_TYPES2 = new Set([
|
|
1607
|
+
"Array",
|
|
1608
|
+
"Promise",
|
|
1609
|
+
"Map",
|
|
1610
|
+
"Set",
|
|
1611
|
+
"Record",
|
|
1612
|
+
"Partial",
|
|
1613
|
+
"Required",
|
|
1614
|
+
"Pick",
|
|
1615
|
+
"Omit",
|
|
1616
|
+
"Exclude",
|
|
1617
|
+
"Extract",
|
|
1618
|
+
"NonNullable",
|
|
1619
|
+
"Parameters",
|
|
1620
|
+
"ReturnType",
|
|
1621
|
+
"Readonly",
|
|
1622
|
+
"ReadonlyArray",
|
|
1623
|
+
"Awaited",
|
|
1624
|
+
"Date",
|
|
1625
|
+
"RegExp",
|
|
1626
|
+
"Error",
|
|
1627
|
+
"Function",
|
|
1628
|
+
"Object",
|
|
1629
|
+
"String",
|
|
1630
|
+
"Number",
|
|
1631
|
+
"Boolean",
|
|
1632
|
+
"Symbol",
|
|
1633
|
+
"BigInt"
|
|
1634
|
+
]);
|
|
949
1635
|
async function extract(options) {
|
|
950
|
-
const {
|
|
1636
|
+
const {
|
|
1637
|
+
entryFile,
|
|
1638
|
+
baseDir,
|
|
1639
|
+
content,
|
|
1640
|
+
maxTypeDepth,
|
|
1641
|
+
maxExternalTypeDepth,
|
|
1642
|
+
resolveExternalTypes,
|
|
1643
|
+
includeSchema
|
|
1644
|
+
} = options;
|
|
951
1645
|
const diagnostics = [];
|
|
952
1646
|
const exports = [];
|
|
953
1647
|
const result = createProgram({ entryFile, baseDir, content });
|
|
954
1648
|
const { program, sourceFile } = result;
|
|
955
1649
|
if (!sourceFile) {
|
|
956
1650
|
return {
|
|
957
|
-
spec: createEmptySpec(entryFile),
|
|
1651
|
+
spec: createEmptySpec(entryFile, includeSchema),
|
|
958
1652
|
diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
|
|
959
1653
|
};
|
|
960
1654
|
}
|
|
@@ -962,7 +1656,7 @@ async function extract(options) {
|
|
|
962
1656
|
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
963
1657
|
if (!moduleSymbol) {
|
|
964
1658
|
return {
|
|
965
|
-
spec: createEmptySpec(entryFile),
|
|
1659
|
+
spec: createEmptySpec(entryFile, includeSchema),
|
|
966
1660
|
diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
|
|
967
1661
|
};
|
|
968
1662
|
}
|
|
@@ -971,7 +1665,11 @@ async function extract(options) {
|
|
|
971
1665
|
for (const symbol of exportedSymbols) {
|
|
972
1666
|
exportedIds.add(symbol.getName());
|
|
973
1667
|
}
|
|
974
|
-
const ctx = createContext(program, sourceFile, {
|
|
1668
|
+
const ctx = createContext(program, sourceFile, {
|
|
1669
|
+
maxTypeDepth,
|
|
1670
|
+
maxExternalTypeDepth,
|
|
1671
|
+
resolveExternalTypes
|
|
1672
|
+
});
|
|
975
1673
|
ctx.exportedIds = exportedIds;
|
|
976
1674
|
for (const symbol of exportedSymbols) {
|
|
977
1675
|
try {
|
|
@@ -990,11 +1688,31 @@ async function extract(options) {
|
|
|
990
1688
|
}
|
|
991
1689
|
}
|
|
992
1690
|
const meta = await getPackageMeta(entryFile, baseDir);
|
|
1691
|
+
const types = ctx.typeRegistry.getAll();
|
|
1692
|
+
const danglingRefs = collectDanglingRefs(exports, types);
|
|
1693
|
+
for (const ref of danglingRefs) {
|
|
1694
|
+
diagnostics.push({
|
|
1695
|
+
message: `Type '${ref}' is referenced but not defined in types[].`,
|
|
1696
|
+
severity: "warning",
|
|
1697
|
+
code: "DANGLING_REF",
|
|
1698
|
+
suggestion: "The type may be from an external package. Check import paths."
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
const externalTypes = types.filter((t) => t.kind === "external");
|
|
1702
|
+
if (externalTypes.length > 0) {
|
|
1703
|
+
diagnostics.push({
|
|
1704
|
+
message: `${externalTypes.length} external type(s) could not be fully resolved: ${externalTypes.slice(0, 5).map((t) => t.id).join(", ")}${externalTypes.length > 5 ? "..." : ""}`,
|
|
1705
|
+
severity: "warning",
|
|
1706
|
+
code: "EXTERNAL_TYPE_STUBS",
|
|
1707
|
+
suggestion: "Types are from external packages. Full resolution requires type declarations."
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
993
1710
|
const spec = {
|
|
1711
|
+
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
994
1712
|
openpkg: SCHEMA_VERSION,
|
|
995
1713
|
meta,
|
|
996
1714
|
exports,
|
|
997
|
-
types
|
|
1715
|
+
types,
|
|
998
1716
|
generation: {
|
|
999
1717
|
generator: "@openpkg-ts/extract",
|
|
1000
1718
|
timestamp: new Date().toISOString()
|
|
@@ -1002,46 +1720,72 @@ async function extract(options) {
|
|
|
1002
1720
|
};
|
|
1003
1721
|
return { spec, diagnostics };
|
|
1004
1722
|
}
|
|
1723
|
+
function collectAllRefs(obj, refs) {
|
|
1724
|
+
if (obj === null || obj === undefined)
|
|
1725
|
+
return;
|
|
1726
|
+
if (Array.isArray(obj)) {
|
|
1727
|
+
for (const item of obj) {
|
|
1728
|
+
collectAllRefs(item, refs);
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
if (typeof obj === "object") {
|
|
1733
|
+
const record = obj;
|
|
1734
|
+
if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
|
|
1735
|
+
refs.add(record.$ref.slice("#/types/".length));
|
|
1736
|
+
}
|
|
1737
|
+
for (const value of Object.values(record)) {
|
|
1738
|
+
collectAllRefs(value, refs);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function collectDanglingRefs(exports, types) {
|
|
1743
|
+
const definedTypes = new Set(types.map((t) => t.id));
|
|
1744
|
+
const referencedTypes = new Set;
|
|
1745
|
+
collectAllRefs(exports, referencedTypes);
|
|
1746
|
+
collectAllRefs(types, referencedTypes);
|
|
1747
|
+
return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref) && !BUILTIN_TYPES2.has(ref));
|
|
1748
|
+
}
|
|
1005
1749
|
function resolveExportTarget(symbol, checker) {
|
|
1006
1750
|
let targetSymbol = symbol;
|
|
1007
|
-
if (symbol.flags &
|
|
1751
|
+
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
1008
1752
|
const aliasTarget = checker.getAliasedSymbol(symbol);
|
|
1009
1753
|
if (aliasTarget && aliasTarget !== symbol) {
|
|
1010
1754
|
targetSymbol = aliasTarget;
|
|
1011
1755
|
}
|
|
1012
1756
|
}
|
|
1013
1757
|
const declarations = targetSymbol.declarations ?? [];
|
|
1014
|
-
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !==
|
|
1758
|
+
const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts8.SyntaxKind.ExportSpecifier) || declarations[0];
|
|
1015
1759
|
return { declaration, targetSymbol };
|
|
1016
1760
|
}
|
|
1017
|
-
function serializeDeclaration(declaration, exportSymbol,
|
|
1761
|
+
function serializeDeclaration(declaration, exportSymbol, _targetSymbol, exportName, ctx) {
|
|
1018
1762
|
let result = null;
|
|
1019
|
-
if (
|
|
1763
|
+
if (ts8.isFunctionDeclaration(declaration)) {
|
|
1020
1764
|
result = serializeFunctionExport(declaration, ctx);
|
|
1021
|
-
} else if (
|
|
1765
|
+
} else if (ts8.isClassDeclaration(declaration)) {
|
|
1022
1766
|
result = serializeClass(declaration, ctx);
|
|
1023
|
-
} else if (
|
|
1767
|
+
} else if (ts8.isInterfaceDeclaration(declaration)) {
|
|
1024
1768
|
result = serializeInterface(declaration, ctx);
|
|
1025
|
-
} else if (
|
|
1769
|
+
} else if (ts8.isTypeAliasDeclaration(declaration)) {
|
|
1026
1770
|
result = serializeTypeAlias(declaration, ctx);
|
|
1027
|
-
} else if (
|
|
1771
|
+
} else if (ts8.isEnumDeclaration(declaration)) {
|
|
1028
1772
|
result = serializeEnum(declaration, ctx);
|
|
1029
|
-
} else if (
|
|
1773
|
+
} else if (ts8.isVariableDeclaration(declaration)) {
|
|
1030
1774
|
const varStatement = declaration.parent?.parent;
|
|
1031
|
-
if (varStatement &&
|
|
1775
|
+
if (varStatement && ts8.isVariableStatement(varStatement)) {
|
|
1032
1776
|
result = serializeVariable(declaration, varStatement, ctx);
|
|
1033
1777
|
}
|
|
1034
|
-
} else if (
|
|
1035
|
-
result = serializeNamespaceExport(exportSymbol, exportName
|
|
1036
|
-
} else if (
|
|
1037
|
-
result = serializeNamespaceExport(exportSymbol, exportName
|
|
1778
|
+
} else if (ts8.isNamespaceExport(declaration) || ts8.isModuleDeclaration(declaration)) {
|
|
1779
|
+
result = serializeNamespaceExport(exportSymbol, exportName);
|
|
1780
|
+
} else if (ts8.isSourceFile(declaration)) {
|
|
1781
|
+
result = serializeNamespaceExport(exportSymbol, exportName);
|
|
1038
1782
|
}
|
|
1039
1783
|
if (result) {
|
|
1040
1784
|
result = withExportName(result, exportName);
|
|
1041
1785
|
}
|
|
1042
1786
|
return result;
|
|
1043
1787
|
}
|
|
1044
|
-
function serializeNamespaceExport(symbol, exportName
|
|
1788
|
+
function serializeNamespaceExport(symbol, exportName) {
|
|
1045
1789
|
const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
|
|
1046
1790
|
return {
|
|
1047
1791
|
id: exportName,
|
|
@@ -1057,11 +1801,11 @@ function getJSDocFromExportSymbol(symbol) {
|
|
|
1057
1801
|
const examples = [];
|
|
1058
1802
|
const decl = symbol.declarations?.[0];
|
|
1059
1803
|
if (decl) {
|
|
1060
|
-
const exportDecl =
|
|
1061
|
-
if (exportDecl &&
|
|
1062
|
-
const jsDocs =
|
|
1804
|
+
const exportDecl = ts8.isNamespaceExport(decl) ? decl.parent : decl;
|
|
1805
|
+
if (exportDecl && ts8.isExportDeclaration(exportDecl)) {
|
|
1806
|
+
const jsDocs = ts8.getJSDocCommentsAndTags(exportDecl);
|
|
1063
1807
|
for (const doc of jsDocs) {
|
|
1064
|
-
if (
|
|
1808
|
+
if (ts8.isJSDoc(doc) && doc.comment) {
|
|
1065
1809
|
const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
|
|
1066
1810
|
if (commentText) {
|
|
1067
1811
|
return {
|
|
@@ -1119,8 +1863,9 @@ function withExportName(entry, exportName) {
|
|
|
1119
1863
|
name: entry.name
|
|
1120
1864
|
};
|
|
1121
1865
|
}
|
|
1122
|
-
function createEmptySpec(entryFile) {
|
|
1866
|
+
function createEmptySpec(entryFile, includeSchema) {
|
|
1123
1867
|
return {
|
|
1868
|
+
...includeSchema ? { $schema: SCHEMA_URL } : {},
|
|
1124
1869
|
openpkg: SCHEMA_VERSION,
|
|
1125
1870
|
meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
|
|
1126
1871
|
exports: [],
|
|
@@ -1145,4 +1890,4 @@ async function getPackageMeta(entryFile, baseDir) {
|
|
|
1145
1890
|
} catch {}
|
|
1146
1891
|
return { name: path3.basename(searchDir) };
|
|
1147
1892
|
}
|
|
1148
|
-
export { TypeRegistry, getJSDocComment, getSourceLocation,
|
|
1893
|
+
export { TypeRegistry, getJSDocComment, getSourceLocation, getParamDescription, extractTypeParameters, isSymbolDeprecated, createProgram, BUILTIN_TYPE_SCHEMAS, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, isPureRefSchema, withDescription, schemaIsAny, schemasAreEqual, deduplicateSchemas, findDiscriminatorProperty, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
|