1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | import * as doctrine from 'doctrine';
|
17 |
|
18 | import * as ts from './ts-ast';
|
19 |
|
20 | const {parseType, parseParamType} = require('doctrine/lib/typed.js');
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | export function closureTypeToTypeScript(
|
30 | closureType: string|null|undefined, templateTypes: string[] = []): ts.Type {
|
31 | if (!closureType) {
|
32 | return ts.anyType;
|
33 | }
|
34 | let ast;
|
35 | try {
|
36 | ast = parseType(closureType);
|
37 | } catch {
|
38 | return ts.anyType;
|
39 | }
|
40 | return convert(ast, templateTypes);
|
41 | }
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | export function closureParamToTypeScript(
|
53 | name: string,
|
54 | closureType: string|null|undefined,
|
55 | templateTypes: string[] = [],
|
56 | ): ts.ParamType {
|
57 | if (!closureType) {
|
58 | return new ts.ParamType({
|
59 | name,
|
60 | type: ts.anyType,
|
61 | optional: false,
|
62 | rest: false,
|
63 | });
|
64 | }
|
65 |
|
66 | let ast;
|
67 | try {
|
68 | ast = parseParamType(closureType);
|
69 | } catch {
|
70 | return new ts.ParamType({
|
71 | name,
|
72 | type: ts.anyType,
|
73 |
|
74 |
|
75 |
|
76 | optional: closureType.endsWith('='),
|
77 | rest: false,
|
78 | });
|
79 | }
|
80 |
|
81 | return convertParam(name, ast, templateTypes);
|
82 | }
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | function convertParam(
|
89 | name: string, node: doctrine.Type, templateTypes: string[]): ts.ParamType {
|
90 | switch (node.type) {
|
91 | case 'OptionalType':
|
92 | return new ts.ParamType({
|
93 | name,
|
94 | type: convert(node.expression, templateTypes),
|
95 | optional: true,
|
96 | rest: false,
|
97 | });
|
98 |
|
99 | case 'RestType':
|
100 | return new ts.ParamType({
|
101 | name,
|
102 |
|
103 |
|
104 |
|
105 | type: new ts.ArrayType(
|
106 | node.expression !== undefined ?
|
107 | convert(node.expression, templateTypes) :
|
108 | ts.anyType),
|
109 | optional: false,
|
110 | rest: true,
|
111 | });
|
112 |
|
113 | default:
|
114 | return new ts.ParamType({
|
115 | name,
|
116 | type: convert(node, templateTypes),
|
117 | optional: false,
|
118 | rest: false,
|
119 | });
|
120 | }
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | function convert(node: doctrine.Type, templateTypes: string[]): ts.Type {
|
127 | let nullable;
|
128 | if (isNullable(node)) {
|
129 | nullable = true;
|
130 | node = node.expression;
|
131 | } else if (isNonNullable(node)) {
|
132 | nullable = false;
|
133 | node = node.expression;
|
134 | } else if (isName(node) && templateTypes.includes(node.name)) {
|
135 |
|
136 |
|
137 |
|
138 | nullable = false;
|
139 | } else {
|
140 | nullable = nullableByDefault(node);
|
141 | }
|
142 |
|
143 | let t: ts.Type;
|
144 |
|
145 | if (isParameterizedArray(node)) {
|
146 | t = convertArray(node, templateTypes);
|
147 | } else if (isParameterizedObject(node)) {
|
148 | t = convertIndexableObject(node, templateTypes);
|
149 | } else if (isBarePromise(node)) {
|
150 |
|
151 |
|
152 | t = new ts.ParameterizedType('Promise', [ts.anyType]);
|
153 | } else if (isParameterizedType(node)) {
|
154 | t = convertParameterizedType(node, templateTypes);
|
155 | } else if (isUnion(node)) {
|
156 | t = convertUnion(node, templateTypes);
|
157 | } else if (isFunction(node)) {
|
158 | t = convertFunction(node, templateTypes);
|
159 | } else if (isBareArray(node)) {
|
160 | t = new ts.ArrayType(ts.anyType);
|
161 | } else if (isRecordType(node)) {
|
162 | t = convertRecord(node, templateTypes);
|
163 | } else if (isAllLiteral(node)) {
|
164 | t = ts.anyType;
|
165 | } else if (isNullableLiteral(node)) {
|
166 | t = ts.anyType;
|
167 | } else if (isNullLiteral(node)) {
|
168 | t = ts.nullType;
|
169 | } else if (isUndefinedLiteral(node)) {
|
170 | t = ts.undefinedType;
|
171 | } else if (isVoidLiteral(node)) {
|
172 | t = new ts.NameType('void');
|
173 | } else if (isName(node)) {
|
174 | t = new ts.NameType(renameMap.get(node.name) || node.name);
|
175 | } else {
|
176 | console.error('Unknown syntax.');
|
177 | return ts.anyType;
|
178 | }
|
179 |
|
180 | if (nullable) {
|
181 | t = new ts.UnionType([t, ts.nullType]);
|
182 | }
|
183 |
|
184 | return t;
|
185 | }
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | const renameMap = new Map<string, string>([
|
192 |
|
193 |
|
194 |
|
195 | ['Object', 'object'],
|
196 |
|
197 | ['ITemplateArray', 'TemplateStringsArray'],
|
198 | ]);
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | const parameterizedRenameMap = new Map<string, string>([
|
204 | ['HTMLCollection', 'HTMLCollectionOf'],
|
205 | ['NodeList', 'NodeListOf'],
|
206 | ]);
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | function nullableByDefault(node: doctrine.Type): boolean {
|
213 | if (isName(node)) {
|
214 | switch (node.name) {
|
215 | case 'string':
|
216 | case 'number':
|
217 | case 'boolean':
|
218 | case 'void':
|
219 | return false;
|
220 | }
|
221 | return true;
|
222 | }
|
223 | return isParameterizedArray(node);
|
224 | }
|
225 |
|
226 | function convertArray(
|
227 | node: doctrine.type.TypeApplication, templateTypes: string[]): ts.Type {
|
228 | const applications = node.applications;
|
229 | return new ts.ArrayType(
|
230 | applications.length === 1 ? convert(applications[0], templateTypes) :
|
231 | ts.anyType);
|
232 | }
|
233 |
|
234 | function convertIndexableObject(
|
235 | node: doctrine.type.TypeApplication,
|
236 | templateTypes: string[]): ts.IndexableObjectType|ts.NameType {
|
237 | if (node.applications.length !== 2) {
|
238 | console.error('Parameterized Object must have two parameters.');
|
239 | return ts.anyType;
|
240 | }
|
241 | return new ts.IndexableObjectType(
|
242 | convert(node.applications[0], templateTypes),
|
243 | convert(node.applications[1], templateTypes));
|
244 | }
|
245 |
|
246 | function convertParameterizedType(
|
247 | node: doctrine.type.TypeApplication,
|
248 | templateTypes: string[]): ts.ParameterizedType|ts.NameType {
|
249 | if (!isName(node.expression)) {
|
250 | console.error('Could not find name of parameterized type');
|
251 | return ts.anyType;
|
252 | }
|
253 | const types = node.applications.map(
|
254 | (application) => convert(application, templateTypes));
|
255 | const name = renameMap.get(node.expression.name) ||
|
256 | parameterizedRenameMap.get(node.expression.name) || node.expression.name;
|
257 | return new ts.ParameterizedType(name, types);
|
258 | }
|
259 |
|
260 | function convertUnion(
|
261 | node: doctrine.type.UnionType, templateTypes: string[]): ts.Type {
|
262 | return new ts.UnionType(
|
263 | node.elements.map((element) => convert(element, templateTypes)));
|
264 | }
|
265 |
|
266 | function convertFunction(
|
267 | node: doctrine.type.FunctionType,
|
268 | templateTypes: string[]): ts.FunctionType|ts.ConstructorType {
|
269 | const params = node.params.map(
|
270 | (param, idx) => convertParam(
|
271 |
|
272 | 'p' + idx,
|
273 | param,
|
274 | templateTypes));
|
275 | if (node.new) {
|
276 | return new ts.ConstructorType(
|
277 | params,
|
278 |
|
279 |
|
280 |
|
281 | isName(node.this) ? new ts.NameType(node.this.name) : ts.anyType);
|
282 | } else {
|
283 | return new ts.FunctionType(
|
284 | params,
|
285 |
|
286 | node.result ?
|
287 | convert(node.result as {} as doctrine.Type, templateTypes) :
|
288 | ts.anyType);
|
289 | }
|
290 | }
|
291 |
|
292 | function convertRecord(node: doctrine.type.RecordType, templateTypes: string[]):
|
293 | ts.RecordType|ts.NameType {
|
294 | const fields = [];
|
295 | for (const field of node.fields) {
|
296 | if (field.type !== 'FieldType') {
|
297 | return ts.anyType;
|
298 | }
|
299 | const fieldType =
|
300 | field.value ? convert(field.value, templateTypes) : ts.anyType;
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | let optional = false;
|
308 | if (fieldType.kind === 'union') {
|
309 | fieldType.members = fieldType.members.filter((member) => {
|
310 | if (member.kind === 'name' && member.name === 'undefined') {
|
311 | optional = true;
|
312 | return false;
|
313 | }
|
314 | return true;
|
315 | });
|
316 |
|
317 |
|
318 | fieldType.simplify();
|
319 | }
|
320 |
|
321 | fields.push(new ts.ParamType({name: field.key, type: fieldType, optional}));
|
322 | }
|
323 | return new ts.RecordType(fields);
|
324 | }
|
325 |
|
326 | function isParameterizedArray(node: doctrine.Type):
|
327 | node is doctrine.type.TypeApplication {
|
328 | return node.type === 'TypeApplication' &&
|
329 | node.expression.type === 'NameExpression' &&
|
330 | node.expression.name === 'Array';
|
331 | }
|
332 |
|
333 | function isParameterizedType(node: doctrine.Type):
|
334 | node is doctrine.type.TypeApplication {
|
335 | return node.type === 'TypeApplication';
|
336 | }
|
337 |
|
338 | function isBareArray(node: doctrine.Type):
|
339 | node is doctrine.type.NameExpression {
|
340 | return node.type === 'NameExpression' && node.name === 'Array';
|
341 | }
|
342 |
|
343 | function isBarePromise(node: doctrine.Type):
|
344 | node is doctrine.type.NameExpression {
|
345 | return node.type === 'NameExpression' && node.name === 'Promise';
|
346 | }
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | function isParameterizedObject(node: doctrine.Type):
|
352 | node is doctrine.type.TypeApplication {
|
353 | return node.type === 'TypeApplication' &&
|
354 | node.expression.type === 'NameExpression' &&
|
355 | node.expression.name === 'Object';
|
356 | }
|
357 |
|
358 | function isUnion(node: doctrine.Type): node is doctrine.type.UnionType {
|
359 | return node.type === 'UnionType';
|
360 | }
|
361 |
|
362 | function isFunction(node: doctrine.Type): node is doctrine.type.FunctionType {
|
363 | return node.type === 'FunctionType';
|
364 | }
|
365 |
|
366 | function isRecordType(node: doctrine.Type): node is doctrine.type.RecordType {
|
367 | return node.type === 'RecordType';
|
368 | }
|
369 |
|
370 | function isNullable(node: doctrine.Type): node is doctrine.type.NullableType {
|
371 | return node.type === 'NullableType';
|
372 | }
|
373 |
|
374 | function isNonNullable(node: doctrine.Type):
|
375 | node is doctrine.type.NonNullableType {
|
376 | return node.type === 'NonNullableType';
|
377 | }
|
378 |
|
379 | function isAllLiteral(node: doctrine.Type): node is doctrine.type.AllLiteral {
|
380 | return node.type === 'AllLiteral';
|
381 | }
|
382 |
|
383 | function isNullLiteral(node: doctrine.Type): node is doctrine.type.NullLiteral {
|
384 | return node.type === 'NullLiteral';
|
385 | }
|
386 |
|
387 | function isNullableLiteral(node: doctrine.Type):
|
388 | node is doctrine.type.NullableLiteral {
|
389 | return node.type === 'NullableLiteral';
|
390 | }
|
391 |
|
392 | function isUndefinedLiteral(node: doctrine.Type):
|
393 | node is doctrine.type.UndefinedLiteral {
|
394 | return node.type === 'UndefinedLiteral';
|
395 | }
|
396 |
|
397 | function isVoidLiteral(node: doctrine.Type): node is doctrine.type.VoidLiteral {
|
398 | return node.type === 'VoidLiteral';
|
399 | }
|
400 |
|
401 | function isName(node: doctrine.Type): node is doctrine.type.NameExpression {
|
402 | return node.type === 'NameExpression';
|
403 | }
|