1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
5 | const ts = tslib_1.__importStar(require("typescript"));
|
6 | const jsonPointer_1 = require("../jsonPointer");
|
7 | const ast = tslib_1.__importStar(require("./astBuilder"));
|
8 | const config_1 = tslib_1.__importDefault(require("./config"));
|
9 | const jsonSchema_1 = require("./jsonSchema");
|
10 | const referenceResolver_1 = tslib_1.__importDefault(require("./referenceResolver"));
|
11 | const type_1 = require("./type");
|
12 | const typeTree_1 = require("./typeTree");
|
13 | const utils = tslib_1.__importStar(require("./utils"));
|
14 | const debug = (0, debug_1.default)('dtsgen');
|
15 | class DtsGenerator {
|
16 | constructor(contents) {
|
17 | this.resolver = new referenceResolver_1.default();
|
18 | this.contents = contents;
|
19 | }
|
20 | async generate() {
|
21 | const plugins = await this.getPlugins();
|
22 | const preProcess = await this.getPreProcess(plugins.pre);
|
23 | for (const p of preProcess) {
|
24 | this.contents = p(this.contents);
|
25 | }
|
26 | debug('generate type definition files.');
|
27 | this.contents.forEach((schema) => this.resolver.registerSchema(schema));
|
28 | await this.resolver.resolve();
|
29 | const tree = (0, typeTree_1.buildTypeTree)(this.resolver.getAllRegisteredSchema());
|
30 | const root = this.walk(tree);
|
31 | const file = ts.createSourceFile('_.d.ts', '', config_1.default.target, false, ts.ScriptKind.TS);
|
32 | Object.assign(file, {
|
33 | statements: ts.factory.createNodeArray(root),
|
34 | });
|
35 | const postProcess = await this.getPostProcess(plugins.post);
|
36 | const result = ts.transform(file, postProcess);
|
37 | const transformedNodes = result.transformed[0];
|
38 | const transformedContent = config_1.default.outputAST
|
39 | ? JSON.stringify(transformedNodes, null, 2)
|
40 | : (function () {
|
41 | const printer = ts.createPrinter();
|
42 | return printer.printNode(ts.EmitHint.SourceFile, transformedNodes, file);
|
43 | })();
|
44 | result.dispose();
|
45 | return transformedContent;
|
46 | }
|
47 | async getPlugins() {
|
48 | const pre = [];
|
49 | const post = [];
|
50 | for (const [name, option] of Object.entries(config_1.default.plugins)) {
|
51 | const plugin = await (0, type_1.loadPlugin)(name, option);
|
52 | if (plugin == null) {
|
53 | continue;
|
54 | }
|
55 | if (plugin.preProcess != null) {
|
56 | pre.push({ plugin, option });
|
57 | }
|
58 | if (plugin.postProcess != null) {
|
59 | post.push({ plugin, option });
|
60 | }
|
61 | }
|
62 | return { pre, post };
|
63 | }
|
64 | async getPreProcess(pre) {
|
65 | debug('load pre process plugin.');
|
66 | const result = [];
|
67 | const inputSchemas = this.resolver.getAllRegisteredIdAndSchema();
|
68 | for (const pc of pre) {
|
69 | const p = pc.plugin.preProcess;
|
70 | if (p == null) {
|
71 | continue;
|
72 | }
|
73 | const handler = await p({ inputSchemas, option: pc.option });
|
74 | if (handler != null) {
|
75 | result.push(handler);
|
76 | debug(' pre process plugin:', pc.plugin.meta.name, pc.plugin.meta.description);
|
77 | }
|
78 | }
|
79 | return result;
|
80 | }
|
81 | async getPostProcess(post) {
|
82 | debug('load post process plugin.');
|
83 | const result = [];
|
84 | const inputSchemas = this.resolver.getAllRegisteredIdAndSchema();
|
85 | for (const pc of post) {
|
86 | const p = pc.plugin.postProcess;
|
87 | if (p == null) {
|
88 | continue;
|
89 | }
|
90 | const factory = await p({ inputSchemas, option: pc.option });
|
91 | if (factory != null) {
|
92 | result.push(factory);
|
93 | debug(' post process plugin:', pc.plugin.meta.name, pc.plugin.meta.description);
|
94 | }
|
95 | }
|
96 | return result;
|
97 | }
|
98 | walk(tree, root = true) {
|
99 | const result = [];
|
100 | const keys = [...tree.children.keys()].sort();
|
101 | for (const key of keys) {
|
102 | const value = tree.children.get(key);
|
103 | if (value === undefined) {
|
104 | continue;
|
105 | }
|
106 | if (value.schema !== undefined) {
|
107 | const schema = value.schema;
|
108 | debug(` walk doProcess: key=${key} schemaId=${schema.id.getAbsoluteId()}`);
|
109 | result.push(this.walkSchema(schema, root));
|
110 | }
|
111 | if (value.children.size > 0) {
|
112 | result.push(ast.buildNamespaceNode(key, this.walk(value, false), root));
|
113 | }
|
114 | }
|
115 | return result;
|
116 | }
|
117 | walkSchema(schema, root) {
|
118 | const normalized = this.normalizeContent(schema);
|
119 | this.currentSchema = normalized;
|
120 | return ast.addOptionalInformation(ast.addComment(this.parseSchema(normalized, root), normalized, true), normalized, true);
|
121 | }
|
122 | parseSchema(schema, root = false) {
|
123 | const type = schema.content.type;
|
124 | switch (type) {
|
125 | case 'any':
|
126 | return this.generateAnyTypeModel(schema, root);
|
127 | case 'array':
|
128 | return this.generateTypeCollection(schema, root);
|
129 | case 'object':
|
130 | default:
|
131 | return this.generateDeclareType(schema, root);
|
132 | }
|
133 | }
|
134 | normalizeContent(schema, pointer) {
|
135 | if (pointer != null) {
|
136 | schema = (0, jsonSchema_1.getSubSchema)(schema, pointer);
|
137 | }
|
138 | return Object.assign(schema, {
|
139 | content: this.normalizeSchemaContent(schema.content),
|
140 | });
|
141 | }
|
142 | normalizeSchemaContent(content) {
|
143 | var _a;
|
144 | if (typeof content === 'boolean') {
|
145 | content = content ? {} : { not: {} };
|
146 | }
|
147 | else {
|
148 | if (content.allOf) {
|
149 | const work = {};
|
150 | const allOf = content.allOf;
|
151 | delete content.allOf;
|
152 | for (const sub of allOf) {
|
153 | if (typeof sub === 'object' && sub.$ref) {
|
154 | const ref = this.resolver.dereference(sub.$ref);
|
155 | const normalized = this.normalizeContent(ref).content;
|
156 | utils.mergeSchema(work, normalized);
|
157 | }
|
158 | else {
|
159 | const normalized = this.normalizeSchemaContent(sub);
|
160 | utils.mergeSchema(work, normalized);
|
161 | }
|
162 | }
|
163 | utils.mergeSchema(work, content);
|
164 | content = Object.assign(content, work);
|
165 | }
|
166 | if (content.type === undefined &&
|
167 | ((_a = content.properties) !== null && _a !== void 0 ? _a : content.additionalProperties)) {
|
168 | content.type = 'object';
|
169 | }
|
170 | if (content.nullable) {
|
171 | const type = content.type;
|
172 | const anyOf = content.anyOf;
|
173 | if (Array.isArray(anyOf)) {
|
174 | anyOf.push({ type: 'null' });
|
175 | }
|
176 | else if (type == null) {
|
177 | content.type = 'null';
|
178 | }
|
179 | else if (!Array.isArray(type)) {
|
180 | content.type = [type, 'null'];
|
181 | }
|
182 | else {
|
183 | type.push('null');
|
184 | }
|
185 | }
|
186 | const types = content.type;
|
187 | if (Array.isArray(types)) {
|
188 | const reduced = utils.reduceTypes(types);
|
189 | content.type = reduced.length === 1 ? reduced[0] : reduced;
|
190 | }
|
191 | }
|
192 | return content;
|
193 | }
|
194 | generateDeclareType(schema, root) {
|
195 | const content = schema.content;
|
196 | if (content.$ref ||
|
197 | content.oneOf ||
|
198 | content.anyOf ||
|
199 | content.enum ||
|
200 | 'const' in content ||
|
201 | content.type !== 'object') {
|
202 | const type = this.generateTypeProperty(schema);
|
203 | return ast.buildTypeAliasNode(schema.id, type, root);
|
204 | }
|
205 | else {
|
206 | const members = this.generateProperties(schema);
|
207 | return ast.buildInterfaceNode(schema.id, members, root);
|
208 | }
|
209 | }
|
210 | generateAnyTypeModel(schema, root) {
|
211 | const member = ast.buildIndexSignatureNode('name', ast.buildStringKeyword(), ast.buildAnyKeyword());
|
212 | return ast.buildInterfaceNode(schema.id, [member], root);
|
213 | }
|
214 | generateTypeCollection(schema, root) {
|
215 | const type = this.generateArrayTypeProperty(schema);
|
216 | return ast.buildTypeAliasNode(schema.id, type, root);
|
217 | }
|
218 | generateProperties(baseSchema) {
|
219 | const result = [];
|
220 | const content = baseSchema.content;
|
221 | if (content.additionalProperties) {
|
222 | const schema = this.normalizeContent(baseSchema, '/additionalProperties');
|
223 | const valueType = content.additionalProperties === true
|
224 | ? ast.buildAnyKeyword()
|
225 | : this.generateTypeProperty(schema);
|
226 | const node = ast.buildIndexSignatureNode('name', ast.buildStringKeyword(), valueType);
|
227 | result.push(ast.addOptionalInformation(node, schema, true));
|
228 | }
|
229 | if (content.properties) {
|
230 | for (const propertyName of Object.keys(content.properties)) {
|
231 | const schema = this.normalizeContent(baseSchema, '/properties/' + (0, jsonPointer_1.tilde)(propertyName));
|
232 | const node = ast.buildPropertySignature(schema, propertyName, this.generateTypeProperty(schema), baseSchema.content.required, false);
|
233 | result.push(ast.addOptionalInformation(ast.addComment(node, schema, true), schema, true));
|
234 | }
|
235 | }
|
236 | if (content.patternProperties) {
|
237 | const properties = Object.keys(content.patternProperties);
|
238 | const node = ast.buildPropertySignature({ content: { readOnly: false } }, 'pattern', ast.buildUnionTypeNode(properties, (propertyName) => this.generateTypeProperty(this.normalizeContent(baseSchema, '/patternProperties/' + (0, jsonPointer_1.tilde)(propertyName))), true), baseSchema.content.required, true);
|
239 | result.push(ts.addSyntheticTrailingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, ` Patterns: ${properties.join(' | ')} `));
|
240 | }
|
241 | return result;
|
242 | }
|
243 | generateTypeProperty(schema, terminate = true) {
|
244 | const content = schema.content;
|
245 | if (content.$ref) {
|
246 | const ref = this.resolver.dereference(content.$ref);
|
247 | const refSchema = this.normalizeContent(ref);
|
248 | const node = ast.addOptionalInformation(ast.addComment(ast.buildTypeReferenceNode(refSchema, this.currentSchema), refSchema, false), refSchema, false);
|
249 | return node;
|
250 | }
|
251 | if (content.anyOf) {
|
252 | const mergeContent = Object.assign({}, content);
|
253 | delete mergeContent.anyOf;
|
254 | return this.generateUnionType(schema, content.anyOf, mergeContent, '/anyOf/', terminate);
|
255 | }
|
256 | if (content.oneOf) {
|
257 | const mergeContent = Object.assign({}, content);
|
258 | delete mergeContent.oneOf;
|
259 | return this.generateUnionType(schema, content.oneOf, mergeContent, '/oneOf/', terminate);
|
260 | }
|
261 | return this.generateLiteralTypeProperty(schema, terminate);
|
262 | }
|
263 | generateLiteralTypeProperty(schema, terminate = true) {
|
264 | const content = schema.content;
|
265 | if (content.enum) {
|
266 | return ast.buildUnionTypeNode(content.enum, (value) => {
|
267 | return this.generateLiteralTypeNode(content, value);
|
268 | }, terminate);
|
269 | }
|
270 | else if (content.not) {
|
271 | return ast.buildVoidKeyword();
|
272 | }
|
273 | else if ('const' in content) {
|
274 | return this.generateLiteralTypeNode(content, content.const);
|
275 | }
|
276 | else {
|
277 | return this.generateType(schema, terminate);
|
278 | }
|
279 | }
|
280 | checkExistOtherType(content, base) {
|
281 | var _a;
|
282 | const schema = Object.assign({}, base, { content });
|
283 | const result = this.generateLiteralTypeProperty(schema, false);
|
284 | if (result.kind === ts.SyntaxKind.AnyKeyword) {
|
285 | return undefined;
|
286 | }
|
287 | else if (result.kind === ts.SyntaxKind.TypeLiteral) {
|
288 | const node = result;
|
289 | if (node.members.length === 1 &&
|
290 | ((_a = node.members[0]) === null || _a === void 0 ? void 0 : _a.kind) === ts.SyntaxKind.IndexSignature) {
|
291 | return undefined;
|
292 | }
|
293 | }
|
294 | return result;
|
295 | }
|
296 | generateLiteralTypeNode(content, value) {
|
297 | switch (content.type) {
|
298 | case 'integer':
|
299 | case 'number':
|
300 | return ast.buildNumericLiteralTypeNode(String(value));
|
301 | case 'boolean':
|
302 | return ast.buildBooleanLiteralTypeNode(Boolean(value));
|
303 | case 'null':
|
304 | return ast.buildNullKeyword();
|
305 | case 'string':
|
306 | return ast.buildStringLiteralTypeNode(String(value));
|
307 | }
|
308 | if (value === null) {
|
309 | return ast.buildNullKeyword();
|
310 | }
|
311 | switch (typeof value) {
|
312 | case 'number':
|
313 | return ast.buildNumericLiteralTypeNode(`${value}`);
|
314 | case 'boolean':
|
315 | return ast.buildBooleanLiteralTypeNode(value);
|
316 | case 'string':
|
317 | return ast.buildStringLiteralTypeNode(value);
|
318 | }
|
319 | return ast.buildStringLiteralTypeNode(String(value));
|
320 | }
|
321 | generateUnionType(baseSchema, contents, mergeContent, path, terminate) {
|
322 | const merged = [];
|
323 | const children = contents.map((_, index) => {
|
324 | const schema = this.normalizeContent(baseSchema, path + index.toString());
|
325 | merged.push(utils.mergeSchema(schema.content, mergeContent));
|
326 | return schema;
|
327 | });
|
328 | const allRef = merged.every((b) => !b);
|
329 | const baseType = this.checkExistOtherType(mergeContent, baseSchema);
|
330 | const result = ast.addOptionalInformation(ast.addComment(ast.buildUnionTypeNode(children, (schema, index) => {
|
331 | const node = schema.id.isEmpty()
|
332 | ? ast.addOptionalInformation(this.generateTypeProperty(schema, false), schema, false)
|
333 | : ast.addOptionalInformation(ast.buildTypeReferenceNode(schema, this.currentSchema), schema, false);
|
334 | if (baseType != null && !allRef && !merged[index]) {
|
335 | return ast.buildIntersectionTypeNode([baseType, node], false);
|
336 | }
|
337 | return node;
|
338 | }, terminate), baseSchema, false), baseSchema, terminate);
|
339 | if (baseType != null && allRef) {
|
340 | return ast.buildIntersectionTypeNode([baseType, result], terminate);
|
341 | }
|
342 | return result;
|
343 | }
|
344 | generateArrayTypeProperty(schema, terminate = true) {
|
345 | const items = schema.content.items;
|
346 | const minItems = schema.content.minItems;
|
347 | const maxItems = schema.content.maxItems;
|
348 | const getAdditionalItemNode = () => {
|
349 | const additionalItems = schema.content.additionalItems
|
350 | ? this.normalizeContent(schema, '/additionalItems')
|
351 | : schema.content.additionalItems === false
|
352 | ? false
|
353 | : undefined;
|
354 | return additionalItems === undefined
|
355 | ? undefined
|
356 | : additionalItems === false
|
357 | ? false
|
358 | : this.generateTypeProperty(additionalItems, false);
|
359 | };
|
360 | if (items == null) {
|
361 | return ast.buildSimpleArrayNode(ast.buildAnyKeyword());
|
362 | }
|
363 | else if (!Array.isArray(items)) {
|
364 | const subSchema = this.normalizeContent(schema, '/items');
|
365 | const node = this.generateTypeProperty(subSchema, false);
|
366 | if (minItems != null && maxItems != null && maxItems < minItems) {
|
367 | return ast.buildNeverKeyword();
|
368 | }
|
369 | else if ((minItems === undefined || minItems === 0) &&
|
370 | maxItems === undefined) {
|
371 | return ast.buildSimpleArrayNode(ast.addOptionalInformation(node, subSchema, false));
|
372 | }
|
373 | else {
|
374 | return ast.addOptionalInformation(ast.buildTupleTypeNode(node, minItems, maxItems), schema, terminate);
|
375 | }
|
376 | }
|
377 | else if (items.length === 0 &&
|
378 | (minItems === undefined || minItems === 0) &&
|
379 | maxItems === undefined) {
|
380 | const additionalItemsNode = getAdditionalItemNode();
|
381 | return additionalItemsNode === false
|
382 | ? ast.buildTupleTypeNode([], 0, 0, additionalItemsNode)
|
383 | : ast.buildSimpleArrayNode(additionalItemsNode !== null && additionalItemsNode !== void 0 ? additionalItemsNode : ast.buildAnyKeyword());
|
384 | }
|
385 | else if (minItems != null &&
|
386 | maxItems != null &&
|
387 | maxItems < minItems) {
|
388 | return ast.buildNeverKeyword();
|
389 | }
|
390 | else {
|
391 | const types = [];
|
392 | for (let i = 0; i < items.length; i++) {
|
393 | const type = this.normalizeContent(schema, '/items/' + i.toString());
|
394 | if (type.id.isEmpty()) {
|
395 | types.push(this.generateTypeProperty(type, false));
|
396 | }
|
397 | else {
|
398 | types.push(ast.addOptionalInformation(ast.buildTypeReferenceNode(type, this.currentSchema), type, false));
|
399 | }
|
400 | }
|
401 | const additionalItemsNode = getAdditionalItemNode();
|
402 | return ast.addOptionalInformation(ast.buildTupleTypeNode(types, minItems, maxItems, additionalItemsNode), schema, terminate);
|
403 | }
|
404 | }
|
405 | generateType(schema, terminate) {
|
406 | const type = schema.content.type;
|
407 | if (type == null) {
|
408 | return ast.buildAnyKeyword();
|
409 | }
|
410 | else if (typeof type === 'string') {
|
411 | return this.generateTypeName(schema, type, terminate);
|
412 | }
|
413 | else {
|
414 | const types = utils.reduceTypes(type);
|
415 | if (types.length <= 1) {
|
416 | schema.content.type = types[0];
|
417 | return this.generateType(schema, terminate);
|
418 | }
|
419 | else {
|
420 | return ast.buildUnionTypeNode(types, (t) => {
|
421 | return this.generateTypeName(schema, t, false);
|
422 | }, terminate);
|
423 | }
|
424 | }
|
425 | }
|
426 | generateTypeName(schema, type, terminate) {
|
427 | const tsType = utils.toTSType(type, schema.content);
|
428 | if (tsType) {
|
429 | if (tsType === ts.SyntaxKind.NullKeyword) {
|
430 | return ast.buildNullKeyword();
|
431 | }
|
432 | else {
|
433 | return ast.buildKeyword(tsType);
|
434 | }
|
435 | }
|
436 | else if (type === 'object') {
|
437 | const elements = this.generateProperties(schema);
|
438 | if (elements.length > 0) {
|
439 | return ast.buildTypeLiteralNode(elements);
|
440 | }
|
441 | else {
|
442 | return ast.buildFreeFormObjectTypeLiteralNode();
|
443 | }
|
444 | }
|
445 | else if (type === 'array') {
|
446 | return this.generateArrayTypeProperty(schema, terminate);
|
447 | }
|
448 | else {
|
449 | throw new Error('unknown type: ' + type);
|
450 | }
|
451 | }
|
452 | }
|
453 | exports.default = DtsGenerator;
|