UNPKG

16.2 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
7 * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
8 * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
9 * Google as part of the polymer project is also subject to an additional IP
10 * rights grant found at http://polymer.github.io/PATENTS.txt
11 */
12Object.defineProperty(exports, "__esModule", { value: true });
13class Document {
14 constructor(data) {
15 this.kind = 'document';
16 this.path = data.path;
17 this.members = data.members || [];
18 this.referencePaths = new Set(Array.from(data.referencePaths || []));
19 this.header = data.header || '';
20 }
21 /**
22 * Iterate over all nodes in the document, depth first. Includes all
23 * recursive ancestors, and the document itself.
24 */
25 *traverse() {
26 for (const m of this.members) {
27 yield* m.traverse();
28 }
29 yield this;
30 }
31 /**
32 * Clean up this AST.
33 */
34 simplify() {
35 for (const node of this.traverse()) {
36 if (node.kind === 'union') {
37 node.simplify();
38 }
39 }
40 }
41 serialize() {
42 let out = '';
43 if (this.header) {
44 out += formatComment(this.header, 0) + '\n';
45 }
46 if (this.referencePaths.size > 0) {
47 for (const ref of this.referencePaths) {
48 out += `/// <reference path="${ref}" />\n`;
49 }
50 out += '\n';
51 }
52 out += this.members.map((m) => m.serialize()).join('\n');
53 return out;
54 }
55}
56exports.Document = Document;
57class Namespace {
58 constructor(data) {
59 this.kind = 'namespace';
60 this.name = data.name;
61 this.description = data.description || '';
62 this.members = data.members || [];
63 }
64 *traverse() {
65 for (const m of this.members) {
66 yield* m.traverse();
67 }
68 yield this;
69 }
70 serialize(depth = 0) {
71 let out = '';
72 if (this.description) {
73 out += formatComment(this.description, depth);
74 }
75 const i = indent(depth);
76 out += i;
77 if (depth === 0) {
78 out += 'declare ';
79 }
80 out += `namespace ${this.name} {\n`;
81 for (const member of this.members) {
82 out += '\n' + member.serialize(depth + 1);
83 }
84 out += `${i}}\n`;
85 return out;
86 }
87}
88exports.Namespace = Namespace;
89class Class {
90 constructor(data) {
91 this.kind = 'class';
92 this.name = data.name;
93 this.description = data.description || '';
94 this.extends = data.extends || '';
95 this.mixins = data.mixins || [];
96 this.properties = data.properties || [];
97 this.methods = data.methods || [];
98 }
99 *traverse() {
100 for (const p of this.properties) {
101 yield* p.traverse();
102 }
103 for (const m of this.methods) {
104 yield* m.traverse();
105 }
106 yield this;
107 }
108 serialize(depth = 0) {
109 let out = '';
110 const i = indent(depth);
111 if (this.description) {
112 out += formatComment(this.description, depth);
113 }
114 out += i;
115 if (depth === 0) {
116 out += 'declare ';
117 }
118 out += `class ${this.name}`;
119 if (this.mixins.length) {
120 const i2 = indent(depth + 1);
121 out += ' extends';
122 for (const mixin of this.mixins) {
123 out += `\n${i2}${mixin}(`;
124 }
125 out += `\n${i2}${this.extends || 'Object'}`;
126 out += ')'.repeat(this.mixins.length);
127 }
128 else if (this.extends) {
129 out += ' extends ' + this.extends;
130 }
131 out += ' {\n';
132 for (const property of this.properties) {
133 out += property.serialize(depth + 1);
134 }
135 for (const method of this.methods) {
136 out += method.serialize(depth + 1);
137 }
138 if (!out.endsWith('\n')) {
139 out += '\n';
140 }
141 out += `${i}}\n`;
142 return out;
143 }
144}
145exports.Class = Class;
146class Interface {
147 constructor(data) {
148 this.kind = 'interface';
149 this.name = data.name;
150 this.description = data.description || '';
151 this.extends = data.extends || [];
152 this.properties = data.properties || [];
153 this.methods = data.methods || [];
154 }
155 *traverse() {
156 for (const p of this.properties) {
157 yield* p.traverse();
158 }
159 for (const m of this.methods) {
160 yield* m.traverse();
161 }
162 yield this;
163 }
164 serialize(depth = 0) {
165 let out = '';
166 const i = indent(depth);
167 if (this.description) {
168 out += formatComment(this.description, depth);
169 }
170 out += i;
171 out += `interface ${this.name}`;
172 if (this.extends.length) {
173 out += ' extends ' + this.extends.join(', ');
174 }
175 out += ' {\n';
176 for (const property of this.properties) {
177 out += property.serialize(depth + 1);
178 }
179 for (const method of this.methods) {
180 out += method.serialize(depth + 1);
181 }
182 if (!out.endsWith('\n')) {
183 out += '\n';
184 }
185 out += `${i}}\n`;
186 return out;
187 }
188}
189exports.Interface = Interface;
190// A class mixin function using the pattern described at:
191// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html
192class Mixin {
193 constructor(data) {
194 this.kind = 'mixin';
195 this.name = data.name;
196 this.description = data.description || '';
197 this.interfaces = data.interfaces || [];
198 }
199 *traverse() {
200 yield this;
201 }
202 serialize(depth = 0) {
203 let out = '';
204 const i = indent(depth);
205 const i2 = indent(depth + 1);
206 if (this.description) {
207 out += formatComment(this.description, depth);
208 }
209 out += i;
210 if (depth === 0) {
211 out += 'declare ';
212 }
213 out += `function ${this.name}`;
214 out += `<T extends new(...args: any[]) => {}>(base: T): {\n`;
215 out += `${i2}new(...args: any[]): ${this.interfaces.join(' & ')}\n`;
216 out += `${i}} & T\n`;
217 return out;
218 }
219}
220exports.Mixin = Mixin;
221class FunctionLike {
222 constructor(data) {
223 this.name = data.name;
224 this.description = data.description || '';
225 this.params = data.params || [];
226 this.returns = data.returns || exports.anyType;
227 this.templateTypes = data.templateTypes || [];
228 this.returnsDescription = data.returnsDescription || '';
229 }
230 serialize(depth = 0) {
231 let out = '';
232 const i = indent(depth);
233 const annotations = [];
234 for (const p of this.params) {
235 if (p.description) {
236 annotations.push(`@param ${p.name} ${p.description}`);
237 }
238 }
239 if (this.returnsDescription) {
240 annotations.push(`@returns ${this.returnsDescription}`);
241 }
242 let combinedDescription = this.description;
243 if (annotations.length > 0) {
244 if (combinedDescription) {
245 combinedDescription += '\n\n';
246 }
247 combinedDescription += annotations.join('\n');
248 }
249 if (combinedDescription) {
250 out += '\n' + formatComment(combinedDescription, depth);
251 }
252 if (depth === 0) {
253 out += 'declare ';
254 }
255 out += i;
256 if (this.kind === 'function') {
257 out += 'function ';
258 }
259 out += this.name;
260 if (this.templateTypes.length > 0) {
261 out += `<${this.templateTypes.join(', ')}>`;
262 }
263 out += '(';
264 out += this.params.map((p) => p.serialize()).join(', ');
265 out += `): ${this.returns.serialize()};\n`;
266 return out;
267 }
268}
269exports.FunctionLike = FunctionLike;
270class Function extends FunctionLike {
271 constructor() {
272 super(...arguments);
273 this.kind = 'function';
274 }
275 *traverse() {
276 for (const p of this.params) {
277 yield* p.traverse();
278 }
279 yield* this.returns.traverse();
280 yield this;
281 }
282}
283exports.Function = Function;
284class Method extends FunctionLike {
285 constructor() {
286 super(...arguments);
287 this.kind = 'method';
288 }
289 *traverse() {
290 for (const p of this.params) {
291 yield* p.traverse();
292 }
293 yield* this.returns.traverse();
294 yield this;
295 }
296}
297exports.Method = Method;
298class Property {
299 constructor(data) {
300 this.kind = 'property';
301 this.name = data.name;
302 this.description = data.description || '';
303 this.type = data.type || exports.anyType;
304 }
305 *traverse() {
306 yield* this.type.traverse();
307 yield this;
308 }
309 serialize(depth = 0) {
310 let out = '';
311 const i = indent(depth);
312 if (this.description) {
313 out += '\n' + formatComment(this.description, depth);
314 }
315 out += `${i}${quotePropertyName(this.name)}: ${this.type.serialize()};\n`;
316 return out;
317 }
318}
319exports.Property = Property;
320class Param {
321 constructor(data) {
322 this.kind = 'param';
323 this.name = data.name;
324 this.type = data.type || exports.anyType;
325 this.optional = data.optional || false;
326 this.rest = data.rest || false;
327 this.description = data.description || '';
328 }
329 *traverse() {
330 yield* this.type.traverse();
331 yield this;
332 }
333 serialize() {
334 let out = '';
335 if (this.rest) {
336 out += '...';
337 }
338 out += this.name;
339 if (this.optional) {
340 out += '?';
341 }
342 out += ': ' + this.type.serialize();
343 return out;
344 }
345}
346exports.Param = Param;
347// string, MyClass, null, undefined, any
348class NameType {
349 constructor(name) {
350 this.kind = 'name';
351 this.name = name;
352 }
353 ;
354 *traverse() {
355 yield this;
356 }
357 serialize() {
358 return this.name;
359 }
360}
361exports.NameType = NameType;
362// foo|bar
363class UnionType {
364 constructor(members) {
365 this.kind = 'union';
366 this.members = members;
367 }
368 *traverse() {
369 for (const m of this.members) {
370 yield* m.traverse();
371 }
372 yield this;
373 }
374 /**
375 * Simplify this union type:
376 *
377 * 1) Flatten nested unions (`foo|(bar|baz)` -> `foo|bar|baz`).
378 * 2) De-duplicate identical members (`foo|bar|foo` -> `foo|bar`).
379 */
380 simplify() {
381 const flattened = [];
382 for (const m of this.members) {
383 if (m.kind === 'union') {
384 // Note we are not recursing here, because we assume we're being called
385 // via a depth-first walk, so any union members have already been
386 // simplified.
387 flattened.push(...m.members);
388 }
389 else {
390 flattened.push(m);
391 }
392 }
393 // TODO This only de-dupes Name types. We should de-dupe Arrays and
394 // Functions too.
395 const deduped = [];
396 const names = new Set();
397 let hasNull = false;
398 let hasUndefined = false;
399 for (const m of flattened) {
400 if (m.kind === 'name') {
401 if (m.name === 'null') {
402 hasNull = true;
403 }
404 else if (m.name === 'undefined') {
405 hasUndefined = true;
406 }
407 else if (!names.has(m.name)) {
408 deduped.push(m);
409 names.add(m.name);
410 }
411 }
412 else {
413 deduped.push(m);
414 }
415 }
416 // Always put `null` and `undefined` at the end because it's more readable.
417 // Preserve declared order for everything else.
418 if (hasNull) {
419 deduped.push(exports.nullType);
420 }
421 if (hasUndefined) {
422 deduped.push(exports.undefinedType);
423 }
424 this.members = deduped;
425 }
426 serialize() {
427 return this.members
428 .map((member) => {
429 let s = member.serialize();
430 if (member.kind === 'function') {
431 // The function syntax is ambiguous when part of a union, so add
432 // parens (e.g. `() => string|null` vs `(() => string)|null`).
433 s = '(' + s + ')';
434 }
435 return s;
436 })
437 .join('|');
438 }
439}
440exports.UnionType = UnionType;
441// Array<foo>
442class ArrayType {
443 constructor(itemType) {
444 this.kind = 'array';
445 this.itemType = itemType;
446 }
447 *traverse() {
448 yield* this.itemType.traverse();
449 yield this;
450 }
451 serialize() {
452 if (this.itemType.kind === 'name') {
453 // Use the concise `foo[]` syntax when the item type is simple.
454 return `${this.itemType.serialize()}[]`;
455 }
456 else {
457 // Otherwise use the `Array<foo>` syntax which is easier to read with
458 // complex types (e.g. arrays of arrays).
459 return `Array<${this.itemType.serialize()}>`;
460 }
461 }
462}
463exports.ArrayType = ArrayType;
464// (foo: bar) => baz
465class FunctionType {
466 constructor(params, returns) {
467 this.kind = 'function';
468 this.params = params;
469 this.returns = returns;
470 }
471 *traverse() {
472 for (const p of this.params) {
473 yield* p.traverse();
474 }
475 yield* this.returns.traverse();
476 yield this;
477 }
478 serialize() {
479 const params = this.params.map((param) => param.serialize());
480 return `(${params.join(', ')}) => ${this.returns.serialize()}`;
481 }
482}
483exports.FunctionType = FunctionType;
484// {new(foo): bar}
485class ConstructorType {
486 constructor(params, returns) {
487 this.kind = 'constructor';
488 this.params = params;
489 this.returns = returns;
490 }
491 *traverse() {
492 for (const p of this.params) {
493 yield* p.traverse();
494 }
495 yield* this.returns.traverse();
496 yield this;
497 }
498 serialize() {
499 const params = this.params.map((param) => param.serialize());
500 return `{new(${params.join(', ')}): ${this.returns.serialize()}}`;
501 }
502}
503exports.ConstructorType = ConstructorType;
504// foo: bar
505class ParamType {
506 constructor(name, type, optional = false) {
507 this.kind = 'param';
508 this.name = name;
509 this.type = type;
510 this.optional = optional;
511 }
512 *traverse() {
513 yield* this.type.traverse();
514 yield this;
515 }
516 serialize() {
517 return `${this.name}${this.optional ? '?' : ''}: ${this.type.serialize()}`;
518 }
519}
520exports.ParamType = ParamType;
521class RecordType {
522 constructor(fields) {
523 this.kind = 'record';
524 this.fields = fields;
525 }
526 *traverse() {
527 for (const m of this.fields) {
528 yield* m.traverse();
529 }
530 yield this;
531 }
532 serialize() {
533 const fields = this.fields.map((field) => field.serialize());
534 return `{${fields.join(', ')}}`;
535 }
536}
537exports.RecordType = RecordType;
538exports.anyType = new NameType('any');
539exports.nullType = new NameType('null');
540exports.undefinedType = new NameType('undefined');
541function quotePropertyName(name) {
542 // TODO We should escape reserved words, and there are many more safe
543 // characters than are included in this RegExp.
544 // See https://mathiasbynens.be/notes/javascript-identifiers-es6
545 const safe = name.match(/^[_$a-zA-Z][_$a-zA-Z0-9]*$/);
546 return safe ? name : JSON.stringify(name);
547}
548const indentSpaces = 2;
549function indent(depth) {
550 return ' '.repeat(depth * indentSpaces);
551}
552function formatComment(comment, depth) {
553 const i = indent(depth);
554 return `${i}/**\n` +
555 comment.replace(/^(.)/gm, ' $1').replace(/^/gm, `${i} *`) + `\n${i} */\n`;
556}
557//# sourceMappingURL=ts-ast.js.map
\No newline at end of file