1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const ts_morph_1 = require("ts-morph");
|
4 | const errors_1 = require("../errors");
|
5 | const locations_1 = require("../locations");
|
6 | const types_1 = require("../types");
|
7 | const util_1 = require("../util");
|
8 | const parser_helpers_1 = require("./parser-helpers");
|
9 | function parseType(typeNode, typeTable, lociTable) {
|
10 |
|
11 | if (ts_morph_1.TypeGuards.isTypeReferenceNode(typeNode)) {
|
12 | if (typeNode.getType().isArray() &&
|
13 | typeNode.getTypeArguments().length > 0) {
|
14 |
|
15 | return parseArrayConstructorType(typeNode, typeTable, lociTable);
|
16 | }
|
17 | return parseTypeReference(typeNode, typeTable, lociTable);
|
18 | }
|
19 | else if (ts_morph_1.TypeGuards.isNullLiteral(typeNode)) {
|
20 | return util_1.ok(types_1.nullType());
|
21 |
|
22 | }
|
23 | else if (ts_morph_1.TypeGuards.isBooleanKeyword(typeNode)) {
|
24 | return util_1.ok(types_1.booleanType());
|
25 |
|
26 | }
|
27 | else if (ts_morph_1.TypeGuards.isStringKeyword(typeNode)) {
|
28 | return util_1.ok(types_1.stringType());
|
29 |
|
30 | }
|
31 | else if (ts_morph_1.TypeGuards.isNumberKeyword(typeNode)) {
|
32 | return util_1.ok(types_1.floatType());
|
33 | }
|
34 | else if (ts_morph_1.TypeGuards.isLiteralTypeNode(typeNode)) {
|
35 | return parseLiteralType(typeNode);
|
36 | }
|
37 | else if (ts_morph_1.TypeGuards.isArrayTypeNode(typeNode)) {
|
38 | return parseArrayType(typeNode, typeTable, lociTable);
|
39 | }
|
40 | else if (ts_morph_1.TypeGuards.isTypeLiteralNode(typeNode)) {
|
41 | return parseObjectLiteralType(typeNode, typeTable, lociTable);
|
42 | }
|
43 | else if (ts_morph_1.TypeGuards.isUnionTypeNode(typeNode)) {
|
44 | return parseUnionType(typeNode, typeTable, lociTable);
|
45 | }
|
46 | else if (ts_morph_1.TypeGuards.isIndexedAccessTypeNode(typeNode)) {
|
47 | return parseIndexedAccessType(typeNode, typeTable, lociTable);
|
48 | }
|
49 | else {
|
50 | throw new errors_1.TypeNotAllowedError("unknown type", {
|
51 | file: typeNode.getSourceFile().getFilePath(),
|
52 | position: typeNode.getPos()
|
53 | });
|
54 | }
|
55 | }
|
56 | exports.parseType = parseType;
|
57 |
|
58 | const SPOT_TYPE_ALIASES = [
|
59 | "Date",
|
60 | "DateTime",
|
61 | "Number",
|
62 | "Double",
|
63 | "Float",
|
64 | "Integer",
|
65 | "Int32",
|
66 | "Int64",
|
67 | "String"
|
68 | ];
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function parseTypeReference(typeNode, typeTable, lociTable) {
|
77 | var _a;
|
78 | const declarationResult = getTargetDeclarationFromTypeReference(typeNode);
|
79 | if (declarationResult.isErr())
|
80 | return declarationResult;
|
81 | const declaration = declarationResult.unwrap();
|
82 | const name = declaration.getName();
|
83 | const description = (_a = parser_helpers_1.getJsDoc(declaration)) === null || _a === void 0 ? void 0 : _a.getDescription().trim();
|
84 | if (ts_morph_1.TypeGuards.isTypeAliasDeclaration(declaration)) {
|
85 | const decTypeNode = declaration.getTypeNodeOrThrow();
|
86 |
|
87 |
|
88 | if (SPOT_TYPE_ALIASES.includes(name)) {
|
89 | if (ts_morph_1.TypeGuards.isTypeReferenceNode(decTypeNode)) {
|
90 | throw new Error(`Internal type ${name} must not be redefined`);
|
91 | }
|
92 | else if (declaration.getType().isString()) {
|
93 | switch (name) {
|
94 | case "String":
|
95 | return util_1.ok(types_1.stringType());
|
96 | case "Date":
|
97 | return util_1.ok(types_1.dateType());
|
98 | case "DateTime":
|
99 | return util_1.ok(types_1.dateTimeType());
|
100 | default:
|
101 | throw new Error(`Internal type ${name} must not be redefined`);
|
102 | }
|
103 | }
|
104 | else if (declaration.getType().isNumber()) {
|
105 | switch (name) {
|
106 | case "Number":
|
107 | case "Float":
|
108 | return util_1.ok(types_1.floatType());
|
109 | case "Double":
|
110 | return util_1.ok(types_1.doubleType());
|
111 | case "Integer":
|
112 | case "Int32":
|
113 | return util_1.ok(types_1.int32Type());
|
114 | case "Int64":
|
115 | return util_1.ok(types_1.int64Type());
|
116 | default:
|
117 | throw new Error(`Internal type ${name} must not be redefined`);
|
118 | }
|
119 | }
|
120 | else {
|
121 | throw new Error(`Internal type ${name} must not be redefined`);
|
122 | }
|
123 | }
|
124 | else {
|
125 | if (typeTable.exists(name)) {
|
126 | if (!lociTable.equalsMorphNode(locations_1.LociTable.typeKey(name), decTypeNode)) {
|
127 | throw new Error(`Type ${name} defined multiple times`);
|
128 | }
|
129 | }
|
130 | else {
|
131 | const targetTypeResult = parseType(decTypeNode, typeTable, lociTable);
|
132 | if (targetTypeResult.isErr())
|
133 | return targetTypeResult;
|
134 | typeTable.add(name, { type: targetTypeResult.unwrap(), description });
|
135 | lociTable.addMorphNode(locations_1.LociTable.typeKey(name), decTypeNode);
|
136 | }
|
137 | return util_1.ok(types_1.referenceType(name));
|
138 | }
|
139 | }
|
140 | else {
|
141 | if (SPOT_TYPE_ALIASES.includes(name)) {
|
142 | throw new Error(`Internal type ${name} must not be redefined`);
|
143 | }
|
144 | else {
|
145 | if (typeTable.exists(name)) {
|
146 | if (!lociTable.equalsMorphNode(locations_1.LociTable.typeKey(name), declaration)) {
|
147 | throw new Error(`Type ${name} defined multiple times`);
|
148 | }
|
149 | }
|
150 | else {
|
151 | const targetTypeResult = parseInterfaceDeclaration(declaration, typeTable, lociTable);
|
152 | if (targetTypeResult.isErr())
|
153 | return targetTypeResult;
|
154 | typeTable.add(name, { type: targetTypeResult.unwrap(), description });
|
155 | lociTable.addMorphNode(locations_1.LociTable.typeKey(name), declaration);
|
156 | }
|
157 | return util_1.ok(types_1.referenceType(name));
|
158 | }
|
159 | }
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | function parseLiteralType(typeNode) {
|
167 | const literal = typeNode.getLiteral();
|
168 | if (ts_morph_1.TypeGuards.isBooleanLiteral(literal)) {
|
169 | return util_1.ok(types_1.booleanLiteralType(literal.getLiteralValue()));
|
170 | }
|
171 | else if (ts_morph_1.TypeGuards.isStringLiteral(literal)) {
|
172 | return util_1.ok(types_1.stringLiteralType(literal.getLiteralText()));
|
173 | }
|
174 | else if (ts_morph_1.TypeGuards.isNumericLiteral(literal)) {
|
175 | const numericValue = literal.getLiteralValue();
|
176 | return util_1.ok(Number.isInteger(numericValue)
|
177 | ? types_1.intLiteralType(numericValue)
|
178 | : types_1.floatLiteralType(numericValue));
|
179 | }
|
180 | else {
|
181 | return util_1.err(new errors_1.TypeNotAllowedError("unexpected literal type", {
|
182 | file: typeNode.getSourceFile().getFilePath(),
|
183 | position: typeNode.getPos()
|
184 | }));
|
185 | }
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | function parseArrayType(typeNode, typeTable, lociTable) {
|
200 | const elementDataTypeResult = parseType(typeNode.getElementTypeNode(), typeTable, lociTable);
|
201 | if (elementDataTypeResult.isErr())
|
202 | return elementDataTypeResult;
|
203 | return util_1.ok(types_1.arrayType(elementDataTypeResult.unwrap()));
|
204 | }
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | function parseArrayConstructorType(typeNode, typeTable, lociTable) {
|
213 | const typeArguments = typeNode.getTypeArguments();
|
214 | if (typeArguments.length !== 1) {
|
215 | return util_1.err(new errors_1.ParserError("Array types must declare exactly one argument", {
|
216 | file: typeNode.getSourceFile().getFilePath(),
|
217 | position: typeNode.getPos()
|
218 | }));
|
219 | }
|
220 | const elementDataTypeResult = parseType(typeArguments[0], typeTable, lociTable);
|
221 | if (elementDataTypeResult.isErr())
|
222 | return elementDataTypeResult;
|
223 | return util_1.ok(types_1.arrayType(elementDataTypeResult.unwrap()));
|
224 | }
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | function parseObjectLiteralType(typeNode, typeTable, lociTable) {
|
237 | var _a;
|
238 | const indexSignatures = typeNode.getIndexSignatures();
|
239 | if (indexSignatures.length > 0) {
|
240 | return util_1.err(new errors_1.TypeNotAllowedError("indexed types are not supported", {
|
241 | file: indexSignatures[0].getSourceFile().getFilePath(),
|
242 | position: indexSignatures[0].getPos()
|
243 | }));
|
244 | }
|
245 | const objectProperties = [];
|
246 | for (const ps of typeNode.getProperties()) {
|
247 | const propTypeResult = parseType(ps.getTypeNodeOrThrow(), typeTable, lociTable);
|
248 | if (propTypeResult.isErr())
|
249 | return propTypeResult;
|
250 | const prop = {
|
251 | name: parser_helpers_1.getPropertyName(ps),
|
252 | description: (_a = parser_helpers_1.getJsDoc(ps)) === null || _a === void 0 ? void 0 : _a.getDescription().trim(),
|
253 | type: propTypeResult.unwrap(),
|
254 | optional: ps.hasQuestionToken()
|
255 | };
|
256 | objectProperties.push(prop);
|
257 | }
|
258 | return util_1.ok(types_1.objectType(objectProperties));
|
259 | }
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | function parseInterfaceDeclaration(interfaceDeclaration, typeTable, lociTable) {
|
269 | var _a;
|
270 | const indexSignatures = interfaceDeclaration.getIndexSignatures();
|
271 | if (indexSignatures.length > 0) {
|
272 | return util_1.err(new errors_1.TypeNotAllowedError("indexed types are not supported", {
|
273 | file: indexSignatures[0].getSourceFile().getFilePath(),
|
274 | position: indexSignatures[0].getPos()
|
275 | }));
|
276 | }
|
277 | const propertySignatures = interfaceDeclaration
|
278 | .getType()
|
279 | .getProperties()
|
280 | .map(propertySymbol => {
|
281 | const vd = propertySymbol.getValueDeclarationOrThrow();
|
282 | if (!ts_morph_1.TypeGuards.isPropertySignature(vd)) {
|
283 | throw new Error("expected property signature");
|
284 | }
|
285 | return vd;
|
286 | });
|
287 | const objectProperties = [];
|
288 | for (const ps of propertySignatures) {
|
289 | const propTypeResult = parseType(ps.getTypeNodeOrThrow(), typeTable, lociTable);
|
290 | if (propTypeResult.isErr())
|
291 | return propTypeResult;
|
292 | const prop = {
|
293 | name: parser_helpers_1.getPropertyName(ps),
|
294 | description: (_a = parser_helpers_1.getJsDoc(ps)) === null || _a === void 0 ? void 0 : _a.getDescription().trim(),
|
295 | type: propTypeResult.unwrap(),
|
296 | optional: ps.hasQuestionToken()
|
297 | };
|
298 | objectProperties.push(prop);
|
299 | }
|
300 | return util_1.ok(types_1.objectType(objectProperties));
|
301 | }
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | function parseUnionType(typeNode, typeTable, lociTable) {
|
310 | const allowedTargetTypes = typeNode
|
311 | .getTypeNodes()
|
312 | .filter(type => !type.getType().isUndefined());
|
313 | switch (allowedTargetTypes.length) {
|
314 | case 0:
|
315 | return util_1.err(new errors_1.TypeNotAllowedError("malformed union type", {
|
316 | file: typeNode.getSourceFile().getFilePath(),
|
317 | position: typeNode.getPos()
|
318 | }));
|
319 | case 1:
|
320 |
|
321 | return parseType(allowedTargetTypes[0], typeTable, lociTable);
|
322 | default: {
|
323 | const types = [];
|
324 | for (const tn of allowedTargetTypes) {
|
325 | const typeResult = parseType(tn, typeTable, lociTable);
|
326 | if (typeResult.isErr())
|
327 | return typeResult;
|
328 | types.push(typeResult.unwrap());
|
329 | }
|
330 | return util_1.ok(types_1.unionType(types, types_1.inferDiscriminator(types, typeTable)));
|
331 | }
|
332 | }
|
333 | }
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 | function parseIndexedAccessType(typeNode, typeTable, lociTable) {
|
342 | const propertyAccessChainResult = resolveIndexAccessPropertyAccessChain(typeNode);
|
343 | if (propertyAccessChainResult.isErr())
|
344 | return propertyAccessChainResult;
|
345 | const rootReferenceResult = resolveIndexedAccessRootReference(typeNode);
|
346 | if (rootReferenceResult.isErr())
|
347 | return rootReferenceResult;
|
348 | const refTypeResult = parseTypeReference(rootReferenceResult.unwrap(), typeTable, lociTable);
|
349 | if (refTypeResult.isErr())
|
350 | return refTypeResult;
|
351 | const refType = refTypeResult.unwrap();
|
352 | if (refType.kind !== types_1.TypeKind.REFERENCE) {
|
353 | return util_1.err(new errors_1.TypeNotAllowedError("Indexed access type must be reference", {
|
354 | file: typeNode.getSourceFile().getFilePath(),
|
355 | position: typeNode.getPos()
|
356 | }));
|
357 | }
|
358 | const resolvedType = resolveIndexedAccessType(propertyAccessChainResult.unwrap(), refType, typeTable);
|
359 | return util_1.ok(resolvedType);
|
360 | }
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | function resolveIndexedAccessType(propertyChain, currentType, typeTable) {
|
369 | if (propertyChain.length === 0)
|
370 | return currentType;
|
371 | if (currentType.kind === types_1.TypeKind.OBJECT) {
|
372 | const property = currentType.properties.find(p => p.name === propertyChain[0]);
|
373 | if (property === undefined) {
|
374 | throw new Error("Indexed type property not found");
|
375 | }
|
376 | return resolveIndexedAccessType(propertyChain.slice(1), property.type, typeTable);
|
377 | }
|
378 | if (currentType.kind === types_1.TypeKind.REFERENCE) {
|
379 | const referencedType = typeTable.getOrError(currentType.name).type;
|
380 | return resolveIndexedAccessType(propertyChain, referencedType, typeTable);
|
381 | }
|
382 | throw new Error("Indexed type error");
|
383 | }
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 | function resolveIndexedAccessRootReference(typeNode) {
|
390 | const objectType = typeNode.getObjectTypeNode();
|
391 | if (ts_morph_1.TypeGuards.isIndexedAccessTypeNode(objectType)) {
|
392 | return resolveIndexedAccessRootReference(objectType);
|
393 | }
|
394 | if (!ts_morph_1.TypeGuards.isTypeReferenceNode(objectType)) {
|
395 | return util_1.err(new errors_1.TypeNotAllowedError("Indexed access type must be reference", {
|
396 | file: objectType.getSourceFile().getFilePath(),
|
397 | position: objectType.getPos()
|
398 | }));
|
399 | }
|
400 | return util_1.ok(objectType);
|
401 | }
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | function resolveIndexAccessPropertyAccessChain(typeNode, accResult = util_1.ok([])) {
|
409 | if (accResult.isErr())
|
410 | return accResult;
|
411 | const acc = accResult.unwrap();
|
412 | const literalTypeNode = typeNode.getIndexTypeNode();
|
413 | if (!ts_morph_1.TypeGuards.isLiteralTypeNode(literalTypeNode)) {
|
414 | throw new Error("expected type literal");
|
415 | }
|
416 | const literalTypeResult = parseLiteralType(literalTypeNode);
|
417 | if (literalTypeResult.isErr())
|
418 | return literalTypeResult;
|
419 | const literalType = literalTypeResult.unwrap();
|
420 | if (literalType.kind !== types_1.TypeKind.STRING_LITERAL) {
|
421 | throw new Error("expected string literal");
|
422 | }
|
423 | const chainParent = typeNode.getObjectTypeNode();
|
424 | if (ts_morph_1.TypeGuards.isIndexedAccessTypeNode(chainParent)) {
|
425 | return resolveIndexAccessPropertyAccessChain(chainParent, util_1.ok(acc.concat(literalType.value)));
|
426 | }
|
427 | return util_1.ok(acc.concat(literalType.value).reverse());
|
428 | }
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | function getTargetDeclarationFromTypeReference(typeReference) {
|
436 |
|
437 | const symbol = typeReference.getTypeName().getSymbolOrThrow();
|
438 |
|
439 | const targetSymbol = symbol.isAlias()
|
440 | ? symbol.getAliasedSymbolOrThrow()
|
441 | : symbol;
|
442 | const declarations = targetSymbol.getDeclarations();
|
443 | const location = typeReference.getSourceFile().getFilePath();
|
444 | const line = typeReference.getStartLineNumber();
|
445 | const typeName = symbol.getName();
|
446 | if (typeName === "Map") {
|
447 | return util_1.err(new errors_1.TypeNotAllowedError("Map type is not supported", {
|
448 | file: location,
|
449 | position: typeReference.getPos()
|
450 | }));
|
451 | }
|
452 | if (declarations.length !== 1) {
|
453 |
|
454 | const errorMsg = `${location}#${line}: expected exactly one declaration for ${typeName}`;
|
455 | if (typeName === "String") {
|
456 | throw new Error(`${errorMsg}\nDid you forget to import String? => import { String } from "@airtasker/spot"`);
|
457 | }
|
458 | else {
|
459 | throw new Error(errorMsg);
|
460 | }
|
461 |
|
462 | }
|
463 | const targetDeclaration = declarations[0];
|
464 |
|
465 |
|
466 | if (ts_morph_1.TypeGuards.isEnumDeclaration(targetDeclaration)) {
|
467 | return util_1.err(new errors_1.TypeNotAllowedError("Enums are not supported", {
|
468 | file: targetDeclaration.getSourceFile().getFilePath(),
|
469 | position: targetDeclaration.getPos()
|
470 | }));
|
471 | }
|
472 |
|
473 | if (ts_morph_1.TypeGuards.isEnumMember(targetDeclaration)) {
|
474 | return util_1.err(new errors_1.TypeNotAllowedError("Enums are not supported", {
|
475 | file: targetDeclaration.getSourceFile().getFilePath(),
|
476 | position: targetDeclaration.getPos()
|
477 | }));
|
478 | }
|
479 | if (ts_morph_1.TypeGuards.isInterfaceDeclaration(targetDeclaration) ||
|
480 | ts_morph_1.TypeGuards.isTypeAliasDeclaration(targetDeclaration)) {
|
481 | return util_1.ok(targetDeclaration);
|
482 | }
|
483 | throw new Error("expected a type alias or interface declaration");
|
484 | }
|