langgraph-api 0.2.8__py3-none-any.whl → 0.2.10__py3-none-any.whl

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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

@@ -1,2016 +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
-
16
- import * as ts from "typescript";
17
- import * as vm from "node:vm";
18
- import * as fs from "node:fs/promises";
19
- import * as path from "node:path";
20
- import { createHash } from "node:crypto";
21
- import { JSONSchema7, JSONSchema7TypeName } from "json-schema";
22
- import dedent from "dedent";
23
-
24
- const REGEX_FILE_NAME_OR_SPACE = /(\bimport\(".*?"\)|".*?")\.| /g;
25
- const REGEX_TJS_JSDOC = /^-([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g;
26
- const REGEX_GROUP_JSDOC = /^[.]?([\w]+)\s+(\S|\S[\s\S]*\S)\s*$/g;
27
- /**
28
- * Resolve required file, his path and a property name,
29
- * pattern: require([file_path]).[property_name]
30
- *
31
- * the part ".[property_name]" is optional in the regex
32
- *
33
- * will match:
34
- *
35
- * require('./path.ts')
36
- * require('./path.ts').objectName
37
- * require("./path.ts")
38
- * require("./path.ts").objectName
39
- * require('@module-name')
40
- *
41
- * match[2] = file_path (a path to the file with quotes)
42
- * match[3] = (optional) property_name (a property name, exported in the file)
43
- *
44
- * for more details, see tests/require.test.ts
45
- */
46
- const REGEX_REQUIRE =
47
- /^(\s+)?require\((\'@?[a-zA-Z0-9.\/_-]+\'|\"@?[a-zA-Z0-9.\/_-]+\")\)(\.([a-zA-Z0-9_$]+))?(\s+|$)/;
48
- const NUMERIC_INDEX_PATTERN = "^[0-9]+$";
49
-
50
- function pathEqual(actual: string, expected: string): boolean {
51
- return (
52
- actual === expected || normalizePath(actual) === normalizePath(expected)
53
- );
54
- }
55
-
56
- function normalizePath(path: string): string {
57
- const replace: [RegExp, string][] = [
58
- [/\\/g, "/"],
59
- [/(\w):/, "/$1"],
60
- [/(\w+)\/\.\.\/?/g, ""],
61
- [/^\.\//, ""],
62
- [/\/\.\//, "/"],
63
- [/\/\.$/, ""],
64
- [/\/$/, ""],
65
- ];
66
-
67
- replace.forEach((array) => {
68
- while (array[0].test(path)) {
69
- path = path.replace(array[0], array[1]);
70
- }
71
- });
72
-
73
- return path;
74
- }
75
-
76
- function getDefaultArgs(): Args {
77
- return {
78
- ref: true,
79
- aliasRef: false,
80
- topRef: false,
81
- titles: false,
82
- defaultProps: false,
83
- noExtraProps: false,
84
- propOrder: false,
85
- typeOfKeyword: false,
86
- required: false,
87
- strictNullChecks: false,
88
- esModuleInterop: false,
89
- experimentalDecorators: true,
90
- out: "",
91
- validationKeywords: [],
92
- include: [],
93
- excludePrivate: false,
94
- uniqueNames: false,
95
- rejectDateType: false,
96
- id: "",
97
- defaultNumberType: "number",
98
- constAsEnum: false,
99
- };
100
- }
101
-
102
- type ValidationKeywords = {
103
- [prop: string]: boolean;
104
- };
105
-
106
- type Args = {
107
- ref: boolean;
108
- aliasRef: boolean;
109
- topRef: boolean;
110
- titles: boolean;
111
- defaultProps: boolean;
112
- noExtraProps: boolean;
113
- propOrder: boolean;
114
- typeOfKeyword: boolean;
115
- required: boolean;
116
- strictNullChecks: boolean;
117
- esModuleInterop: boolean;
118
- experimentalDecorators: boolean;
119
- out: string;
120
- validationKeywords: string[];
121
- include: string[];
122
- excludePrivate: boolean;
123
- uniqueNames: boolean;
124
- rejectDateType: boolean;
125
- id: string;
126
- defaultNumberType: "number" | "integer";
127
- constAsEnum: boolean;
128
- };
129
-
130
- type PartialArgs = Partial<Args>;
131
-
132
- type PrimitiveType = number | boolean | string | null;
133
-
134
- type MetaDefinitionFields = "ignore";
135
- type RedefinedFields =
136
- | "items"
137
- | "additionalItems"
138
- | "contains"
139
- | "properties"
140
- | "patternProperties"
141
- | "additionalProperties"
142
- | "dependencies"
143
- | "propertyNames"
144
- | "if"
145
- | "then"
146
- | "else"
147
- | "allOf"
148
- | "anyOf"
149
- | "oneOf"
150
- | "not"
151
- | "definitions";
152
-
153
- type DefinitionOrBoolean = Definition | boolean;
154
- interface Definition extends Omit<JSONSchema7, RedefinedFields> {
155
- // Non-standard fields
156
- propertyOrder?: string[];
157
- defaultProperties?: string[];
158
- typeof?: "function";
159
-
160
- // Fields that must be redefined because they make use of this definition itself
161
- items?: DefinitionOrBoolean | DefinitionOrBoolean[];
162
- additionalItems?: DefinitionOrBoolean;
163
- contains?: JSONSchema7;
164
- properties?: {
165
- [key: string]: DefinitionOrBoolean;
166
- };
167
- patternProperties?: {
168
- [key: string]: DefinitionOrBoolean;
169
- };
170
- additionalProperties?: DefinitionOrBoolean;
171
- dependencies?: {
172
- [key: string]: DefinitionOrBoolean | string[];
173
- };
174
- propertyNames?: DefinitionOrBoolean;
175
- if?: DefinitionOrBoolean;
176
- then?: DefinitionOrBoolean;
177
- else?: DefinitionOrBoolean;
178
- allOf?: DefinitionOrBoolean[];
179
- anyOf?: DefinitionOrBoolean[];
180
- oneOf?: DefinitionOrBoolean[];
181
- not?: DefinitionOrBoolean;
182
- definitions?: {
183
- [key: string]: DefinitionOrBoolean;
184
- };
185
- }
186
-
187
- /** A looser Definition type that allows for indexing with arbitrary strings. */
188
- type DefinitionIndex = { [key: string]: Definition[keyof Definition] };
189
-
190
- type SymbolRef = {
191
- name: string;
192
- typeName: string;
193
- fullyQualifiedName: string;
194
- symbol: ts.Symbol;
195
- };
196
-
197
- function extend(target: any, ..._: any[]): any {
198
- if (target == null) {
199
- // TypeError if undefined or null
200
- throw new TypeError("Cannot convert undefined or null to object");
201
- }
202
-
203
- const to = Object(target);
204
-
205
- for (var index = 1; index < arguments.length; index++) {
206
- const nextSource = arguments[index];
207
-
208
- if (nextSource != null) {
209
- // Skip over if undefined or null
210
- for (const nextKey in nextSource) {
211
- // Avoid bugs when hasOwnProperty is shadowed
212
- if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
213
- to[nextKey] = nextSource[nextKey];
214
- }
215
- }
216
- }
217
- }
218
- return to;
219
- }
220
-
221
- function unique(arr: string[]): string[] {
222
- const temp: Record<string, true> = {};
223
- for (const e of arr) {
224
- temp[e] = true;
225
- }
226
- const r: string[] = [];
227
- for (const k in temp) {
228
- // Avoid bugs when hasOwnProperty is shadowed
229
- if (Object.prototype.hasOwnProperty.call(temp, k)) {
230
- r.push(k);
231
- }
232
- }
233
- return r;
234
- }
235
-
236
- /**
237
- * Resolve required file
238
- */
239
- function resolveRequiredFile(
240
- symbol: ts.Symbol,
241
- key: string,
242
- fileName: string,
243
- objectName: string,
244
- ): any {
245
- const sourceFile = getSourceFile(symbol);
246
- const requiredFilePath = /^[.\/]+/.test(fileName)
247
- ? fileName === "."
248
- ? path.resolve(sourceFile.fileName)
249
- : path.resolve(path.dirname(sourceFile.fileName), fileName)
250
- : fileName;
251
- const requiredFile = require(requiredFilePath);
252
- if (!requiredFile) {
253
- throw Error("Required: File couldn't be loaded");
254
- }
255
- const requiredObject = objectName
256
- ? requiredFile[objectName]
257
- : requiredFile.default;
258
- if (requiredObject === undefined) {
259
- throw Error("Required: Variable is undefined");
260
- }
261
- if (typeof requiredObject === "function") {
262
- throw Error("Required: Can't use function as a variable");
263
- }
264
- if (key === "examples" && !Array.isArray(requiredObject)) {
265
- throw Error("Required: Variable isn't an array");
266
- }
267
- return requiredObject;
268
- }
269
-
270
- function regexRequire(value: string) {
271
- return REGEX_REQUIRE.exec(value);
272
- }
273
-
274
- /**
275
- * Try to parse a value and returns the string if it fails.
276
- */
277
- function parseValue(symbol: ts.Symbol, key: string, value: string): any {
278
- const match = regexRequire(value);
279
- if (match) {
280
- const fileName = match[2].substring(1, match[2].length - 2).trim();
281
- const objectName = match[4];
282
- return resolveRequiredFile(symbol, key, fileName, objectName);
283
- }
284
- try {
285
- return JSON.parse(value);
286
- } catch (error) {
287
- return value;
288
- }
289
- }
290
-
291
- function extractLiteralValue(typ: ts.Type): PrimitiveType | undefined {
292
- let str = (typ as ts.LiteralType).value;
293
- if (str === undefined) {
294
- str = (typ as any).text;
295
- }
296
- if (typ.flags & ts.TypeFlags.StringLiteral) {
297
- return str as string;
298
- } else if (typ.flags & ts.TypeFlags.BooleanLiteral) {
299
- return (typ as any).intrinsicName === "true";
300
- } else if (typ.flags & ts.TypeFlags.EnumLiteral) {
301
- // or .text for old TS
302
- const num = parseFloat(str as string);
303
- return isNaN(num) ? (str as string) : num;
304
- } else if (typ.flags & ts.TypeFlags.NumberLiteral) {
305
- return parseFloat(str as string);
306
- }
307
- return undefined;
308
- }
309
-
310
- /**
311
- * Checks whether a type is a tuple type.
312
- */
313
- function resolveTupleType(propertyType: ts.Type): ts.TupleTypeNode | null {
314
- if (
315
- !propertyType.getSymbol() &&
316
- propertyType.getFlags() & ts.TypeFlags.Object &&
317
- (propertyType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference
318
- ) {
319
- return (propertyType as ts.TypeReference).target as any;
320
- }
321
- if (
322
- !(
323
- propertyType.getFlags() & ts.TypeFlags.Object &&
324
- (propertyType as ts.ObjectType).objectFlags & ts.ObjectFlags.Tuple
325
- )
326
- ) {
327
- return null;
328
- }
329
- return propertyType as any;
330
- }
331
-
332
- const simpleTypesAllowedProperties: Record<string, true> = {
333
- type: true,
334
- description: true,
335
- };
336
-
337
- function addSimpleType(def: Definition, type: JSONSchema7TypeName): boolean {
338
- for (const k in def) {
339
- if (!simpleTypesAllowedProperties[k]) {
340
- return false;
341
- }
342
- }
343
-
344
- if (!def.type) {
345
- def.type = type;
346
- } else if (typeof def.type !== "string") {
347
- if (
348
- !(def.type as Object[]).every((val) => {
349
- return typeof val === "string";
350
- })
351
- ) {
352
- return false;
353
- }
354
-
355
- if (def.type.indexOf("null") === -1) {
356
- def.type.push("null");
357
- }
358
- } else {
359
- if (typeof def.type !== "string") {
360
- return false;
361
- }
362
-
363
- if (def.type !== "null") {
364
- def.type = [def.type, "null"];
365
- }
366
- }
367
- return true;
368
- }
369
-
370
- function makeNullable(def: Definition): Definition {
371
- if (!addSimpleType(def, "null")) {
372
- const union = def.oneOf || def.anyOf;
373
- if (union) {
374
- union.push({ type: "null" });
375
- } else {
376
- const subdef: DefinitionIndex = {};
377
- for (var k in def as any) {
378
- if (def.hasOwnProperty(k)) {
379
- subdef[k] = def[k as keyof Definition];
380
- delete def[k as keyof typeof def];
381
- }
382
- }
383
- def.anyOf = [subdef, { type: "null" }];
384
- }
385
- }
386
- return def;
387
- }
388
-
389
- /**
390
- * Given a Symbol, returns a canonical Definition. That can be either:
391
- * 1) The Symbol's valueDeclaration parameter if defined, or
392
- * 2) The sole entry in the Symbol's declarations array, provided that array has a length of 1.
393
- *
394
- * valueDeclaration is listed as a required parameter in the definition of a Symbol, but I've
395
- * experienced crashes when it's undefined at runtime, which is the reason for this function's
396
- * existence. Not sure if that's a compiler API bug or what.
397
- */
398
- function getCanonicalDeclaration(sym: ts.Symbol): ts.Declaration {
399
- if (sym.valueDeclaration !== undefined) {
400
- return sym.valueDeclaration;
401
- } else if (sym.declarations?.length === 1) {
402
- return sym.declarations[0];
403
- }
404
-
405
- const declarationCount = sym.declarations?.length ?? 0;
406
- throw new Error(
407
- `Symbol "${sym.name}" has no valueDeclaration and ${declarationCount} declarations.`,
408
- );
409
- }
410
-
411
- /**
412
- * Given a Symbol, finds the place it was declared and chases parent pointers until we find a
413
- * node where SyntaxKind === SourceFile.
414
- */
415
- function getSourceFile(sym: ts.Symbol): ts.SourceFile {
416
- let currentDecl: ts.Node = getCanonicalDeclaration(sym);
417
-
418
- while (currentDecl.kind !== ts.SyntaxKind.SourceFile) {
419
- if (currentDecl.parent === undefined) {
420
- throw new Error(
421
- `Unable to locate source file for declaration "${sym.name}".`,
422
- );
423
- }
424
- currentDecl = currentDecl.parent;
425
- }
426
-
427
- return currentDecl as ts.SourceFile;
428
- }
429
-
430
- /**
431
- * JSDoc keywords that should be used to annotate the JSON schema.
432
- *
433
- * Many of these validation keywords are defined here: http://json-schema.org/latest/json-schema-validation.html
434
- */
435
- // prettier-ignore
436
- const validationKeywords = {
437
- multipleOf: true, // 6.1.
438
- maximum: true, // 6.2.
439
- exclusiveMaximum: true, // 6.3.
440
- minimum: true, // 6.4.
441
- exclusiveMinimum: true, // 6.5.
442
- maxLength: true, // 6.6.
443
- minLength: true, // 6.7.
444
- pattern: true, // 6.8.
445
- items: true, // 6.9.
446
- // additionalItems: true, // 6.10.
447
- maxItems: true, // 6.11.
448
- minItems: true, // 6.12.
449
- uniqueItems: true, // 6.13.
450
- contains: true, // 6.14.
451
- maxProperties: true, // 6.15.
452
- minProperties: true, // 6.16.
453
- // required: true, // 6.17. This is not required. It is auto-generated.
454
- // properties: true, // 6.18. This is not required. It is auto-generated.
455
- // patternProperties: true, // 6.19.
456
- additionalProperties: true, // 6.20.
457
- // dependencies: true, // 6.21.
458
- // propertyNames: true, // 6.22.
459
- enum: true, // 6.23.
460
- // const: true, // 6.24.
461
- type: true, // 6.25.
462
- // allOf: true, // 6.26.
463
- // anyOf: true, // 6.27.
464
- // oneOf: true, // 6.28.
465
- // not: true, // 6.29.
466
- examples: true, // Draft 6 (draft-handrews-json-schema-validation-01)
467
-
468
- ignore: true,
469
- description: true,
470
- format: true,
471
- default: true,
472
- $ref: true,
473
- id: true,
474
- $id: true,
475
- $comment: true,
476
- title: true
477
- };
478
-
479
- /**
480
- * Subset of descriptive, non-type keywords that are permitted alongside a $ref.
481
- * Prior to JSON Schema draft 2019-09, $ref is a special keyword that doesn't
482
- * permit keywords alongside it, and so AJV may raise warnings if it encounters
483
- * any type-related keywords; see https://github.com/ajv-validator/ajv/issues/1121
484
- */
485
- const annotationKeywords: { [k in keyof typeof validationKeywords]?: true } = {
486
- description: true,
487
- default: true,
488
- examples: true,
489
- title: true,
490
- // A JSDoc $ref annotation can appear as a $ref.
491
- $ref: true,
492
- };
493
-
494
- const subDefinitions: Record<string, true> = {
495
- items: true,
496
- additionalProperties: true,
497
- contains: true,
498
- };
499
-
500
- class JsonSchemaGenerator {
501
- private tc: ts.TypeChecker;
502
-
503
- /**
504
- * Holds all symbols within a custom SymbolRef object, containing useful
505
- * information.
506
- */
507
- private symbols: SymbolRef[];
508
- /**
509
- * All types for declarations of classes, interfaces, enums, and type aliases
510
- * defined in all TS files.
511
- */
512
- private allSymbols: { [name: string]: ts.Type };
513
- /**
514
- * All symbols for declarations of classes, interfaces, enums, and type aliases
515
- * defined in non-default-lib TS files.
516
- */
517
- private userSymbols: { [name: string]: ts.Symbol };
518
- /**
519
- * Maps from the names of base types to the names of the types that inherit from
520
- * them.
521
- */
522
- private inheritingTypes: { [baseName: string]: string[] };
523
-
524
- /**
525
- * This map holds references to all reffed definitions, including schema
526
- * overrides and generated definitions.
527
- */
528
- private reffedDefinitions: { [key: string]: Definition } = {};
529
-
530
- /**
531
- * This map only holds explicit schema overrides. This helps differentiate between
532
- * user defined schema overrides and generated definitions.
533
- */
534
- private schemaOverrides = new Map<string, Definition>();
535
-
536
- /**
537
- * This is a set of all the user-defined validation keywords.
538
- */
539
- private userValidationKeywords: ValidationKeywords;
540
-
541
- /**
542
- * If true, this makes constants be defined as enums with a single value. This is useful
543
- * for cases where constant values are not supported, such as OpenAPI.
544
- */
545
- private constAsEnum: boolean;
546
-
547
- /**
548
- * Types are assigned names which are looked up by their IDs. This is the
549
- * map from type IDs to type names.
550
- */
551
- private typeNamesById: { [id: number]: string } = {};
552
- /**
553
- * Whenever a type is assigned its name, its entry in this dictionary is set,
554
- * so that we don't give the same name to two separate types.
555
- */
556
- private typeIdsByName: { [name: string]: number } = {};
557
-
558
- constructor(
559
- symbols: SymbolRef[],
560
- allSymbols: { [name: string]: ts.Type },
561
- userSymbols: { [name: string]: ts.Symbol },
562
- inheritingTypes: { [baseName: string]: string[] },
563
- tc: ts.TypeChecker,
564
- private args = getDefaultArgs(),
565
- ) {
566
- this.symbols = symbols;
567
- this.allSymbols = allSymbols;
568
- this.userSymbols = userSymbols;
569
- this.inheritingTypes = inheritingTypes;
570
- this.tc = tc;
571
- this.userValidationKeywords = args.validationKeywords.reduce(
572
- (acc, word) => ({ ...acc, [word]: true }),
573
- {},
574
- );
575
- this.constAsEnum = args.constAsEnum;
576
- }
577
-
578
- public get ReffedDefinitions(): { [key: string]: Definition } {
579
- return this.reffedDefinitions;
580
- }
581
-
582
- private isFromDefaultLib(symbol: ts.Symbol) {
583
- const declarations = symbol.getDeclarations();
584
- if (declarations && declarations.length > 0 && declarations[0].parent) {
585
- return declarations[0].parent.getSourceFile().hasNoDefaultLib;
586
- }
587
- return false;
588
- }
589
-
590
- private resetSchemaSpecificProperties(includeAllOverrides: boolean = false) {
591
- this.reffedDefinitions = {};
592
- this.typeIdsByName = {};
593
- this.typeNamesById = {};
594
-
595
- // restore schema overrides
596
- if (includeAllOverrides) {
597
- this.schemaOverrides.forEach((value, key) => {
598
- this.reffedDefinitions[key] = value;
599
- });
600
- }
601
- }
602
-
603
- /**
604
- * Parse the comments of a symbol into the definition and other annotations.
605
- */
606
- private parseCommentsIntoDefinition(
607
- symbol: ts.Symbol,
608
- definition: Definition,
609
- otherAnnotations: Record<string, true>,
610
- ): void {
611
- if (!symbol) {
612
- return;
613
- }
614
-
615
- if (!this.isFromDefaultLib(symbol)) {
616
- // the comments for a symbol
617
- const comments = symbol.getDocumentationComment(this.tc);
618
-
619
- if (comments.length) {
620
- definition.description = comments
621
- .map((comment) => {
622
- const newlineNormalizedComment = comment.text.replace(
623
- /\r\n/g,
624
- "\n",
625
- );
626
-
627
- // If a comment contains a "{@link XYZ}" inline tag that could not be
628
- // resolved by the TS checker, then this comment will contain a trailing
629
- // whitespace that we need to remove.
630
- if (comment.kind === "linkText") {
631
- return newlineNormalizedComment.trim();
632
- }
633
-
634
- return newlineNormalizedComment;
635
- })
636
- .join("")
637
- .trim();
638
- }
639
- }
640
-
641
- // jsdocs are separate from comments
642
- const jsdocs = symbol.getJsDocTags();
643
- jsdocs.forEach((doc) => {
644
- // if we have @TJS-... annotations, we have to parse them
645
- let name = doc.name;
646
- const originalText = doc.text ? doc.text.map((t) => t.text).join("") : "";
647
- let text = originalText;
648
- // In TypeScript versions prior to 3.7, it stops parsing the annotation
649
- // at the first non-alphanumeric character and puts the rest of the line as the
650
- // "text" of the annotation, so we have a little hack to check for the name
651
- // "TJS" and then we sort of re-parse the annotation to support prior versions
652
- // of TypeScript.
653
- if (name.startsWith("TJS-")) {
654
- name = name.slice(4);
655
- if (!text) {
656
- text = "true";
657
- }
658
- } else if (name === "TJS" && text.startsWith("-")) {
659
- let match: string[] | RegExpExecArray | null = new RegExp(
660
- REGEX_TJS_JSDOC,
661
- ).exec(originalText);
662
- if (match) {
663
- name = match[1];
664
- text = match[2];
665
- } else {
666
- // Treat empty text as boolean true
667
- name = (text as string).replace(/^[\s\-]+/, "");
668
- text = "true";
669
- }
670
- }
671
-
672
- // In TypeScript ~3.5, the annotation name splits at the dot character so we have
673
- // to process the "." and beyond from the value
674
- if (subDefinitions[name]) {
675
- const match: string[] | RegExpExecArray | null = new RegExp(
676
- REGEX_GROUP_JSDOC,
677
- ).exec(text);
678
- if (match) {
679
- const k = match[1];
680
- const v = match[2];
681
- (definition as DefinitionIndex)[name] = {
682
- ...(definition as Record<string, Record<string, unknown>>)[name],
683
- [k]: v ? parseValue(symbol, k, v) : true,
684
- };
685
- return;
686
- }
687
- }
688
-
689
- // In TypeScript 3.7+, the "." is kept as part of the annotation name
690
- if (name.includes(".")) {
691
- const parts = name.split(".");
692
- const key = parts[0] as keyof Definition;
693
- if (parts.length === 2 && subDefinitions[key]) {
694
- (definition as DefinitionIndex)[key] = {
695
- ...(definition[key] as Record<string, unknown>),
696
- [parts[1]]: text ? parseValue(symbol, name, text) : true,
697
- };
698
- }
699
- }
700
-
701
- if (
702
- validationKeywords[name as keyof typeof validationKeywords] ||
703
- this.userValidationKeywords[name]
704
- ) {
705
- (definition as DefinitionIndex)[name] =
706
- text === undefined ? "" : parseValue(symbol, name, text);
707
- } else {
708
- // special annotations
709
- otherAnnotations[doc.name] = true;
710
- }
711
- });
712
- }
713
-
714
- private getDefinitionForRootType(
715
- propertyType: ts.Type,
716
- reffedType: ts.Symbol,
717
- definition: Definition,
718
- defaultNumberType = this.args.defaultNumberType,
719
- ignoreUndefined = false,
720
- ): Definition {
721
- const tupleType = resolveTupleType(propertyType);
722
-
723
- if (tupleType) {
724
- // tuple
725
- const elemTypes: ts.NodeArray<ts.TypeNode> = (propertyType as any)
726
- .typeArguments;
727
- const fixedTypes = elemTypes.map((elType) =>
728
- this.getTypeDefinition(elType as any),
729
- );
730
- definition.type = "array";
731
- if (fixedTypes.length > 0) {
732
- definition.items = fixedTypes;
733
- }
734
- const targetTupleType = (propertyType as ts.TupleTypeReference).target;
735
- definition.minItems = targetTupleType.minLength;
736
- if (targetTupleType.hasRestElement) {
737
- definition.additionalItems = fixedTypes[fixedTypes.length - 1];
738
- fixedTypes.splice(fixedTypes.length - 1, 1);
739
- } else {
740
- definition.maxItems = targetTupleType.fixedLength;
741
- }
742
- } else {
743
- const propertyTypeString = this.tc.typeToString(
744
- propertyType,
745
- undefined,
746
- ts.TypeFormatFlags.UseFullyQualifiedType,
747
- );
748
- const flags = propertyType.flags;
749
- const arrayType = this.tc.getIndexTypeOfType(
750
- propertyType,
751
- ts.IndexKind.Number,
752
- );
753
-
754
- if (flags & ts.TypeFlags.String) {
755
- definition.type = "string";
756
- } else if (flags & ts.TypeFlags.Number) {
757
- const isInteger =
758
- definition.type === "integer" ||
759
- reffedType?.getName() === "integer" ||
760
- defaultNumberType === "integer";
761
- definition.type = isInteger ? "integer" : "number";
762
- } else if (flags & ts.TypeFlags.Boolean) {
763
- definition.type = "boolean";
764
- } else if (flags & ts.TypeFlags.ESSymbol) {
765
- definition.type = "object";
766
- } else if (flags & ts.TypeFlags.Null) {
767
- definition.type = "null";
768
- } else if (
769
- flags & ts.TypeFlags.Undefined ||
770
- propertyTypeString === "void"
771
- ) {
772
- if (!ignoreUndefined) {
773
- throw new Error("Not supported: root type undefined");
774
- }
775
- // will be deleted
776
- definition.type = "undefined" as any;
777
- } else if (flags & ts.TypeFlags.Any || flags & ts.TypeFlags.Unknown) {
778
- // no type restriction, so that anything will match
779
- } else if (propertyTypeString === "Date" && !this.args.rejectDateType) {
780
- definition.type = "string";
781
- definition.format = definition.format || "date-time";
782
- } else if (propertyTypeString === "object") {
783
- definition.type = "object";
784
- definition.properties = {};
785
- definition.additionalProperties = true;
786
- } else if (propertyTypeString === "bigint") {
787
- definition.type = "number";
788
- definition.properties = {};
789
- definition.additionalProperties = false;
790
- } else {
791
- const value = extractLiteralValue(propertyType);
792
- if (value !== undefined) {
793
- // typeof value can be: "string", "boolean", "number", or "object" if value is null
794
- const typeofValue = typeof value;
795
- switch (typeofValue) {
796
- case "string":
797
- case "boolean":
798
- definition.type = typeofValue;
799
- break;
800
- case "number":
801
- definition.type = this.args.defaultNumberType;
802
- break;
803
- case "object":
804
- definition.type = "null";
805
- break;
806
- default:
807
- throw new Error(`Not supported: ${value} as a enum value`);
808
- }
809
- if (this.constAsEnum) {
810
- definition.enum = [value];
811
- } else {
812
- definition.const = value;
813
- }
814
- } else if (arrayType !== undefined) {
815
- if (
816
- propertyType.flags & ts.TypeFlags.Object &&
817
- (propertyType as ts.ObjectType).objectFlags &
818
- (ts.ObjectFlags.Anonymous |
819
- ts.ObjectFlags.Interface |
820
- ts.ObjectFlags.Mapped)
821
- ) {
822
- definition.type = "object";
823
- definition.additionalProperties = false;
824
- definition.patternProperties = {
825
- [NUMERIC_INDEX_PATTERN]: this.getTypeDefinition(arrayType),
826
- };
827
- if (
828
- !!Array.from((propertyType as any).members as any[])?.find(
829
- (member: [string]) => member[0] !== "__index",
830
- )
831
- ) {
832
- this.getClassDefinition(propertyType, definition);
833
- }
834
- } else if (propertyType.flags & ts.TypeFlags.TemplateLiteral) {
835
- definition.type = "string";
836
- // @ts-ignore
837
- const { texts, types } = propertyType;
838
- const pattern: string[] = [];
839
- for (let i = 0; i < texts.length; i++) {
840
- const text = texts[i].replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
841
- const type = types[i];
842
-
843
- if (i === 0) {
844
- pattern.push(`^`);
845
- }
846
-
847
- if (type) {
848
- if (type.flags & ts.TypeFlags.String) {
849
- pattern.push(`${text}.*`);
850
- }
851
-
852
- if (
853
- type.flags & ts.TypeFlags.Number ||
854
- type.flags & ts.TypeFlags.BigInt
855
- ) {
856
- pattern.push(`${text}[0-9]*`);
857
- }
858
-
859
- if (type.flags & ts.TypeFlags.Undefined) {
860
- pattern.push(`${text}undefined`);
861
- }
862
-
863
- if (type.flags & ts.TypeFlags.Null) {
864
- pattern.push(`${text}null`);
865
- }
866
- }
867
-
868
- if (i === texts.length - 1) {
869
- pattern.push(`${text}$`);
870
- }
871
- }
872
- definition.pattern = pattern.join("");
873
- } else {
874
- definition.type = "array";
875
- if (!definition.items) {
876
- definition.items = this.getTypeDefinition(arrayType);
877
- }
878
- }
879
- } else {
880
- // Report that type could not be processed
881
- const error = new TypeError(
882
- "Unsupported type: " + propertyTypeString,
883
- );
884
- (error as any).type = propertyType;
885
- throw error;
886
- // definition = this.getTypeDefinition(propertyType, tc);
887
- }
888
- }
889
- }
890
-
891
- return definition;
892
- }
893
-
894
- private getReferencedTypeSymbol(prop: ts.Symbol): ts.Symbol | undefined {
895
- const decl = prop.getDeclarations();
896
- if (decl?.length) {
897
- const type = (decl[0] as any).type as ts.TypeReferenceNode;
898
- if (type && type.kind & ts.SyntaxKind.TypeReference && type.typeName) {
899
- const symbol = this.tc.getSymbolAtLocation(type.typeName);
900
- if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
901
- return this.tc.getAliasedSymbol(symbol);
902
- }
903
- return symbol;
904
- }
905
- }
906
- return undefined;
907
- }
908
-
909
- private getDefinitionForProperty(
910
- prop: ts.Symbol,
911
- node: ts.Node,
912
- ): Definition | null {
913
- if (prop.flags & ts.SymbolFlags.Method) {
914
- return null;
915
- }
916
- const propertyName = prop.getName();
917
- const propertyType = this.tc.getTypeOfSymbolAtLocation(prop, node);
918
-
919
- const reffedType = this.getReferencedTypeSymbol(prop);
920
-
921
- const definition = this.getTypeDefinition(
922
- propertyType,
923
- undefined,
924
- undefined,
925
- prop,
926
- reffedType,
927
- );
928
-
929
- if (this.args.titles) {
930
- definition.title = propertyName;
931
- }
932
-
933
- if (definition.hasOwnProperty("ignore")) {
934
- return null;
935
- }
936
-
937
- // try to get default value
938
- const valDecl = prop.valueDeclaration as ts.VariableDeclaration;
939
- if (valDecl?.initializer) {
940
- let initial = valDecl.initializer;
941
-
942
- while (ts.isTypeAssertionExpression(initial)) {
943
- initial = initial.expression;
944
- }
945
-
946
- if ((initial as any).expression) {
947
- // node
948
- console.warn("initializer is expression for property " + propertyName);
949
- } else if (
950
- (initial as any).kind &&
951
- (initial as any).kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral
952
- ) {
953
- definition.default = initial.getText();
954
- } else {
955
- try {
956
- const sandbox = { sandboxvar: null as any };
957
- vm.runInNewContext("sandboxvar=" + initial.getText(), sandbox);
958
-
959
- const val = sandbox.sandboxvar;
960
- if (
961
- val === null ||
962
- typeof val === "string" ||
963
- typeof val === "number" ||
964
- typeof val === "boolean" ||
965
- Object.prototype.toString.call(val) === "[object Array]"
966
- ) {
967
- definition.default = val;
968
- } else if (val) {
969
- console.warn(
970
- "unknown initializer for property " + propertyName + ": " + val,
971
- );
972
- }
973
- } catch (e) {
974
- console.warn(
975
- "exception evaluating initializer for property " + propertyName,
976
- );
977
- }
978
- }
979
- }
980
-
981
- return definition;
982
- }
983
-
984
- private getEnumDefinition(
985
- clazzType: ts.Type,
986
- definition: Definition,
987
- ): Definition {
988
- const node = clazzType.getSymbol()!.getDeclarations()![0];
989
- const fullName = this.tc.typeToString(
990
- clazzType,
991
- undefined,
992
- ts.TypeFormatFlags.UseFullyQualifiedType,
993
- );
994
- const members: ts.NodeArray<ts.EnumMember> =
995
- node.kind === ts.SyntaxKind.EnumDeclaration
996
- ? (node as ts.EnumDeclaration).members
997
- : ts.factory.createNodeArray([node as ts.EnumMember]);
998
- var enumValues: (number | boolean | string | null)[] = [];
999
- const enumTypes: JSONSchema7TypeName[] = [];
1000
-
1001
- const addType = (type: JSONSchema7TypeName) => {
1002
- if (enumTypes.indexOf(type) === -1) {
1003
- enumTypes.push(type);
1004
- }
1005
- };
1006
-
1007
- members.forEach((member) => {
1008
- const caseLabel = (member.name as ts.Identifier).text;
1009
- const constantValue = this.tc.getConstantValue(member);
1010
- if (constantValue !== undefined) {
1011
- enumValues.push(constantValue);
1012
- addType(typeof constantValue as JSONSchema7TypeName); // can be only string or number;
1013
- } else {
1014
- // try to extract the enums value; it will probably by a cast expression
1015
- const initial: ts.Expression | undefined = member.initializer;
1016
- if (initial) {
1017
- if ((initial as any).expression) {
1018
- // node
1019
- const exp = (initial as any).expression;
1020
- const text = (exp as any).text;
1021
- // if it is an expression with a text literal, chances are it is the enum convention:
1022
- // CASELABEL = 'literal' as any
1023
- if (text) {
1024
- enumValues.push(text);
1025
- addType("string");
1026
- } else if (
1027
- exp.kind === ts.SyntaxKind.TrueKeyword ||
1028
- exp.kind === ts.SyntaxKind.FalseKeyword
1029
- ) {
1030
- enumValues.push(exp.kind === ts.SyntaxKind.TrueKeyword);
1031
- addType("boolean");
1032
- } else {
1033
- console.warn(
1034
- "initializer is expression for enum: " +
1035
- fullName +
1036
- "." +
1037
- caseLabel,
1038
- );
1039
- }
1040
- } else if (
1041
- initial.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral
1042
- ) {
1043
- enumValues.push(initial.getText());
1044
- addType("string");
1045
- } else if (initial.kind === ts.SyntaxKind.NullKeyword) {
1046
- enumValues.push(null);
1047
- addType("null");
1048
- }
1049
- }
1050
- }
1051
- });
1052
-
1053
- if (enumTypes.length) {
1054
- definition.type = enumTypes.length === 1 ? enumTypes[0] : enumTypes;
1055
- }
1056
-
1057
- if (enumValues.length > 0) {
1058
- if (enumValues.length > 1) {
1059
- definition.enum = enumValues;
1060
- } else {
1061
- definition.const = enumValues[0];
1062
- }
1063
- }
1064
-
1065
- return definition;
1066
- }
1067
-
1068
- private getUnionDefinition(
1069
- unionType: ts.UnionType,
1070
- unionModifier: keyof Definition,
1071
- definition: Definition,
1072
- ): Definition {
1073
- const enumValues: PrimitiveType[] = [];
1074
- const simpleTypes: JSONSchema7TypeName[] = [];
1075
- const schemas: Definition[] = [];
1076
-
1077
- const pushSimpleType = (type: JSONSchema7TypeName) => {
1078
- if (simpleTypes.indexOf(type) === -1) {
1079
- simpleTypes.push(type);
1080
- }
1081
- };
1082
-
1083
- const pushEnumValue = (val: PrimitiveType) => {
1084
- if (enumValues.indexOf(val) === -1) {
1085
- enumValues.push(val);
1086
- }
1087
- };
1088
-
1089
- for (const valueType of unionType.types) {
1090
- const value = extractLiteralValue(valueType);
1091
- if (value !== undefined) {
1092
- pushEnumValue(value);
1093
- } else {
1094
- const symbol = valueType.aliasSymbol;
1095
- const def = this.getTypeDefinition(
1096
- valueType,
1097
- undefined,
1098
- undefined,
1099
- symbol,
1100
- symbol,
1101
- undefined,
1102
- undefined,
1103
- true,
1104
- );
1105
- if (def.type === ("undefined" as any)) {
1106
- continue;
1107
- }
1108
- const keys = Object.keys(def);
1109
- if (keys.length === 1 && keys[0] === "type") {
1110
- if (typeof def.type !== "string") {
1111
- console.error("Expected only a simple type.");
1112
- } else {
1113
- pushSimpleType(def.type);
1114
- }
1115
- } else {
1116
- schemas.push(def);
1117
- }
1118
- }
1119
- }
1120
-
1121
- if (enumValues.length > 0) {
1122
- // If the values are true and false, just add "boolean" as simple type
1123
- const isOnlyBooleans =
1124
- enumValues.length === 2 &&
1125
- typeof enumValues[0] === "boolean" &&
1126
- typeof enumValues[1] === "boolean" &&
1127
- enumValues[0] !== enumValues[1];
1128
-
1129
- if (isOnlyBooleans) {
1130
- pushSimpleType("boolean");
1131
- } else {
1132
- const enumSchema: Definition =
1133
- enumValues.length > 1
1134
- ? { enum: enumValues.sort() }
1135
- : { const: enumValues[0] };
1136
-
1137
- // If all values are of the same primitive type, add a "type" field to the schema
1138
- if (
1139
- enumValues.every((x) => {
1140
- return typeof x === "string";
1141
- })
1142
- ) {
1143
- enumSchema.type = "string";
1144
- } else if (
1145
- enumValues.every((x) => {
1146
- return typeof x === "number";
1147
- })
1148
- ) {
1149
- enumSchema.type = "number";
1150
- } else if (
1151
- enumValues.every((x) => {
1152
- return typeof x === "boolean";
1153
- })
1154
- ) {
1155
- enumSchema.type = "boolean";
1156
- }
1157
-
1158
- schemas.push(enumSchema);
1159
- }
1160
- }
1161
-
1162
- if (simpleTypes.length > 0) {
1163
- schemas.push({
1164
- type: simpleTypes.length === 1 ? simpleTypes[0] : simpleTypes,
1165
- });
1166
- }
1167
-
1168
- if (schemas.length === 1) {
1169
- for (const k in schemas[0]) {
1170
- if (schemas[0].hasOwnProperty(k)) {
1171
- if (k === "description" && definition.hasOwnProperty(k)) {
1172
- // If we already have a more specific description, don't overwrite it.
1173
- continue;
1174
- }
1175
- (definition as DefinitionIndex)[k] =
1176
- schemas[0][k as keyof Definition];
1177
- }
1178
- }
1179
- } else {
1180
- (definition as DefinitionIndex)[unionModifier] = schemas;
1181
- }
1182
- return definition;
1183
- }
1184
-
1185
- private getIntersectionDefinition(
1186
- intersectionType: ts.IntersectionType,
1187
- definition: Definition,
1188
- ): Definition {
1189
- const simpleTypes: JSONSchema7TypeName[] = [];
1190
- const schemas: Definition[] = [];
1191
-
1192
- const pushSimpleType = (type: JSONSchema7TypeName) => {
1193
- if (simpleTypes.indexOf(type) === -1) {
1194
- simpleTypes.push(type);
1195
- }
1196
- };
1197
-
1198
- for (const intersectionMember of intersectionType.types) {
1199
- const def = this.getTypeDefinition(intersectionMember);
1200
- const keys = Object.keys(def);
1201
- if (keys.length === 1 && keys[0] === "type") {
1202
- if (typeof def.type !== "string") {
1203
- console.error("Expected only a simple type.");
1204
- } else {
1205
- pushSimpleType(def.type);
1206
- }
1207
- } else {
1208
- schemas.push(def);
1209
- }
1210
- }
1211
-
1212
- if (simpleTypes.length > 0) {
1213
- schemas.push({
1214
- type: simpleTypes.length === 1 ? simpleTypes[0] : simpleTypes,
1215
- });
1216
- }
1217
-
1218
- if (schemas.length === 1) {
1219
- for (const k in schemas[0]) {
1220
- if (schemas[0].hasOwnProperty(k)) {
1221
- (definition as DefinitionIndex)[k] =
1222
- schemas[0][k as keyof Definition];
1223
- }
1224
- }
1225
- } else {
1226
- definition.allOf = schemas;
1227
- }
1228
- return definition;
1229
- }
1230
-
1231
- private getClassDefinition(
1232
- clazzType: ts.Type,
1233
- definition: Definition,
1234
- ): Definition {
1235
- const node = clazzType.getSymbol()!.getDeclarations()![0];
1236
-
1237
- // Example: typeof globalThis may not have any declaration
1238
- if (!node) {
1239
- definition.type = "object";
1240
- return definition;
1241
- }
1242
-
1243
- if (this.args.typeOfKeyword && node.kind === ts.SyntaxKind.FunctionType) {
1244
- definition.typeof = "function";
1245
- return definition;
1246
- }
1247
-
1248
- const clazz = node as ts.ClassDeclaration;
1249
- const props = this.tc.getPropertiesOfType(clazzType).filter((prop) => {
1250
- // filter never and undefined
1251
- const propertyFlagType = this.tc
1252
- .getTypeOfSymbolAtLocation(prop, node)
1253
- .getFlags();
1254
- if (
1255
- ts.TypeFlags.Never === propertyFlagType ||
1256
- ts.TypeFlags.Undefined === propertyFlagType
1257
- ) {
1258
- return false;
1259
- }
1260
- if (!this.args.excludePrivate) {
1261
- return true;
1262
- }
1263
-
1264
- const decls = prop.declarations;
1265
- return !(
1266
- decls &&
1267
- decls.filter((decl) => {
1268
- const mods = (decl as any).modifiers;
1269
- return (
1270
- mods &&
1271
- mods.filter((mod: any) => mod.kind === ts.SyntaxKind.PrivateKeyword)
1272
- .length > 0
1273
- );
1274
- }).length > 0
1275
- );
1276
- });
1277
- const fullName = this.tc.typeToString(
1278
- clazzType,
1279
- undefined,
1280
- ts.TypeFormatFlags.UseFullyQualifiedType,
1281
- );
1282
-
1283
- const modifierFlags = ts.getCombinedModifierFlags(node);
1284
-
1285
- if (
1286
- modifierFlags & ts.ModifierFlags.Abstract &&
1287
- this.inheritingTypes[fullName]
1288
- ) {
1289
- const oneOf = this.inheritingTypes[fullName].map((typename) => {
1290
- return this.getTypeDefinition(this.allSymbols[typename]);
1291
- });
1292
-
1293
- definition.oneOf = oneOf;
1294
- } else {
1295
- if (clazz.members) {
1296
- const indexSignatures =
1297
- clazz.members == null
1298
- ? []
1299
- : clazz.members.filter(
1300
- (x) => x.kind === ts.SyntaxKind.IndexSignature,
1301
- );
1302
- if (indexSignatures.length === 1) {
1303
- // for case "array-types"
1304
- const indexSignature =
1305
- indexSignatures[0] as ts.IndexSignatureDeclaration;
1306
- if (indexSignature.parameters.length !== 1) {
1307
- throw new Error(
1308
- "Not supported: IndexSignatureDeclaration parameters.length != 1",
1309
- );
1310
- }
1311
- const indexSymbol: ts.Symbol = (indexSignature.parameters[0] as any)
1312
- .symbol;
1313
- const indexType = this.tc.getTypeOfSymbolAtLocation(
1314
- indexSymbol,
1315
- node,
1316
- );
1317
- const isIndexedObject =
1318
- indexType.flags === ts.TypeFlags.String ||
1319
- indexType.flags === ts.TypeFlags.Number;
1320
- if (indexType.flags !== ts.TypeFlags.Number && !isIndexedObject) {
1321
- throw new Error(
1322
- "Not supported: IndexSignatureDeclaration with index symbol other than a number or a string",
1323
- );
1324
- }
1325
-
1326
- const typ = this.tc.getTypeAtLocation(indexSignature.type!);
1327
- let def: Definition | undefined;
1328
- if (typ.flags & ts.TypeFlags.IndexedAccess) {
1329
- const targetName = ts.escapeLeadingUnderscores(
1330
- (clazzType as any).mapper?.target?.value,
1331
- );
1332
- const indexedAccessType = typ as ts.IndexedAccessType;
1333
- const symbols: Map<ts.__String, ts.Symbol> = (
1334
- indexedAccessType.objectType as any
1335
- ).members;
1336
- const targetSymbol = symbols?.get(targetName);
1337
-
1338
- if (targetSymbol) {
1339
- const targetNode = targetSymbol.getDeclarations()![0];
1340
- const targetDef = this.getDefinitionForProperty(
1341
- targetSymbol,
1342
- targetNode,
1343
- );
1344
- if (targetDef) {
1345
- def = targetDef;
1346
- }
1347
- }
1348
- }
1349
- if (!def) {
1350
- def = this.getTypeDefinition(typ, undefined, "anyOf");
1351
- }
1352
- if (isIndexedObject) {
1353
- definition.type = "object";
1354
- if (!Object.keys(definition.patternProperties || {}).length) {
1355
- definition.additionalProperties = def;
1356
- }
1357
- } else {
1358
- definition.type = "array";
1359
- if (!definition.items) {
1360
- definition.items = def;
1361
- }
1362
- }
1363
- }
1364
- }
1365
-
1366
- const propertyDefinitions = props.reduce<Record<string, Definition>>(
1367
- (all, prop) => {
1368
- const propertyName = prop.getName();
1369
- const propDef = this.getDefinitionForProperty(prop, node);
1370
- if (propDef != null) {
1371
- all[propertyName] = propDef;
1372
- }
1373
- return all;
1374
- },
1375
- {},
1376
- );
1377
-
1378
- if (definition.type === undefined) {
1379
- definition.type = "object";
1380
- }
1381
-
1382
- if (
1383
- definition.type === "object" &&
1384
- Object.keys(propertyDefinitions).length > 0
1385
- ) {
1386
- definition.properties = propertyDefinitions;
1387
- }
1388
-
1389
- if (this.args.defaultProps) {
1390
- definition.defaultProperties = [];
1391
- }
1392
- if (
1393
- this.args.noExtraProps &&
1394
- definition.additionalProperties === undefined
1395
- ) {
1396
- definition.additionalProperties = false;
1397
- }
1398
- if (this.args.propOrder) {
1399
- // propertyOrder is non-standard, but useful:
1400
- // https://github.com/json-schema/json-schema/issues/87
1401
- const propertyOrder = props.reduce(
1402
- (order: string[], prop: ts.Symbol) => {
1403
- order.push(prop.getName());
1404
- return order;
1405
- },
1406
- [],
1407
- );
1408
-
1409
- definition.propertyOrder = propertyOrder;
1410
- }
1411
- if (this.args.required) {
1412
- const requiredProps = props.reduce(
1413
- (required: string[], prop: ts.Symbol) => {
1414
- const def = {};
1415
- this.parseCommentsIntoDefinition(prop, def, {});
1416
- const allUnionTypesFlags: number[] =
1417
- (prop as any).links?.type?.types?.map?.((t: any) => t.flags) ||
1418
- [];
1419
- if (
1420
- !(prop.flags & ts.SymbolFlags.Optional) &&
1421
- !(prop.flags & ts.SymbolFlags.Method) &&
1422
- !allUnionTypesFlags.includes(ts.TypeFlags.Undefined) &&
1423
- !allUnionTypesFlags.includes(ts.TypeFlags.Void) &&
1424
- !def.hasOwnProperty("ignore")
1425
- ) {
1426
- required.push(prop.getName());
1427
- }
1428
- return required;
1429
- },
1430
- [],
1431
- );
1432
-
1433
- if (requiredProps.length > 0) {
1434
- definition.required = unique(requiredProps).sort();
1435
- }
1436
- }
1437
- }
1438
- return definition;
1439
- }
1440
-
1441
- /**
1442
- * Gets/generates a globally unique type name for the given type
1443
- */
1444
- private getTypeName(typ: ts.Type): string {
1445
- const id = (typ as any).id as number;
1446
- if (this.typeNamesById[id]) {
1447
- // Name already assigned?
1448
- return this.typeNamesById[id];
1449
- }
1450
- return this.makeTypeNameUnique(
1451
- typ,
1452
- this.tc
1453
- .typeToString(
1454
- typ,
1455
- undefined,
1456
- ts.TypeFormatFlags.NoTruncation |
1457
- ts.TypeFormatFlags.UseFullyQualifiedType,
1458
- )
1459
- .replace(REGEX_FILE_NAME_OR_SPACE, ""),
1460
- );
1461
- }
1462
-
1463
- private makeTypeNameUnique(typ: ts.Type, baseName: string): string {
1464
- const id = (typ as any).id as number;
1465
-
1466
- let name = baseName;
1467
- // If a type with same name exists
1468
- // Try appending "_1", "_2", etc.
1469
- for (
1470
- let i = 1;
1471
- this.typeIdsByName[name] !== undefined && this.typeIdsByName[name] !== id;
1472
- ++i
1473
- ) {
1474
- name = baseName + "_" + i;
1475
- }
1476
-
1477
- this.typeNamesById[id] = name;
1478
- this.typeIdsByName[name] = id;
1479
- return name;
1480
- }
1481
-
1482
- private recursiveTypeRef = new Map();
1483
-
1484
- private getTypeDefinition(
1485
- typ: ts.Type,
1486
- asRef = this.args.ref,
1487
- unionModifier: keyof Definition = "anyOf",
1488
- prop?: ts.Symbol,
1489
- reffedType?: ts.Symbol,
1490
- pairedSymbol?: ts.Symbol,
1491
- forceNotRef: boolean = false,
1492
- ignoreUndefined = false,
1493
- ): Definition {
1494
- const definition: Definition = {}; // real definition
1495
-
1496
- // 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
1497
- while (
1498
- typ.aliasSymbol &&
1499
- (typ.aliasSymbol.escapedName === "Readonly" ||
1500
- typ.aliasSymbol.escapedName === "Mutable") &&
1501
- typ.aliasTypeArguments &&
1502
- typ.aliasTypeArguments[0]
1503
- ) {
1504
- typ = typ.aliasTypeArguments[0];
1505
- reffedType = undefined;
1506
- }
1507
-
1508
- if (
1509
- this.args.typeOfKeyword &&
1510
- typ.flags & ts.TypeFlags.Object &&
1511
- (typ as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous
1512
- ) {
1513
- definition.typeof = "function";
1514
- return definition;
1515
- }
1516
-
1517
- let returnedDefinition = definition; // returned definition, may be a $ref
1518
-
1519
- // Parse property comments now to skip recursive if ignore.
1520
- if (prop) {
1521
- const defs: Definition & { [k in MetaDefinitionFields]?: "" } = {};
1522
- const others = {};
1523
- this.parseCommentsIntoDefinition(prop, defs, others);
1524
- if (defs.hasOwnProperty("ignore") || defs.hasOwnProperty("type")) {
1525
- return defs;
1526
- }
1527
- }
1528
-
1529
- const symbol = typ.getSymbol();
1530
- // FIXME: We can't just compare the name of the symbol - it ignores the namespace
1531
- let isRawType =
1532
- !symbol ||
1533
- // Window is incorrectly marked as rawType here for some reason
1534
- (this.tc.getFullyQualifiedName(symbol) !== "Window" &&
1535
- (this.tc.getFullyQualifiedName(symbol) === "Date" ||
1536
- symbol.name === "integer" ||
1537
- this.tc.getIndexInfoOfType(typ, ts.IndexKind.Number) !== undefined));
1538
-
1539
- if (
1540
- isRawType &&
1541
- (typ as any).aliasSymbol?.escapedName &&
1542
- (typ as any).types
1543
- ) {
1544
- isRawType = false;
1545
- }
1546
-
1547
- // special case: an union where all child are string literals -> make an enum instead
1548
- let isStringEnum = false;
1549
- if (typ.flags & ts.TypeFlags.Union) {
1550
- const unionType = typ as ts.UnionType;
1551
- isStringEnum = unionType.types.every((propType) => {
1552
- return (propType.getFlags() & ts.TypeFlags.StringLiteral) !== 0;
1553
- });
1554
- }
1555
-
1556
- // aliased types must be handled slightly different
1557
- const asTypeAliasRef =
1558
- asRef && reffedType && (this.args.aliasRef || isStringEnum);
1559
- if (!asTypeAliasRef) {
1560
- if (
1561
- isRawType ||
1562
- (typ.getFlags() & ts.TypeFlags.Object &&
1563
- (typ as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous)
1564
- ) {
1565
- asRef = false; // raw types and inline types cannot be reffed,
1566
- // unless we are handling a type alias
1567
- // or it is recursive type - see below
1568
- }
1569
- }
1570
-
1571
- let fullTypeName = "";
1572
- if (asTypeAliasRef) {
1573
- const typeName = this.tc
1574
- .getFullyQualifiedName(
1575
- reffedType!.getFlags() & ts.SymbolFlags.Alias
1576
- ? this.tc.getAliasedSymbol(reffedType!)
1577
- : reffedType!,
1578
- )
1579
- .replace(REGEX_FILE_NAME_OR_SPACE, "");
1580
- if (this.args.uniqueNames && reffedType) {
1581
- const sourceFile = getSourceFile(reffedType);
1582
- const relativePath = path.relative(process.cwd(), sourceFile.fileName);
1583
- fullTypeName = `${typeName}.${generateHashOfNode(
1584
- getCanonicalDeclaration(reffedType!),
1585
- relativePath,
1586
- )}`;
1587
- } else {
1588
- fullTypeName = this.makeTypeNameUnique(typ, typeName);
1589
- }
1590
- } else {
1591
- // typ.symbol can be undefined
1592
- if (this.args.uniqueNames && typ.symbol) {
1593
- const sym = typ.symbol;
1594
- const sourceFile = getSourceFile(sym);
1595
- const relativePath = path.relative(process.cwd(), sourceFile.fileName);
1596
- fullTypeName = `${this.getTypeName(typ)}.${generateHashOfNode(
1597
- getCanonicalDeclaration(sym),
1598
- relativePath,
1599
- )}`;
1600
- } else if (
1601
- reffedType &&
1602
- this.schemaOverrides.has(reffedType.escapedName as string)
1603
- ) {
1604
- fullTypeName = reffedType.escapedName as string;
1605
- } else {
1606
- fullTypeName = this.getTypeName(typ);
1607
- }
1608
- }
1609
-
1610
- // Handle recursive types
1611
- if (!isRawType || !!typ.aliasSymbol) {
1612
- if (this.recursiveTypeRef.has(fullTypeName) && !forceNotRef) {
1613
- asRef = true;
1614
- } else {
1615
- this.recursiveTypeRef.set(fullTypeName, definition);
1616
- }
1617
- }
1618
-
1619
- if (asRef) {
1620
- // We don't return the full definition, but we put it into
1621
- // reffedDefinitions below.
1622
- returnedDefinition = {
1623
- $ref: `${this.args.id}#/definitions/` + fullTypeName,
1624
- };
1625
- }
1626
-
1627
- // Parse comments
1628
- const otherAnnotations: Record<string, true> = {};
1629
- this.parseCommentsIntoDefinition(reffedType!, definition, otherAnnotations); // handle comments in the type alias declaration
1630
- this.parseCommentsIntoDefinition(symbol!, definition, otherAnnotations);
1631
- this.parseCommentsIntoDefinition(
1632
- typ.aliasSymbol!,
1633
- definition,
1634
- otherAnnotations,
1635
- );
1636
- if (prop) {
1637
- this.parseCommentsIntoDefinition(
1638
- prop,
1639
- returnedDefinition,
1640
- otherAnnotations,
1641
- );
1642
- }
1643
- if (pairedSymbol && symbol && this.isFromDefaultLib(symbol)) {
1644
- this.parseCommentsIntoDefinition(
1645
- pairedSymbol,
1646
- definition,
1647
- otherAnnotations,
1648
- );
1649
- }
1650
-
1651
- // Create the actual definition only if is an inline definition, or
1652
- // if it will be a $ref and it is not yet created.
1653
- // Prioritise overrides.
1654
- const overrideDefinition = this.schemaOverrides.get(fullTypeName);
1655
- if (overrideDefinition) {
1656
- this.reffedDefinitions[fullTypeName] = overrideDefinition;
1657
- } else if (!asRef || !this.reffedDefinitions[fullTypeName]) {
1658
- if (asRef) {
1659
- // must be here to prevent recursivity problems
1660
- let reffedDefinition: Definition;
1661
- if (
1662
- asTypeAliasRef &&
1663
- reffedType &&
1664
- typ.symbol !== reffedType &&
1665
- symbol
1666
- ) {
1667
- reffedDefinition = this.getTypeDefinition(
1668
- typ,
1669
- true,
1670
- undefined,
1671
- symbol,
1672
- symbol,
1673
- );
1674
- } else {
1675
- reffedDefinition = definition;
1676
- }
1677
- this.reffedDefinitions[fullTypeName] = reffedDefinition;
1678
- if (this.args.titles && fullTypeName) {
1679
- definition.title = fullTypeName;
1680
- }
1681
- }
1682
- const node =
1683
- symbol?.getDeclarations() !== undefined
1684
- ? symbol.getDeclarations()![0]
1685
- : null;
1686
-
1687
- if (definition.type === undefined) {
1688
- // if users override the type, do not try to infer it
1689
- if (
1690
- typ.flags & ts.TypeFlags.Union &&
1691
- (node === null || node.kind !== ts.SyntaxKind.EnumDeclaration)
1692
- ) {
1693
- this.getUnionDefinition(
1694
- typ as ts.UnionType,
1695
- unionModifier,
1696
- definition,
1697
- );
1698
- } else if (typ.flags & ts.TypeFlags.Intersection) {
1699
- if (this.args.noExtraProps) {
1700
- // extend object instead of using allOf because allOf does not work well with additional properties. See #107
1701
- if (this.args.noExtraProps) {
1702
- definition.additionalProperties = false;
1703
- }
1704
-
1705
- const types = (typ as ts.IntersectionType).types;
1706
- for (const member of types) {
1707
- const other = this.getTypeDefinition(
1708
- member,
1709
- false,
1710
- undefined,
1711
- undefined,
1712
- undefined,
1713
- undefined,
1714
- true,
1715
- );
1716
- definition.type = other.type; // should always be object
1717
- definition.properties = {
1718
- ...definition.properties,
1719
- ...other.properties,
1720
- };
1721
-
1722
- if (Object.keys(other.default || {}).length > 0) {
1723
- definition.default = extend(
1724
- definition.default || {},
1725
- other.default,
1726
- );
1727
- }
1728
- if (other.required) {
1729
- definition.required = unique(
1730
- (definition.required || []).concat(other.required),
1731
- ).sort();
1732
- }
1733
- }
1734
- } else {
1735
- this.getIntersectionDefinition(
1736
- typ as ts.IntersectionType,
1737
- definition,
1738
- );
1739
- }
1740
- } else if (isRawType) {
1741
- if (pairedSymbol) {
1742
- this.parseCommentsIntoDefinition(pairedSymbol, definition, {});
1743
- }
1744
- this.getDefinitionForRootType(
1745
- typ,
1746
- reffedType!,
1747
- definition,
1748
- undefined,
1749
- ignoreUndefined,
1750
- );
1751
- } else if (
1752
- node &&
1753
- (node.kind === ts.SyntaxKind.EnumDeclaration ||
1754
- node.kind === ts.SyntaxKind.EnumMember)
1755
- ) {
1756
- this.getEnumDefinition(typ, definition);
1757
- } else if (
1758
- symbol &&
1759
- symbol.flags & ts.SymbolFlags.TypeLiteral &&
1760
- symbol.members!.size === 0 &&
1761
- !(node && node.kind === ts.SyntaxKind.MappedType)
1762
- ) {
1763
- // {} is TypeLiteral with no members. Need special case because it doesn't have declarations.
1764
- definition.type = "object";
1765
- definition.properties = {};
1766
- } else {
1767
- this.getClassDefinition(typ, definition);
1768
- }
1769
- }
1770
- }
1771
-
1772
- if (this.recursiveTypeRef.get(fullTypeName) === definition) {
1773
- this.recursiveTypeRef.delete(fullTypeName);
1774
- // If the type was recursive (there is reffedDefinitions) - lets replace it to reference
1775
- if (this.reffedDefinitions[fullTypeName]) {
1776
- const annotations = Object.entries(returnedDefinition).reduce<
1777
- Record<string, unknown>
1778
- >((acc, [key, value]) => {
1779
- if (
1780
- annotationKeywords[key as keyof typeof annotationKeywords] &&
1781
- typeof value !== undefined
1782
- ) {
1783
- acc[key] = value;
1784
- }
1785
- return acc;
1786
- }, {});
1787
-
1788
- returnedDefinition = {
1789
- $ref: `${this.args.id}#/definitions/` + fullTypeName,
1790
- ...annotations,
1791
- };
1792
- }
1793
- }
1794
-
1795
- if (otherAnnotations["nullable"]) {
1796
- makeNullable(returnedDefinition);
1797
- }
1798
-
1799
- return returnedDefinition;
1800
- }
1801
-
1802
- public setSchemaOverride(symbolName: string, schema: Definition): void {
1803
- this.schemaOverrides.set(symbolName, schema);
1804
- }
1805
-
1806
- public getSchemaForSymbol(
1807
- symbolName: string,
1808
- includeReffedDefinitions: boolean = true,
1809
- includeAllOverrides: boolean = false,
1810
- ): Definition {
1811
- const overrideDefinition = this.schemaOverrides.get(symbolName);
1812
- if (!this.allSymbols[symbolName] && !overrideDefinition) {
1813
- throw new Error(`type ${symbolName} not found`);
1814
- }
1815
-
1816
- this.resetSchemaSpecificProperties(includeAllOverrides);
1817
-
1818
- let def;
1819
- if (overrideDefinition) {
1820
- def = { ...overrideDefinition };
1821
- } else {
1822
- def = overrideDefinition
1823
- ? overrideDefinition
1824
- : this.getTypeDefinition(
1825
- this.allSymbols[symbolName],
1826
- this.args.topRef,
1827
- undefined,
1828
- undefined,
1829
- undefined,
1830
- this.userSymbols[symbolName] || undefined,
1831
- );
1832
- }
1833
-
1834
- if (
1835
- this.args.ref &&
1836
- includeReffedDefinitions &&
1837
- Object.keys(this.reffedDefinitions).length > 0
1838
- ) {
1839
- def.definitions = this.reffedDefinitions;
1840
- }
1841
- def["$schema"] = "http://json-schema.org/draft-07/schema#";
1842
- const id = this.args.id;
1843
- if (id) {
1844
- def["$id"] = this.args.id;
1845
- }
1846
- return def;
1847
- }
1848
-
1849
- public getSchemaForSymbols(
1850
- symbolNames: string[],
1851
- includeReffedDefinitions: boolean = true,
1852
- includeAllOverrides: boolean = false,
1853
- ): Definition {
1854
- const root: {
1855
- $id?: string;
1856
- $schema: string;
1857
- definitions: Record<string, Definition>;
1858
- } = {
1859
- $schema: "http://json-schema.org/draft-07/schema#",
1860
- definitions: {},
1861
- };
1862
-
1863
- this.resetSchemaSpecificProperties(includeAllOverrides);
1864
-
1865
- const id = this.args.id;
1866
-
1867
- if (id) {
1868
- root["$id"] = id;
1869
- }
1870
-
1871
- for (const symbolName of symbolNames) {
1872
- root.definitions[symbolName] = this.getTypeDefinition(
1873
- this.allSymbols[symbolName],
1874
- this.args.topRef,
1875
- undefined,
1876
- undefined,
1877
- undefined,
1878
- this.userSymbols[symbolName],
1879
- );
1880
- }
1881
- if (
1882
- this.args.ref &&
1883
- includeReffedDefinitions &&
1884
- Object.keys(this.reffedDefinitions).length > 0
1885
- ) {
1886
- root.definitions = { ...root.definitions, ...this.reffedDefinitions };
1887
- }
1888
- return root;
1889
- }
1890
-
1891
- public getSymbols(name?: string): SymbolRef[] {
1892
- if (name === void 0) {
1893
- return this.symbols;
1894
- }
1895
-
1896
- return this.symbols.filter((symbol) => symbol.typeName === name);
1897
- }
1898
-
1899
- public getUserSymbols(): string[] {
1900
- return Object.keys(this.userSymbols);
1901
- }
1902
-
1903
- public getMainFileSymbols(
1904
- program: ts.Program,
1905
- onlyIncludeFiles?: string[],
1906
- ): string[] {
1907
- function includeFile(file: ts.SourceFile): boolean {
1908
- if (onlyIncludeFiles === undefined) {
1909
- return !file.isDeclarationFile;
1910
- }
1911
- return (
1912
- onlyIncludeFiles.filter((f) => pathEqual(f, file.fileName)).length > 0
1913
- );
1914
- }
1915
- const files = program.getSourceFiles().filter(includeFile);
1916
- if (files.length) {
1917
- return Object.keys(this.userSymbols).filter((key) => {
1918
- const symbol = this.userSymbols[key];
1919
- if (!symbol || !symbol.declarations || !symbol.declarations.length) {
1920
- return false;
1921
- }
1922
- let node: ts.Node = symbol.declarations[0];
1923
- while (node?.parent) {
1924
- node = node.parent;
1925
- }
1926
- return files.indexOf(node.getSourceFile()) > -1;
1927
- });
1928
- }
1929
- return [];
1930
- }
1931
- }
1932
-
1933
- function generateHashOfNode(node: ts.Node, relativePath: string): string {
1934
- return createHash("md5")
1935
- .update(relativePath)
1936
- .update(node.pos.toString())
1937
- .digest("hex")
1938
- .substring(0, 8);
1939
- }
1940
-
1941
- export function buildGenerator(
1942
- program: ts.Program,
1943
- args: PartialArgs = {},
1944
- ): JsonSchemaGenerator | null {
1945
- // Use defaults unless otherwise specified
1946
- const settings = getDefaultArgs();
1947
-
1948
- for (const pref in args) {
1949
- if (args.hasOwnProperty(pref)) {
1950
- (settings as Record<string, Partial<Args>[keyof Args]>)[
1951
- pref as keyof Args
1952
- ] = args[pref as keyof Args];
1953
- }
1954
- }
1955
-
1956
- const typeChecker = program.getTypeChecker();
1957
-
1958
- const symbols: SymbolRef[] = [];
1959
- const allSymbols: { [name: string]: ts.Type } = {};
1960
- const userSymbols: { [name: string]: ts.Symbol } = {};
1961
- const inheritingTypes: { [baseName: string]: string[] } = {};
1962
- const workingDir = program.getCurrentDirectory();
1963
-
1964
- program.getSourceFiles().forEach((sourceFile, _sourceFileIdx) => {
1965
- const relativePath = path.relative(workingDir, sourceFile.fileName);
1966
-
1967
- function inspect(node: ts.Node, tc: ts.TypeChecker) {
1968
- if (
1969
- node.kind === ts.SyntaxKind.ClassDeclaration ||
1970
- node.kind === ts.SyntaxKind.InterfaceDeclaration ||
1971
- node.kind === ts.SyntaxKind.EnumDeclaration ||
1972
- node.kind === ts.SyntaxKind.TypeAliasDeclaration
1973
- ) {
1974
- const symbol: ts.Symbol = (node as any).symbol;
1975
- const nodeType = tc.getTypeAtLocation(node);
1976
- const fullyQualifiedName = tc.getFullyQualifiedName(symbol);
1977
- const typeName = fullyQualifiedName.replace(/".*"\./, "");
1978
- const name = !args.uniqueNames
1979
- ? typeName
1980
- : `${typeName}.${generateHashOfNode(node, relativePath)}`;
1981
-
1982
- symbols.push({ name, typeName, fullyQualifiedName, symbol });
1983
- if (!userSymbols[name]) {
1984
- allSymbols[name] = nodeType;
1985
- }
1986
-
1987
- const baseTypes = nodeType.getBaseTypes() || [];
1988
-
1989
- baseTypes.forEach((baseType) => {
1990
- var baseName = tc.typeToString(
1991
- baseType,
1992
- undefined,
1993
- ts.TypeFormatFlags.UseFullyQualifiedType,
1994
- );
1995
- if (!inheritingTypes[baseName]) {
1996
- inheritingTypes[baseName] = [];
1997
- }
1998
- inheritingTypes[baseName].push(name);
1999
- });
2000
- } else {
2001
- ts.forEachChild(node, (n) => inspect(n, tc));
2002
- }
2003
- }
2004
-
2005
- inspect(sourceFile, typeChecker);
2006
- });
2007
-
2008
- return new JsonSchemaGenerator(
2009
- symbols,
2010
- allSymbols,
2011
- userSymbols,
2012
- inheritingTypes,
2013
- typeChecker,
2014
- settings,
2015
- );
2016
- }