UNPKG

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