@openpkg-ts/extract 0.11.2 → 0.11.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  extract
4
- } from "../shared/chunk-570ne82m.js";
4
+ } from "../shared/chunk-twaykyxs.js";
5
5
 
6
6
  // src/cli/spec.ts
7
7
  import * as fs from "node:fs";
@@ -1,6 +1,95 @@
1
1
  // src/ast/registry.ts
2
+ import ts from "typescript";
3
+ var PRIMITIVES = new Set([
4
+ "string",
5
+ "number",
6
+ "boolean",
7
+ "void",
8
+ "any",
9
+ "undefined",
10
+ "null",
11
+ "never",
12
+ "unknown",
13
+ "object",
14
+ "symbol",
15
+ "bigint"
16
+ ]);
17
+ var BUILTINS = new Set([
18
+ "Array",
19
+ "ArrayBuffer",
20
+ "ArrayBufferLike",
21
+ "ArrayLike",
22
+ "Promise",
23
+ "Map",
24
+ "Set",
25
+ "WeakMap",
26
+ "WeakSet",
27
+ "Date",
28
+ "RegExp",
29
+ "Error",
30
+ "Function",
31
+ "Object",
32
+ "String",
33
+ "Number",
34
+ "Boolean",
35
+ "Symbol",
36
+ "BigInt",
37
+ "Uint8Array",
38
+ "Int8Array",
39
+ "Uint16Array",
40
+ "Int16Array",
41
+ "Uint32Array",
42
+ "Int32Array",
43
+ "Float32Array",
44
+ "Float64Array",
45
+ "BigInt64Array",
46
+ "BigUint64Array",
47
+ "DataView",
48
+ "ReadonlyArray",
49
+ "Readonly",
50
+ "Partial",
51
+ "Required",
52
+ "Pick",
53
+ "Omit",
54
+ "Record",
55
+ "Exclude",
56
+ "Extract",
57
+ "NonNullable",
58
+ "Parameters",
59
+ "ReturnType",
60
+ "ConstructorParameters",
61
+ "InstanceType",
62
+ "ThisType",
63
+ "Awaited",
64
+ "PromiseLike",
65
+ "Iterable",
66
+ "Iterator",
67
+ "IterableIterator",
68
+ "Generator",
69
+ "AsyncGenerator",
70
+ "AsyncIterable",
71
+ "AsyncIterator",
72
+ "AsyncIterableIterator",
73
+ "SharedArrayBuffer",
74
+ "Atomics",
75
+ "JSON",
76
+ "Math",
77
+ "console",
78
+ "globalThis"
79
+ ]);
80
+ function isGenericTypeParameter(name) {
81
+ if (/^[A-Z]$/.test(name))
82
+ return true;
83
+ if (/^T[A-Z]/.test(name))
84
+ return true;
85
+ if (["Key", "Value", "Item", "Element"].includes(name))
86
+ return true;
87
+ return false;
88
+ }
89
+
2
90
  class TypeRegistry {
3
91
  types = new Map;
92
+ processing = new Set;
4
93
  add(type) {
5
94
  this.types.set(type.id, type);
6
95
  }
@@ -13,37 +102,114 @@ class TypeRegistry {
13
102
  getAll() {
14
103
  return Array.from(this.types.values());
15
104
  }
16
- registerFromSymbol(symbol, checker) {
105
+ registerType(type, checker, exportedIds) {
106
+ const symbol = type.getSymbol() || type.aliasSymbol;
107
+ if (!symbol)
108
+ return;
17
109
  const name = symbol.getName();
110
+ if (PRIMITIVES.has(name))
111
+ return;
112
+ if (BUILTINS.has(name))
113
+ return;
114
+ if (name.startsWith("__"))
115
+ return;
116
+ if (symbol.flags & ts.SymbolFlags.EnumMember)
117
+ return;
118
+ if (symbol.flags & ts.SymbolFlags.TypeParameter)
119
+ return;
120
+ if (isGenericTypeParameter(name))
121
+ return;
18
122
  if (this.has(name))
19
- return this.get(name);
20
- const type = {
123
+ return name;
124
+ if (exportedIds.has(name))
125
+ return name;
126
+ if (this.processing.has(name))
127
+ return name;
128
+ this.processing.add(name);
129
+ try {
130
+ const specType = this.buildSpecType(symbol, type, checker);
131
+ if (specType) {
132
+ this.add(specType);
133
+ return specType.id;
134
+ }
135
+ } finally {
136
+ this.processing.delete(name);
137
+ }
138
+ return;
139
+ }
140
+ buildSpecType(symbol, type, checker) {
141
+ const name = symbol.getName();
142
+ const decl = symbol.declarations?.[0];
143
+ let kind = "type";
144
+ if (decl) {
145
+ if (ts.isClassDeclaration(decl))
146
+ kind = "class";
147
+ else if (ts.isInterfaceDeclaration(decl))
148
+ kind = "interface";
149
+ else if (ts.isEnumDeclaration(decl))
150
+ kind = "enum";
151
+ }
152
+ const typeString = checker.typeToString(type);
153
+ return {
21
154
  id: name,
22
155
  name,
23
- kind: "type"
156
+ kind,
157
+ type: typeString !== name ? typeString : undefined
24
158
  };
25
- this.add(type);
26
- return type;
159
+ }
160
+ registerFromSymbol(symbol, checker) {
161
+ const name = symbol.getName();
162
+ if (this.has(name))
163
+ return this.get(name);
164
+ const type = checker.getDeclaredTypeOfSymbol(symbol);
165
+ const specType = this.buildSpecType(symbol, type, checker);
166
+ if (specType) {
167
+ this.add(specType);
168
+ return specType;
169
+ }
170
+ return;
27
171
  }
28
172
  }
29
173
 
30
174
  // src/ast/utils.ts
31
- import ts from "typescript";
175
+ import ts2 from "typescript";
176
+ function parseExamplesFromTags(tags) {
177
+ const examples = [];
178
+ for (const tag of tags) {
179
+ if (tag.name !== "example")
180
+ continue;
181
+ const text = tag.text.trim();
182
+ const fenceMatch = text.match(/^```(\w*)\n([\s\S]*?)\n?```$/);
183
+ if (fenceMatch) {
184
+ const lang = fenceMatch[1] || undefined;
185
+ const code = fenceMatch[2].trim();
186
+ const example = { code };
187
+ if (lang && ["ts", "js", "tsx", "jsx", "shell", "json"].includes(lang)) {
188
+ example.language = lang;
189
+ }
190
+ examples.push(example);
191
+ } else if (text) {
192
+ examples.push({ code: text });
193
+ }
194
+ }
195
+ return examples;
196
+ }
32
197
  function getJSDocComment(node) {
33
- const jsDocTags = ts.getJSDocTags(node);
198
+ const jsDocTags = ts2.getJSDocTags(node);
34
199
  const tags = jsDocTags.map((tag) => ({
35
200
  name: tag.tagName.text,
36
- text: typeof tag.comment === "string" ? tag.comment : ts.getTextOfJSDocComment(tag.comment) ?? ""
201
+ text: typeof tag.comment === "string" ? tag.comment : ts2.getTextOfJSDocComment(tag.comment) ?? ""
37
202
  }));
38
203
  const jsDocComments = node.jsDoc;
39
204
  let description;
40
205
  if (jsDocComments && jsDocComments.length > 0) {
41
206
  const firstDoc = jsDocComments[0];
42
207
  if (firstDoc.comment) {
43
- description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts.getTextOfJSDocComment(firstDoc.comment);
208
+ description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts2.getTextOfJSDocComment(firstDoc.comment);
44
209
  }
45
210
  }
46
- return { description, tags };
211
+ const examples = parseExamplesFromTags(tags);
212
+ return { description, tags, examples };
47
213
  }
48
214
  function getSourceLocation(node, sourceFile) {
49
215
  const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
@@ -55,34 +221,34 @@ function getSourceLocation(node, sourceFile) {
55
221
 
56
222
  // src/compiler/program.ts
57
223
  import * as path from "node:path";
58
- import ts2 from "typescript";
224
+ import ts3 from "typescript";
59
225
  var DEFAULT_COMPILER_OPTIONS = {
60
- target: ts2.ScriptTarget.Latest,
61
- module: ts2.ModuleKind.CommonJS,
226
+ target: ts3.ScriptTarget.Latest,
227
+ module: ts3.ModuleKind.CommonJS,
62
228
  lib: ["lib.es2021.d.ts"],
63
229
  declaration: true,
64
- moduleResolution: ts2.ModuleResolutionKind.NodeJs
230
+ moduleResolution: ts3.ModuleResolutionKind.NodeJs
65
231
  };
66
232
  function createProgram({
67
233
  entryFile,
68
234
  baseDir = path.dirname(entryFile),
69
235
  content
70
236
  }) {
71
- const configPath = ts2.findConfigFile(baseDir, ts2.sys.fileExists, "tsconfig.json");
237
+ const configPath = ts3.findConfigFile(baseDir, ts3.sys.fileExists, "tsconfig.json");
72
238
  let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
73
239
  if (configPath) {
74
- const configFile = ts2.readConfigFile(configPath, ts2.sys.readFile);
75
- const parsedConfig = ts2.parseJsonConfigFileContent(configFile.config, ts2.sys, path.dirname(configPath));
240
+ const configFile = ts3.readConfigFile(configPath, ts3.sys.readFile);
241
+ const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path.dirname(configPath));
76
242
  compilerOptions = { ...compilerOptions, ...parsedConfig.options };
77
243
  }
78
244
  const allowJsVal = compilerOptions.allowJs;
79
245
  if (typeof allowJsVal === "boolean" && allowJsVal) {
80
246
  compilerOptions = { ...compilerOptions, allowJs: false, checkJs: false };
81
247
  }
82
- const compilerHost = ts2.createCompilerHost(compilerOptions, true);
248
+ const compilerHost = ts3.createCompilerHost(compilerOptions, true);
83
249
  let inMemorySource;
84
250
  if (content !== undefined) {
85
- inMemorySource = ts2.createSourceFile(entryFile, content, ts2.ScriptTarget.Latest, true, ts2.ScriptKind.TS);
251
+ inMemorySource = ts3.createSourceFile(entryFile, content, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
86
252
  const originalGetSourceFile = compilerHost.getSourceFile.bind(compilerHost);
87
253
  compilerHost.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
88
254
  if (fileName === entryFile) {
@@ -91,7 +257,7 @@ function createProgram({
91
257
  return originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
92
258
  };
93
259
  }
94
- const program = ts2.createProgram([entryFile], compilerOptions, compilerHost);
260
+ const program = ts3.createProgram([entryFile], compilerOptions, compilerHost);
95
261
  const sourceFile = inMemorySource ?? program.getSourceFile(entryFile);
96
262
  return {
97
263
  program,
@@ -109,7 +275,7 @@ function serializeClass(node, ctx) {
109
275
  if (!name)
110
276
  return null;
111
277
  const declSourceFile = node.getSourceFile();
112
- const { description, tags } = getJSDocComment(node);
278
+ const { description, tags, examples } = getJSDocComment(node);
113
279
  const source = getSourceLocation(node, declSourceFile);
114
280
  return {
115
281
  id: name,
@@ -118,7 +284,8 @@ function serializeClass(node, ctx) {
118
284
  description,
119
285
  tags,
120
286
  source,
121
- members: []
287
+ members: [],
288
+ ...examples.length > 0 ? { examples } : {}
122
289
  };
123
290
  }
124
291
 
@@ -129,7 +296,7 @@ function serializeEnum(node, ctx) {
129
296
  if (!name)
130
297
  return null;
131
298
  const declSourceFile = node.getSourceFile();
132
- const { description, tags } = getJSDocComment(node);
299
+ const { description, tags, examples } = getJSDocComment(node);
133
300
  const source = getSourceLocation(node, declSourceFile);
134
301
  const members = node.members.map((member) => {
135
302
  const memberSymbol = ctx.typeChecker.getSymbolAtLocation(member.name);
@@ -147,14 +314,17 @@ function serializeEnum(node, ctx) {
147
314
  description,
148
315
  tags,
149
316
  source,
150
- members
317
+ members,
318
+ ...examples.length > 0 ? { examples } : {}
151
319
  };
152
320
  }
153
321
 
154
322
  // src/types/parameters.ts
155
- function extractParameters(signature, checker) {
323
+ function extractParameters(signature, ctx) {
324
+ const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
156
325
  return signature.getParameters().map((param) => {
157
326
  const type = checker.getTypeOfSymbolAtLocation(param, param.valueDeclaration);
327
+ registerReferencedTypes(type, ctx);
158
328
  return {
159
329
  name: param.getName(),
160
330
  schema: { type: checker.typeToString(type) },
@@ -162,6 +332,29 @@ function extractParameters(signature, checker) {
162
332
  };
163
333
  });
164
334
  }
335
+ function registerReferencedTypes(type, ctx) {
336
+ if (ctx.visitedTypes.has(type))
337
+ return;
338
+ ctx.visitedTypes.add(type);
339
+ const { typeChecker: checker, typeRegistry, exportedIds } = ctx;
340
+ typeRegistry.registerType(type, checker, exportedIds);
341
+ const typeArgs = type.typeArguments;
342
+ if (typeArgs) {
343
+ for (const arg of typeArgs) {
344
+ registerReferencedTypes(arg, ctx);
345
+ }
346
+ }
347
+ if (type.isUnion()) {
348
+ for (const t of type.types) {
349
+ registerReferencedTypes(t, ctx);
350
+ }
351
+ }
352
+ if (type.isIntersection()) {
353
+ for (const t of type.types) {
354
+ registerReferencedTypes(t, ctx);
355
+ }
356
+ }
357
+ }
165
358
 
166
359
  // src/serializers/functions.ts
167
360
  function serializeFunctionExport(node, ctx) {
@@ -170,13 +363,14 @@ function serializeFunctionExport(node, ctx) {
170
363
  if (!name)
171
364
  return null;
172
365
  const declSourceFile = node.getSourceFile();
173
- const { description, tags } = getJSDocComment(node);
366
+ const { description, tags, examples } = getJSDocComment(node);
174
367
  const source = getSourceLocation(node, declSourceFile);
175
368
  const type = ctx.typeChecker.getTypeAtLocation(node);
176
369
  const callSignatures = type.getCallSignatures();
177
370
  const signatures = callSignatures.map((sig) => {
178
- const params = extractParameters(sig, ctx.typeChecker);
371
+ const params = extractParameters(sig, ctx);
179
372
  const returnType = ctx.typeChecker.getReturnTypeOfSignature(sig);
373
+ registerReferencedTypes(returnType, ctx);
180
374
  return {
181
375
  parameters: params,
182
376
  returns: {
@@ -191,7 +385,8 @@ function serializeFunctionExport(node, ctx) {
191
385
  description,
192
386
  tags,
193
387
  source,
194
- signatures
388
+ signatures,
389
+ ...examples.length > 0 ? { examples } : {}
195
390
  };
196
391
  }
197
392
 
@@ -202,7 +397,7 @@ function serializeInterface(node, ctx) {
202
397
  if (!name)
203
398
  return null;
204
399
  const declSourceFile = node.getSourceFile();
205
- const { description, tags } = getJSDocComment(node);
400
+ const { description, tags, examples } = getJSDocComment(node);
206
401
  const source = getSourceLocation(node, declSourceFile);
207
402
  return {
208
403
  id: name,
@@ -211,7 +406,8 @@ function serializeInterface(node, ctx) {
211
406
  description,
212
407
  tags,
213
408
  source,
214
- members: []
409
+ members: [],
410
+ ...examples.length > 0 ? { examples } : {}
215
411
  };
216
412
  }
217
413
 
@@ -222,7 +418,7 @@ function serializeTypeAlias(node, ctx) {
222
418
  if (!name)
223
419
  return null;
224
420
  const declSourceFile = node.getSourceFile();
225
- const { description, tags } = getJSDocComment(node);
421
+ const { description, tags, examples } = getJSDocComment(node);
226
422
  const source = getSourceLocation(node, declSourceFile);
227
423
  const type = ctx.typeChecker.getTypeAtLocation(node);
228
424
  const typeString = ctx.typeChecker.typeToString(type);
@@ -233,7 +429,8 @@ function serializeTypeAlias(node, ctx) {
233
429
  description,
234
430
  tags,
235
431
  source,
236
- ...typeString !== name ? { type: typeString } : {}
432
+ ...typeString !== name ? { type: typeString } : {},
433
+ ...examples.length > 0 ? { examples } : {}
237
434
  };
238
435
  }
239
436
 
@@ -244,10 +441,11 @@ function serializeVariable(node, statement, ctx) {
244
441
  if (!name)
245
442
  return null;
246
443
  const declSourceFile = node.getSourceFile();
247
- const { description, tags } = getJSDocComment(statement);
444
+ const { description, tags, examples } = getJSDocComment(statement);
248
445
  const source = getSourceLocation(node, declSourceFile);
249
446
  const type = ctx.typeChecker.getTypeAtLocation(node);
250
447
  const typeString = ctx.typeChecker.typeToString(type);
448
+ registerReferencedTypes(type, ctx);
251
449
  return {
252
450
  id: name,
253
451
  name,
@@ -255,7 +453,8 @@ function serializeVariable(node, statement, ctx) {
255
453
  description,
256
454
  tags,
257
455
  source,
258
- ...typeString && typeString !== name ? { type: typeString } : {}
456
+ ...typeString && typeString !== name ? { type: typeString } : {},
457
+ ...examples.length > 0 ? { examples } : {}
259
458
  };
260
459
  }
261
460
 
@@ -263,7 +462,7 @@ function serializeVariable(node, statement, ctx) {
263
462
  import * as fs from "node:fs";
264
463
  import * as path2 from "node:path";
265
464
  import { SCHEMA_VERSION } from "@openpkg-ts/spec";
266
- import ts3 from "typescript";
465
+ import ts4 from "typescript";
267
466
 
268
467
  // src/serializers/context.ts
269
468
  function createContext(program, sourceFile, options = {}) {
@@ -273,7 +472,9 @@ function createContext(program, sourceFile, options = {}) {
273
472
  sourceFile,
274
473
  maxTypeDepth: options.maxTypeDepth ?? 20,
275
474
  resolveExternalTypes: options.resolveExternalTypes ?? true,
276
- typeRegistry: new TypeRegistry
475
+ typeRegistry: new TypeRegistry,
476
+ exportedIds: new Set,
477
+ visitedTypes: new Set
277
478
  };
278
479
  }
279
480
 
@@ -290,7 +491,6 @@ async function extract(options) {
290
491
  diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
291
492
  };
292
493
  }
293
- const ctx = createContext(program, sourceFile, { maxTypeDepth, resolveExternalTypes });
294
494
  const typeChecker = program.getTypeChecker();
295
495
  const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
296
496
  if (!moduleSymbol) {
@@ -300,13 +500,19 @@ async function extract(options) {
300
500
  };
301
501
  }
302
502
  const exportedSymbols = typeChecker.getExportsOfModule(moduleSymbol);
503
+ const exportedIds = new Set;
504
+ for (const symbol of exportedSymbols) {
505
+ exportedIds.add(symbol.getName());
506
+ }
507
+ const ctx = createContext(program, sourceFile, { maxTypeDepth, resolveExternalTypes });
508
+ ctx.exportedIds = exportedIds;
303
509
  for (const symbol of exportedSymbols) {
304
510
  try {
305
511
  const { declaration, targetSymbol } = resolveExportTarget(symbol, typeChecker);
306
512
  if (!declaration)
307
513
  continue;
308
514
  const exportName = symbol.getName();
309
- const exp = serializeDeclaration(declaration, targetSymbol, exportName, ctx);
515
+ const exp = serializeDeclaration(declaration, symbol, targetSymbol, exportName, ctx);
310
516
  if (exp)
311
517
  exports.push(exp);
312
518
  } catch (err) {
@@ -331,39 +537,111 @@ async function extract(options) {
331
537
  }
332
538
  function resolveExportTarget(symbol, checker) {
333
539
  let targetSymbol = symbol;
334
- if (symbol.flags & ts3.SymbolFlags.Alias) {
540
+ if (symbol.flags & ts4.SymbolFlags.Alias) {
335
541
  const aliasTarget = checker.getAliasedSymbol(symbol);
336
542
  if (aliasTarget && aliasTarget !== symbol) {
337
543
  targetSymbol = aliasTarget;
338
544
  }
339
545
  }
340
546
  const declarations = targetSymbol.declarations ?? [];
341
- const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts3.SyntaxKind.ExportSpecifier) || declarations[0];
547
+ const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts4.SyntaxKind.ExportSpecifier) || declarations[0];
342
548
  return { declaration, targetSymbol };
343
549
  }
344
- function serializeDeclaration(declaration, symbol, exportName, ctx) {
550
+ function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
345
551
  let result = null;
346
- if (ts3.isFunctionDeclaration(declaration)) {
552
+ if (ts4.isFunctionDeclaration(declaration)) {
347
553
  result = serializeFunctionExport(declaration, ctx);
348
- } else if (ts3.isClassDeclaration(declaration)) {
554
+ } else if (ts4.isClassDeclaration(declaration)) {
349
555
  result = serializeClass(declaration, ctx);
350
- } else if (ts3.isInterfaceDeclaration(declaration)) {
556
+ } else if (ts4.isInterfaceDeclaration(declaration)) {
351
557
  result = serializeInterface(declaration, ctx);
352
- } else if (ts3.isTypeAliasDeclaration(declaration)) {
558
+ } else if (ts4.isTypeAliasDeclaration(declaration)) {
353
559
  result = serializeTypeAlias(declaration, ctx);
354
- } else if (ts3.isEnumDeclaration(declaration)) {
560
+ } else if (ts4.isEnumDeclaration(declaration)) {
355
561
  result = serializeEnum(declaration, ctx);
356
- } else if (ts3.isVariableDeclaration(declaration)) {
562
+ } else if (ts4.isVariableDeclaration(declaration)) {
357
563
  const varStatement = declaration.parent?.parent;
358
- if (varStatement && ts3.isVariableStatement(varStatement)) {
564
+ if (varStatement && ts4.isVariableStatement(varStatement)) {
359
565
  result = serializeVariable(declaration, varStatement, ctx);
360
566
  }
567
+ } else if (ts4.isNamespaceExport(declaration) || ts4.isModuleDeclaration(declaration)) {
568
+ result = serializeNamespaceExport(exportSymbol, exportName, ctx);
569
+ } else if (ts4.isSourceFile(declaration)) {
570
+ result = serializeNamespaceExport(exportSymbol, exportName, ctx);
361
571
  }
362
572
  if (result) {
363
573
  result = withExportName(result, exportName);
364
574
  }
365
575
  return result;
366
576
  }
577
+ function serializeNamespaceExport(symbol, exportName, ctx) {
578
+ const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
579
+ return {
580
+ id: exportName,
581
+ name: exportName,
582
+ kind: "namespace",
583
+ description,
584
+ tags,
585
+ ...examples.length > 0 ? { examples } : {}
586
+ };
587
+ }
588
+ function getJSDocFromExportSymbol(symbol) {
589
+ const tags = [];
590
+ const examples = [];
591
+ const decl = symbol.declarations?.[0];
592
+ if (decl) {
593
+ const exportDecl = ts4.isNamespaceExport(decl) ? decl.parent : decl;
594
+ if (exportDecl && ts4.isExportDeclaration(exportDecl)) {
595
+ const jsDocs = ts4.getJSDocCommentsAndTags(exportDecl);
596
+ for (const doc of jsDocs) {
597
+ if (ts4.isJSDoc(doc) && doc.comment) {
598
+ const commentText = typeof doc.comment === "string" ? doc.comment : doc.comment.map((c) => ("text" in c) ? c.text : "").join("");
599
+ if (commentText) {
600
+ return {
601
+ description: commentText,
602
+ tags: extractJSDocTags(doc),
603
+ examples: extractExamples(doc)
604
+ };
605
+ }
606
+ }
607
+ }
608
+ }
609
+ }
610
+ const docComment = symbol.getDocumentationComment(undefined);
611
+ const description = docComment.map((c) => c.text).join(`
612
+ `) || undefined;
613
+ const jsTags = symbol.getJsDocTags();
614
+ for (const tag of jsTags) {
615
+ const text = tag.text?.map((t) => t.text).join("") ?? "";
616
+ if (tag.name === "example") {
617
+ examples.push(text);
618
+ } else {
619
+ tags.push({ name: tag.name, text });
620
+ }
621
+ }
622
+ return { description, tags, examples };
623
+ }
624
+ function extractJSDocTags(doc) {
625
+ const tags = [];
626
+ for (const tag of doc.tags ?? []) {
627
+ if (tag.tagName.text !== "example") {
628
+ const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
629
+ tags.push({ name: tag.tagName.text, text });
630
+ }
631
+ }
632
+ return tags;
633
+ }
634
+ function extractExamples(doc) {
635
+ const examples = [];
636
+ for (const tag of doc.tags ?? []) {
637
+ if (tag.tagName.text === "example") {
638
+ const text = typeof tag.comment === "string" ? tag.comment : tag.comment?.map((c) => ("text" in c) ? c.text : "").join("") ?? "";
639
+ if (text)
640
+ examples.push(text);
641
+ }
642
+ }
643
+ return examples;
644
+ }
367
645
  function withExportName(entry, exportName) {
368
646
  if (entry.name === exportName) {
369
647
  return entry;
@@ -400,4 +678,4 @@ async function getPackageMeta(entryFile, baseDir) {
400
678
  } catch {}
401
679
  return { name: path2.basename(searchDir) };
402
680
  }
403
- export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, extractParameters, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
681
+ export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, serializeClass, serializeEnum, extractParameters, registerReferencedTypes, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
@@ -2,20 +2,31 @@ import { SpecType } from "@openpkg-ts/spec";
2
2
  import ts from "typescript";
3
3
  declare class TypeRegistry {
4
4
  private types;
5
+ private processing;
5
6
  add(type: SpecType): void;
6
7
  get(id: string): SpecType | undefined;
7
8
  has(id: string): boolean;
8
9
  getAll(): SpecType[];
10
+ /**
11
+ * Register a type from a ts.Type, extracting its structure.
12
+ * Returns the type ID if registered, undefined if skipped.
13
+ */
14
+ registerType(type: ts.Type, checker: ts.TypeChecker, exportedIds: Set<string>): string | undefined;
15
+ private buildSpecType;
9
16
  registerFromSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): SpecType | undefined;
10
17
  }
11
- import { SpecSource, SpecTag } from "@openpkg-ts/spec";
18
+ import { SpecExample, SpecSource, SpecTag } from "@openpkg-ts/spec";
12
19
  import ts2 from "typescript";
13
20
  declare function getJSDocComment(node: ts2.Node): {
14
21
  description?: string;
15
22
  tags: SpecTag[];
23
+ examples: SpecExample[];
16
24
  };
17
25
  declare function getSourceLocation(node: ts2.Node, sourceFile: ts2.SourceFile): SpecSource;
18
26
  import { OpenPkg } from "@openpkg-ts/spec";
27
+ import { TypeChecker as TypeChecker_lrgbhchnsl } from "typescript";
28
+ import { Program as Program_jbfzpxflck } from "typescript";
29
+ import { SourceFile as SourceFile_eubaywigwb } from "typescript";
19
30
  interface ExtractOptions {
20
31
  entryFile: string;
21
32
  baseDir?: string;
@@ -38,9 +49,9 @@ interface Diagnostic {
38
49
  };
39
50
  }
40
51
  interface SerializerContext {
41
- typeChecker: import("typescript").TypeChecker;
42
- program: import("typescript").Program;
43
- sourceFile: import("typescript").SourceFile;
52
+ typeChecker: TypeChecker_lrgbhchnsl;
53
+ program: Program_jbfzpxflck;
54
+ sourceFile: SourceFile_eubaywigwb;
44
55
  maxTypeDepth: number;
45
56
  resolveExternalTypes: boolean;
46
57
  }
@@ -91,6 +102,9 @@ interface SerializerContext2 {
91
102
  maxTypeDepth: number;
92
103
  resolveExternalTypes: boolean;
93
104
  typeRegistry: TypeRegistry;
105
+ exportedIds: Set<string>;
106
+ /** Track visited types to prevent infinite recursion */
107
+ visitedTypes: Set<ts6.Type>;
94
108
  }
95
109
  declare function serializeClass(node: ts7.ClassDeclaration, ctx: SerializerContext2): SpecExport | null;
96
110
  import { SpecExport as SpecExport2 } from "@openpkg-ts/spec";
@@ -113,11 +127,16 @@ declare function formatTypeReference(type: ts13.Type, checker: ts13.TypeChecker)
113
127
  declare function collectReferencedTypes(type: ts13.Type, checker: ts13.TypeChecker, visited?: Set<string>): string[];
114
128
  import { SpecSignatureParameter } from "@openpkg-ts/spec";
115
129
  import ts14 from "typescript";
116
- declare function extractParameters(signature: ts14.Signature, checker: ts14.TypeChecker): SpecSignatureParameter[];
130
+ declare function extractParameters(signature: ts14.Signature, ctx: SerializerContext2): SpecSignatureParameter[];
131
+ /**
132
+ * Recursively register types referenced by a ts.Type.
133
+ * Uses ctx.visitedTypes to prevent infinite recursion on circular types.
134
+ */
135
+ declare function registerReferencedTypes(type: ts14.Type, ctx: SerializerContext2): void;
117
136
  import { SpecSchema as SpecSchema3 } from "@openpkg-ts/spec";
118
137
  import ts15 from "typescript";
119
138
  declare function buildSchema(type: ts15.Type, checker: ts15.TypeChecker, depth?: number): SpecSchema3;
120
139
  import ts16 from "typescript";
121
140
  declare function isExported(node: ts16.Node): boolean;
122
141
  declare function getNodeName(node: ts16.Node): string | undefined;
123
- export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerAdapter, isSchemaType, isExported, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
142
+ export { zodAdapter, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, registerReferencedTypes, registerAdapter, isSchemaType, isExported, getSourceLocation, getNodeName, getJSDocComment, formatTypeReference, findAdapter, extractStandardSchemas, extractSchemaType, extractParameters, extract, createProgram, collectReferencedTypes, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaResult, SerializerContext, SchemaAdapter, ProgramResult, ProgramOptions, ExtractResult, ExtractOptions, Diagnostic };
package/dist/src/index.js CHANGED
@@ -5,13 +5,14 @@ import {
5
5
  extractParameters,
6
6
  getJSDocComment,
7
7
  getSourceLocation,
8
+ registerReferencedTypes,
8
9
  serializeClass,
9
10
  serializeEnum,
10
11
  serializeFunctionExport,
11
12
  serializeInterface,
12
13
  serializeTypeAlias,
13
14
  serializeVariable
14
- } from "../shared/chunk-570ne82m.js";
15
+ } from "../shared/chunk-twaykyxs.js";
15
16
  // src/schema/adapters/arktype.ts
16
17
  var arktypeAdapter = {
17
18
  name: "arktype",
@@ -119,6 +120,7 @@ export {
119
120
  serializeFunctionExport,
120
121
  serializeEnum,
121
122
  serializeClass,
123
+ registerReferencedTypes,
122
124
  registerAdapter,
123
125
  isSchemaType,
124
126
  isExported,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/extract",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",
@@ -40,7 +40,7 @@
40
40
  "format": "biome format --write src/"
41
41
  },
42
42
  "dependencies": {
43
- "@openpkg-ts/spec": "workspace:*",
43
+ "@openpkg-ts/spec": "^0.11.1",
44
44
  "commander": "^12.0.0",
45
45
  "typescript": "^5.0.0"
46
46
  },