@openpkg-ts/extract 0.13.0 → 0.14.1

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.
@@ -1,6 +1,4 @@
1
1
  // src/ast/registry.ts
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
2
  import ts from "typescript";
5
3
  var PRIMITIVES = new Set([
6
4
  "string",
@@ -94,48 +92,6 @@ function isExternalType(decl) {
94
92
  return false;
95
93
  return sourceFile.fileName.includes("node_modules");
96
94
  }
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
95
 
140
96
  class TypeRegistry {
141
97
  types = new Map;
@@ -167,17 +123,19 @@ class TypeRegistry {
167
123
  return;
168
124
  if (symbol.flags & ts.SymbolFlags.TypeParameter)
169
125
  return;
126
+ if (symbol.flags & ts.SymbolFlags.Method)
127
+ return;
128
+ if (symbol.flags & ts.SymbolFlags.Function)
129
+ return;
170
130
  if (isGenericTypeParameter(name))
171
131
  return;
172
132
  if (this.has(name))
173
133
  return name;
174
- if (exportedIds.has(name))
175
- return name;
176
134
  if (this.processing.has(name))
177
135
  return name;
178
136
  this.processing.add(name);
179
137
  try {
180
- const specType = this.buildSpecType(symbol, type, checker);
138
+ const specType = this.buildStubType(symbol, checker);
181
139
  if (specType) {
182
140
  this.add(specType);
183
141
  return specType.id;
@@ -187,7 +145,7 @@ class TypeRegistry {
187
145
  }
188
146
  return;
189
147
  }
190
- buildSpecType(symbol, type, checker) {
148
+ buildStubType(symbol, checker) {
191
149
  const name = symbol.getName();
192
150
  const decl = symbol.declarations?.[0];
193
151
  let kind = "type";
@@ -203,139 +161,21 @@ class TypeRegistry {
203
161
  if (external) {
204
162
  kind = "external";
205
163
  }
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
- }
164
+ const type = checker.getDeclaredTypeOfSymbol(symbol);
165
+ const typeString = checker.typeToString(type);
212
166
  return {
213
167
  id: name,
214
168
  name,
215
169
  kind,
216
- schema,
217
- members: members?.length ? members : undefined,
218
- ...external ? { external: true } : {},
219
- ...source ? { source } : {}
170
+ schema: { type: typeString },
171
+ ...external ? { external: true } : {}
220
172
  };
221
173
  }
222
- buildShallowSchema(type, checker) {
223
- if (type.isUnion()) {
224
- const allEnumLiterals = type.types.every((t) => t.flags & (ts.TypeFlags.EnumLiteral | ts.TypeFlags.NumberLiteral));
225
- if (allEnumLiterals) {
226
- const values = type.types.map((t) => {
227
- if (t.flags & ts.TypeFlags.NumberLiteral) {
228
- return t.value;
229
- }
230
- return checker.typeToString(t);
231
- }).filter((v) => v !== undefined);
232
- if (values.every((v) => typeof v === "number")) {
233
- return { type: "number", enum: values };
234
- }
235
- return { type: "string", enum: values };
236
- }
237
- return {
238
- anyOf: type.types.map((t) => {
239
- if (t.flags & ts.TypeFlags.EnumLiteral) {
240
- const value = t.value;
241
- if (typeof value === "number") {
242
- return { type: "number", enum: [value] };
243
- }
244
- return { type: checker.typeToString(t) };
245
- }
246
- const sym = t.getSymbol() || t.aliasSymbol;
247
- if (sym && sym.flags & ts.SymbolFlags.EnumMember) {
248
- return { type: checker.typeToString(t) };
249
- }
250
- if (sym && !sym.getName().startsWith("__")) {
251
- return { $ref: sym.getName() };
252
- }
253
- if (t.flags & ts.TypeFlags.StringLiteral) {
254
- return { type: "string", enum: [t.value] };
255
- }
256
- if (t.flags & ts.TypeFlags.NumberLiteral) {
257
- return { type: "number", enum: [t.value] };
258
- }
259
- return { type: checker.typeToString(t) };
260
- })
261
- };
262
- }
263
- if (type.isIntersection()) {
264
- return {
265
- allOf: type.types.map((t) => {
266
- const sym = t.getSymbol() || t.aliasSymbol;
267
- if (sym && !sym.getName().startsWith("__")) {
268
- return { $ref: sym.getName() };
269
- }
270
- return { type: checker.typeToString(t) };
271
- })
272
- };
273
- }
274
- const props = type.getProperties();
275
- if (props.length > 0) {
276
- const properties = {};
277
- const required = [];
278
- for (const prop of props.slice(0, 30)) {
279
- const propName = prop.getName();
280
- if (propName.startsWith("_"))
281
- continue;
282
- const propType = checker.getTypeOfSymbol(prop);
283
- const callSigs = propType.getCallSignatures();
284
- if (callSigs.length > 0) {
285
- properties[propName] = { type: "function" };
286
- } else {
287
- const propSym = propType.getSymbol() || propType.aliasSymbol;
288
- const symName = propSym?.getName();
289
- if (propSym && symName && !symName.startsWith("__") && symName !== propName) {
290
- properties[propName] = { $ref: symName };
291
- } else {
292
- properties[propName] = { type: checker.typeToString(propType) };
293
- }
294
- }
295
- if (!(prop.flags & ts.SymbolFlags.Optional)) {
296
- required.push(propName);
297
- }
298
- }
299
- return {
300
- type: "object",
301
- properties,
302
- ...required.length ? { required } : {}
303
- };
304
- }
305
- return { type: checker.typeToString(type) };
306
- }
307
- extractShallowMembers(decl, checker) {
308
- const members = [];
309
- for (const member of decl.members) {
310
- if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
311
- const name = member.name?.getText();
312
- if (!name || name.startsWith("#") || name.startsWith("_"))
313
- continue;
314
- const type = checker.getTypeAtLocation(member);
315
- const sym = type.getSymbol() || type.aliasSymbol;
316
- members.push({
317
- name,
318
- kind: "property",
319
- schema: sym && !sym.getName().startsWith("__") ? { $ref: sym.getName() } : { type: checker.typeToString(type) }
320
- });
321
- } else if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
322
- const name = member.name?.getText();
323
- if (!name || name.startsWith("#") || name.startsWith("_"))
324
- continue;
325
- members.push({
326
- name,
327
- kind: "method"
328
- });
329
- }
330
- }
331
- return members;
332
- }
333
174
  registerFromSymbol(symbol, checker) {
334
175
  const name = symbol.getName();
335
176
  if (this.has(name))
336
177
  return this.get(name);
337
- const type = checker.getDeclaredTypeOfSymbol(symbol);
338
- const specType = this.buildSpecType(symbol, type, checker);
178
+ const specType = this.buildStubType(symbol, checker);
339
179
  if (specType) {
340
180
  this.add(specType);
341
181
  return specType;
@@ -428,9 +268,24 @@ function extractTypeParameters(node, checker) {
428
268
  };
429
269
  });
430
270
  }
271
+ function isSymbolDeprecated(symbol) {
272
+ if (!symbol) {
273
+ return false;
274
+ }
275
+ const jsDocTags = symbol.getJsDocTags();
276
+ if (jsDocTags.some((tag) => tag.name.toLowerCase() === "deprecated")) {
277
+ return true;
278
+ }
279
+ for (const declaration of symbol.getDeclarations() ?? []) {
280
+ if (ts2.getJSDocDeprecatedTag(declaration)) {
281
+ return true;
282
+ }
283
+ }
284
+ return false;
285
+ }
431
286
 
432
287
  // src/compiler/program.ts
433
- import * as path2 from "node:path";
288
+ import * as path from "node:path";
434
289
  import ts3 from "typescript";
435
290
  var DEFAULT_COMPILER_OPTIONS = {
436
291
  target: ts3.ScriptTarget.Latest,
@@ -441,14 +296,14 @@ var DEFAULT_COMPILER_OPTIONS = {
441
296
  };
442
297
  function createProgram({
443
298
  entryFile,
444
- baseDir = path2.dirname(entryFile),
299
+ baseDir = path.dirname(entryFile),
445
300
  content
446
301
  }) {
447
302
  const configPath = ts3.findConfigFile(baseDir, ts3.sys.fileExists, "tsconfig.json");
448
303
  let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
449
304
  if (configPath) {
450
305
  const configFile = ts3.readConfigFile(configPath, ts3.sys.readFile);
451
- const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path2.dirname(configPath));
306
+ const parsedConfig = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path.dirname(configPath));
452
307
  compilerOptions = { ...compilerOptions, ...parsedConfig.options };
453
308
  }
454
309
  const allowJsVal = compilerOptions.allowJs;
@@ -480,6 +335,30 @@ function createProgram({
480
335
 
481
336
  // src/types/schema-builder.ts
482
337
  import ts4 from "typescript";
338
+ var BUILTIN_TYPE_SCHEMAS = {
339
+ Date: { type: "string", format: "date-time" },
340
+ RegExp: { type: "object", description: "RegExp" },
341
+ Error: { type: "object" },
342
+ Promise: { type: "object" },
343
+ Map: { type: "object" },
344
+ Set: { type: "object" },
345
+ WeakMap: { type: "object" },
346
+ WeakSet: { type: "object" },
347
+ Function: { type: "object" },
348
+ ArrayBuffer: { type: "string", format: "binary" },
349
+ ArrayBufferLike: { type: "string", format: "binary" },
350
+ DataView: { type: "string", format: "binary" },
351
+ Uint8Array: { type: "string", format: "byte" },
352
+ Uint16Array: { type: "string", format: "byte" },
353
+ Uint32Array: { type: "string", format: "byte" },
354
+ Int8Array: { type: "string", format: "byte" },
355
+ Int16Array: { type: "string", format: "byte" },
356
+ Int32Array: { type: "string", format: "byte" },
357
+ Float32Array: { type: "string", format: "byte" },
358
+ Float64Array: { type: "string", format: "byte" },
359
+ BigInt64Array: { type: "string", format: "byte" },
360
+ BigUint64Array: { type: "string", format: "byte" }
361
+ };
483
362
  var PRIMITIVES2 = new Set([
484
363
  "string",
485
364
  "number",
@@ -575,13 +454,20 @@ function buildSchema(type, checker, ctx, _depth = 0) {
575
454
  if (isAtMaxDepth(ctx)) {
576
455
  return { type: checker.typeToString(type) };
577
456
  }
578
- if (ctx && ctx.visitedTypes.has(type)) {
457
+ if (ctx?.visitedTypes.has(type)) {
458
+ const callSignatures = type.getCallSignatures();
459
+ if (callSignatures.length > 0) {
460
+ return buildFunctionSchema(callSignatures, checker, ctx);
461
+ }
579
462
  const symbol2 = type.getSymbol() || type.aliasSymbol;
580
463
  if (symbol2) {
581
- return { $ref: symbol2.getName() };
464
+ return { $ref: `#/types/${symbol2.getName()}` };
582
465
  }
583
466
  return { type: checker.typeToString(type) };
584
467
  }
468
+ if (ctx && type.flags & ts4.TypeFlags.Object) {
469
+ ctx.visitedTypes.add(type);
470
+ }
585
471
  if (type.flags & ts4.TypeFlags.String)
586
472
  return { type: "string" };
587
473
  if (type.flags & ts4.TypeFlags.Number)
@@ -680,21 +566,27 @@ function buildSchema(type, checker, ctx, _depth = 0) {
680
566
  const symbol2 = typeRef.target.getSymbol();
681
567
  const name = symbol2?.getName();
682
568
  if (name && BUILTIN_TYPES.has(name)) {
683
- return { $ref: name };
569
+ return { $ref: `#/types/${name}` };
684
570
  }
685
571
  if (name && (isBuiltinGeneric(name) || !isAnonymous(typeRef.target))) {
686
572
  if (ctx) {
687
573
  return withDepth(ctx, () => ({
688
- $ref: name,
574
+ $ref: `#/types/${name}`,
689
575
  typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
690
576
  }));
691
577
  }
692
578
  return {
693
- $ref: name,
579
+ $ref: `#/types/${name}`,
694
580
  typeArguments: typeRef.typeArguments.map((t) => buildSchema(t, checker, ctx))
695
581
  };
696
582
  }
697
583
  }
584
+ if (type.flags & ts4.TypeFlags.Object) {
585
+ const callSignatures = type.getCallSignatures();
586
+ if (callSignatures.length > 0) {
587
+ return buildFunctionSchema(callSignatures, checker, ctx);
588
+ }
589
+ }
698
590
  const symbol = type.getSymbol() || type.aliasSymbol;
699
591
  if (symbol && !isAnonymous(type)) {
700
592
  const name = symbol.getName();
@@ -702,18 +594,14 @@ function buildSchema(type, checker, ctx, _depth = 0) {
702
594
  return { type: name };
703
595
  }
704
596
  if (BUILTIN_TYPES.has(name)) {
705
- return { $ref: name };
597
+ return { $ref: `#/types/${name}` };
706
598
  }
707
599
  if (!name.startsWith("__")) {
708
- return { $ref: name };
600
+ return { $ref: `#/types/${name}` };
709
601
  }
710
602
  }
711
603
  if (type.flags & ts4.TypeFlags.Object) {
712
604
  const objectType = type;
713
- const callSignatures = type.getCallSignatures();
714
- if (callSignatures.length > 0) {
715
- return buildFunctionSchema(callSignatures, checker, ctx);
716
- }
717
605
  const properties = type.getProperties();
718
606
  if (properties.length > 0 || objectType.objectFlags & ts4.ObjectFlags.Anonymous) {
719
607
  return buildObjectSchema(properties, checker, ctx);
@@ -772,6 +660,110 @@ function buildObjectSchema(properties, checker, ctx) {
772
660
  }
773
661
  return buildProps();
774
662
  }
663
+ function isPureRefSchema(schema) {
664
+ return typeof schema === "object" && Object.keys(schema).length === 1 && "$ref" in schema;
665
+ }
666
+ function withDescription(schema, description) {
667
+ if (isPureRefSchema(schema)) {
668
+ return {
669
+ allOf: [schema],
670
+ description
671
+ };
672
+ }
673
+ return { ...schema, description };
674
+ }
675
+ function schemaIsAny(schema) {
676
+ if (typeof schema === "string") {
677
+ return schema === "any";
678
+ }
679
+ if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
680
+ return true;
681
+ }
682
+ return false;
683
+ }
684
+ function schemasAreEqual(left, right) {
685
+ if (typeof left !== typeof right) {
686
+ return false;
687
+ }
688
+ if (typeof left === "string" && typeof right === "string") {
689
+ return left === right;
690
+ }
691
+ if (left == null || right == null) {
692
+ return left === right;
693
+ }
694
+ const normalize = (value) => {
695
+ if (Array.isArray(value)) {
696
+ return value.map((item) => normalize(item));
697
+ }
698
+ if (value && typeof value === "object") {
699
+ const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
700
+ return Object.fromEntries(sortedEntries);
701
+ }
702
+ return value;
703
+ };
704
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
705
+ }
706
+ function deduplicateSchemas(schemas) {
707
+ const result = [];
708
+ for (const schema of schemas) {
709
+ const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
710
+ if (!isDuplicate) {
711
+ result.push(schema);
712
+ }
713
+ }
714
+ return result;
715
+ }
716
+ function findDiscriminatorProperty(unionTypes, checker) {
717
+ const memberProps = [];
718
+ for (const t of unionTypes) {
719
+ if (t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined)) {
720
+ continue;
721
+ }
722
+ const props = t.getProperties();
723
+ if (!props || props.length === 0) {
724
+ return;
725
+ }
726
+ const propValues = new Map;
727
+ for (const prop of props) {
728
+ const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
729
+ if (!declaration)
730
+ continue;
731
+ try {
732
+ const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
733
+ if (propType.isStringLiteral()) {
734
+ propValues.set(prop.getName(), propType.value);
735
+ } else if (propType.isNumberLiteral()) {
736
+ propValues.set(prop.getName(), propType.value);
737
+ }
738
+ } catch {}
739
+ }
740
+ memberProps.push(propValues);
741
+ }
742
+ if (memberProps.length < 2) {
743
+ return;
744
+ }
745
+ const firstMember = memberProps[0];
746
+ for (const [propName, firstValue] of firstMember) {
747
+ const values = new Set([firstValue]);
748
+ let isDiscriminator = true;
749
+ for (let i = 1;i < memberProps.length; i++) {
750
+ const value = memberProps[i].get(propName);
751
+ if (value === undefined) {
752
+ isDiscriminator = false;
753
+ break;
754
+ }
755
+ if (values.has(value)) {
756
+ isDiscriminator = false;
757
+ break;
758
+ }
759
+ values.add(value);
760
+ }
761
+ if (isDiscriminator) {
762
+ return propName;
763
+ }
764
+ }
765
+ return;
766
+ }
775
767
 
776
768
  // src/types/parameters.ts
777
769
  import ts5 from "typescript";
@@ -889,7 +881,9 @@ function extractDefaultValue(initializer) {
889
881
  }
890
882
  return initializer.getText();
891
883
  }
892
- function registerReferencedTypes(type, ctx) {
884
+ function registerReferencedTypes(type, ctx, depth = 0) {
885
+ if (depth > ctx.maxTypeDepth)
886
+ return;
893
887
  if (ctx.visitedTypes.has(type))
894
888
  return;
895
889
  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);
@@ -901,17 +895,24 @@ function registerReferencedTypes(type, ctx) {
901
895
  const typeArgs = type.typeArguments;
902
896
  if (typeArgs) {
903
897
  for (const arg of typeArgs) {
904
- registerReferencedTypes(arg, ctx);
898
+ registerReferencedTypes(arg, ctx, depth + 1);
905
899
  }
906
900
  }
907
901
  if (type.isUnion()) {
908
902
  for (const t of type.types) {
909
- registerReferencedTypes(t, ctx);
903
+ registerReferencedTypes(t, ctx, depth + 1);
910
904
  }
911
905
  }
912
906
  if (type.isIntersection()) {
913
907
  for (const t of type.types) {
914
- registerReferencedTypes(t, ctx);
908
+ registerReferencedTypes(t, ctx, depth + 1);
909
+ }
910
+ }
911
+ if (type.flags & ts5.TypeFlags.Object) {
912
+ const props = type.getProperties();
913
+ for (const prop of props.slice(0, 20)) {
914
+ const propType = checker.getTypeOfSymbol(prop);
915
+ registerReferencedTypes(propType, ctx, depth + 1);
915
916
  }
916
917
  }
917
918
  }
@@ -941,7 +942,7 @@ function serializeClass(node, ctx) {
941
942
  members.push(propMember);
942
943
  } else if (ts6.isMethodDeclaration(member)) {
943
944
  const methodMember = serializeMethod(member, ctx);
944
- if (methodMember && methodMember.name) {
945
+ if (methodMember?.name) {
945
946
  if (!methodsByName.has(methodMember.name)) {
946
947
  methodsByName.set(methodMember.name, methodMember);
947
948
  } else {
@@ -1023,8 +1024,8 @@ function serializeProperty(node, ctx) {
1023
1024
  const { description, tags } = getJSDocComment(node);
1024
1025
  const visibility = getVisibility(node);
1025
1026
  const type = checker.getTypeAtLocation(node);
1026
- const schema = buildSchema(type, checker, ctx);
1027
1027
  registerReferencedTypes(type, ctx);
1028
+ const schema = buildSchema(type, checker, ctx);
1028
1029
  const flags = {};
1029
1030
  if (isStatic(node))
1030
1031
  flags.static = true;
@@ -1247,7 +1248,7 @@ function serializeInterface(node, ctx) {
1247
1248
  members.push(propMember);
1248
1249
  } else if (ts7.isMethodSignature(member)) {
1249
1250
  const methodMember = serializeMethodSignature(member, ctx);
1250
- if (methodMember && methodMember.name) {
1251
+ if (methodMember?.name) {
1251
1252
  if (!methodsByName.has(methodMember.name)) {
1252
1253
  methodsByName.set(methodMember.name, methodMember);
1253
1254
  }
@@ -1398,8 +1399,8 @@ function serializeTypeAlias(node, ctx) {
1398
1399
  const source = getSourceLocation(node, declSourceFile);
1399
1400
  const typeParameters = extractTypeParameters(node, ctx.typeChecker);
1400
1401
  const type = ctx.typeChecker.getTypeAtLocation(node);
1401
- const schema = buildSchema(type, ctx.typeChecker, ctx);
1402
1402
  registerReferencedTypes(type, ctx);
1403
+ const schema = buildSchema(type, ctx.typeChecker, ctx);
1403
1404
  return {
1404
1405
  id: name,
1405
1406
  name,
@@ -1423,8 +1424,8 @@ function serializeVariable(node, statement, ctx) {
1423
1424
  const { description, tags, examples } = getJSDocComment(statement);
1424
1425
  const source = getSourceLocation(node, declSourceFile);
1425
1426
  const type = ctx.typeChecker.getTypeAtLocation(node);
1426
- const schema = buildSchema(type, ctx.typeChecker, ctx);
1427
1427
  registerReferencedTypes(type, ctx);
1428
+ const schema = buildSchema(type, ctx.typeChecker, ctx);
1428
1429
  return {
1429
1430
  id: name,
1430
1431
  name,
@@ -1438,9 +1439,9 @@ function serializeVariable(node, statement, ctx) {
1438
1439
  }
1439
1440
 
1440
1441
  // src/builder/spec-builder.ts
1441
- import * as fs2 from "node:fs";
1442
- import * as path3 from "node:path";
1443
- import { SCHEMA_VERSION } from "@openpkg-ts/spec";
1442
+ import * as fs from "node:fs";
1443
+ import * as path2 from "node:path";
1444
+ import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
1444
1445
  import ts8 from "typescript";
1445
1446
 
1446
1447
  // src/serializers/context.ts
@@ -1460,15 +1461,92 @@ function createContext(program, sourceFile, options = {}) {
1460
1461
  }
1461
1462
 
1462
1463
  // src/builder/spec-builder.ts
1464
+ var BUILTIN_TYPES2 = new Set([
1465
+ "Array",
1466
+ "ArrayBuffer",
1467
+ "ArrayBufferLike",
1468
+ "ArrayLike",
1469
+ "Promise",
1470
+ "Map",
1471
+ "Set",
1472
+ "WeakMap",
1473
+ "WeakSet",
1474
+ "Record",
1475
+ "Partial",
1476
+ "Required",
1477
+ "Pick",
1478
+ "Omit",
1479
+ "Exclude",
1480
+ "Extract",
1481
+ "NonNullable",
1482
+ "Parameters",
1483
+ "ReturnType",
1484
+ "Readonly",
1485
+ "ReadonlyArray",
1486
+ "Awaited",
1487
+ "PromiseLike",
1488
+ "Iterable",
1489
+ "Iterator",
1490
+ "IterableIterator",
1491
+ "Generator",
1492
+ "AsyncGenerator",
1493
+ "AsyncIterable",
1494
+ "AsyncIterator",
1495
+ "AsyncIterableIterator",
1496
+ "Date",
1497
+ "RegExp",
1498
+ "Error",
1499
+ "Function",
1500
+ "Object",
1501
+ "String",
1502
+ "Number",
1503
+ "Boolean",
1504
+ "Symbol",
1505
+ "BigInt",
1506
+ "Uint8Array",
1507
+ "Int8Array",
1508
+ "Uint16Array",
1509
+ "Int16Array",
1510
+ "Uint32Array",
1511
+ "Int32Array",
1512
+ "Float32Array",
1513
+ "Float64Array",
1514
+ "BigInt64Array",
1515
+ "BigUint64Array",
1516
+ "DataView",
1517
+ "SharedArrayBuffer",
1518
+ "ConstructorParameters",
1519
+ "InstanceType",
1520
+ "ThisType"
1521
+ ]);
1522
+ function shouldSkipDanglingRef(name) {
1523
+ if (name.startsWith("__"))
1524
+ return true;
1525
+ if (/^[A-Z]$/.test(name))
1526
+ return true;
1527
+ if (/^T[A-Z]/.test(name))
1528
+ return true;
1529
+ if (["Key", "Value", "Item", "Element"].includes(name))
1530
+ return true;
1531
+ return false;
1532
+ }
1463
1533
  async function extract(options) {
1464
- const { entryFile, baseDir, content, maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes } = options;
1534
+ const {
1535
+ entryFile,
1536
+ baseDir,
1537
+ content,
1538
+ maxTypeDepth,
1539
+ maxExternalTypeDepth,
1540
+ resolveExternalTypes,
1541
+ includeSchema
1542
+ } = options;
1465
1543
  const diagnostics = [];
1466
1544
  const exports = [];
1467
1545
  const result = createProgram({ entryFile, baseDir, content });
1468
1546
  const { program, sourceFile } = result;
1469
1547
  if (!sourceFile) {
1470
1548
  return {
1471
- spec: createEmptySpec(entryFile),
1549
+ spec: createEmptySpec(entryFile, includeSchema),
1472
1550
  diagnostics: [{ message: `Could not load source file: ${entryFile}`, severity: "error" }]
1473
1551
  };
1474
1552
  }
@@ -1476,7 +1554,7 @@ async function extract(options) {
1476
1554
  const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
1477
1555
  if (!moduleSymbol) {
1478
1556
  return {
1479
- spec: createEmptySpec(entryFile),
1557
+ spec: createEmptySpec(entryFile, includeSchema),
1480
1558
  diagnostics: [{ message: "Could not get module symbol", severity: "warning" }]
1481
1559
  };
1482
1560
  }
@@ -1485,7 +1563,11 @@ async function extract(options) {
1485
1563
  for (const symbol of exportedSymbols) {
1486
1564
  exportedIds.add(symbol.getName());
1487
1565
  }
1488
- const ctx = createContext(program, sourceFile, { maxTypeDepth, maxExternalTypeDepth, resolveExternalTypes });
1566
+ const ctx = createContext(program, sourceFile, {
1567
+ maxTypeDepth,
1568
+ maxExternalTypeDepth,
1569
+ resolveExternalTypes
1570
+ });
1489
1571
  ctx.exportedIds = exportedIds;
1490
1572
  for (const symbol of exportedSymbols) {
1491
1573
  try {
@@ -1504,11 +1586,30 @@ async function extract(options) {
1504
1586
  }
1505
1587
  }
1506
1588
  const meta = await getPackageMeta(entryFile, baseDir);
1589
+ const types = ctx.typeRegistry.getAll();
1590
+ const danglingRefs = collectDanglingRefs(exports, types);
1591
+ for (const ref of danglingRefs) {
1592
+ diagnostics.push({
1593
+ message: `Type '${ref}' is referenced but not defined in types[].`,
1594
+ severity: "warning",
1595
+ code: "DANGLING_REF",
1596
+ suggestion: "The type may be from an external package. Check import paths."
1597
+ });
1598
+ }
1599
+ const externalTypes = types.filter((t) => t.kind === "external");
1600
+ if (externalTypes.length > 0) {
1601
+ diagnostics.push({
1602
+ message: `${externalTypes.length} external type(s) from dependencies: ${externalTypes.slice(0, 5).map((t) => t.id).join(", ")}${externalTypes.length > 5 ? "..." : ""}`,
1603
+ severity: "info",
1604
+ code: "EXTERNAL_TYPES"
1605
+ });
1606
+ }
1507
1607
  const spec = {
1608
+ ...includeSchema ? { $schema: SCHEMA_URL } : {},
1508
1609
  openpkg: SCHEMA_VERSION,
1509
1610
  meta,
1510
1611
  exports,
1511
- types: ctx.typeRegistry.getAll(),
1612
+ types,
1512
1613
  generation: {
1513
1614
  generator: "@openpkg-ts/extract",
1514
1615
  timestamp: new Date().toISOString()
@@ -1516,6 +1617,32 @@ async function extract(options) {
1516
1617
  };
1517
1618
  return { spec, diagnostics };
1518
1619
  }
1620
+ function collectAllRefs(obj, refs) {
1621
+ if (obj === null || obj === undefined)
1622
+ return;
1623
+ if (Array.isArray(obj)) {
1624
+ for (const item of obj) {
1625
+ collectAllRefs(item, refs);
1626
+ }
1627
+ return;
1628
+ }
1629
+ if (typeof obj === "object") {
1630
+ const record = obj;
1631
+ if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
1632
+ refs.add(record.$ref.slice("#/types/".length));
1633
+ }
1634
+ for (const value of Object.values(record)) {
1635
+ collectAllRefs(value, refs);
1636
+ }
1637
+ }
1638
+ }
1639
+ function collectDanglingRefs(exports, types) {
1640
+ const definedTypes = new Set(types.map((t) => t.id));
1641
+ const referencedTypes = new Set;
1642
+ collectAllRefs(exports, referencedTypes);
1643
+ collectAllRefs(types, referencedTypes);
1644
+ return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref) && !BUILTIN_TYPES2.has(ref) && !shouldSkipDanglingRef(ref));
1645
+ }
1519
1646
  function resolveExportTarget(symbol, checker) {
1520
1647
  let targetSymbol = symbol;
1521
1648
  if (symbol.flags & ts8.SymbolFlags.Alias) {
@@ -1528,7 +1655,7 @@ function resolveExportTarget(symbol, checker) {
1528
1655
  const declaration = targetSymbol.valueDeclaration || declarations.find((decl) => decl.kind !== ts8.SyntaxKind.ExportSpecifier) || declarations[0];
1529
1656
  return { declaration, targetSymbol };
1530
1657
  }
1531
- function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
1658
+ function serializeDeclaration(declaration, exportSymbol, _targetSymbol, exportName, ctx) {
1532
1659
  let result = null;
1533
1660
  if (ts8.isFunctionDeclaration(declaration)) {
1534
1661
  result = serializeFunctionExport(declaration, ctx);
@@ -1546,16 +1673,16 @@ function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportNam
1546
1673
  result = serializeVariable(declaration, varStatement, ctx);
1547
1674
  }
1548
1675
  } else if (ts8.isNamespaceExport(declaration) || ts8.isModuleDeclaration(declaration)) {
1549
- result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1676
+ result = serializeNamespaceExport(exportSymbol, exportName);
1550
1677
  } else if (ts8.isSourceFile(declaration)) {
1551
- result = serializeNamespaceExport(exportSymbol, exportName, ctx);
1678
+ result = serializeNamespaceExport(exportSymbol, exportName);
1552
1679
  }
1553
1680
  if (result) {
1554
1681
  result = withExportName(result, exportName);
1555
1682
  }
1556
1683
  return result;
1557
1684
  }
1558
- function serializeNamespaceExport(symbol, exportName, ctx) {
1685
+ function serializeNamespaceExport(symbol, exportName) {
1559
1686
  const { description, tags, examples } = getJSDocFromExportSymbol(symbol);
1560
1687
  return {
1561
1688
  id: exportName,
@@ -1633,10 +1760,11 @@ function withExportName(entry, exportName) {
1633
1760
  name: entry.name
1634
1761
  };
1635
1762
  }
1636
- function createEmptySpec(entryFile) {
1763
+ function createEmptySpec(entryFile, includeSchema) {
1637
1764
  return {
1765
+ ...includeSchema ? { $schema: SCHEMA_URL } : {},
1638
1766
  openpkg: SCHEMA_VERSION,
1639
- meta: { name: path3.basename(entryFile, path3.extname(entryFile)) },
1767
+ meta: { name: path2.basename(entryFile, path2.extname(entryFile)) },
1640
1768
  exports: [],
1641
1769
  generation: {
1642
1770
  generator: "@openpkg-ts/extract",
@@ -1645,18 +1773,18 @@ function createEmptySpec(entryFile) {
1645
1773
  };
1646
1774
  }
1647
1775
  async function getPackageMeta(entryFile, baseDir) {
1648
- const searchDir = baseDir ?? path3.dirname(entryFile);
1649
- const pkgPath = path3.join(searchDir, "package.json");
1776
+ const searchDir = baseDir ?? path2.dirname(entryFile);
1777
+ const pkgPath = path2.join(searchDir, "package.json");
1650
1778
  try {
1651
- if (fs2.existsSync(pkgPath)) {
1652
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
1779
+ if (fs.existsSync(pkgPath)) {
1780
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
1653
1781
  return {
1654
- name: pkg.name ?? path3.basename(searchDir),
1782
+ name: pkg.name ?? path2.basename(searchDir),
1655
1783
  version: pkg.version,
1656
1784
  description: pkg.description
1657
1785
  };
1658
1786
  }
1659
1787
  } catch {}
1660
- return { name: path3.basename(searchDir) };
1788
+ return { name: path2.basename(searchDir) };
1661
1789
  }
1662
- export { TypeRegistry, getJSDocComment, getSourceLocation, createProgram, isPrimitiveName, isBuiltinGeneric, isAnonymous, buildSchema, extractParameters, registerReferencedTypes, serializeClass, serializeEnum, serializeFunctionExport, serializeInterface, serializeTypeAlias, serializeVariable, extract };
1790
+ 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 };