@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.
- package/dist/bin/tspec.js +3 -3
- package/dist/shared/{chunk-ksf9k654.js → chunk-khwn5myc.js} +343 -215
- package/dist/src/index.d.ts +201 -57
- package/dist/src/index.js +361 -43
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
207
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1442
|
-
import * as
|
|
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 {
|
|
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, {
|
|
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
|
|
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,
|
|
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
|
|
1676
|
+
result = serializeNamespaceExport(exportSymbol, exportName);
|
|
1550
1677
|
} else if (ts8.isSourceFile(declaration)) {
|
|
1551
|
-
result = serializeNamespaceExport(exportSymbol, exportName
|
|
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
|
|
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:
|
|
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 ??
|
|
1649
|
-
const pkgPath =
|
|
1776
|
+
const searchDir = baseDir ?? path2.dirname(entryFile);
|
|
1777
|
+
const pkgPath = path2.join(searchDir, "package.json");
|
|
1650
1778
|
try {
|
|
1651
|
-
if (
|
|
1652
|
-
const pkg = JSON.parse(
|
|
1779
|
+
if (fs.existsSync(pkgPath)) {
|
|
1780
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
1653
1781
|
return {
|
|
1654
|
-
name: pkg.name ??
|
|
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:
|
|
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 };
|