UNPKG

9 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 });
13const ts = require("./ts-ast");
14const { parseType, parseParamType } = require('doctrine/lib/typed.js');
15/**
16 * Convert from a type annotation in Closure syntax to a TypeScript type
17 * expression AST (e.g `Array` => `Array<any>|null`).
18 */
19function closureTypeToTypeScript(closureType, templateTypes = []) {
20 if (!closureType) {
21 return ts.anyType;
22 }
23 let ast;
24 try {
25 ast = parseType(closureType);
26 }
27 catch (_a) {
28 return ts.anyType;
29 }
30 return convert(ast, templateTypes);
31}
32exports.closureTypeToTypeScript = closureTypeToTypeScript;
33/**
34 * Convert from a parameter type annotation in Closure syntax to a TypeScript
35 * type expression AST
36 * (e.g `Array=` => `{type: 'Array<any>|null', optional: true}`).
37 */
38function closureParamToTypeScript(closureType, templateTypes = []) {
39 if (!closureType) {
40 return { type: ts.anyType, optional: false, rest: false };
41 }
42 let ast;
43 try {
44 ast = parseParamType(closureType);
45 }
46 catch (_a) {
47 return {
48 type: ts.anyType,
49 // It's important we try to get optional right even if we can't parse
50 // the annotation, because non-optional arguments can't follow optional
51 // ones.
52 optional: closureType.endsWith('='),
53 rest: false,
54 };
55 }
56 // Optional and Rest types are always the top-level node.
57 switch (ast.type) {
58 case 'OptionalType':
59 return {
60 type: convert(ast.expression, templateTypes),
61 optional: true,
62 rest: false,
63 };
64 case 'RestType':
65 return {
66 // The Closure type annotation for a rest parameter looks like
67 // `...foo`, where `foo` is implicitly an array. The TypeScript
68 // equivalent is explicitly an array, so we wrap it in one here.
69 type: new ts.ArrayType(convert(ast.expression, templateTypes)),
70 optional: false,
71 rest: true,
72 };
73 default:
74 return {
75 type: convert(ast, templateTypes),
76 optional: false,
77 rest: false,
78 };
79 }
80}
81exports.closureParamToTypeScript = closureParamToTypeScript;
82/**
83 * Format the given Closure type expression AST node as a TypeScript type
84 * annotation string.
85 */
86function convert(node, templateTypes) {
87 let nullable;
88 if (isNullable(node)) {
89 nullable = true;
90 node = node.expression;
91 }
92 else if (isNonNullable(node)) {
93 nullable = false;
94 node = node.expression;
95 }
96 else if (isName(node) && templateTypes.includes(node.name)) {
97 // A template type "T" looks naively like a regular name type to doctrine
98 // (e.g. a class called "T"), which would be nullable by default. However,
99 // template types are not nullable by default.
100 nullable = false;
101 }
102 else {
103 nullable = nullableByDefault(node);
104 }
105 let t;
106 if (isParameterizedArray(node)) {
107 t = convertArray(node, templateTypes);
108 }
109 else if (isUnion(node)) {
110 t = convertUnion(node, templateTypes);
111 }
112 else if (isFunction(node)) {
113 t = convertFunction(node, templateTypes);
114 }
115 else if (isBareArray(node)) {
116 t = new ts.ArrayType(ts.anyType);
117 }
118 else if (isRecordType(node)) {
119 t = convertRecord(node, templateTypes);
120 }
121 else if (isAllLiteral(node)) {
122 t = ts.anyType;
123 }
124 else if (isNullableLiteral(node)) {
125 t = ts.anyType;
126 }
127 else if (isNullLiteral(node)) {
128 t = ts.nullType;
129 }
130 else if (isUndefinedLiteral(node)) {
131 t = ts.undefinedType;
132 }
133 else if (isVoidLiteral(node)) {
134 t = new ts.NameType('void');
135 }
136 else if (isName(node)) {
137 if (node.name === 'Object') {
138 // Closure's `Object` type excludes primitives, so it is closest to
139 // TypeScript's `object`. (Technically this should be `object|Symbol`,
140 // but we will concede that technicality.)
141 t = new ts.NameType('object');
142 }
143 else {
144 t = new ts.NameType(node.name);
145 }
146 }
147 else {
148 console.error('Unknown syntax.');
149 return ts.anyType;
150 }
151 if (nullable) {
152 t = new ts.UnionType([t, ts.nullType]);
153 }
154 return t;
155}
156/**
157 * Return whether the given AST node is an expression that is nullable by
158 * default in the Closure type system.
159 */
160function nullableByDefault(node) {
161 if (isName(node)) {
162 switch (node.name) {
163 case 'string':
164 case 'number':
165 case 'boolean':
166 case 'void':
167 return false;
168 }
169 return true;
170 }
171 return isParameterizedArray(node);
172}
173function convertArray(node, templateTypes) {
174 const applications = node.applications;
175 return new ts.ArrayType(applications.length === 1 ? convert(applications[0], templateTypes) :
176 ts.anyType);
177}
178function convertUnion(node, templateTypes) {
179 return new ts.UnionType(node.elements.map((element) => convert(element, templateTypes)));
180}
181function convertFunction(node, templateTypes) {
182 const params = node.params.map((param, idx) => new ts.ParamType(
183 // TypeScript wants named parameters, but we don't have names.
184 'p' + idx, convert(param, templateTypes)));
185 if (node.new) {
186 return new ts.ConstructorType(params,
187 // It doesn't make sense for a constructor to return something other
188 // than a named type. Also, in this context the name type is not
189 // nullable by default, so it's simpler to just directly convert here.
190 isName(node.this) ? new ts.NameType(node.this.name) : ts.anyType);
191 }
192 else {
193 return new ts.FunctionType(params,
194 // Cast because type is wrong: `FunctionType.result` is not an array.
195 node.result ? convert(node.result, templateTypes) : ts.anyType);
196 }
197}
198function convertRecord(node, templateTypes) {
199 const fields = [];
200 for (const field of node.fields) {
201 if (field.type !== 'FieldType') {
202 return ts.anyType;
203 }
204 let fieldType = field.value ? convert(field.value, templateTypes) : ts.anyType;
205 // In Closure you can't declare a record field optional, instead you
206 // declare `foo: bar|undefined`. In TypeScript we can represent this as
207 // `foo?: bar`. This also matches the semantics better, since Closure would
208 // allow the field to be omitted when it is `|undefined`, but TypeScript
209 // would require it to be explicitly set to `undefined`.
210 let optional = false;
211 if (fieldType.kind === 'union') {
212 fieldType.members = fieldType.members.filter((member) => {
213 if (member.kind === 'name' && member.name === 'undefined') {
214 optional = true;
215 return false;
216 }
217 return true;
218 });
219 // No need for a union if we collapsed it to one member.
220 fieldType.simplify();
221 }
222 fields.push(new ts.ParamType(field.key, fieldType, optional));
223 }
224 return new ts.RecordType(fields);
225}
226function isParameterizedArray(node) {
227 return node.type === 'TypeApplication' &&
228 node.expression.type === 'NameExpression' &&
229 node.expression.name === 'Array';
230}
231function isBareArray(node) {
232 return node.type === 'NameExpression' && node.name === 'Array';
233}
234function isUnion(node) {
235 return node.type === 'UnionType';
236}
237function isFunction(node) {
238 return node.type === 'FunctionType';
239}
240function isRecordType(node) {
241 return node.type === 'RecordType';
242}
243function isNullable(node) {
244 return node.type === 'NullableType';
245}
246function isNonNullable(node) {
247 return node.type === 'NonNullableType';
248}
249function isAllLiteral(node) {
250 return node.type === 'AllLiteral';
251}
252function isNullLiteral(node) {
253 return node.type === 'NullLiteral';
254}
255function isNullableLiteral(node) {
256 return node.type === 'NullableLiteral';
257}
258function isUndefinedLiteral(node) {
259 return node.type === 'UndefinedLiteral';
260}
261function isVoidLiteral(node) {
262 return node.type === 'VoidLiteral';
263}
264function isName(node) {
265 return node.type === 'NameExpression';
266}
267//# sourceMappingURL=closure-types.js.map
\No newline at end of file