@openpkg-ts/extract 0.11.3 → 0.12.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.
@@ -0,0 +1,1148 @@
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 typeString = checker.typeToString(type);
207
+ const source = decl ? buildExternalSource(decl) : undefined;
208
+ return {
209
+ id: name,
210
+ name,
211
+ kind,
212
+ type: typeString !== name ? typeString : undefined,
213
+ ...external ? { external: true } : {},
214
+ ...source ? { source } : {}
215
+ };
216
+ }
217
+ registerFromSymbol(symbol, checker) {
218
+ const name = symbol.getName();
219
+ if (this.has(name))
220
+ return this.get(name);
221
+ const type = checker.getDeclaredTypeOfSymbol(symbol);
222
+ const specType = this.buildSpecType(symbol, type, checker);
223
+ if (specType) {
224
+ this.add(specType);
225
+ return specType;
226
+ }
227
+ return;
228
+ }
229
+ }
230
+
231
+ // src/ast/utils.ts
232
+ import ts2 from "typescript";
233
+ function parseExamplesFromTags(tags) {
234
+ const examples = [];
235
+ for (const tag of tags) {
236
+ if (tag.name !== "example")
237
+ continue;
238
+ const text = tag.text.trim();
239
+ const fenceMatch = text.match(/^```(\w*)\n([\s\S]*?)\n?```$/);
240
+ if (fenceMatch) {
241
+ const lang = fenceMatch[1] || undefined;
242
+ const code = fenceMatch[2].trim();
243
+ const example = { code };
244
+ if (lang && ["ts", "js", "tsx", "jsx", "shell", "json"].includes(lang)) {
245
+ example.language = lang;
246
+ }
247
+ examples.push(example);
248
+ } else if (text) {
249
+ examples.push({ code: text });
250
+ }
251
+ }
252
+ return examples;
253
+ }
254
+ function getJSDocComment(node) {
255
+ const jsDocTags = ts2.getJSDocTags(node);
256
+ const tags = jsDocTags.map((tag) => ({
257
+ name: tag.tagName.text,
258
+ text: typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment) ?? ""
259
+ }));
260
+ const jsDocComments = node.jsDoc;
261
+ let description;
262
+ if (jsDocComments && jsDocComments.length > 0) {
263
+ const firstDoc = jsDocComments[0];
264
+ if (firstDoc.comment) {
265
+ description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts2.getTextOfJSDocComment(firstDoc.comment);
266
+ }
267
+ }
268
+ const examples = parseExamplesFromTags(tags);
269
+ return { description, tags, examples };
270
+ }
271
+ function getSourceLocation(node, sourceFile) {
272
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
273
+ return {
274
+ file: sourceFile.fileName,
275
+ line: line + 1
276
+ };
277
+ }
278
+ function getParamDescription(propertyName, jsdocTags, inferredAlias) {
279
+ for (const tag of jsdocTags) {
280
+ if (tag.tagName.text !== "param")
281
+ continue;
282
+ const paramTag = tag;
283
+ const tagParamName = paramTag.name?.getText() ?? "";
284
+ const isMatch = tagParamName === propertyName || inferredAlias && tagParamName === `${inferredAlias}.${propertyName}` || tagParamName.endsWith(`.${propertyName}`);
285
+ if (isMatch) {
286
+ const comment = typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment);
287
+ return comment?.trim() || undefined;
288
+ }
289
+ }
290
+ return;
291
+ }
292
+
293
+ // src/compiler/program.ts
294
+ import * as path2 from "node:path";
295
+ import ts3 from "typescript";
296
+ var DEFAULT_COMPILER_OPTIONS = {
297
+ target: ts3.ScriptTarget.Latest,
298
+ module: ts3.ModuleKind.CommonJS,
299
+ lib: ["lib.es2021.d.ts"],
300
+ declaration: true,
301
+ moduleResolution: ts3.ModuleResolutionKind.NodeJs
302
+ };
303
+ function createProgram({
304
+ entryFile,
305
+ baseDir = path2.dirname(entryFile),
306
+ content
307
+ }) {
308
+ const configPath = ts3.findConfigFile(baseDir, ts3.sys.fileExists, "tsconfig.json");
309
+ let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
310
+ if (configPath) {
311
+ const configFile = ts3.readConfigFile(configPath, ts3.sys.readFile);
312
+ const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path2.dirname(configPath));
313
+ compilerOptions = { ...compilerOptions, ...parsedConfig.options };
314
+ }
315
+ const allowJsVal = compilerOptions.allowJs;
316
+ if (typeof allowJsVal === "boolean" && allowJsVal) {
317
+ compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
318
+ }
319
+ const compilerHost = ts3.createCompilerHost(compilerOptions, true);
320
+ let inMemorySource;
321
+ if (content !== undefined) {
322
+ inMemorySource = ts3.createSourceFile(entryFile, content, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
323
+ const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
324
+ compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
325
+ if (fileName === entryFile) {
326
+ return inMemorySource;
327
+ }
328
+ return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
329
+ };
330
+ }
331
+ const program = ts3.createProgram([entryFile], compilerOptions, compilerHost);
332
+ const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
333
+ return {
334
+ program,
335
+ compilerHost,
336
+ compilerOptions,
337
+ sourceFile,
338
+ configPath
339
+ };
340
+ }
341
+
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
+ // src/types/schema-builder.ts
394
+ import ts4 from "typescript";
395
+ var PRIMITIVES2 = new Set([
396
+ "string",
397
+ "number",
398
+ "boolean",
399
+ "void",
400
+ "undefined",
401
+ "null",
402
+ "any",
403
+ "unknown",
404
+ "never",
405
+ "object",
406
+ "symbol",
407
+ "bigint"
408
+ ]);
409
+ var BUILTIN_GENERICS = new Set([
410
+ "Array",
411
+ "ReadonlyArray",
412
+ "Promise",
413
+ "PromiseLike",
414
+ "Map",
415
+ "Set",
416
+ "WeakMap",
417
+ "WeakSet",
418
+ "Iterable",
419
+ "Iterator",
420
+ "IterableIterator",
421
+ "AsyncIterable",
422
+ "AsyncIterator",
423
+ "AsyncIterableIterator",
424
+ "Generator",
425
+ "AsyncGenerator",
426
+ "Partial",
427
+ "Required",
428
+ "Readonly",
429
+ "Pick",
430
+ "Omit",
431
+ "Record",
432
+ "Exclude",
433
+ "Extract",
434
+ "NonNullable",
435
+ "Parameters",
436
+ "ReturnType",
437
+ "ConstructorParameters",
438
+ "InstanceType",
439
+ "Awaited"
440
+ ]);
441
+ var BUILTIN_TYPES = new Set([
442
+ "Date",
443
+ "RegExp",
444
+ "Error",
445
+ "Function",
446
+ "ArrayBuffer",
447
+ "SharedArrayBuffer",
448
+ "DataView",
449
+ "Uint8Array",
450
+ "Int8Array",
451
+ "Uint16Array",
452
+ "Int16Array",
453
+ "Uint32Array",
454
+ "Int32Array",
455
+ "Float32Array",
456
+ "Float64Array",
457
+ "BigInt64Array",
458
+ "BigUint64Array"
459
+ ]);
460
+ function isPrimitiveName(name) {
461
+ return PRIMITIVES2.has(name);
462
+ }
463
+ function isBuiltinGeneric(name) {
464
+ return BUILTIN_GENERICS.has(name);
465
+ }
466
+ function isAnonymous(type) {
467
+ const symbol = type.getSymbol() || type.aliasSymbol;
468
+ if (!symbol)
469
+ return true;
470
+ const name = symbol.getName();
471
+ return name.startsWith("__") || name === "";
472
+ }
473
+ function withDepth(ctx, fn) {
474
+ ctx.currentDepth++;
475
+ try {
476
+ return fn();
477
+ } finally {
478
+ ctx.currentDepth--;
479
+ }
480
+ }
481
+ function isAtMaxDepth(ctx) {
482
+ if (!ctx)
483
+ return false;
484
+ return ctx.currentDepth >= ctx.maxTypeDepth;
485
+ }
486
+ function buildSchema(type, checker, ctx, _depth = 0) {
487
+ if (isAtMaxDepth(ctx)) {
488
+ return { type: checker.typeToString(type) };
489
+ }
490
+ if (ctx && ctx.visitedTypes.has(type)) {
491
+ const symbol2 = type.getSymbol() || type.aliasSymbol;
492
+ if (symbol2) {
493
+ return { $ref: symbol2.getName() };
494
+ }
495
+ return { type: checker.typeToString(type) };
496
+ }
497
+ if (type.flags & ts4.TypeFlags.String)
498
+ return { type: "string" };
499
+ if (type.flags & ts4.TypeFlags.Number)
500
+ return { type: "number" };
501
+ if (type.flags & ts4.TypeFlags.Boolean)
502
+ return { type: "boolean" };
503
+ if (type.flags & ts4.TypeFlags.Undefined)
504
+ return { type: "undefined" };
505
+ if (type.flags & ts4.TypeFlags.Null)
506
+ return { type: "null" };
507
+ if (type.flags & ts4.TypeFlags.Void)
508
+ return { type: "void" };
509
+ if (type.flags & ts4.TypeFlags.Any)
510
+ return { type: "any" };
511
+ if (type.flags & ts4.TypeFlags.Unknown)
512
+ return { type: "unknown" };
513
+ if (type.flags & ts4.TypeFlags.Never)
514
+ return { type: "never" };
515
+ if (type.flags & ts4.TypeFlags.BigInt)
516
+ return { type: "bigint" };
517
+ if (type.flags & ts4.TypeFlags.ESSymbol)
518
+ return { type: "symbol" };
519
+ if (type.flags & ts4.TypeFlags.StringLiteral) {
520
+ const literal = type.value;
521
+ return { type: "string", enum: [literal] };
522
+ }
523
+ if (type.flags & ts4.TypeFlags.NumberLiteral) {
524
+ const literal = type.value;
525
+ return { type: "number", enum: [literal] };
526
+ }
527
+ if (type.flags & ts4.TypeFlags.BooleanLiteral) {
528
+ const intrinsicName = type.intrinsicName;
529
+ return { type: "boolean", enum: [intrinsicName === "true"] };
530
+ }
531
+ if (type.isUnion()) {
532
+ const types = type.types;
533
+ const allStringLiterals = types.every((t) => t.flags & ts4.TypeFlags.StringLiteral);
534
+ if (allStringLiterals) {
535
+ const enumValues = types.map((t) => t.value);
536
+ return { type: "string", enum: enumValues };
537
+ }
538
+ const allNumberLiterals = types.every((t) => t.flags & ts4.TypeFlags.NumberLiteral);
539
+ if (allNumberLiterals) {
540
+ const enumValues = types.map((t) => t.value);
541
+ return { type: "number", enum: enumValues };
542
+ }
543
+ if (ctx) {
544
+ return withDepth(ctx, () => ({
545
+ anyOf: types.map((t) => buildSchema(t, checker, ctx))
546
+ }));
547
+ }
548
+ return { anyOf: types.map((t) => buildSchema(t, checker, ctx)) };
549
+ }
550
+ if (type.isIntersection()) {
551
+ if (ctx) {
552
+ return withDepth(ctx, () => ({
553
+ allOf: type.types.map((t) => buildSchema(t, checker, ctx))
554
+ }));
555
+ }
556
+ return { allOf: type.types.map((t) => buildSchema(t, checker, ctx)) };
557
+ }
558
+ if (checker.isArrayType(type)) {
559
+ const typeRef2 = type;
560
+ const elementType = typeRef2.typeArguments?.[0];
561
+ if (elementType) {
562
+ if (ctx) {
563
+ return withDepth(ctx, () => ({
564
+ type: "array",
565
+ items: buildSchema(elementType, checker, ctx)
566
+ }));
567
+ }
568
+ return { type: "array", items: buildSchema(elementType, checker, ctx) };
569
+ }
570
+ return { type: "array" };
571
+ }
572
+ if (checker.isTupleType(type)) {
573
+ const typeRef2 = type;
574
+ const elementTypes = typeRef2.typeArguments ?? [];
575
+ if (ctx) {
576
+ return withDepth(ctx, () => ({
577
+ type: "tuple",
578
+ items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
579
+ minItems: elementTypes.length,
580
+ maxItems: elementTypes.length
581
+ }));
582
+ }
583
+ return {
584
+ type: "tuple",
585
+ items: elementTypes.map((t) => buildSchema(t, checker, ctx)),
586
+ minItems: elementTypes.length,
587
+ maxItems: elementTypes.length
588
+ };
589
+ }
590
+ const typeRef = type;
591
+ if (typeRef.target && typeRef.typeArguments && typeRef.typeArguments.length > 0) {
592
+ const symbol2 = typeRef.target.getSymbol();
593
+ const name = symbol2?.getName();
594
+ if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
595
+ if (ctx) {
596
+ return withDepth(ctx, () => ({
597
+ $ref: name,
598
+ typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
599
+ }));
600
+ }
601
+ return {
602
+ $ref: name,
603
+ typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
604
+ };
605
+ }
606
+ }
607
+ const symbol = type.getSymbol() || type.aliasSymbol;
608
+ if (symbol && !isAnonymous(type)) {
609
+ const name = symbol.getName();
610
+ if (isPrimitiveName(name)) {
611
+ return { type: name };
612
+ }
613
+ if (BUILTIN_TYPES.has(name)) {
614
+ return { $ref: name };
615
+ }
616
+ if (!name.startsWith("__")) {
617
+ return { $ref: name };
618
+ }
619
+ }
620
+ if (type.flags & ts4.TypeFlags.Object) {
621
+ const objectType = type;
622
+ const callSignatures = type.getCallSignatures();
623
+ if (callSignatures.length > 0) {
624
+ return buildFunctionSchema(callSignatures, checker, ctx);
625
+ }
626
+ const properties = type.getProperties();
627
+ if (properties.length > 0 || objectType.objectFlags & ts4.ObjectFlags.Anonymous) {
628
+ return buildObjectSchema(properties, checker, ctx);
629
+ }
630
+ }
631
+ return { type: checker.typeToString(type) };
632
+ }
633
+ function buildFunctionSchema(callSignatures, checker, ctx) {
634
+ const buildSignatures = () => {
635
+ const signatures = callSignatures.map((sig) => {
636
+ const params = sig.getParameters().map((param) => {
637
+ const paramType = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
638
+ return {
639
+ name: param.getName(),
640
+ schema: buildSchema(paramType, checker, ctx),
641
+ required: !(param.flags & ts4.SymbolFlags.Optional)
642
+ };
643
+ });
644
+ const returnType = checker.getReturnTypeOfSignature(sig);
645
+ return {
646
+ parameters: params,
647
+ returns: {
648
+ schema: buildSchema(returnType, checker, ctx)
649
+ }
650
+ };
651
+ });
652
+ return signatures;
653
+ };
654
+ if (ctx) {
655
+ return withDepth(ctx, () => ({ type: "function", signatures: buildSignatures() }));
656
+ }
657
+ return { type: "function", signatures: buildSignatures() };
658
+ }
659
+ function buildObjectSchema(properties, checker, ctx) {
660
+ const buildProps = () => {
661
+ const props = {};
662
+ const required = [];
663
+ for (const prop of properties) {
664
+ const propName = prop.getName();
665
+ if (propName.startsWith("_"))
666
+ continue;
667
+ const propType = checker.getTypeOfSymbol(prop);
668
+ props[propName] = buildSchema(propType, checker, ctx);
669
+ if (!(prop.flags & ts4.SymbolFlags.Optional)) {
670
+ required.push(propName);
671
+ }
672
+ }
673
+ return {
674
+ type: "object",
675
+ properties: props,
676
+ ...required.length > 0 ? { required } : {}
677
+ };
678
+ };
679
+ if (ctx) {
680
+ return withDepth(ctx, buildProps);
681
+ }
682
+ return buildProps();
683
+ }
684
+
685
+ // src/types/parameters.ts
686
+ import ts5 from "typescript";
687
+ function extractParameters(signature, ctx) {
688
+ const { typeChecker: checker } = ctx;
689
+ const result = [];
690
+ const signatureDecl = signature.getDeclaration();
691
+ const jsdocTags = signatureDecl ? ts5.getJSDocTags(signatureDecl) : [];
692
+ for (const param of signature.getParameters()) {
693
+ const decl = param.valueDeclaration;
694
+ const type = checker.getTypeOfSymbolAtLocation(param, decl ?? param.valueDeclaration);
695
+ if (decl && ts5.isObjectBindingPattern(decl.name)) {
696
+ const expandedParams = expandBindingPattern(decl, type, jsdocTags, ctx);
697
+ result.push(...expandedParams);
698
+ } else {
699
+ registerReferencedTypes(type, ctx);
700
+ result.push({
701
+ name: param.getName(),
702
+ schema: buildSchema(type, checker, ctx),
703
+ required: !(param.flags & 16777216)
704
+ });
705
+ }
706
+ }
707
+ return result;
708
+ }
709
+ function expandBindingPattern(paramDecl, paramType, jsdocTags, ctx) {
710
+ const { typeChecker: checker } = ctx;
711
+ const result = [];
712
+ const bindingPattern = paramDecl.name;
713
+ const allProperties = getEffectiveProperties(paramType, checker);
714
+ const inferredAlias = inferParamAlias(jsdocTags);
715
+ for (const element of bindingPattern.elements) {
716
+ if (!ts5.isBindingElement(element))
717
+ continue;
718
+ const propertyName = element.propertyName ? ts5.isIdentifier(element.propertyName) ? element.propertyName.text : element.propertyName.getText() : ts5.isIdentifier(element.name) ? element.name.text : element.name.getText();
719
+ const propSymbol = allProperties.get(propertyName);
720
+ if (!propSymbol)
721
+ continue;
722
+ const propType = checker.getTypeOfSymbol(propSymbol);
723
+ registerReferencedTypes(propType, ctx);
724
+ const isOptional = !!(propSymbol.flags & ts5.SymbolFlags.Optional) || element.initializer !== undefined;
725
+ const description = getParamDescription(propertyName, jsdocTags, inferredAlias);
726
+ const param = {
727
+ name: propertyName,
728
+ schema: buildSchema(propType, checker, ctx),
729
+ required: !isOptional
730
+ };
731
+ if (description) {
732
+ param.description = description;
733
+ }
734
+ if (element.initializer) {
735
+ param.default = extractDefaultValue(element.initializer);
736
+ }
737
+ result.push(param);
738
+ }
739
+ return result;
740
+ }
741
+ function getEffectiveProperties(type, _checker) {
742
+ const properties = new Map;
743
+ if (type.isIntersection()) {
744
+ for (const subType of type.types) {
745
+ for (const prop of subType.getProperties()) {
746
+ properties.set(prop.getName(), prop);
747
+ }
748
+ }
749
+ } else {
750
+ for (const prop of type.getProperties()) {
751
+ properties.set(prop.getName(), prop);
752
+ }
753
+ }
754
+ return properties;
755
+ }
756
+ function inferParamAlias(jsdocTags) {
757
+ const prefixes = [];
758
+ for (const tag of jsdocTags) {
759
+ if (tag.tagName.text !== "param")
760
+ continue;
761
+ const tagText = typeof tag.comment === "string" ? tag.comment : ts5.getTextOfJSDocComment(tag.comment) ?? "";
762
+ const paramTag = tag;
763
+ const paramName = paramTag.name?.getText() ?? "";
764
+ if (paramName.includes(".")) {
765
+ const prefix = paramName.split(".")[0];
766
+ if (prefix && !prefix.startsWith("__")) {
767
+ prefixes.push(prefix);
768
+ }
769
+ } else if (tagText.includes(".")) {
770
+ const match = tagText.match(/^(\w+)\./);
771
+ if (match && !match[1].startsWith("__")) {
772
+ prefixes.push(match[1]);
773
+ }
774
+ }
775
+ }
776
+ if (prefixes.length === 0)
777
+ return;
778
+ const counts = new Map;
779
+ for (const p of prefixes)
780
+ counts.set(p, (counts.get(p) ?? 0) + 1);
781
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
782
+ }
783
+ function extractDefaultValue(initializer) {
784
+ if (ts5.isStringLiteral(initializer)) {
785
+ return initializer.text;
786
+ }
787
+ if (ts5.isNumericLiteral(initializer)) {
788
+ return Number(initializer.text);
789
+ }
790
+ if (initializer.kind === ts5.SyntaxKind.TrueKeyword) {
791
+ return true;
792
+ }
793
+ if (initializer.kind === ts5.SyntaxKind.FalseKeyword) {
794
+ return false;
795
+ }
796
+ if (initializer.kind === ts5.SyntaxKind.NullKeyword) {
797
+ return null;
798
+ }
799
+ return initializer.getText();
800
+ }
801
+ function registerReferencedTypes(type, ctx) {
802
+ if (ctx.visitedTypes.has(type))
803
+ return;
804
+ ctx.visitedTypes.add(type);
805
+ const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
806
+ typeRegistry.registerType(type, checker, exportedIds);
807
+ const typeArgs = type.typeArguments;
808
+ if (typeArgs) {
809
+ for (const arg of typeArgs) {
810
+ registerReferencedTypes(arg, ctx);
811
+ }
812
+ }
813
+ if (type.isUnion()) {
814
+ for (const t of type.types) {
815
+ registerReferencedTypes(t, ctx);
816
+ }
817
+ }
818
+ if (type.isIntersection()) {
819
+ for (const t of type.types) {
820
+ registerReferencedTypes(t, ctx);
821
+ }
822
+ }
823
+ }
824
+
825
+ // src/serializers/functions.ts
826
+ function serializeFunctionExport(node, ctx) {
827
+ const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
828
+ const name = symbol?.getName() ?? node.name?.getText();
829
+ if (!name)
830
+ return null;
831
+ const declSourceFile = node.getSourceFile();
832
+ const { description, tags, examples } = getJSDocComment(node);
833
+ const source = getSourceLocation(node, declSourceFile);
834
+ const type = ctx.typeChecker.getTypeAtLocation(node);
835
+ const callSignatures = type.getCallSignatures();
836
+ const signatures = callSignatures.map((sig) => {
837
+ const params = extractParameters(sig, ctx);
838
+ const returnType = ctx.typeChecker.getReturnTypeOfSignature(sig);
839
+ registerReferencedTypes(returnType, ctx);
840
+ return {
841
+ parameters: params,
842
+ returns: {
843
+ schema: buildSchema(returnType, ctx.typeChecker, ctx)
844
+ }
845
+ };
846
+ });
847
+ return {
848
+ id: name,
849
+ name,
850
+ kind: "function",
851
+ description,
852
+ tags,
853
+ source,
854
+ signatures,
855
+ ...examples.length > 0 ? { examples } : {}
856
+ };
857
+ }
858
+
859
+ // src/serializers/interfaces.ts
860
+ function serializeInterface(node, ctx) {
861
+ const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
862
+ const name = symbol?.getName() ?? node.name?.getText();
863
+ if (!name)
864
+ return null;
865
+ const declSourceFile = node.getSourceFile();
866
+ const { description, tags, examples } = getJSDocComment(node);
867
+ const source = getSourceLocation(node, declSourceFile);
868
+ return {
869
+ id: name,
870
+ name,
871
+ kind: "interface",
872
+ description,
873
+ tags,
874
+ source,
875
+ members: [],
876
+ ...examples.length > 0 ? { examples } : {}
877
+ };
878
+ }
879
+
880
+ // src/serializers/type-aliases.ts
881
+ function serializeTypeAlias(node, ctx) {
882
+ const symbol = ctx.typeChecker.getSymbolAtLocation(node.name ?? node);
883
+ const name = symbol?.getName() ?? node.name?.getText();
884
+ if (!name)
885
+ return null;
886
+ const declSourceFile = node.getSourceFile();
887
+ const { description, tags, examples } = getJSDocComment(node);
888
+ const source = getSourceLocation(node, declSourceFile);
889
+ const type = ctx.typeChecker.getTypeAtLocation(node);
890
+ registerReferencedTypes(type, ctx);
891
+ return {
892
+ id: name,
893
+ name,
894
+ kind: "type",
895
+ description,
896
+ tags,
897
+ source,
898
+ schema: buildSchema(type, ctx.typeChecker, ctx),
899
+ ...examples.length > 0 ? { examples } : {}
900
+ };
901
+ }
902
+
903
+ // src/serializers/variables.ts
904
+ function serializeVariable(node, statement, ctx) {
905
+ const symbol = ctx.typeChecker.getSymbolAtLocation(node.name);
906
+ const name = symbol?.getName() ?? node.name.getText();
907
+ if (!name)
908
+ return null;
909
+ const declSourceFile = node.getSourceFile();
910
+ const { description, tags, examples } = getJSDocComment(statement);
911
+ const source = getSourceLocation(node, declSourceFile);
912
+ const type = ctx.typeChecker.getTypeAtLocation(node);
913
+ registerReferencedTypes(type, ctx);
914
+ return {
915
+ id: name,
916
+ name,
917
+ kind: "variable",
918
+ description,
919
+ tags,
920
+ source,
921
+ schema: buildSchema(type, ctx.typeChecker, ctx),
922
+ ...examples.length > 0 ? { examples } : {}
923
+ };
924
+ }
925
+
926
+ // src/builder/spec-builder.ts
927
+ import * as fs2 from "node:fs";
928
+ import * as path3 from "node:path";
929
+ import { SCHEMA_VERSION } from "@openpkg-ts/spec";
930
+ import ts6 from "typescript";
931
+
932
+ // src/serializers/context.ts
933
+ function createContext(program, sourceFile, options = {}) {
934
+ return {
935
+ typeChecker: program.getTypeChecker(),
936
+ program,
937
+ sourceFile,
938
+ maxTypeDepth: options.maxTypeDepth ?? 4,
939
+ maxExternalTypeDepth: options.maxExternalTypeDepth ?? 2,
940
+ currentDepth: 0,
941
+ resolveExternalTypes: options.resolveExternalTypes ?? true,
942
+ typeRegistry: new TypeRegistry,
943
+ exportedIds: new Set,
944
+ visitedTypes: new Set
945
+ };
946
+ }
947
+
948
+ // src/builder/spec-builder.ts
949
+ async function extract(options) {
950
+ const { entryFile, baseDir, content, maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes } = options;
951
+ const diagnostics = [];
952
+ const exports = [];
953
+ const result = createProgram({ entryFile, baseDir, content });
954
+ const { program, sourceFile } = result;
955
+ if (!sourceFile) {
956
+ return {
957
+ spec: createEmptySpec(entryFile),
958
+ diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
959
+ };
960
+ }
961
+ const typeChecker = program.getTypeChecker();
962
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
963
+ if (!moduleSymbol) {
964
+ return {
965
+ spec: createEmptySpec(entryFile),
966
+ diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
967
+ };
968
+ }
969
+ const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
970
+ const exportedIds = new Set;
971
+ for (const symbol of exportedSymbols) {
972
+ exportedIds.add(symbol.getName());
973
+ }
974
+ const ctx = createContext(program, sourceFile, { maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes });
975
+ ctx.exportedIds = exportedIds;
976
+ for (const symbol of exportedSymbols) {
977
+ try {
978
+ const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
979
+ if (!declaration)
980
+ continue;
981
+ const exportName = symbol.getName();
982
+ const exp = serializeDeclaration(declaration, symbol, targetSymbol, exportName, ctx);
983
+ if (exp)
984
+ exports.push(exp);
985
+ } catch (err) {
986
+ diagnostics.push({
987
+ message: `Failed to serialize ${symbol.getName()}: ${err}`,
988
+ severity: "warning"
989
+ });
990
+ }
991
+ }
992
+ const meta = await getPackageMeta(entryFile, baseDir);
993
+ const spec = {
994
+ openpkg: SCHEMA_VERSION,
995
+ meta,
996
+ exports,
997
+ types: ctx.typeRegistry.getAll(),
998
+ generation: {
999
+ generator: "@openpkg-ts/extract",
1000
+ timestamp: new Date().toISOString()
1001
+ }
1002
+ };
1003
+ return { spec, diagnostics };
1004
+ }
1005
+ function resolveExportTarget(symbol, checker) {
1006
+ let targetSymbol = symbol;
1007
+ if (symbol.flags & ts6.SymbolFlags.Alias) {
1008
+ const aliasTarget = checker.getAliasedSymbol(symbol);
1009
+ if (aliasTarget && aliasTarget !== symbol) {
1010
+ targetSymbol = aliasTarget;
1011
+ }
1012
+ }
1013
+ const declarations = targetSymbol.declarations ?? [];
1014
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts6.SyntaxKind.ExportSpecifier) || declarations[0];
1015
+ return { declaration, targetSymbol };
1016
+ }
1017
+ function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
1018
+ let result = null;
1019
+ if (ts6.isFunctionDeclaration(declaration)) {
1020
+ result = serializeFunctionExport(declaration, ctx);
1021
+ } else if (ts6.isClassDeclaration(declaration)) {
1022
+ result = serializeClass(declaration, ctx);
1023
+ } else if (ts6.isInterfaceDeclaration(declaration)) {
1024
+ result = serializeInterface(declaration, ctx);
1025
+ } else if (ts6.isTypeAliasDeclaration(declaration)) {
1026
+ result = serializeTypeAlias(declaration, ctx);
1027
+ } else if (ts6.isEnumDeclaration(declaration)) {
1028
+ result = serializeEnum(declaration, ctx);
1029
+ } else if (ts6.isVariableDeclaration(declaration)) {
1030
+ const varStatement = declaration.parent?.parent;
1031
+ if (varStatement && ts6.isVariableStatement(varStatement)) {
1032
+ result = serializeVariable(declaration, varStatement, ctx);
1033
+ }
1034
+ } else if (ts6.isNamespaceExport(declaration) || ts6.isModuleDeclaration(declaration)) {
1035
+ result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1036
+ } else if (ts6.isSourceFile(declaration)) {
1037
+ result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1038
+ }
1039
+ if (result) {
1040
+ result = withExportName(result, exportName);
1041
+ }
1042
+ return result;
1043
+ }
1044
+ function serializeNamespaceExport(symbol, exportName, ctx) {
1045
+ const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
1046
+ return {
1047
+ id: exportName,
1048
+ name: exportName,
1049
+ kind: "namespace",
1050
+ description,
1051
+ tags,
1052
+ ...examples.length > 0 ? { examples } : {}
1053
+ };
1054
+ }
1055
+ function getJSDocFromExportSymbol(symbol) {
1056
+ const tags = [];
1057
+ const examples = [];
1058
+ const decl = symbol.declarations?.[0];
1059
+ if (decl) {
1060
+ const exportDecl = ts6.isNamespaceExport(decl) ? decl.parent : decl;
1061
+ if (exportDecl && ts6.isExportDeclaration(exportDecl)) {
1062
+ const jsDocs = ts6.getJSDocCommentsAndTags(exportDecl);
1063
+ for (const doc of jsDocs) {
1064
+ if (ts6.isJSDoc(doc) && doc.comment) {
1065
+ const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
1066
+ if (commentText) {
1067
+ return {
1068
+ description: commentText,
1069
+ tags: extractJSDocTags(doc),
1070
+ examples: extractExamples(doc)
1071
+ };
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+ }
1077
+ const docComment = symbol.getDocumentationComment(undefined);
1078
+ const description = docComment.map((c) => c.text).join(`
1079
+ `) || undefined;
1080
+ const jsTags = symbol.getJsDocTags();
1081
+ for (const tag of jsTags) {
1082
+ const text = tag.text?.map((t) => t.text).join("") ?? "";
1083
+ if (tag.name === "example") {
1084
+ examples.push(text);
1085
+ } else {
1086
+ tags.push({ name: tag.name, text });
1087
+ }
1088
+ }
1089
+ return { description, tags, examples };
1090
+ }
1091
+ function extractJSDocTags(doc) {
1092
+ const tags = [];
1093
+ for (const tag of doc.tags ?? []) {
1094
+ if (tag.tagName.text !== "example") {
1095
+ const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
1096
+ tags.push({ name: tag.tagName.text, text });
1097
+ }
1098
+ }
1099
+ return tags;
1100
+ }
1101
+ function extractExamples(doc) {
1102
+ const examples = [];
1103
+ for (const tag of doc.tags ?? []) {
1104
+ if (tag.tagName.text === "example") {
1105
+ const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
1106
+ if (text)
1107
+ examples.push(text);
1108
+ }
1109
+ }
1110
+ return examples;
1111
+ }
1112
+ function withExportName(entry, exportName) {
1113
+ if (entry.name === exportName) {
1114
+ return entry;
1115
+ }
1116
+ return {
1117
+ ...entry,
1118
+ id: exportName,
1119
+ name: entry.name
1120
+ };
1121
+ }
1122
+ function createEmptySpec(entryFile) {
1123
+ return {
1124
+ openpkg: SCHEMA_VERSION,
1125
+ meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
1126
+ exports: [],
1127
+ generation: {
1128
+ generator: "@openpkg-ts/extract",
1129
+ timestamp: new Date().toISOString()
1130
+ }
1131
+ };
1132
+ }
1133
+ async function getPackageMeta(entryFile, baseDir) {
1134
+ const searchDir = baseDir ?? path3.dirname(entryFile);
1135
+ const pkgPath = path3.join(searchDir, "package.json");
1136
+ try {
1137
+ if (fs2.existsSync(pkgPath)) {
1138
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1139
+ return {
1140
+ name: pkg.name ?? path3.basename(searchDir),
1141
+ version: pkg.version,
1142
+ description: pkg.description
1143
+ };
1144
+ }
1145
+ } catch {}
1146
+ return { name: path3.basename(searchDir) };
1147
+ }
1148
+ export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };