UNPKG

19.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const debug_1 = tslib_1.__importDefault(require("debug"));
5const ts = tslib_1.__importStar(require("typescript"));
6const jsonPointer_1 = require("../jsonPointer");
7const ast = tslib_1.__importStar(require("./astBuilder"));
8const config_1 = tslib_1.__importDefault(require("./config"));
9const jsonSchema_1 = require("./jsonSchema");
10const referenceResolver_1 = tslib_1.__importDefault(require("./referenceResolver"));
11const type_1 = require("./type");
12const typeTree_1 = require("./typeTree");
13const utils = tslib_1.__importStar(require("./utils"));
14const debug = (0, debug_1.default)('dtsgen');
15class 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}
453exports.default = DtsGenerator;