dtsgenerator 3.12.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import Debug from 'debug';
2
2
  import * as ts from 'typescript';
3
- import { get, set, tilde } from '../jsonPointer';
3
+ import { tilde } from '../jsonPointer';
4
4
  import * as ast from './astBuilder';
5
5
  import config from './config';
6
6
  import { getSubSchema, NormalizedSchema } from './jsonSchema';
@@ -13,10 +13,10 @@ import {
13
13
  PreProcessHandler,
14
14
  loadPlugin,
15
15
  } from './type';
16
+ import { buildTypeTree, TypeTree } from './typeTree';
16
17
  import * as utils from './utils';
17
18
 
18
19
  const debug = Debug('dtsgen');
19
- const typeMarker = Symbol();
20
20
 
21
21
  interface PluginConfig {
22
22
  plugin: Plugin;
@@ -42,11 +42,8 @@ export default class DtsGenerator {
42
42
  this.contents.forEach((schema) => this.resolver.registerSchema(schema));
43
43
  await this.resolver.resolve();
44
44
 
45
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
46
- const map = this.buildSchemaMergedMap(
47
- this.resolver.getAllRegisteredSchema()
48
- );
49
- const root = this.walk(map, true);
45
+ const tree = buildTypeTree(this.resolver.getAllRegisteredSchema());
46
+ const root = this.walk(tree);
50
47
  const file = ts.createSourceFile(
51
48
  '_.d.ts',
52
49
  '',
@@ -77,31 +74,6 @@ export default class DtsGenerator {
77
74
  return transformedContent;
78
75
  }
79
76
 
80
- private buildSchemaMergedMap(schemas: IterableIterator<Schema>): any {
81
- const map: any = {};
82
- const paths: { path: string[]; type: Schema }[] = [];
83
- for (const type of schemas) {
84
- const path = type.id.toNames();
85
- paths.push({ path, type });
86
- }
87
-
88
- for (const item of paths) {
89
- const path = item.path;
90
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
91
- const parent = get(map, path, true);
92
- if (parent == null) {
93
- set(map, path, { [typeMarker]: item.type });
94
- } else {
95
- Object.assign(parent, { [typeMarker]: item.type });
96
- }
97
- }
98
- if (Object.keys(map).length === 0) {
99
- throw new Error('There is no schema in the input contents.');
100
- }
101
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
102
- return map;
103
- }
104
-
105
77
  private async getPlugins(): Promise<{
106
78
  pre: PluginConfig[];
107
79
  post: PluginConfig[];
@@ -169,24 +141,22 @@ export default class DtsGenerator {
169
141
  return result;
170
142
  }
171
143
 
172
- private walk(map: any, root: boolean): ts.Statement[] {
144
+ private walk(tree: TypeTree, root = true): ts.Statement[] {
173
145
  const result: ts.Statement[] = [];
174
- const keys = Object.keys(map).sort();
146
+ const keys = [...tree.children.keys()].sort();
175
147
  for (const key of keys) {
176
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
177
- const value = map[key];
178
- if (
179
- typeof value === 'object' &&
180
- Object.prototype.hasOwnProperty.call(value, typeMarker)
181
- ) {
182
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
183
- const schema = value[typeMarker] as Schema;
148
+ const value = tree.children.get(key);
149
+ if (value === undefined) {
150
+ continue;
151
+ }
152
+ if (value.schema !== undefined) {
153
+ const schema = value.schema;
184
154
  debug(
185
155
  ` walk doProcess: key=${key} schemaId=${schema.id.getAbsoluteId()}`
186
156
  );
187
157
  result.push(this.walkSchema(schema, root));
188
158
  }
189
- if (typeof value === 'object' && Object.keys(value).length > 0) {
159
+ if (value.children.size > 0) {
190
160
  result.push(
191
161
  ast.buildNamespaceNode(key, this.walk(value, false), root)
192
162
  );
@@ -199,25 +169,33 @@ export default class DtsGenerator {
199
169
  const normalized = this.normalizeContent(schema);
200
170
  this.currentSchema = normalized;
201
171
 
202
- const getNode = () => {
203
- const type = normalized.content.type;
204
- switch (type) {
205
- case 'any':
206
- return this.generateAnyTypeModel(normalized, root);
207
- case 'array':
208
- return this.generateTypeCollection(normalized, root);
209
- case 'object':
210
- default:
211
- return this.generateDeclareType(normalized, root);
212
- }
213
- };
214
172
  return ast.addOptionalInformation(
215
- ast.addComment(getNode(), normalized, true),
173
+ ast.addComment(
174
+ this.parseSchema(normalized, root),
175
+ normalized,
176
+ true
177
+ ),
216
178
  normalized,
217
179
  true
218
180
  );
219
181
  }
220
182
 
183
+ private parseSchema(
184
+ schema: NormalizedSchema,
185
+ root = false
186
+ ): ts.DeclarationStatement {
187
+ const type = schema.content.type;
188
+ switch (type) {
189
+ case 'any':
190
+ return this.generateAnyTypeModel(schema, root);
191
+ case 'array':
192
+ return this.generateTypeCollection(schema, root);
193
+ case 'object':
194
+ default:
195
+ return this.generateDeclareType(schema, root);
196
+ }
197
+ }
198
+
221
199
  private normalizeContent(
222
200
  schema: Schema,
223
201
  pointer?: string
@@ -361,18 +339,21 @@ export default class DtsGenerator {
361
339
  }
362
340
  }
363
341
  if (content.patternProperties) {
364
- const schemasTypes = [];
365
- for (const propertyName of Object.keys(content.patternProperties)) {
366
- const schema = this.normalizeContent(
367
- baseSchema,
368
- '/patternProperties/' + tilde(propertyName)
369
- );
370
- schemasTypes.push(this.generateTypeProperty(schema));
371
- }
342
+ const properties = Object.keys(content.patternProperties);
372
343
  const node = ast.buildPropertySignature(
373
344
  { content: { readOnly: false } } as NormalizedSchema,
374
345
  'pattern',
375
- ts.createUnionTypeNode(schemasTypes),
346
+ ast.buildUnionTypeNode(
347
+ properties,
348
+ (propertyName) =>
349
+ this.generateTypeProperty(
350
+ this.normalizeContent(
351
+ baseSchema,
352
+ '/patternProperties/' + tilde(propertyName)
353
+ )
354
+ ),
355
+ true
356
+ ),
376
357
  baseSchema.content.required,
377
358
  true
378
359
  );
@@ -380,9 +361,7 @@ export default class DtsGenerator {
380
361
  ts.addSyntheticTrailingComment(
381
362
  node,
382
363
  ts.SyntaxKind.MultiLineCommentTrivia,
383
- ` Patterns: ${Object.keys(content.patternProperties).join(
384
- ' | '
385
- )} `
364
+ ` Patterns: ${properties.join(' | ')} `
386
365
  )
387
366
  );
388
367
  }
@@ -401,20 +380,21 @@ export default class DtsGenerator {
401
380
  );
402
381
  }
403
382
  const refSchema = this.normalizeContent(ref);
404
- const node = ast.buildTypeReferenceNode(
405
- refSchema,
406
- this.currentSchema
407
- );
408
- return ast.addOptionalInformation(
409
- ast.addComment(node, refSchema, false),
383
+ const node = ast.addOptionalInformation(
384
+ ast.addComment(
385
+ ast.buildTypeReferenceNode(refSchema, this.currentSchema),
386
+ refSchema,
387
+ false
388
+ ),
410
389
  refSchema,
411
390
  false
412
391
  );
392
+ return node;
413
393
  }
414
394
  if (content.anyOf) {
415
395
  const mergeContent = Object.assign({}, content);
416
396
  delete mergeContent.anyOf;
417
- return this.generateArrayedType(
397
+ return this.generateUnionType(
418
398
  schema,
419
399
  content.anyOf,
420
400
  mergeContent,
@@ -425,7 +405,7 @@ export default class DtsGenerator {
425
405
  if (content.oneOf) {
426
406
  const mergeContent = Object.assign({}, content);
427
407
  delete mergeContent.oneOf;
428
- return this.generateArrayedType(
408
+ return this.generateUnionType(
429
409
  schema,
430
410
  content.oneOf,
431
411
  mergeContent,
@@ -433,6 +413,13 @@ export default class DtsGenerator {
433
413
  terminate
434
414
  );
435
415
  }
416
+ return this.generateLiteralTypeProperty(schema, terminate);
417
+ }
418
+ private generateLiteralTypeProperty(
419
+ schema: NormalizedSchema,
420
+ terminate = true
421
+ ): ts.TypeNode {
422
+ const content = schema.content;
436
423
  if (content.enum) {
437
424
  return ast.buildUnionTypeNode(
438
425
  content.enum,
@@ -449,6 +436,25 @@ export default class DtsGenerator {
449
436
  return this.generateType(schema, terminate);
450
437
  }
451
438
  }
439
+ private checkExistOtherType(
440
+ content: JsonSchemaObject,
441
+ base: NormalizedSchema
442
+ ): ts.TypeNode | undefined {
443
+ const schema = Object.assign({}, base, { content });
444
+ const result = this.generateLiteralTypeProperty(schema, false);
445
+ if (result.kind === ts.SyntaxKind.AnyKeyword) {
446
+ return undefined;
447
+ } else if (result.kind === ts.SyntaxKind.TypeLiteral) {
448
+ const node = result as ts.TypeLiteralNode;
449
+ if (
450
+ node.members.length === 1 &&
451
+ node.members[0].kind === ts.SyntaxKind.IndexSignature
452
+ ) {
453
+ return undefined;
454
+ }
455
+ }
456
+ return result;
457
+ }
452
458
  private generateLiteralTypeNode(
453
459
  content: JsonSchemaObject,
454
460
  value: any
@@ -458,11 +464,11 @@ export default class DtsGenerator {
458
464
  case 'number':
459
465
  return ast.buildNumericLiteralTypeNode(String(value));
460
466
  case 'boolean':
461
- return ast.buildBooleanLiteralTypeNode(value);
467
+ return ast.buildBooleanLiteralTypeNode(Boolean(value));
462
468
  case 'null':
463
469
  return ast.buildNullKeyword();
464
470
  case 'string':
465
- return ast.buildStringLiteralTypeNode(value);
471
+ return ast.buildStringLiteralTypeNode(String(value));
466
472
  }
467
473
  if (value === null) {
468
474
  return ast.buildNullKeyword();
@@ -478,39 +484,51 @@ export default class DtsGenerator {
478
484
  return ast.buildStringLiteralTypeNode(String(value));
479
485
  }
480
486
 
481
- private generateArrayedType(
487
+ private generateUnionType(
482
488
  baseSchema: NormalizedSchema,
483
489
  contents: JsonSchema[],
484
- mergeContent: JsonSchema,
490
+ mergeContent: JsonSchemaObject,
485
491
  path: string,
486
492
  terminate: boolean
487
493
  ): ts.TypeNode {
488
- return ast.addOptionalInformation(
494
+ const merged: boolean[] = [];
495
+ const children = contents.map((_, index) => {
496
+ const schema = this.normalizeContent(
497
+ baseSchema,
498
+ path + index.toString()
499
+ );
500
+ merged.push(utils.mergeSchema(schema.content, mergeContent));
501
+ return schema;
502
+ });
503
+ const allRef = merged.every((b) => !b);
504
+ const baseType = this.checkExistOtherType(mergeContent, baseSchema);
505
+
506
+ const result = ast.addOptionalInformation(
489
507
  ast.addComment(
490
508
  ast.buildUnionTypeNode(
491
- contents,
492
- (_, index) => {
493
- const schema = this.normalizeContent(
494
- baseSchema,
495
- path + index.toString()
496
- );
497
- utils.mergeSchema(schema.content, mergeContent);
498
- if (schema.id.isEmpty()) {
499
- return ast.addOptionalInformation(
500
- this.generateTypeProperty(schema, false),
501
- schema,
502
- false
503
- );
504
- } else {
505
- return ast.addOptionalInformation(
506
- ast.buildTypeReferenceNode(
507
- schema,
508
- this.currentSchema
509
- ),
510
- schema,
509
+ children,
510
+ (schema, index) => {
511
+ const node = schema.id.isEmpty()
512
+ ? ast.addOptionalInformation(
513
+ this.generateTypeProperty(schema, false),
514
+ schema,
515
+ false
516
+ )
517
+ : ast.addOptionalInformation(
518
+ ast.buildTypeReferenceNode(
519
+ schema,
520
+ this.currentSchema
521
+ ),
522
+ schema,
523
+ false
524
+ );
525
+ if (baseType != null && !allRef && !merged[index]) {
526
+ return ast.buildIntersectionTypeNode(
527
+ [baseType, node],
511
528
  false
512
529
  );
513
530
  }
531
+ return node;
514
532
  },
515
533
  terminate
516
534
  ),
@@ -520,6 +538,10 @@ export default class DtsGenerator {
520
538
  baseSchema,
521
539
  terminate
522
540
  );
541
+ if (baseType != null && allRef) {
542
+ return ast.buildIntersectionTypeNode([baseType, result], terminate);
543
+ }
544
+ return result;
523
545
  }
524
546
 
525
547
  private generateArrayTypeProperty(
@@ -234,13 +234,15 @@ export function searchAllSubSchema(
234
234
  return;
235
235
  }
236
236
  addParameterSchema(
237
- array.map((item: ParameterOrReference, index: number): [
238
- string,
239
- ParameterOrReference
240
- ] => {
241
- const key = 'name' in item ? item.name : `${index}`;
242
- return [key, item];
243
- }),
237
+ array.map(
238
+ (
239
+ item: ParameterOrReference,
240
+ index: number
241
+ ): [string, ParameterOrReference] => {
242
+ const key = 'name' in item ? item.name : `${index}`;
243
+ return [key, item];
244
+ }
245
+ ),
244
246
  keys
245
247
  );
246
248
  }
@@ -552,12 +554,24 @@ export function selectSchemaType(content: JsonSchema | OpenApiSchema): {
552
554
  );
553
555
  }
554
556
  if ('$schema' in content) {
555
- const { $schema: schema } = content;
556
- const match = /http:\/\/json-schema\.org\/draft-(\d+)\/schema#?/.exec(
557
- schema ?? ''
558
- );
557
+ const schema = content['$schema'] ?? '';
558
+ if (/^https?:\/\/json-schema.org\/schema#?$/.test(schema)) {
559
+ return { type: 'Latest' };
560
+ }
561
+ const match =
562
+ /^https?:\/\/json-schema\.org\/(?:draft\/(\d{4}-\d{2})|draft-(\d+))\/schema#?$/.exec(
563
+ schema
564
+ );
559
565
  if (match) {
560
- const version = Number(match[1]);
566
+ if (match[1] != null) {
567
+ switch (match[1]) {
568
+ case '2019-09':
569
+ return { type: '2019-09' };
570
+ case '2020-12':
571
+ return { type: '2020-12' };
572
+ }
573
+ }
574
+ const version = Number(match[2]);
561
575
  if (version <= 4) {
562
576
  return { type: 'Draft04' };
563
577
  } else {
@@ -21,7 +21,7 @@ export default class ReferenceResolver {
21
21
  public getAllRegisteredSchema(): IterableIterator<Schema> {
22
22
  return this.schemaCache.values();
23
23
  }
24
- public getAllRegisteredIdAndSchema(): Iterator<[string, Schema]> {
24
+ public getAllRegisteredIdAndSchema(): IterableIterator<[string, Schema]> {
25
25
  return this.schemaCache.entries();
26
26
  }
27
27
 
package/src/core/type.ts CHANGED
@@ -21,7 +21,12 @@ export type JsonSchema = JsonSchemaDraft04.Schema | JsonSchemaDraft07.Schema;
21
21
  export type JsonSchemaObject =
22
22
  | JsonSchemaDraft04.Schema
23
23
  | JsonSchemaDraft07.SchemaObject;
24
- export type SchemaType = 'Draft04' | 'Draft07';
24
+ export type SchemaType =
25
+ | 'Draft04'
26
+ | 'Draft07'
27
+ | '2019-09'
28
+ | '2020-12'
29
+ | 'Latest';
25
30
  export function isJsonSchemaDraft04(
26
31
  _content: JsonSchemaObject,
27
32
  type: SchemaType
@@ -109,7 +114,7 @@ function deepCopy(obj: any): any {
109
114
 
110
115
  export interface PluginContext {
111
116
  option: boolean | Record<string, unknown>;
112
- inputSchemas: Iterator<[string, Schema]>;
117
+ inputSchemas: IterableIterator<[string, Schema]>;
113
118
  }
114
119
 
115
120
  export type PreProcessHandler = (contents: Schema[]) => Schema[];
@@ -0,0 +1,39 @@
1
+ import { Schema } from './type';
2
+
3
+ export interface TypeTree {
4
+ children: Map<string, TypeTree>;
5
+ schema?: Schema;
6
+ }
7
+
8
+ export function buildTypeTree(schemas: IterableIterator<Schema>): TypeTree {
9
+ const tree: TypeTree = buildTree();
10
+ for (const type of schemas) {
11
+ setTypeToTree(tree, type.id.toNames(), type);
12
+ }
13
+ if (tree.children.size === 0) {
14
+ throw new Error('There is no schema in the input contents.');
15
+ }
16
+ return tree;
17
+ }
18
+
19
+ function setTypeToTree(tree: TypeTree, paths: string[], type: Schema): void {
20
+ if (paths.length === 0) {
21
+ throw new Error('The paths is nothing for set the type to TypeTree.');
22
+ }
23
+ let t: TypeTree = tree;
24
+ for (const key of paths) {
25
+ let next = t.children.get(key);
26
+ if (next === undefined) {
27
+ next = buildTree();
28
+ t.children.set(key, next);
29
+ }
30
+ t = next;
31
+ }
32
+ t.schema = type;
33
+ }
34
+
35
+ function buildTree(): TypeTree {
36
+ return {
37
+ children: new Map(),
38
+ };
39
+ }
package/src/core/utils.ts CHANGED
@@ -52,18 +52,16 @@ export function reduceTypes(types: SimpleTypes[]): SimpleTypes[] {
52
52
  return types;
53
53
  }
54
54
  const set = new Set<SimpleTypes>(types);
55
- if (set.delete('integer')) {
56
- set.add('number');
57
- }
58
55
  return Array.from(set.values());
59
56
  }
60
57
 
61
58
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
62
- export function mergeSchema(a: any, b: any): any {
59
+ export function mergeSchema(a: any, b: any): boolean {
63
60
  if ('$ref' in a || '$ref' in b) {
64
- return { $ref: b['$ref'] || a['$ref'] };
61
+ a.$ref = b.$ref || a.$ref;
62
+ return false;
65
63
  }
66
- Object.keys(b).forEach((key: string) => {
64
+ Object.keys(b as object).forEach((key: string) => {
67
65
  const value = b[key];
68
66
  if (a[key] != null && typeof value !== typeof a[key]) {
69
67
  debug(`mergeSchema warning: type is mismatched, key=${key}`);
@@ -72,11 +70,11 @@ export function mergeSchema(a: any, b: any): any {
72
70
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
73
71
  a[key] = (a[key] ?? []).concat(value);
74
72
  } else if (value != null && typeof value === 'object') {
75
- a[key] = mergeSchema(a[key] || {}, value);
73
+ a[key] ??= {};
74
+ mergeSchema(a[key], value);
76
75
  } else {
77
76
  a[key] = value;
78
77
  }
79
78
  });
80
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
81
- return a;
79
+ return true;
82
80
  }
package/src/utils.ts CHANGED
@@ -1,9 +1,13 @@
1
+ import * as fs from 'fs';
1
2
  import { URL } from 'url';
2
3
  import glob from 'glob';
3
4
  import proxy from 'https-proxy-agent';
5
+ import { ScriptTarget } from 'typescript';
6
+ import { CommandOptions, defaultConfigFile } from './commandOptions';
7
+ import { Config } from './core/config';
4
8
 
5
9
  export function readStream(
6
- stream: NodeJS.ReadStream,
10
+ stream: NodeJS.ReadableStream,
7
11
  encoding: BufferEncoding = 'utf8'
8
12
  ): Promise<string> {
9
13
  stream.setEncoding(encoding);
@@ -36,7 +40,7 @@ function noProxy(url: URL): boolean {
36
40
  }
37
41
  return false;
38
42
  }
39
- function buildProxyOptions(url: string): RequestInit | undefined {
43
+ export function buildProxyOptions(url: string): RequestInit | undefined {
40
44
  const parsedUrl = new URL(url);
41
45
  let proxyUrl;
42
46
  if (!noProxy(parsedUrl)) {
@@ -73,3 +77,76 @@ export function globFiles(
73
77
  });
74
78
  });
75
79
  }
80
+
81
+ export function readConfig(options: CommandOptions): Partial<Config> {
82
+ let pc: Partial<Config> = {};
83
+ const configFile = options.configFile ?? defaultConfigFile;
84
+ try {
85
+ pc = loadJSON(configFile);
86
+ pc.configFile = configFile;
87
+ } catch (err) {
88
+ if (options.configFile != null) {
89
+ console.error(
90
+ 'Error to load config file from ' + options.configFile
91
+ );
92
+ }
93
+ }
94
+
95
+ if (pc.input == null) {
96
+ pc.input = {
97
+ files: [],
98
+ urls: [],
99
+ stdin: false,
100
+ };
101
+ }
102
+ if (options.files.length > 0) {
103
+ pc.input.files = options.files;
104
+ } else if (pc.input.files == null) {
105
+ pc.input.files = [];
106
+ }
107
+ if (options.urls.length > 0) {
108
+ pc.input.urls = options.urls;
109
+ } else if (pc.input.urls == null) {
110
+ pc.input.urls = [];
111
+ }
112
+ pc.input.stdin = options.isReadFromStdin();
113
+
114
+ if (options.out != null) {
115
+ pc.outputFile = options.out;
116
+ }
117
+ if (options.target != null) {
118
+ pc.target = convertToScriptTarget(options.target);
119
+ } else if (pc.target != null) {
120
+ pc.target = convertToScriptTarget(pc.target as unknown as string);
121
+ }
122
+ pc.outputAST = !!options.outputAST;
123
+ return pc;
124
+ }
125
+ function loadJSON(file: string): Partial<Config> {
126
+ const content = fs.readFileSync(file, 'utf-8');
127
+ return JSON.parse(content) as Partial<Config>;
128
+ }
129
+ function convertToScriptTarget(target: string): ScriptTarget {
130
+ switch (target.trim().toLowerCase()) {
131
+ case 'es3':
132
+ return ScriptTarget.ES3;
133
+ case 'es5':
134
+ return ScriptTarget.ES5;
135
+ case 'es2015':
136
+ return ScriptTarget.ES2015;
137
+ case 'es2016':
138
+ return ScriptTarget.ES2016;
139
+ case 'es2017':
140
+ return ScriptTarget.ES2017;
141
+ case 'es2018':
142
+ return ScriptTarget.ES2018;
143
+ case 'es2019':
144
+ return ScriptTarget.ES2019;
145
+ case 'es2020':
146
+ return ScriptTarget.ES2020;
147
+ case 'esnext':
148
+ return ScriptTarget.ESNext;
149
+ default:
150
+ return ScriptTarget.Latest;
151
+ }
152
+ }
package/tsconfig.json CHANGED
@@ -36,7 +36,7 @@
36
36
  "noUnusedLocals": true, /* Report errors on unused locals. */
37
37
  "noUnusedParameters": true, /* Report errors on unused parameters. */
38
38
  "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39
- // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39
+ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40
40
 
41
41
  /* Module Resolution Options */
42
42
  // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
@@ -1,3 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: [horiuchi]