@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,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
|
-
|
|
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
|
|
20
|
-
|
|
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
|
|
156
|
+
kind,
|
|
157
|
+
type: typeString !== name ? typeString : undefined
|
|
24
158
|
};
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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 =
|
|
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 :
|
|
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 :
|
|
208
|
+
description = typeof firstDoc.comment === "string" ? firstDoc.comment : ts2.getTextOfJSDocComment(firstDoc.comment);
|
|
44
209
|
}
|
|
45
210
|
}
|
|
46
|
-
|
|
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
|
|
224
|
+
import ts3 from "typescript";
|
|
59
225
|
var DEFAULT_COMPILER_OPTIONS = {
|
|
60
|
-
target:
|
|
61
|
-
module:
|
|
226
|
+
target: ts3.ScriptTarget.Latest,
|
|
227
|
+
module: ts3.ModuleKind.CommonJS,
|
|
62
228
|
lib: ["lib.es2021.d.ts"],
|
|
63
229
|
declaration: true,
|
|
64
|
-
moduleResolution:
|
|
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 =
|
|
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 =
|
|
75
|
-
const parsedConfig =
|
|
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 =
|
|
248
|
+
const compilerHost = ts3.createCompilerHost(compilerOptions, true);
|
|
83
249
|
let inMemorySource;
|
|
84
250
|
if (content !== undefined) {
|
|
85
|
-
inMemorySource =
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
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 &
|
|
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 !==
|
|
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,
|
|
550
|
+
function serializeDeclaration(declaration, exportSymbol, targetSymbol, exportName, ctx) {
|
|
345
551
|
let result = null;
|
|
346
|
-
if (
|
|
552
|
+
if (ts4.isFunctionDeclaration(declaration)) {
|
|
347
553
|
result = serializeFunctionExport(declaration, ctx);
|
|
348
|
-
} else if (
|
|
554
|
+
} else if (ts4.isClassDeclaration(declaration)) {
|
|
349
555
|
result = serializeClass(declaration, ctx);
|
|
350
|
-
} else if (
|
|
556
|
+
} else if (ts4.isInterfaceDeclaration(declaration)) {
|
|
351
557
|
result = serializeInterface(declaration, ctx);
|
|
352
|
-
} else if (
|
|
558
|
+
} else if (ts4.isTypeAliasDeclaration(declaration)) {
|
|
353
559
|
result = serializeTypeAlias(declaration, ctx);
|
|
354
|
-
} else if (
|
|
560
|
+
} else if (ts4.isEnumDeclaration(declaration)) {
|
|
355
561
|
result = serializeEnum(declaration, ctx);
|
|
356
|
-
} else if (
|
|
562
|
+
} else if (ts4.isVariableDeclaration(declaration)) {
|
|
357
563
|
const varStatement = declaration.parent?.parent;
|
|
358
|
-
if (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 };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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:
|
|
42
|
-
program:
|
|
43
|
-
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,
|
|
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-
|
|
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.
|
|
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": "
|
|
43
|
+
"@openpkg-ts/spec": "^0.11.1",
|
|
44
44
|
"commander": "^12.0.0",
|
|
45
45
|
"typescript": "^5.0.0"
|
|
46
46
|
},
|