@langchain/langgraph-api 1.1.2 → 1.1.8
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/package.json +9 -8
- package/CHANGELOG.md +0 -297
- package/dist/api/assistants.d.mts +0 -3
- package/dist/api/assistants.mjs +0 -193
- package/dist/api/meta.d.mts +0 -3
- package/dist/api/meta.mjs +0 -65
- package/dist/api/runs.d.mts +0 -3
- package/dist/api/runs.mjs +0 -324
- package/dist/api/store.d.mts +0 -3
- package/dist/api/store.mjs +0 -111
- package/dist/api/threads.d.mts +0 -3
- package/dist/api/threads.mjs +0 -143
- package/dist/auth/custom.d.mts +0 -9
- package/dist/auth/custom.mjs +0 -32
- package/dist/auth/index.d.mts +0 -43
- package/dist/auth/index.mjs +0 -163
- package/dist/cli/entrypoint.d.mts +0 -1
- package/dist/cli/entrypoint.mjs +0 -41
- package/dist/cli/spawn.d.mts +0 -42
- package/dist/cli/spawn.mjs +0 -47
- package/dist/cli/utils/ipc/client.d.mts +0 -5
- package/dist/cli/utils/ipc/client.mjs +0 -47
- package/dist/cli/utils/ipc/utils/get-pipe-path.d.mts +0 -1
- package/dist/cli/utils/ipc/utils/get-pipe-path.mjs +0 -29
- package/dist/cli/utils/ipc/utils/temporary-directory.d.mts +0 -5
- package/dist/cli/utils/ipc/utils/temporary-directory.mjs +0 -40
- package/dist/command.d.mts +0 -11
- package/dist/command.mjs +0 -15
- package/dist/experimental/embed.d.mts +0 -42
- package/dist/experimental/embed.mjs +0 -299
- package/dist/graph/api.d.mts +0 -1
- package/dist/graph/api.mjs +0 -2
- package/dist/graph/load.d.mts +0 -19
- package/dist/graph/load.hooks.d.mts +0 -2
- package/dist/graph/load.hooks.mjs +0 -52
- package/dist/graph/load.mjs +0 -96
- package/dist/graph/load.utils.d.mts +0 -22
- package/dist/graph/load.utils.mjs +0 -49
- package/dist/graph/parser/index.d.mts +0 -23
- package/dist/graph/parser/index.mjs +0 -58
- package/dist/graph/parser/parser.d.mts +0 -77
- package/dist/graph/parser/parser.mjs +0 -429
- package/dist/graph/parser/parser.worker.d.mts +0 -1
- package/dist/graph/parser/parser.worker.mjs +0 -7
- package/dist/graph/parser/schema/types.d.mts +0 -154
- package/dist/graph/parser/schema/types.mjs +0 -1496
- package/dist/graph/parser/schema/types.template.d.mts +0 -1
- package/dist/graph/parser/schema/types.template.mts +0 -92
- package/dist/http/custom.d.mts +0 -6
- package/dist/http/custom.mjs +0 -10
- package/dist/http/middleware.d.mts +0 -11
- package/dist/http/middleware.mjs +0 -57
- package/dist/logging.d.mts +0 -10
- package/dist/logging.mjs +0 -115
- package/dist/loopback.d.mts +0 -4
- package/dist/loopback.mjs +0 -10
- package/dist/preload.d.mts +0 -1
- package/dist/preload.mjs +0 -29
- package/dist/queue.d.mts +0 -2
- package/dist/queue.mjs +0 -119
- package/dist/schemas.d.mts +0 -1552
- package/dist/schemas.mjs +0 -492
- package/dist/semver/index.d.mts +0 -15
- package/dist/semver/index.mjs +0 -46
- package/dist/server.d.mts +0 -175
- package/dist/server.mjs +0 -181
- package/dist/state.d.mts +0 -3
- package/dist/state.mjs +0 -30
- package/dist/storage/checkpoint.d.mts +0 -19
- package/dist/storage/checkpoint.mjs +0 -127
- package/dist/storage/context.d.mts +0 -3
- package/dist/storage/context.mjs +0 -11
- package/dist/storage/importMap.d.mts +0 -55
- package/dist/storage/importMap.mjs +0 -55
- package/dist/storage/ops.d.mts +0 -169
- package/dist/storage/ops.mjs +0 -1262
- package/dist/storage/persist.d.mts +0 -18
- package/dist/storage/persist.mjs +0 -81
- package/dist/storage/store.d.mts +0 -17
- package/dist/storage/store.mjs +0 -41
- package/dist/storage/types.d.mts +0 -301
- package/dist/storage/types.mjs +0 -1
- package/dist/stream.d.mts +0 -43
- package/dist/stream.mjs +0 -235
- package/dist/ui/load.d.mts +0 -8
- package/dist/ui/load.mjs +0 -53
- package/dist/utils/abort.d.mts +0 -1
- package/dist/utils/abort.mjs +0 -8
- package/dist/utils/hono.d.mts +0 -5
- package/dist/utils/hono.mjs +0 -24
- package/dist/utils/importMap.d.mts +0 -55
- package/dist/utils/importMap.mjs +0 -55
- package/dist/utils/runnableConfig.d.mts +0 -3
- package/dist/utils/runnableConfig.mjs +0 -45
- package/dist/utils/serde.d.mts +0 -5
- package/dist/utils/serde.mjs +0 -20
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -12
- package/dist/webhook.d.mts +0 -11
- package/dist/webhook.mjs +0 -30
|
@@ -1,1496 +0,0 @@
|
|
|
1
|
-
// Copied from typescript-json-schema#70de093f2e148afab527aaf0d4dc38ce19de7715
|
|
2
|
-
//
|
|
3
|
-
// Copyright (c) 2016, typescript-json-schema contributors
|
|
4
|
-
// All rights reserved.
|
|
5
|
-
//
|
|
6
|
-
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
7
|
-
//
|
|
8
|
-
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
9
|
-
//
|
|
10
|
-
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
11
|
-
//
|
|
12
|
-
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
13
|
-
//
|
|
14
|
-
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
15
|
-
import * as ts from "typescript";
|
|
16
|
-
import * as vm from "node:vm";
|
|
17
|
-
import * as path from "node:path";
|
|
18
|
-
import { createHash } from "node:crypto";
|
|
19
|
-
const REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"\)|".*?")\.| /g;
|
|
20
|
-
const REGEX_TJS_JSDOC = /^-([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g;
|
|
21
|
-
const REGEX_GROUP_JSDOC = /^[.]?([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g;
|
|
22
|
-
/**
|
|
23
|
-
* Resolve required file, his path and a property name,
|
|
24
|
-
* pattern: require([file_path]).[property_name]
|
|
25
|
-
*
|
|
26
|
-
* the part ".[property_name]" is optional in the regex
|
|
27
|
-
*
|
|
28
|
-
* will match:
|
|
29
|
-
*
|
|
30
|
-
* require('./path.ts')
|
|
31
|
-
* require('./path.ts').objectName
|
|
32
|
-
* require("./path.ts")
|
|
33
|
-
* require("./path.ts").objectName
|
|
34
|
-
* require('@module-name')
|
|
35
|
-
*
|
|
36
|
-
* match[2] = file_path (a path to the file with quotes)
|
|
37
|
-
* match[3] = (optional) property_name (a property name, exported in the file)
|
|
38
|
-
*
|
|
39
|
-
* for more details, see tests/require.test.ts
|
|
40
|
-
*/
|
|
41
|
-
const REGEX_REQUIRE = /^(\s+)?require\((\'@?[a-zA-Z0-9.\/_-]+\'|\"@?[a-zA-Z0-9.\/_-]+\")\)(\.([a-zA-Z0-9_$]+))?(\s+|$)/;
|
|
42
|
-
const NUMERIC_INDEX_PATTERN = "^[0-9]+$";
|
|
43
|
-
function pathEqual(actual, expected) {
|
|
44
|
-
return (actual === expected || normalizePath(actual) === normalizePath(expected));
|
|
45
|
-
}
|
|
46
|
-
function normalizePath(path) {
|
|
47
|
-
const replace = [
|
|
48
|
-
[/\\/g, "/"],
|
|
49
|
-
[/(\w):/, "/$1"],
|
|
50
|
-
[/(\w+)\/\.\.\/?/g, ""],
|
|
51
|
-
[/^\.\//, ""],
|
|
52
|
-
[/\/\.\//, "/"],
|
|
53
|
-
[/\/\.$/, ""],
|
|
54
|
-
[/\/$/, ""],
|
|
55
|
-
];
|
|
56
|
-
replace.forEach((array) => {
|
|
57
|
-
while (array[0].test(path)) {
|
|
58
|
-
path = path.replace(array[0], array[1]);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
return path;
|
|
62
|
-
}
|
|
63
|
-
function getDefaultArgs() {
|
|
64
|
-
return {
|
|
65
|
-
ref: true,
|
|
66
|
-
aliasRef: false,
|
|
67
|
-
topRef: false,
|
|
68
|
-
titles: false,
|
|
69
|
-
defaultProps: false,
|
|
70
|
-
noExtraProps: false,
|
|
71
|
-
propOrder: false,
|
|
72
|
-
typeOfKeyword: false,
|
|
73
|
-
required: false,
|
|
74
|
-
strictNullChecks: false,
|
|
75
|
-
esModuleInterop: false,
|
|
76
|
-
experimentalDecorators: true,
|
|
77
|
-
out: "",
|
|
78
|
-
validationKeywords: [],
|
|
79
|
-
include: [],
|
|
80
|
-
excludePrivate: false,
|
|
81
|
-
uniqueNames: false,
|
|
82
|
-
rejectDateType: false,
|
|
83
|
-
id: "",
|
|
84
|
-
defaultNumberType: "number",
|
|
85
|
-
constAsEnum: false,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function extend(target, ..._) {
|
|
89
|
-
if (target == null) {
|
|
90
|
-
// TypeError if undefined or null
|
|
91
|
-
throw new TypeError("Cannot convert undefined or null to object");
|
|
92
|
-
}
|
|
93
|
-
const to = Object(target);
|
|
94
|
-
for (var index = 1; index < arguments.length; index++) {
|
|
95
|
-
const nextSource = arguments[index];
|
|
96
|
-
if (nextSource != null) {
|
|
97
|
-
// Skip over if undefined or null
|
|
98
|
-
for (const nextKey in nextSource) {
|
|
99
|
-
// Avoid bugs when hasOwnProperty is shadowed
|
|
100
|
-
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
|
101
|
-
to[nextKey] = nextSource[nextKey];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return to;
|
|
107
|
-
}
|
|
108
|
-
function unique(arr) {
|
|
109
|
-
const temp = {};
|
|
110
|
-
for (const e of arr) {
|
|
111
|
-
temp[e] = true;
|
|
112
|
-
}
|
|
113
|
-
const r = [];
|
|
114
|
-
for (const k in temp) {
|
|
115
|
-
// Avoid bugs when hasOwnProperty is shadowed
|
|
116
|
-
if (Object.prototype.hasOwnProperty.call(temp, k)) {
|
|
117
|
-
r.push(k);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return r;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Resolve required file
|
|
124
|
-
*/
|
|
125
|
-
function resolveRequiredFile(symbol, key, fileName, objectName) {
|
|
126
|
-
const sourceFile = getSourceFile(symbol);
|
|
127
|
-
const requiredFilePath = /^[.\/]+/.test(fileName)
|
|
128
|
-
? fileName === "."
|
|
129
|
-
? path.resolve(sourceFile.fileName)
|
|
130
|
-
: path.resolve(path.dirname(sourceFile.fileName), fileName)
|
|
131
|
-
: fileName;
|
|
132
|
-
const requiredFile = require(requiredFilePath);
|
|
133
|
-
if (!requiredFile) {
|
|
134
|
-
throw Error("Required: File couldn't be loaded");
|
|
135
|
-
}
|
|
136
|
-
const requiredObject = objectName
|
|
137
|
-
? requiredFile[objectName]
|
|
138
|
-
: requiredFile.default;
|
|
139
|
-
if (requiredObject === undefined) {
|
|
140
|
-
throw Error("Required: Variable is undefined");
|
|
141
|
-
}
|
|
142
|
-
if (typeof requiredObject === "function") {
|
|
143
|
-
throw Error("Required: Can't use function as a variable");
|
|
144
|
-
}
|
|
145
|
-
if (key === "examples" && !Array.isArray(requiredObject)) {
|
|
146
|
-
throw Error("Required: Variable isn't an array");
|
|
147
|
-
}
|
|
148
|
-
return requiredObject;
|
|
149
|
-
}
|
|
150
|
-
function regexRequire(value) {
|
|
151
|
-
return REGEX_REQUIRE.exec(value);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Try to parse a value and returns the string if it fails.
|
|
155
|
-
*/
|
|
156
|
-
function parseValue(symbol, key, value) {
|
|
157
|
-
const match = regexRequire(value);
|
|
158
|
-
if (match) {
|
|
159
|
-
const fileName = match[2].substring(1, match[2].length - 2).trim();
|
|
160
|
-
const objectName = match[4];
|
|
161
|
-
return resolveRequiredFile(symbol, key, fileName, objectName);
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
return JSON.parse(value);
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
return value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
function extractLiteralValue(typ) {
|
|
171
|
-
let str = typ.value;
|
|
172
|
-
if (str === undefined) {
|
|
173
|
-
str = typ.text;
|
|
174
|
-
}
|
|
175
|
-
if (typ.flags & ts.TypeFlags.StringLiteral) {
|
|
176
|
-
return str;
|
|
177
|
-
}
|
|
178
|
-
else if (typ.flags & ts.TypeFlags.BooleanLiteral) {
|
|
179
|
-
return typ.intrinsicName === "true";
|
|
180
|
-
}
|
|
181
|
-
else if (typ.flags & ts.TypeFlags.EnumLiteral) {
|
|
182
|
-
// or .text for old TS
|
|
183
|
-
const num = parseFloat(str);
|
|
184
|
-
return isNaN(num) ? str : num;
|
|
185
|
-
}
|
|
186
|
-
else if (typ.flags & ts.TypeFlags.NumberLiteral) {
|
|
187
|
-
return parseFloat(str);
|
|
188
|
-
}
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Checks whether a type is a tuple type.
|
|
193
|
-
*/
|
|
194
|
-
function resolveTupleType(propertyType) {
|
|
195
|
-
if (!propertyType.getSymbol() &&
|
|
196
|
-
propertyType.getFlags() & ts.TypeFlags.Object &&
|
|
197
|
-
propertyType.objectFlags & ts.ObjectFlags.Reference) {
|
|
198
|
-
return propertyType.target;
|
|
199
|
-
}
|
|
200
|
-
if (!(propertyType.getFlags() & ts.TypeFlags.Object &&
|
|
201
|
-
propertyType.objectFlags & ts.ObjectFlags.Tuple)) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
return propertyType;
|
|
205
|
-
}
|
|
206
|
-
const simpleTypesAllowedProperties = {
|
|
207
|
-
type: true,
|
|
208
|
-
description: true,
|
|
209
|
-
};
|
|
210
|
-
function addSimpleType(def, type) {
|
|
211
|
-
for (const k in def) {
|
|
212
|
-
if (!simpleTypesAllowedProperties[k]) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (!def.type) {
|
|
217
|
-
def.type = type;
|
|
218
|
-
}
|
|
219
|
-
else if (typeof def.type !== "string") {
|
|
220
|
-
if (!def.type.every((val) => {
|
|
221
|
-
return typeof val === "string";
|
|
222
|
-
})) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
if (def.type.indexOf("null") === -1) {
|
|
226
|
-
def.type.push("null");
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
if (typeof def.type !== "string") {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
if (def.type !== "null") {
|
|
234
|
-
def.type = [def.type, "null"];
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return true;
|
|
238
|
-
}
|
|
239
|
-
function makeNullable(def) {
|
|
240
|
-
if (!addSimpleType(def, "null")) {
|
|
241
|
-
const union = def.oneOf || def.anyOf;
|
|
242
|
-
if (union) {
|
|
243
|
-
union.push({ type: "null" });
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
const subdef = {};
|
|
247
|
-
for (var k in def) {
|
|
248
|
-
if (def.hasOwnProperty(k)) {
|
|
249
|
-
subdef[k] = def[k];
|
|
250
|
-
delete def[k];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
def.anyOf = [subdef, { type: "null" }];
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return def;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Given a Symbol, returns a canonical Definition. That can be either:
|
|
260
|
-
* 1) The Symbol's valueDeclaration parameter if defined, or
|
|
261
|
-
* 2) The sole entry in the Symbol's declarations array, provided that array has a length of 1.
|
|
262
|
-
*
|
|
263
|
-
* valueDeclaration is listed as a required parameter in the definition of a Symbol, but I've
|
|
264
|
-
* experienced crashes when it's undefined at runtime, which is the reason for this function's
|
|
265
|
-
* existence. Not sure if that's a compiler API bug or what.
|
|
266
|
-
*/
|
|
267
|
-
function getCanonicalDeclaration(sym) {
|
|
268
|
-
if (sym.valueDeclaration !== undefined) {
|
|
269
|
-
return sym.valueDeclaration;
|
|
270
|
-
}
|
|
271
|
-
else if (sym.declarations?.length === 1) {
|
|
272
|
-
return sym.declarations[0];
|
|
273
|
-
}
|
|
274
|
-
const declarationCount = sym.declarations?.length ?? 0;
|
|
275
|
-
throw new Error(`Symbol "${sym.name}" has no valueDeclaration and ${declarationCount} declarations.`);
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Given a Symbol, finds the place it was declared and chases parent pointers until we find a
|
|
279
|
-
* node where SyntaxKind === SourceFile.
|
|
280
|
-
*/
|
|
281
|
-
function getSourceFile(sym) {
|
|
282
|
-
let currentDecl = getCanonicalDeclaration(sym);
|
|
283
|
-
while (currentDecl.kind !== ts.SyntaxKind.SourceFile) {
|
|
284
|
-
if (currentDecl.parent === undefined) {
|
|
285
|
-
throw new Error(`Unable to locate source file for declaration "${sym.name}".`);
|
|
286
|
-
}
|
|
287
|
-
currentDecl = currentDecl.parent;
|
|
288
|
-
}
|
|
289
|
-
return currentDecl;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* JSDoc keywords that should be used to annotate the JSON schema.
|
|
293
|
-
*
|
|
294
|
-
* Many of these validation keywords are defined here: http://json-schema.org/latest/json-schema-validation.html
|
|
295
|
-
*/
|
|
296
|
-
// prettier-ignore
|
|
297
|
-
const validationKeywords = {
|
|
298
|
-
multipleOf: true, // 6.1.
|
|
299
|
-
maximum: true, // 6.2.
|
|
300
|
-
exclusiveMaximum: true, // 6.3.
|
|
301
|
-
minimum: true, // 6.4.
|
|
302
|
-
exclusiveMinimum: true, // 6.5.
|
|
303
|
-
maxLength: true, // 6.6.
|
|
304
|
-
minLength: true, // 6.7.
|
|
305
|
-
pattern: true, // 6.8.
|
|
306
|
-
items: true, // 6.9.
|
|
307
|
-
// additionalItems: true, // 6.10.
|
|
308
|
-
maxItems: true, // 6.11.
|
|
309
|
-
minItems: true, // 6.12.
|
|
310
|
-
uniqueItems: true, // 6.13.
|
|
311
|
-
contains: true, // 6.14.
|
|
312
|
-
maxProperties: true, // 6.15.
|
|
313
|
-
minProperties: true, // 6.16.
|
|
314
|
-
// required: true, // 6.17. This is not required. It is auto-generated.
|
|
315
|
-
// properties: true, // 6.18. This is not required. It is auto-generated.
|
|
316
|
-
// patternProperties: true, // 6.19.
|
|
317
|
-
additionalProperties: true, // 6.20.
|
|
318
|
-
// dependencies: true, // 6.21.
|
|
319
|
-
// propertyNames: true, // 6.22.
|
|
320
|
-
enum: true, // 6.23.
|
|
321
|
-
// const: true, // 6.24.
|
|
322
|
-
type: true, // 6.25.
|
|
323
|
-
// allOf: true, // 6.26.
|
|
324
|
-
// anyOf: true, // 6.27.
|
|
325
|
-
// oneOf: true, // 6.28.
|
|
326
|
-
// not: true, // 6.29.
|
|
327
|
-
examples: true, // Draft 6 (draft-handrews-json-schema-validation-01)
|
|
328
|
-
ignore: true,
|
|
329
|
-
description: true,
|
|
330
|
-
format: true,
|
|
331
|
-
default: true,
|
|
332
|
-
$ref: true,
|
|
333
|
-
id: true,
|
|
334
|
-
$id: true,
|
|
335
|
-
$comment: true,
|
|
336
|
-
title: true
|
|
337
|
-
};
|
|
338
|
-
/**
|
|
339
|
-
* Subset of descriptive, non-type keywords that are permitted alongside a $ref.
|
|
340
|
-
* Prior to JSON Schema draft 2019-09, $ref is a special keyword that doesn't
|
|
341
|
-
* permit keywords alongside it, and so AJV may raise warnings if it encounters
|
|
342
|
-
* any type-related keywords; see https://github.com/ajv-validator/ajv/issues/1121
|
|
343
|
-
*/
|
|
344
|
-
const annotationKeywords = {
|
|
345
|
-
description: true,
|
|
346
|
-
default: true,
|
|
347
|
-
examples: true,
|
|
348
|
-
title: true,
|
|
349
|
-
// A JSDoc $ref annotation can appear as a $ref.
|
|
350
|
-
$ref: true,
|
|
351
|
-
};
|
|
352
|
-
const subDefinitions = {
|
|
353
|
-
items: true,
|
|
354
|
-
additionalProperties: true,
|
|
355
|
-
contains: true,
|
|
356
|
-
};
|
|
357
|
-
class JsonSchemaGenerator {
|
|
358
|
-
args;
|
|
359
|
-
tc;
|
|
360
|
-
/**
|
|
361
|
-
* Holds all symbols within a custom SymbolRef object, containing useful
|
|
362
|
-
* information.
|
|
363
|
-
*/
|
|
364
|
-
symbols;
|
|
365
|
-
/**
|
|
366
|
-
* All types for declarations of classes, interfaces, enums, and type aliases
|
|
367
|
-
* defined in all TS files.
|
|
368
|
-
*/
|
|
369
|
-
allSymbols;
|
|
370
|
-
/**
|
|
371
|
-
* All symbols for declarations of classes, interfaces, enums, and type aliases
|
|
372
|
-
* defined in non-default-lib TS files.
|
|
373
|
-
*/
|
|
374
|
-
userSymbols;
|
|
375
|
-
/**
|
|
376
|
-
* Maps from the names of base types to the names of the types that inherit from
|
|
377
|
-
* them.
|
|
378
|
-
*/
|
|
379
|
-
inheritingTypes;
|
|
380
|
-
/**
|
|
381
|
-
* This map holds references to all reffed definitions, including schema
|
|
382
|
-
* overrides and generated definitions.
|
|
383
|
-
*/
|
|
384
|
-
reffedDefinitions = {};
|
|
385
|
-
/**
|
|
386
|
-
* This map only holds explicit schema overrides. This helps differentiate between
|
|
387
|
-
* user defined schema overrides and generated definitions.
|
|
388
|
-
*/
|
|
389
|
-
schemaOverrides = new Map();
|
|
390
|
-
/**
|
|
391
|
-
* This is a set of all the user-defined validation keywords.
|
|
392
|
-
*/
|
|
393
|
-
userValidationKeywords;
|
|
394
|
-
/**
|
|
395
|
-
* If true, this makes constants be defined as enums with a single value. This is useful
|
|
396
|
-
* for cases where constant values are not supported, such as OpenAPI.
|
|
397
|
-
*/
|
|
398
|
-
constAsEnum;
|
|
399
|
-
/**
|
|
400
|
-
* Types are assigned names which are looked up by their IDs. This is the
|
|
401
|
-
* map from type IDs to type names.
|
|
402
|
-
*/
|
|
403
|
-
typeNamesById = {};
|
|
404
|
-
/**
|
|
405
|
-
* Whenever a type is assigned its name, its entry in this dictionary is set,
|
|
406
|
-
* so that we don't give the same name to two separate types.
|
|
407
|
-
*/
|
|
408
|
-
typeIdsByName = {};
|
|
409
|
-
constructor(symbols, allSymbols, userSymbols, inheritingTypes, tc, args = getDefaultArgs()) {
|
|
410
|
-
this.args = args;
|
|
411
|
-
this.symbols = symbols;
|
|
412
|
-
this.allSymbols = allSymbols;
|
|
413
|
-
this.userSymbols = userSymbols;
|
|
414
|
-
this.inheritingTypes = inheritingTypes;
|
|
415
|
-
this.tc = tc;
|
|
416
|
-
this.userValidationKeywords = args.validationKeywords.reduce((acc, word) => ({ ...acc, [word]: true }), {});
|
|
417
|
-
this.constAsEnum = args.constAsEnum;
|
|
418
|
-
}
|
|
419
|
-
get ReffedDefinitions() {
|
|
420
|
-
return this.reffedDefinitions;
|
|
421
|
-
}
|
|
422
|
-
isFromDefaultLib(symbol) {
|
|
423
|
-
const declarations = symbol.getDeclarations();
|
|
424
|
-
if (declarations && declarations.length > 0 && declarations[0].parent) {
|
|
425
|
-
return declarations[0].parent.getSourceFile().hasNoDefaultLib;
|
|
426
|
-
}
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
resetSchemaSpecificProperties(includeAllOverrides = false) {
|
|
430
|
-
this.reffedDefinitions = {};
|
|
431
|
-
this.typeIdsByName = {};
|
|
432
|
-
this.typeNamesById = {};
|
|
433
|
-
// restore schema overrides
|
|
434
|
-
if (includeAllOverrides) {
|
|
435
|
-
this.schemaOverrides.forEach((value, key) => {
|
|
436
|
-
this.reffedDefinitions[key] = value;
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Parse the comments of a symbol into the definition and other annotations.
|
|
442
|
-
*/
|
|
443
|
-
parseCommentsIntoDefinition(symbol, definition, otherAnnotations) {
|
|
444
|
-
if (!symbol) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (!this.isFromDefaultLib(symbol)) {
|
|
448
|
-
// the comments for a symbol
|
|
449
|
-
const comments = symbol.getDocumentationComment(this.tc);
|
|
450
|
-
if (comments.length) {
|
|
451
|
-
definition.description = comments
|
|
452
|
-
.map((comment) => {
|
|
453
|
-
const newlineNormalizedComment = comment.text.replace(/\r\n/g, "\n");
|
|
454
|
-
// If a comment contains a "{@link XYZ}" inline tag that could not be
|
|
455
|
-
// resolved by the TS checker, then this comment will contain a trailing
|
|
456
|
-
// whitespace that we need to remove.
|
|
457
|
-
if (comment.kind === "linkText") {
|
|
458
|
-
return newlineNormalizedComment.trim();
|
|
459
|
-
}
|
|
460
|
-
return newlineNormalizedComment;
|
|
461
|
-
})
|
|
462
|
-
.join("")
|
|
463
|
-
.trim();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
// jsdocs are separate from comments
|
|
467
|
-
const jsdocs = symbol.getJsDocTags();
|
|
468
|
-
jsdocs.forEach((doc) => {
|
|
469
|
-
// if we have @TJS-... annotations, we have to parse them
|
|
470
|
-
let name = doc.name;
|
|
471
|
-
const originalText = doc.text ? doc.text.map((t) => t.text).join("") : "";
|
|
472
|
-
let text = originalText;
|
|
473
|
-
// In TypeScript versions prior to 3.7, it stops parsing the annotation
|
|
474
|
-
// at the first non-alphanumeric character and puts the rest of the line as the
|
|
475
|
-
// "text" of the annotation, so we have a little hack to check for the name
|
|
476
|
-
// "TJS" and then we sort of re-parse the annotation to support prior versions
|
|
477
|
-
// of TypeScript.
|
|
478
|
-
if (name.startsWith("TJS-")) {
|
|
479
|
-
name = name.slice(4);
|
|
480
|
-
if (!text) {
|
|
481
|
-
text = "true";
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
else if (name === "TJS" && text.startsWith("-")) {
|
|
485
|
-
let match = new RegExp(REGEX_TJS_JSDOC).exec(originalText);
|
|
486
|
-
if (match) {
|
|
487
|
-
name = match[1];
|
|
488
|
-
text = match[2];
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
// Treat empty text as boolean true
|
|
492
|
-
name = text.replace(/^[\s\-]+/, "");
|
|
493
|
-
text = "true";
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
// In TypeScript ~3.5, the annotation name splits at the dot character so we have
|
|
497
|
-
// to process the "." and beyond from the value
|
|
498
|
-
if (subDefinitions[name]) {
|
|
499
|
-
const match = new RegExp(REGEX_GROUP_JSDOC).exec(text);
|
|
500
|
-
if (match) {
|
|
501
|
-
const k = match[1];
|
|
502
|
-
const v = match[2];
|
|
503
|
-
definition[name] = {
|
|
504
|
-
...definition[name],
|
|
505
|
-
[k]: v ? parseValue(symbol, k, v) : true,
|
|
506
|
-
};
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// In TypeScript 3.7+, the "." is kept as part of the annotation name
|
|
511
|
-
if (name.includes(".")) {
|
|
512
|
-
const parts = name.split(".");
|
|
513
|
-
const key = parts[0];
|
|
514
|
-
if (parts.length === 2 && subDefinitions[key]) {
|
|
515
|
-
definition[key] = {
|
|
516
|
-
...definition[key],
|
|
517
|
-
[parts[1]]: text ? parseValue(symbol, name, text) : true,
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (validationKeywords[name] ||
|
|
522
|
-
this.userValidationKeywords[name]) {
|
|
523
|
-
definition[name] =
|
|
524
|
-
text === undefined ? "" : parseValue(symbol, name, text);
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
// special annotations
|
|
528
|
-
otherAnnotations[doc.name] = true;
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
getDefinitionForRootType(propertyType, reffedType, definition, defaultNumberType = this.args.defaultNumberType, ignoreUndefined = false) {
|
|
533
|
-
const tupleType = resolveTupleType(propertyType);
|
|
534
|
-
if (tupleType) {
|
|
535
|
-
// tuple
|
|
536
|
-
const elemTypes = propertyType
|
|
537
|
-
.typeArguments;
|
|
538
|
-
const fixedTypes = elemTypes.map((elType) => this.getTypeDefinition(elType));
|
|
539
|
-
definition.type = "array";
|
|
540
|
-
if (fixedTypes.length > 0) {
|
|
541
|
-
definition.items = fixedTypes;
|
|
542
|
-
}
|
|
543
|
-
const targetTupleType = propertyType.target;
|
|
544
|
-
definition.minItems = targetTupleType.minLength;
|
|
545
|
-
if (targetTupleType.hasRestElement) {
|
|
546
|
-
definition.additionalItems = fixedTypes[fixedTypes.length - 1];
|
|
547
|
-
fixedTypes.splice(fixedTypes.length - 1, 1);
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
definition.maxItems = targetTupleType.fixedLength;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
const propertyTypeString = this.tc.typeToString(propertyType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
|
|
555
|
-
const flags = propertyType.flags;
|
|
556
|
-
const arrayType = this.tc.getIndexTypeOfType(propertyType, ts.IndexKind.Number);
|
|
557
|
-
if (flags & ts.TypeFlags.String) {
|
|
558
|
-
definition.type = "string";
|
|
559
|
-
}
|
|
560
|
-
else if (flags & ts.TypeFlags.Number) {
|
|
561
|
-
const isInteger = definition.type === "integer" ||
|
|
562
|
-
reffedType?.getName() === "integer" ||
|
|
563
|
-
defaultNumberType === "integer";
|
|
564
|
-
definition.type = isInteger ? "integer" : "number";
|
|
565
|
-
}
|
|
566
|
-
else if (flags & ts.TypeFlags.Boolean) {
|
|
567
|
-
definition.type = "boolean";
|
|
568
|
-
}
|
|
569
|
-
else if (flags & ts.TypeFlags.ESSymbol) {
|
|
570
|
-
definition.type = "object";
|
|
571
|
-
}
|
|
572
|
-
else if (flags & ts.TypeFlags.Null) {
|
|
573
|
-
definition.type = "null";
|
|
574
|
-
}
|
|
575
|
-
else if (flags & ts.TypeFlags.Undefined ||
|
|
576
|
-
propertyTypeString === "void") {
|
|
577
|
-
if (!ignoreUndefined) {
|
|
578
|
-
throw new Error("Not supported: root type undefined");
|
|
579
|
-
}
|
|
580
|
-
// will be deleted
|
|
581
|
-
definition.type = "undefined";
|
|
582
|
-
}
|
|
583
|
-
else if (flags & ts.TypeFlags.Any || flags & ts.TypeFlags.Unknown) {
|
|
584
|
-
// no type restriction, so that anything will match
|
|
585
|
-
}
|
|
586
|
-
else if (propertyTypeString === "Date" && !this.args.rejectDateType) {
|
|
587
|
-
definition.type = "string";
|
|
588
|
-
definition.format = definition.format || "date-time";
|
|
589
|
-
}
|
|
590
|
-
else if (propertyTypeString === "object") {
|
|
591
|
-
definition.type = "object";
|
|
592
|
-
definition.properties = {};
|
|
593
|
-
definition.additionalProperties = true;
|
|
594
|
-
}
|
|
595
|
-
else if (propertyTypeString === "bigint") {
|
|
596
|
-
definition.type = "number";
|
|
597
|
-
definition.properties = {};
|
|
598
|
-
definition.additionalProperties = false;
|
|
599
|
-
}
|
|
600
|
-
else {
|
|
601
|
-
const value = extractLiteralValue(propertyType);
|
|
602
|
-
if (value !== undefined) {
|
|
603
|
-
// typeof value can be: "string", "boolean", "number", or "object" if value is null
|
|
604
|
-
const typeofValue = typeof value;
|
|
605
|
-
switch (typeofValue) {
|
|
606
|
-
case "string":
|
|
607
|
-
case "boolean":
|
|
608
|
-
definition.type = typeofValue;
|
|
609
|
-
break;
|
|
610
|
-
case "number":
|
|
611
|
-
definition.type = this.args.defaultNumberType;
|
|
612
|
-
break;
|
|
613
|
-
case "object":
|
|
614
|
-
definition.type = "null";
|
|
615
|
-
break;
|
|
616
|
-
default:
|
|
617
|
-
throw new Error(`Not supported: ${value} as a enum value`);
|
|
618
|
-
}
|
|
619
|
-
if (this.constAsEnum) {
|
|
620
|
-
definition.enum = [value];
|
|
621
|
-
}
|
|
622
|
-
else {
|
|
623
|
-
definition.const = value;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
else if (arrayType !== undefined) {
|
|
627
|
-
if (propertyType.flags & ts.TypeFlags.Object &&
|
|
628
|
-
propertyType.objectFlags &
|
|
629
|
-
(ts.ObjectFlags.Anonymous |
|
|
630
|
-
ts.ObjectFlags.Interface |
|
|
631
|
-
ts.ObjectFlags.Mapped)) {
|
|
632
|
-
definition.type = "object";
|
|
633
|
-
definition.additionalProperties = false;
|
|
634
|
-
definition.patternProperties = {
|
|
635
|
-
[NUMERIC_INDEX_PATTERN]: this.getTypeDefinition(arrayType),
|
|
636
|
-
};
|
|
637
|
-
if (!!Array.from(propertyType.members)?.find((member) => member[0] !== "__index")) {
|
|
638
|
-
this.getClassDefinition(propertyType, definition);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
else if (propertyType.flags & ts.TypeFlags.TemplateLiteral) {
|
|
642
|
-
definition.type = "string";
|
|
643
|
-
// @ts-ignore
|
|
644
|
-
const { texts, types } = propertyType;
|
|
645
|
-
const pattern = [];
|
|
646
|
-
for (let i = 0; i < texts.length; i++) {
|
|
647
|
-
const text = texts[i].replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
648
|
-
const type = types[i];
|
|
649
|
-
if (i === 0) {
|
|
650
|
-
pattern.push(`^`);
|
|
651
|
-
}
|
|
652
|
-
if (type) {
|
|
653
|
-
if (type.flags & ts.TypeFlags.String) {
|
|
654
|
-
pattern.push(`${text}.*`);
|
|
655
|
-
}
|
|
656
|
-
if (type.flags & ts.TypeFlags.Number ||
|
|
657
|
-
type.flags & ts.TypeFlags.BigInt) {
|
|
658
|
-
pattern.push(`${text}[0-9]*`);
|
|
659
|
-
}
|
|
660
|
-
if (type.flags & ts.TypeFlags.Undefined) {
|
|
661
|
-
pattern.push(`${text}undefined`);
|
|
662
|
-
}
|
|
663
|
-
if (type.flags & ts.TypeFlags.Null) {
|
|
664
|
-
pattern.push(`${text}null`);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
if (i === texts.length - 1) {
|
|
668
|
-
pattern.push(`${text}$`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
definition.pattern = pattern.join("");
|
|
672
|
-
}
|
|
673
|
-
else {
|
|
674
|
-
definition.type = "array";
|
|
675
|
-
if (!definition.items) {
|
|
676
|
-
definition.items = this.getTypeDefinition(arrayType);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
// Report that type could not be processed
|
|
682
|
-
const error = new TypeError("Unsupported type: " + propertyTypeString);
|
|
683
|
-
error.type = propertyType;
|
|
684
|
-
throw error;
|
|
685
|
-
// definition = this.getTypeDefinition(propertyType, tc);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
return definition;
|
|
690
|
-
}
|
|
691
|
-
getReferencedTypeSymbol(prop) {
|
|
692
|
-
const decl = prop.getDeclarations();
|
|
693
|
-
if (decl?.length) {
|
|
694
|
-
const type = decl[0].type;
|
|
695
|
-
if (type && type.kind & ts.SyntaxKind.TypeReference && type.typeName) {
|
|
696
|
-
const symbol = this.tc.getSymbolAtLocation(type.typeName);
|
|
697
|
-
if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
|
|
698
|
-
return this.tc.getAliasedSymbol(symbol);
|
|
699
|
-
}
|
|
700
|
-
return symbol;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
return undefined;
|
|
704
|
-
}
|
|
705
|
-
getDefinitionForProperty(prop, node) {
|
|
706
|
-
if (prop.flags & ts.SymbolFlags.Method) {
|
|
707
|
-
return null;
|
|
708
|
-
}
|
|
709
|
-
const propertyName = prop.getName();
|
|
710
|
-
const propertyType = this.tc.getTypeOfSymbolAtLocation(prop, node);
|
|
711
|
-
const reffedType = this.getReferencedTypeSymbol(prop);
|
|
712
|
-
const definition = this.getTypeDefinition(propertyType, undefined, undefined, prop, reffedType);
|
|
713
|
-
if (this.args.titles) {
|
|
714
|
-
definition.title = propertyName;
|
|
715
|
-
}
|
|
716
|
-
if (definition.hasOwnProperty("ignore")) {
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
// try to get default value
|
|
720
|
-
const valDecl = prop.valueDeclaration;
|
|
721
|
-
if (valDecl?.initializer) {
|
|
722
|
-
let initial = valDecl.initializer;
|
|
723
|
-
while (ts.isTypeAssertionExpression(initial)) {
|
|
724
|
-
initial = initial.expression;
|
|
725
|
-
}
|
|
726
|
-
if (initial.expression) {
|
|
727
|
-
// node
|
|
728
|
-
console.warn("initializer is expression for property " + propertyName);
|
|
729
|
-
}
|
|
730
|
-
else if (initial.kind &&
|
|
731
|
-
initial.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
732
|
-
definition.default = initial.getText();
|
|
733
|
-
}
|
|
734
|
-
else {
|
|
735
|
-
try {
|
|
736
|
-
const sandbox = { sandboxvar: null };
|
|
737
|
-
vm.runInNewContext("sandboxvar=" + initial.getText(), sandbox);
|
|
738
|
-
const val = sandbox.sandboxvar;
|
|
739
|
-
if (val === null ||
|
|
740
|
-
typeof val === "string" ||
|
|
741
|
-
typeof val === "number" ||
|
|
742
|
-
typeof val === "boolean" ||
|
|
743
|
-
Object.prototype.toString.call(val) === "[object Array]") {
|
|
744
|
-
definition.default = val;
|
|
745
|
-
}
|
|
746
|
-
else if (val) {
|
|
747
|
-
console.warn("unknown initializer for property " + propertyName + ": " + val);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
catch (e) {
|
|
751
|
-
console.warn("exception evaluating initializer for property " + propertyName);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return definition;
|
|
756
|
-
}
|
|
757
|
-
getEnumDefinition(clazzType, definition) {
|
|
758
|
-
const node = clazzType.getSymbol().getDeclarations()[0];
|
|
759
|
-
const fullName = this.tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
|
|
760
|
-
const members = node.kind === ts.SyntaxKind.EnumDeclaration
|
|
761
|
-
? node.members
|
|
762
|
-
: ts.factory.createNodeArray([node]);
|
|
763
|
-
var enumValues = [];
|
|
764
|
-
const enumTypes = [];
|
|
765
|
-
const addType = (type) => {
|
|
766
|
-
if (enumTypes.indexOf(type) === -1) {
|
|
767
|
-
enumTypes.push(type);
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
members.forEach((member) => {
|
|
771
|
-
const caseLabel = member.name.text;
|
|
772
|
-
const constantValue = this.tc.getConstantValue(member);
|
|
773
|
-
if (constantValue !== undefined) {
|
|
774
|
-
enumValues.push(constantValue);
|
|
775
|
-
addType(typeof constantValue); // can be only string or number;
|
|
776
|
-
}
|
|
777
|
-
else {
|
|
778
|
-
// try to extract the enums value; it will probably by a cast expression
|
|
779
|
-
const initial = member.initializer;
|
|
780
|
-
if (initial) {
|
|
781
|
-
if (initial.expression) {
|
|
782
|
-
// node
|
|
783
|
-
const exp = initial.expression;
|
|
784
|
-
const text = exp.text;
|
|
785
|
-
// if it is an expression with a text literal, chances are it is the enum convention:
|
|
786
|
-
// CASELABEL = 'literal' as any
|
|
787
|
-
if (text) {
|
|
788
|
-
enumValues.push(text);
|
|
789
|
-
addType("string");
|
|
790
|
-
}
|
|
791
|
-
else if (exp.kind === ts.SyntaxKind.TrueKeyword ||
|
|
792
|
-
exp.kind === ts.SyntaxKind.FalseKeyword) {
|
|
793
|
-
enumValues.push(exp.kind === ts.SyntaxKind.TrueKeyword);
|
|
794
|
-
addType("boolean");
|
|
795
|
-
}
|
|
796
|
-
else {
|
|
797
|
-
console.warn("initializer is expression for enum: " +
|
|
798
|
-
fullName +
|
|
799
|
-
"." +
|
|
800
|
-
caseLabel);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
else if (initial.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
804
|
-
enumValues.push(initial.getText());
|
|
805
|
-
addType("string");
|
|
806
|
-
}
|
|
807
|
-
else if (initial.kind === ts.SyntaxKind.NullKeyword) {
|
|
808
|
-
enumValues.push(null);
|
|
809
|
-
addType("null");
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
if (enumTypes.length) {
|
|
815
|
-
definition.type = enumTypes.length === 1 ? enumTypes[0] : enumTypes;
|
|
816
|
-
}
|
|
817
|
-
if (enumValues.length > 0) {
|
|
818
|
-
if (enumValues.length > 1) {
|
|
819
|
-
definition.enum = enumValues;
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
definition.const = enumValues[0];
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return definition;
|
|
826
|
-
}
|
|
827
|
-
getUnionDefinition(unionType, unionModifier, definition) {
|
|
828
|
-
const enumValues = [];
|
|
829
|
-
const simpleTypes = [];
|
|
830
|
-
const schemas = [];
|
|
831
|
-
const pushSimpleType = (type) => {
|
|
832
|
-
if (simpleTypes.indexOf(type) === -1) {
|
|
833
|
-
simpleTypes.push(type);
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
const pushEnumValue = (val) => {
|
|
837
|
-
if (enumValues.indexOf(val) === -1) {
|
|
838
|
-
enumValues.push(val);
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
for (const valueType of unionType.types) {
|
|
842
|
-
const value = extractLiteralValue(valueType);
|
|
843
|
-
if (value !== undefined) {
|
|
844
|
-
pushEnumValue(value);
|
|
845
|
-
}
|
|
846
|
-
else {
|
|
847
|
-
const symbol = valueType.aliasSymbol;
|
|
848
|
-
const def = this.getTypeDefinition(valueType, undefined, undefined, symbol, symbol, undefined, undefined, true);
|
|
849
|
-
if (def.type === "undefined") {
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
const keys = Object.keys(def);
|
|
853
|
-
if (keys.length === 1 && keys[0] === "type") {
|
|
854
|
-
if (typeof def.type !== "string") {
|
|
855
|
-
console.error("Expected only a simple type.");
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
pushSimpleType(def.type);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
schemas.push(def);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
if (enumValues.length > 0) {
|
|
867
|
-
// If the values are true and false, just add "boolean" as simple type
|
|
868
|
-
const isOnlyBooleans = enumValues.length === 2 &&
|
|
869
|
-
typeof enumValues[0] === "boolean" &&
|
|
870
|
-
typeof enumValues[1] === "boolean" &&
|
|
871
|
-
enumValues[0] !== enumValues[1];
|
|
872
|
-
if (isOnlyBooleans) {
|
|
873
|
-
pushSimpleType("boolean");
|
|
874
|
-
}
|
|
875
|
-
else {
|
|
876
|
-
const enumSchema = enumValues.length > 1
|
|
877
|
-
? { enum: enumValues.sort() }
|
|
878
|
-
: { const: enumValues[0] };
|
|
879
|
-
// If all values are of the same primitive type, add a "type" field to the schema
|
|
880
|
-
if (enumValues.every((x) => {
|
|
881
|
-
return typeof x === "string";
|
|
882
|
-
})) {
|
|
883
|
-
enumSchema.type = "string";
|
|
884
|
-
}
|
|
885
|
-
else if (enumValues.every((x) => {
|
|
886
|
-
return typeof x === "number";
|
|
887
|
-
})) {
|
|
888
|
-
enumSchema.type = "number";
|
|
889
|
-
}
|
|
890
|
-
else if (enumValues.every((x) => {
|
|
891
|
-
return typeof x === "boolean";
|
|
892
|
-
})) {
|
|
893
|
-
enumSchema.type = "boolean";
|
|
894
|
-
}
|
|
895
|
-
schemas.push(enumSchema);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
if (simpleTypes.length > 0) {
|
|
899
|
-
schemas.push({
|
|
900
|
-
type: simpleTypes.length === 1 ? simpleTypes[0] : simpleTypes,
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
if (schemas.length === 1) {
|
|
904
|
-
for (const k in schemas[0]) {
|
|
905
|
-
if (schemas[0].hasOwnProperty(k)) {
|
|
906
|
-
if (k === "description" && definition.hasOwnProperty(k)) {
|
|
907
|
-
// If we already have a more specific description, don't overwrite it.
|
|
908
|
-
continue;
|
|
909
|
-
}
|
|
910
|
-
definition[k] =
|
|
911
|
-
schemas[0][k];
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
definition[unionModifier] = schemas;
|
|
917
|
-
}
|
|
918
|
-
return definition;
|
|
919
|
-
}
|
|
920
|
-
getIntersectionDefinition(intersectionType, definition) {
|
|
921
|
-
const simpleTypes = [];
|
|
922
|
-
const schemas = [];
|
|
923
|
-
const pushSimpleType = (type) => {
|
|
924
|
-
if (simpleTypes.indexOf(type) === -1) {
|
|
925
|
-
simpleTypes.push(type);
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
for (const intersectionMember of intersectionType.types) {
|
|
929
|
-
const def = this.getTypeDefinition(intersectionMember);
|
|
930
|
-
const keys = Object.keys(def);
|
|
931
|
-
if (keys.length === 1 && keys[0] === "type") {
|
|
932
|
-
if (typeof def.type !== "string") {
|
|
933
|
-
console.error("Expected only a simple type.");
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
pushSimpleType(def.type);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
else {
|
|
940
|
-
schemas.push(def);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
if (simpleTypes.length > 0) {
|
|
944
|
-
schemas.push({
|
|
945
|
-
type: simpleTypes.length === 1 ? simpleTypes[0] : simpleTypes,
|
|
946
|
-
});
|
|
947
|
-
}
|
|
948
|
-
if (schemas.length === 1) {
|
|
949
|
-
for (const k in schemas[0]) {
|
|
950
|
-
if (schemas[0].hasOwnProperty(k)) {
|
|
951
|
-
definition[k] =
|
|
952
|
-
schemas[0][k];
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
definition.allOf = schemas;
|
|
958
|
-
}
|
|
959
|
-
return definition;
|
|
960
|
-
}
|
|
961
|
-
getClassDefinition(clazzType, definition) {
|
|
962
|
-
const node = clazzType.getSymbol().getDeclarations()[0];
|
|
963
|
-
// Example: typeof globalThis may not have any declaration
|
|
964
|
-
if (!node) {
|
|
965
|
-
definition.type = "object";
|
|
966
|
-
return definition;
|
|
967
|
-
}
|
|
968
|
-
if (this.args.typeOfKeyword && node.kind === ts.SyntaxKind.FunctionType) {
|
|
969
|
-
definition.typeof = "function";
|
|
970
|
-
return definition;
|
|
971
|
-
}
|
|
972
|
-
const clazz = node;
|
|
973
|
-
const props = this.tc.getPropertiesOfType(clazzType).filter((prop) => {
|
|
974
|
-
// filter never and undefined
|
|
975
|
-
const propertyFlagType = this.tc
|
|
976
|
-
.getTypeOfSymbolAtLocation(prop, node)
|
|
977
|
-
.getFlags();
|
|
978
|
-
if (ts.TypeFlags.Never === propertyFlagType ||
|
|
979
|
-
ts.TypeFlags.Undefined === propertyFlagType) {
|
|
980
|
-
return false;
|
|
981
|
-
}
|
|
982
|
-
if (!this.args.excludePrivate) {
|
|
983
|
-
return true;
|
|
984
|
-
}
|
|
985
|
-
const decls = prop.declarations;
|
|
986
|
-
return !(decls &&
|
|
987
|
-
decls.filter((decl) => {
|
|
988
|
-
const mods = decl.modifiers;
|
|
989
|
-
return (mods &&
|
|
990
|
-
mods.filter((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)
|
|
991
|
-
.length > 0);
|
|
992
|
-
}).length > 0);
|
|
993
|
-
});
|
|
994
|
-
const fullName = this.tc.typeToString(clazzType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
|
|
995
|
-
const modifierFlags = ts.getCombinedModifierFlags(node);
|
|
996
|
-
if (modifierFlags & ts.ModifierFlags.Abstract &&
|
|
997
|
-
this.inheritingTypes[fullName]) {
|
|
998
|
-
const oneOf = this.inheritingTypes[fullName].map((typename) => {
|
|
999
|
-
return this.getTypeDefinition(this.allSymbols[typename]);
|
|
1000
|
-
});
|
|
1001
|
-
definition.oneOf = oneOf;
|
|
1002
|
-
}
|
|
1003
|
-
else {
|
|
1004
|
-
if (clazz.members) {
|
|
1005
|
-
const indexSignatures = clazz.members == null
|
|
1006
|
-
? []
|
|
1007
|
-
: clazz.members.filter((x) => x.kind === ts.SyntaxKind.IndexSignature);
|
|
1008
|
-
if (indexSignatures.length === 1) {
|
|
1009
|
-
// for case "array-types"
|
|
1010
|
-
const indexSignature = indexSignatures[0];
|
|
1011
|
-
if (indexSignature.parameters.length !== 1) {
|
|
1012
|
-
throw new Error("Not supported: IndexSignatureDeclaration parameters.length != 1");
|
|
1013
|
-
}
|
|
1014
|
-
const indexSymbol = indexSignature.parameters[0]
|
|
1015
|
-
.symbol;
|
|
1016
|
-
const indexType = this.tc.getTypeOfSymbolAtLocation(indexSymbol, node);
|
|
1017
|
-
const isIndexedObject = indexType.flags === ts.TypeFlags.String ||
|
|
1018
|
-
indexType.flags === ts.TypeFlags.Number;
|
|
1019
|
-
if (indexType.flags !== ts.TypeFlags.Number && !isIndexedObject) {
|
|
1020
|
-
throw new Error("Not supported: IndexSignatureDeclaration with index symbol other than a number or a string");
|
|
1021
|
-
}
|
|
1022
|
-
const typ = this.tc.getTypeAtLocation(indexSignature.type);
|
|
1023
|
-
let def;
|
|
1024
|
-
if (typ.flags & ts.TypeFlags.IndexedAccess) {
|
|
1025
|
-
const targetName = ts.escapeLeadingUnderscores(clazzType.mapper?.target?.value);
|
|
1026
|
-
const indexedAccessType = typ;
|
|
1027
|
-
const symbols = indexedAccessType.objectType.members;
|
|
1028
|
-
const targetSymbol = symbols?.get(targetName);
|
|
1029
|
-
if (targetSymbol) {
|
|
1030
|
-
const targetNode = targetSymbol.getDeclarations()[0];
|
|
1031
|
-
const targetDef = this.getDefinitionForProperty(targetSymbol, targetNode);
|
|
1032
|
-
if (targetDef) {
|
|
1033
|
-
def = targetDef;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
if (!def) {
|
|
1038
|
-
def = this.getTypeDefinition(typ, undefined, "anyOf");
|
|
1039
|
-
}
|
|
1040
|
-
if (isIndexedObject) {
|
|
1041
|
-
definition.type = "object";
|
|
1042
|
-
if (!Object.keys(definition.patternProperties || {}).length) {
|
|
1043
|
-
definition.additionalProperties = def;
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
else {
|
|
1047
|
-
definition.type = "array";
|
|
1048
|
-
if (!definition.items) {
|
|
1049
|
-
definition.items = def;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
const propertyDefinitions = props.reduce((all, prop) => {
|
|
1055
|
-
const propertyName = prop.getName();
|
|
1056
|
-
const propDef = this.getDefinitionForProperty(prop, node);
|
|
1057
|
-
if (propDef != null) {
|
|
1058
|
-
all[propertyName] = propDef;
|
|
1059
|
-
}
|
|
1060
|
-
return all;
|
|
1061
|
-
}, {});
|
|
1062
|
-
if (definition.type === undefined) {
|
|
1063
|
-
definition.type = "object";
|
|
1064
|
-
}
|
|
1065
|
-
if (definition.type === "object" &&
|
|
1066
|
-
Object.keys(propertyDefinitions).length > 0) {
|
|
1067
|
-
definition.properties = propertyDefinitions;
|
|
1068
|
-
}
|
|
1069
|
-
if (this.args.defaultProps) {
|
|
1070
|
-
definition.defaultProperties = [];
|
|
1071
|
-
}
|
|
1072
|
-
if (this.args.noExtraProps &&
|
|
1073
|
-
definition.additionalProperties === undefined) {
|
|
1074
|
-
definition.additionalProperties = false;
|
|
1075
|
-
}
|
|
1076
|
-
if (this.args.propOrder) {
|
|
1077
|
-
// propertyOrder is non-standard, but useful:
|
|
1078
|
-
// https://github.com/json-schema/json-schema/issues/87
|
|
1079
|
-
const propertyOrder = props.reduce((order, prop) => {
|
|
1080
|
-
order.push(prop.getName());
|
|
1081
|
-
return order;
|
|
1082
|
-
}, []);
|
|
1083
|
-
definition.propertyOrder = propertyOrder;
|
|
1084
|
-
}
|
|
1085
|
-
if (this.args.required) {
|
|
1086
|
-
const requiredProps = props.reduce((required, prop) => {
|
|
1087
|
-
const def = {};
|
|
1088
|
-
this.parseCommentsIntoDefinition(prop, def, {});
|
|
1089
|
-
const allUnionTypesFlags = prop.links?.type?.types?.map?.((t) => t.flags) ||
|
|
1090
|
-
[];
|
|
1091
|
-
if (!(prop.flags & ts.SymbolFlags.Optional) &&
|
|
1092
|
-
!(prop.flags & ts.SymbolFlags.Method) &&
|
|
1093
|
-
!allUnionTypesFlags.includes(ts.TypeFlags.Undefined) &&
|
|
1094
|
-
!allUnionTypesFlags.includes(ts.TypeFlags.Void) &&
|
|
1095
|
-
!def.hasOwnProperty("ignore")) {
|
|
1096
|
-
required.push(prop.getName());
|
|
1097
|
-
}
|
|
1098
|
-
return required;
|
|
1099
|
-
}, []);
|
|
1100
|
-
if (requiredProps.length > 0) {
|
|
1101
|
-
definition.required = unique(requiredProps).sort();
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
return definition;
|
|
1106
|
-
}
|
|
1107
|
-
/**
|
|
1108
|
-
* Gets/generates a globally unique type name for the given type
|
|
1109
|
-
*/
|
|
1110
|
-
getTypeName(typ) {
|
|
1111
|
-
const id = typ.id;
|
|
1112
|
-
if (this.typeNamesById[id]) {
|
|
1113
|
-
// Name already assigned?
|
|
1114
|
-
return this.typeNamesById[id];
|
|
1115
|
-
}
|
|
1116
|
-
return this.makeTypeNameUnique(typ, this.tc
|
|
1117
|
-
.typeToString(typ, undefined, ts.TypeFormatFlags.NoTruncation |
|
|
1118
|
-
ts.TypeFormatFlags.UseFullyQualifiedType)
|
|
1119
|
-
.replace(REGEX_FILE_NAME_OR_SPACE, ""));
|
|
1120
|
-
}
|
|
1121
|
-
makeTypeNameUnique(typ, baseName) {
|
|
1122
|
-
const id = typ.id;
|
|
1123
|
-
let name = baseName;
|
|
1124
|
-
// If a type with same name exists
|
|
1125
|
-
// Try appending "_1", "_2", etc.
|
|
1126
|
-
for (let i = 1; this.typeIdsByName[name] !== undefined && this.typeIdsByName[name] !== id; ++i) {
|
|
1127
|
-
name = baseName + "_" + i;
|
|
1128
|
-
}
|
|
1129
|
-
this.typeNamesById[id] = name;
|
|
1130
|
-
this.typeIdsByName[name] = id;
|
|
1131
|
-
return name;
|
|
1132
|
-
}
|
|
1133
|
-
recursiveTypeRef = new Map();
|
|
1134
|
-
getTypeDefinition(typ, asRef = this.args.ref, unionModifier = "anyOf", prop, reffedType, pairedSymbol, forceNotRef = false, ignoreUndefined = false) {
|
|
1135
|
-
const definition = {}; // real definition
|
|
1136
|
-
// Ignore any number of Readonly and Mutable type wrappings, since they only add and remove readonly modifiers on fields and JSON Schema is not concerned with mutability
|
|
1137
|
-
while (typ.aliasSymbol &&
|
|
1138
|
-
(typ.aliasSymbol.escapedName === "Readonly" ||
|
|
1139
|
-
typ.aliasSymbol.escapedName === "Mutable") &&
|
|
1140
|
-
typ.aliasTypeArguments &&
|
|
1141
|
-
typ.aliasTypeArguments[0]) {
|
|
1142
|
-
typ = typ.aliasTypeArguments[0];
|
|
1143
|
-
reffedType = undefined;
|
|
1144
|
-
}
|
|
1145
|
-
if (this.args.typeOfKeyword &&
|
|
1146
|
-
typ.flags & ts.TypeFlags.Object &&
|
|
1147
|
-
typ.objectFlags & ts.ObjectFlags.Anonymous) {
|
|
1148
|
-
definition.typeof = "function";
|
|
1149
|
-
return definition;
|
|
1150
|
-
}
|
|
1151
|
-
let returnedDefinition = definition; // returned definition, may be a $ref
|
|
1152
|
-
// Parse property comments now to skip recursive if ignore.
|
|
1153
|
-
if (prop) {
|
|
1154
|
-
const defs = {};
|
|
1155
|
-
const others = {};
|
|
1156
|
-
this.parseCommentsIntoDefinition(prop, defs, others);
|
|
1157
|
-
if (defs.hasOwnProperty("ignore") || defs.hasOwnProperty("type")) {
|
|
1158
|
-
return defs;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
const symbol = typ.getSymbol();
|
|
1162
|
-
// FIXME: We can't just compare the name of the symbol - it ignores the namespace
|
|
1163
|
-
let isRawType = !symbol ||
|
|
1164
|
-
// Window is incorrectly marked as rawType here for some reason
|
|
1165
|
-
(this.tc.getFullyQualifiedName(symbol) !== "Window" &&
|
|
1166
|
-
(this.tc.getFullyQualifiedName(symbol) === "Date" ||
|
|
1167
|
-
symbol.name === "integer" ||
|
|
1168
|
-
this.tc.getIndexInfoOfType(typ, ts.IndexKind.Number) !== undefined));
|
|
1169
|
-
if (isRawType &&
|
|
1170
|
-
typ.aliasSymbol?.escapedName &&
|
|
1171
|
-
typ.types) {
|
|
1172
|
-
isRawType = false;
|
|
1173
|
-
}
|
|
1174
|
-
// special case: an union where all child are string literals -> make an enum instead
|
|
1175
|
-
let isStringEnum = false;
|
|
1176
|
-
if (typ.flags & ts.TypeFlags.Union) {
|
|
1177
|
-
const unionType = typ;
|
|
1178
|
-
isStringEnum = unionType.types.every((propType) => {
|
|
1179
|
-
return (propType.getFlags() & ts.TypeFlags.StringLiteral) !== 0;
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
// aliased types must be handled slightly different
|
|
1183
|
-
const asTypeAliasRef = asRef && reffedType && (this.args.aliasRef || isStringEnum);
|
|
1184
|
-
if (!asTypeAliasRef) {
|
|
1185
|
-
if (isRawType ||
|
|
1186
|
-
(typ.getFlags() & ts.TypeFlags.Object &&
|
|
1187
|
-
typ.objectFlags & ts.ObjectFlags.Anonymous)) {
|
|
1188
|
-
asRef = false; // raw types and inline types cannot be reffed,
|
|
1189
|
-
// unless we are handling a type alias
|
|
1190
|
-
// or it is recursive type - see below
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
let fullTypeName = "";
|
|
1194
|
-
if (asTypeAliasRef) {
|
|
1195
|
-
const typeName = this.tc
|
|
1196
|
-
.getFullyQualifiedName(reffedType.getFlags() & ts.SymbolFlags.Alias
|
|
1197
|
-
? this.tc.getAliasedSymbol(reffedType)
|
|
1198
|
-
: reffedType)
|
|
1199
|
-
.replace(REGEX_FILE_NAME_OR_SPACE, "");
|
|
1200
|
-
if (this.args.uniqueNames && reffedType) {
|
|
1201
|
-
const sourceFile = getSourceFile(reffedType);
|
|
1202
|
-
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
|
|
1203
|
-
fullTypeName = `${typeName}.${generateHashOfNode(getCanonicalDeclaration(reffedType), relativePath)}`;
|
|
1204
|
-
}
|
|
1205
|
-
else {
|
|
1206
|
-
fullTypeName = this.makeTypeNameUnique(typ, typeName);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
else {
|
|
1210
|
-
// typ.symbol can be undefined
|
|
1211
|
-
if (this.args.uniqueNames && typ.symbol) {
|
|
1212
|
-
const sym = typ.symbol;
|
|
1213
|
-
const sourceFile = getSourceFile(sym);
|
|
1214
|
-
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
|
|
1215
|
-
fullTypeName = `${this.getTypeName(typ)}.${generateHashOfNode(getCanonicalDeclaration(sym), relativePath)}`;
|
|
1216
|
-
}
|
|
1217
|
-
else if (reffedType &&
|
|
1218
|
-
this.schemaOverrides.has(reffedType.escapedName)) {
|
|
1219
|
-
fullTypeName = reffedType.escapedName;
|
|
1220
|
-
}
|
|
1221
|
-
else {
|
|
1222
|
-
fullTypeName = this.getTypeName(typ);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
// Handle recursive types
|
|
1226
|
-
if (!isRawType || !!typ.aliasSymbol) {
|
|
1227
|
-
if (this.recursiveTypeRef.has(fullTypeName) && !forceNotRef) {
|
|
1228
|
-
asRef = true;
|
|
1229
|
-
}
|
|
1230
|
-
else {
|
|
1231
|
-
this.recursiveTypeRef.set(fullTypeName, definition);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
if (asRef) {
|
|
1235
|
-
// We don't return the full definition, but we put it into
|
|
1236
|
-
// reffedDefinitions below.
|
|
1237
|
-
returnedDefinition = {
|
|
1238
|
-
$ref: `${this.args.id}#/definitions/` + fullTypeName,
|
|
1239
|
-
};
|
|
1240
|
-
}
|
|
1241
|
-
// Parse comments
|
|
1242
|
-
const otherAnnotations = {};
|
|
1243
|
-
this.parseCommentsIntoDefinition(reffedType, definition, otherAnnotations); // handle comments in the type alias declaration
|
|
1244
|
-
this.parseCommentsIntoDefinition(symbol, definition, otherAnnotations);
|
|
1245
|
-
this.parseCommentsIntoDefinition(typ.aliasSymbol, definition, otherAnnotations);
|
|
1246
|
-
if (prop) {
|
|
1247
|
-
this.parseCommentsIntoDefinition(prop, returnedDefinition, otherAnnotations);
|
|
1248
|
-
}
|
|
1249
|
-
if (pairedSymbol && symbol && this.isFromDefaultLib(symbol)) {
|
|
1250
|
-
this.parseCommentsIntoDefinition(pairedSymbol, definition, otherAnnotations);
|
|
1251
|
-
}
|
|
1252
|
-
// Create the actual definition only if is an inline definition, or
|
|
1253
|
-
// if it will be a $ref and it is not yet created.
|
|
1254
|
-
// Prioritise overrides.
|
|
1255
|
-
const overrideDefinition = this.schemaOverrides.get(fullTypeName);
|
|
1256
|
-
if (overrideDefinition) {
|
|
1257
|
-
this.reffedDefinitions[fullTypeName] = overrideDefinition;
|
|
1258
|
-
}
|
|
1259
|
-
else if (!asRef || !this.reffedDefinitions[fullTypeName]) {
|
|
1260
|
-
if (asRef) {
|
|
1261
|
-
// must be here to prevent recursivity problems
|
|
1262
|
-
let reffedDefinition;
|
|
1263
|
-
if (asTypeAliasRef &&
|
|
1264
|
-
reffedType &&
|
|
1265
|
-
typ.symbol !== reffedType &&
|
|
1266
|
-
symbol) {
|
|
1267
|
-
reffedDefinition = this.getTypeDefinition(typ, true, undefined, symbol, symbol);
|
|
1268
|
-
}
|
|
1269
|
-
else {
|
|
1270
|
-
reffedDefinition = definition;
|
|
1271
|
-
}
|
|
1272
|
-
this.reffedDefinitions[fullTypeName] = reffedDefinition;
|
|
1273
|
-
if (this.args.titles && fullTypeName) {
|
|
1274
|
-
definition.title = fullTypeName;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const node = symbol?.getDeclarations() !== undefined
|
|
1278
|
-
? symbol.getDeclarations()[0]
|
|
1279
|
-
: null;
|
|
1280
|
-
if (definition.type === undefined) {
|
|
1281
|
-
// if users override the type, do not try to infer it
|
|
1282
|
-
if (typ.flags & ts.TypeFlags.Union &&
|
|
1283
|
-
(node === null || node.kind !== ts.SyntaxKind.EnumDeclaration)) {
|
|
1284
|
-
this.getUnionDefinition(typ, unionModifier, definition);
|
|
1285
|
-
}
|
|
1286
|
-
else if (typ.flags & ts.TypeFlags.Intersection) {
|
|
1287
|
-
if (this.args.noExtraProps) {
|
|
1288
|
-
// extend object instead of using allOf because allOf does not work well with additional properties. See #107
|
|
1289
|
-
if (this.args.noExtraProps) {
|
|
1290
|
-
definition.additionalProperties = false;
|
|
1291
|
-
}
|
|
1292
|
-
const types = typ.types;
|
|
1293
|
-
for (const member of types) {
|
|
1294
|
-
const other = this.getTypeDefinition(member, false, undefined, undefined, undefined, undefined, true);
|
|
1295
|
-
definition.type = other.type; // should always be object
|
|
1296
|
-
definition.properties = {
|
|
1297
|
-
...definition.properties,
|
|
1298
|
-
...other.properties,
|
|
1299
|
-
};
|
|
1300
|
-
if (Object.keys(other.default || {}).length > 0) {
|
|
1301
|
-
definition.default = extend(definition.default || {}, other.default);
|
|
1302
|
-
}
|
|
1303
|
-
if (other.required) {
|
|
1304
|
-
definition.required = unique((definition.required || []).concat(other.required)).sort();
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
else {
|
|
1309
|
-
this.getIntersectionDefinition(typ, definition);
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
else if (isRawType) {
|
|
1313
|
-
if (pairedSymbol) {
|
|
1314
|
-
this.parseCommentsIntoDefinition(pairedSymbol, definition, {});
|
|
1315
|
-
}
|
|
1316
|
-
this.getDefinitionForRootType(typ, reffedType, definition, undefined, ignoreUndefined);
|
|
1317
|
-
}
|
|
1318
|
-
else if (node &&
|
|
1319
|
-
(node.kind === ts.SyntaxKind.EnumDeclaration ||
|
|
1320
|
-
node.kind === ts.SyntaxKind.EnumMember)) {
|
|
1321
|
-
this.getEnumDefinition(typ, definition);
|
|
1322
|
-
}
|
|
1323
|
-
else if (symbol &&
|
|
1324
|
-
symbol.flags & ts.SymbolFlags.TypeLiteral &&
|
|
1325
|
-
symbol.members.size === 0 &&
|
|
1326
|
-
!(node && node.kind === ts.SyntaxKind.MappedType)) {
|
|
1327
|
-
// {} is TypeLiteral with no members. Need special case because it doesn't have declarations.
|
|
1328
|
-
definition.type = "object";
|
|
1329
|
-
definition.properties = {};
|
|
1330
|
-
}
|
|
1331
|
-
else {
|
|
1332
|
-
this.getClassDefinition(typ, definition);
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
if (this.recursiveTypeRef.get(fullTypeName) === definition) {
|
|
1337
|
-
this.recursiveTypeRef.delete(fullTypeName);
|
|
1338
|
-
// If the type was recursive (there is reffedDefinitions) - lets replace it to reference
|
|
1339
|
-
if (this.reffedDefinitions[fullTypeName]) {
|
|
1340
|
-
const annotations = Object.entries(returnedDefinition).reduce((acc, [key, value]) => {
|
|
1341
|
-
if (annotationKeywords[key] &&
|
|
1342
|
-
typeof value !== undefined) {
|
|
1343
|
-
acc[key] = value;
|
|
1344
|
-
}
|
|
1345
|
-
return acc;
|
|
1346
|
-
}, {});
|
|
1347
|
-
returnedDefinition = {
|
|
1348
|
-
$ref: `${this.args.id}#/definitions/` + fullTypeName,
|
|
1349
|
-
...annotations,
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
if (otherAnnotations["nullable"]) {
|
|
1354
|
-
makeNullable(returnedDefinition);
|
|
1355
|
-
}
|
|
1356
|
-
return returnedDefinition;
|
|
1357
|
-
}
|
|
1358
|
-
setSchemaOverride(symbolName, schema) {
|
|
1359
|
-
this.schemaOverrides.set(symbolName, schema);
|
|
1360
|
-
}
|
|
1361
|
-
getSchemaForSymbol(symbolName, includeReffedDefinitions = true, includeAllOverrides = false) {
|
|
1362
|
-
const overrideDefinition = this.schemaOverrides.get(symbolName);
|
|
1363
|
-
if (!this.allSymbols[symbolName] && !overrideDefinition) {
|
|
1364
|
-
throw new Error(`type ${symbolName} not found`);
|
|
1365
|
-
}
|
|
1366
|
-
this.resetSchemaSpecificProperties(includeAllOverrides);
|
|
1367
|
-
let def;
|
|
1368
|
-
if (overrideDefinition) {
|
|
1369
|
-
def = { ...overrideDefinition };
|
|
1370
|
-
}
|
|
1371
|
-
else {
|
|
1372
|
-
def = overrideDefinition
|
|
1373
|
-
? overrideDefinition
|
|
1374
|
-
: this.getTypeDefinition(this.allSymbols[symbolName], this.args.topRef, undefined, undefined, undefined, this.userSymbols[symbolName] || undefined);
|
|
1375
|
-
}
|
|
1376
|
-
if (this.args.ref &&
|
|
1377
|
-
includeReffedDefinitions &&
|
|
1378
|
-
Object.keys(this.reffedDefinitions).length > 0) {
|
|
1379
|
-
def.definitions = this.reffedDefinitions;
|
|
1380
|
-
}
|
|
1381
|
-
def["$schema"] = "http://json-schema.org/draft-07/schema#";
|
|
1382
|
-
const id = this.args.id;
|
|
1383
|
-
if (id) {
|
|
1384
|
-
def["$id"] = this.args.id;
|
|
1385
|
-
}
|
|
1386
|
-
return def;
|
|
1387
|
-
}
|
|
1388
|
-
getSchemaForSymbols(symbolNames, includeReffedDefinitions = true, includeAllOverrides = false) {
|
|
1389
|
-
const root = {
|
|
1390
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
1391
|
-
definitions: {},
|
|
1392
|
-
};
|
|
1393
|
-
this.resetSchemaSpecificProperties(includeAllOverrides);
|
|
1394
|
-
const id = this.args.id;
|
|
1395
|
-
if (id) {
|
|
1396
|
-
root["$id"] = id;
|
|
1397
|
-
}
|
|
1398
|
-
for (const symbolName of symbolNames) {
|
|
1399
|
-
root.definitions[symbolName] = this.getTypeDefinition(this.allSymbols[symbolName], this.args.topRef, undefined, undefined, undefined, this.userSymbols[symbolName]);
|
|
1400
|
-
}
|
|
1401
|
-
if (this.args.ref &&
|
|
1402
|
-
includeReffedDefinitions &&
|
|
1403
|
-
Object.keys(this.reffedDefinitions).length > 0) {
|
|
1404
|
-
root.definitions = { ...root.definitions, ...this.reffedDefinitions };
|
|
1405
|
-
}
|
|
1406
|
-
return root;
|
|
1407
|
-
}
|
|
1408
|
-
getSymbols(name) {
|
|
1409
|
-
if (name === void 0) {
|
|
1410
|
-
return this.symbols;
|
|
1411
|
-
}
|
|
1412
|
-
return this.symbols.filter((symbol) => symbol.typeName === name);
|
|
1413
|
-
}
|
|
1414
|
-
getUserSymbols() {
|
|
1415
|
-
return Object.keys(this.userSymbols);
|
|
1416
|
-
}
|
|
1417
|
-
getMainFileSymbols(program, onlyIncludeFiles) {
|
|
1418
|
-
function includeFile(file) {
|
|
1419
|
-
if (onlyIncludeFiles === undefined) {
|
|
1420
|
-
return !file.isDeclarationFile;
|
|
1421
|
-
}
|
|
1422
|
-
return (onlyIncludeFiles.filter((f) => pathEqual(f, file.fileName)).length > 0);
|
|
1423
|
-
}
|
|
1424
|
-
const files = program.getSourceFiles().filter(includeFile);
|
|
1425
|
-
if (files.length) {
|
|
1426
|
-
return Object.keys(this.userSymbols).filter((key) => {
|
|
1427
|
-
const symbol = this.userSymbols[key];
|
|
1428
|
-
if (!symbol || !symbol.declarations || !symbol.declarations.length) {
|
|
1429
|
-
return false;
|
|
1430
|
-
}
|
|
1431
|
-
let node = symbol.declarations[0];
|
|
1432
|
-
while (node?.parent) {
|
|
1433
|
-
node = node.parent;
|
|
1434
|
-
}
|
|
1435
|
-
return files.indexOf(node.getSourceFile()) > -1;
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
return [];
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
function generateHashOfNode(node, relativePath) {
|
|
1442
|
-
return createHash("md5")
|
|
1443
|
-
.update(relativePath)
|
|
1444
|
-
.update(node.pos.toString())
|
|
1445
|
-
.digest("hex")
|
|
1446
|
-
.substring(0, 8);
|
|
1447
|
-
}
|
|
1448
|
-
export function buildGenerator(program, args = {}) {
|
|
1449
|
-
// Use defaults unless otherwise specified
|
|
1450
|
-
const settings = getDefaultArgs();
|
|
1451
|
-
for (const pref in args) {
|
|
1452
|
-
if (args.hasOwnProperty(pref)) {
|
|
1453
|
-
settings[pref] = args[pref];
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
const typeChecker = program.getTypeChecker();
|
|
1457
|
-
const symbols = [];
|
|
1458
|
-
const allSymbols = {};
|
|
1459
|
-
const userSymbols = {};
|
|
1460
|
-
const inheritingTypes = {};
|
|
1461
|
-
const workingDir = program.getCurrentDirectory();
|
|
1462
|
-
program.getSourceFiles().forEach((sourceFile, _sourceFileIdx) => {
|
|
1463
|
-
const relativePath = path.relative(workingDir, sourceFile.fileName);
|
|
1464
|
-
function inspect(node, tc) {
|
|
1465
|
-
if (node.kind === ts.SyntaxKind.ClassDeclaration ||
|
|
1466
|
-
node.kind === ts.SyntaxKind.InterfaceDeclaration ||
|
|
1467
|
-
node.kind === ts.SyntaxKind.EnumDeclaration ||
|
|
1468
|
-
node.kind === ts.SyntaxKind.TypeAliasDeclaration) {
|
|
1469
|
-
const symbol = node.symbol;
|
|
1470
|
-
const nodeType = tc.getTypeAtLocation(node);
|
|
1471
|
-
const fullyQualifiedName = tc.getFullyQualifiedName(symbol);
|
|
1472
|
-
const typeName = fullyQualifiedName.replace(/".*"\./, "");
|
|
1473
|
-
const name = !args.uniqueNames
|
|
1474
|
-
? typeName
|
|
1475
|
-
: `${typeName}.${generateHashOfNode(node, relativePath)}`;
|
|
1476
|
-
symbols.push({ name, typeName, fullyQualifiedName, symbol });
|
|
1477
|
-
if (!userSymbols[name]) {
|
|
1478
|
-
allSymbols[name] = nodeType;
|
|
1479
|
-
}
|
|
1480
|
-
const baseTypes = nodeType.getBaseTypes() || [];
|
|
1481
|
-
baseTypes.forEach((baseType) => {
|
|
1482
|
-
var baseName = tc.typeToString(baseType, undefined, ts.TypeFormatFlags.UseFullyQualifiedType);
|
|
1483
|
-
if (!inheritingTypes[baseName]) {
|
|
1484
|
-
inheritingTypes[baseName] = [];
|
|
1485
|
-
}
|
|
1486
|
-
inheritingTypes[baseName].push(name);
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
else {
|
|
1490
|
-
ts.forEachChild(node, (n) => inspect(n, tc));
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
inspect(sourceFile, typeChecker);
|
|
1494
|
-
});
|
|
1495
|
-
return new JsonSchemaGenerator(symbols, allSymbols, userSymbols, inheritingTypes, typeChecker, settings);
|
|
1496
|
-
}
|