• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*@internal*/
2namespace ts {
3    type SerializedEntityName =
4        | Identifier // Globals (i.e., `String`, `Number`, etc.)
5        | PropertyAccessEntityNameExpression // `A.B`
6        ;
7
8    type SerializedTypeNode =
9        | SerializedEntityName
10        | ConditionalExpression // Type Reference or Global fallback
11        | VoidExpression // `void 0` used for null/undefined/never
12        ;
13
14    export interface RuntimeTypeSerializerContext {
15        /** Specifies the current lexical block scope */
16        currentLexicalScope: SourceFile | Block | ModuleBlock | CaseBlock;
17        /** Specifies the containing `class`, but only when there is no other block scope between the current location and the `class`. */
18        currentNameScope: ClassLikeDeclaration | undefined;
19    }
20
21    export interface RuntimeTypeSerializer {
22        /**
23         * Serializes a type node for use with decorator type metadata.
24         *
25         * Types are serialized in the following fashion:
26         * - Void types point to "undefined" (e.g. "void 0")
27         * - Function and Constructor types point to the global "Function" constructor.
28         * - Interface types with a call or construct signature types point to the global
29         *   "Function" constructor.
30         * - Array and Tuple types point to the global "Array" constructor.
31         * - Type predicates and booleans point to the global "Boolean" constructor.
32         * - String literal types and strings point to the global "String" constructor.
33         * - Enum and number types point to the global "Number" constructor.
34         * - Symbol types point to the global "Symbol" constructor.
35         * - Type references to classes (or class-like variables) point to the constructor for the class.
36         * - Anything else points to the global "Object" constructor.
37         *
38         * @param node The type node to serialize.
39         */
40        serializeTypeNode(serializerContext: RuntimeTypeSerializerContext, node: TypeNode): Expression;
41        /**
42         * Serializes the type of a node for use with decorator type metadata.
43         * @param node The node that should have its type serialized.
44         */
45        serializeTypeOfNode(serializerContext: RuntimeTypeSerializerContext, node: PropertyDeclaration | ParameterDeclaration | AccessorDeclaration | ClassLikeDeclaration | MethodDeclaration): Expression;
46        /**
47         * Serializes the types of the parameters of a node for use with decorator type metadata.
48         * @param node The node that should have its parameter types serialized.
49         */
50        serializeParameterTypesOfNode(serializerContext: RuntimeTypeSerializerContext, node: Node, container: ClassLikeDeclaration): ArrayLiteralExpression;
51        /**
52         * Serializes the return type of a node for use with decorator type metadata.
53         * @param node The node that should have its return type serialized.
54         */
55        serializeReturnTypeOfNode(serializerContext: RuntimeTypeSerializerContext, node: Node): SerializedTypeNode;
56    }
57
58    export function createRuntimeTypeSerializer(context: TransformationContext): RuntimeTypeSerializer {
59        const {
60            hoistVariableDeclaration
61        } = context;
62
63        const resolver = context.getEmitResolver();
64        const compilerOptions = context.getCompilerOptions();
65        const languageVersion = getEmitScriptTarget(compilerOptions);
66        const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
67
68        let currentLexicalScope: SourceFile | CaseBlock | ModuleBlock | Block;
69        let currentNameScope: ClassLikeDeclaration | undefined;
70
71        return {
72            serializeTypeNode: (serializerContext, node) => setSerializerContextAnd(serializerContext, serializeTypeNode, node),
73            serializeTypeOfNode: (serializerContext, node) => setSerializerContextAnd(serializerContext, serializeTypeOfNode, node),
74            serializeParameterTypesOfNode: (serializerContext, node, container) => setSerializerContextAnd(serializerContext, serializeParameterTypesOfNode, node, container),
75            serializeReturnTypeOfNode: (serializerContext, node) => setSerializerContextAnd(serializerContext, serializeReturnTypeOfNode, node),
76        };
77
78        function setSerializerContextAnd<TNode extends Node | undefined, R>(serializerContext: RuntimeTypeSerializerContext, cb: (node: TNode) => R, node: TNode): R;
79        function setSerializerContextAnd<TNode extends Node | undefined, T, R>(serializerContext: RuntimeTypeSerializerContext, cb: (node: TNode, arg: T) => R, node: TNode, arg: T): R;
80        function setSerializerContextAnd<TNode extends Node | undefined, T, R>(serializerContext: RuntimeTypeSerializerContext, cb: (node: TNode, arg?: T) => R, node: TNode, arg?: T) {
81            const savedCurrentLexicalScope = currentLexicalScope;
82            const savedCurrentNameScope = currentNameScope;
83
84            currentLexicalScope = serializerContext.currentLexicalScope;
85            currentNameScope = serializerContext.currentNameScope;
86
87            const result = arg === undefined ? cb(node) : cb(node, arg);
88
89            currentLexicalScope = savedCurrentLexicalScope;
90            currentNameScope = savedCurrentNameScope;
91            return result;
92        }
93
94        function getAccessorTypeNode(node: AccessorDeclaration) {
95            const accessors = resolver.getAllAccessorDeclarations(node);
96            return accessors.setAccessor && getSetAccessorTypeAnnotationNode(accessors.setAccessor)
97                || accessors.getAccessor && getEffectiveReturnTypeNode(accessors.getAccessor);
98        }
99
100        /**
101         * Serializes the type of a node for use with decorator type metadata.
102         * @param node The node that should have its type serialized.
103         */
104        function serializeTypeOfNode(node: PropertyDeclaration | ParameterDeclaration | AccessorDeclaration | ClassLikeDeclaration | MethodDeclaration): SerializedTypeNode {
105            switch (node.kind) {
106                case SyntaxKind.PropertyDeclaration:
107                case SyntaxKind.Parameter:
108                    return serializeTypeNode(node.type);
109                case SyntaxKind.SetAccessor:
110                case SyntaxKind.GetAccessor:
111                    return serializeTypeNode(getAccessorTypeNode(node));
112                case SyntaxKind.ClassDeclaration:
113                case SyntaxKind.ClassExpression:
114                case SyntaxKind.MethodDeclaration:
115                    return factory.createIdentifier("Function");
116                default:
117                    return factory.createVoidZero();
118            }
119        }
120
121        /**
122         * Serializes the type of a node for use with decorator type metadata.
123         * @param node The node that should have its type serialized.
124         */
125        function serializeParameterTypesOfNode(node: Node, container: ClassLikeDeclaration): ArrayLiteralExpression {
126            const valueDeclaration =
127                isClassLike(node)
128                    ? getFirstConstructorWithBody(node)
129                    : isFunctionLike(node) && nodeIsPresent((node as FunctionLikeDeclaration).body)
130                        ? node
131                        : undefined;
132
133            const expressions: SerializedTypeNode[] = [];
134            if (valueDeclaration) {
135                const parameters = getParametersOfDecoratedDeclaration(valueDeclaration, container);
136                const numParameters = parameters.length;
137                for (let i = 0; i < numParameters; i++) {
138                    const parameter = parameters[i];
139                    if (i === 0 && isIdentifier(parameter.name) && parameter.name.escapedText === "this") {
140                        continue;
141                    }
142                    if (parameter.dotDotDotToken) {
143                        expressions.push(serializeTypeNode(getRestParameterElementType(parameter.type)));
144                    }
145                    else {
146                        expressions.push(serializeTypeOfNode(parameter));
147                    }
148                }
149            }
150
151            return factory.createArrayLiteralExpression(expressions);
152        }
153
154        function getParametersOfDecoratedDeclaration(node: SignatureDeclaration, container: ClassLikeDeclaration) {
155            if (container && node.kind === SyntaxKind.GetAccessor) {
156                const { setAccessor } = getAllAccessorDeclarations(container.members, node as AccessorDeclaration);
157                if (setAccessor) {
158                    return setAccessor.parameters;
159                }
160            }
161            return node.parameters;
162        }
163
164        /**
165         * Serializes the return type of a node for use with decorator type metadata.
166         * @param node The node that should have its return type serialized.
167         */
168        function serializeReturnTypeOfNode(node: Node): SerializedTypeNode {
169            if (isFunctionLike(node) && node.type) {
170                return serializeTypeNode(node.type);
171            }
172            else if (isAsyncFunction(node)) {
173                return factory.createIdentifier("Promise");
174            }
175
176            return factory.createVoidZero();
177        }
178
179        /**
180         * Serializes a type node for use with decorator type metadata.
181         *
182         * Types are serialized in the following fashion:
183         * - Void types point to "undefined" (e.g. "void 0")
184         * - Function and Constructor types point to the global "Function" constructor.
185         * - Interface types with a call or construct signature types point to the global
186         *   "Function" constructor.
187         * - Array and Tuple types point to the global "Array" constructor.
188         * - Type predicates and booleans point to the global "Boolean" constructor.
189         * - String literal types and strings point to the global "String" constructor.
190         * - Enum and number types point to the global "Number" constructor.
191         * - Symbol types point to the global "Symbol" constructor.
192         * - Type references to classes (or class-like variables) point to the constructor for the class.
193         * - Anything else points to the global "Object" constructor.
194         *
195         * @param node The type node to serialize.
196         */
197        function serializeTypeNode(node: TypeNode | undefined): SerializedTypeNode {
198            if (node === undefined) {
199                return factory.createIdentifier("Object");
200            }
201
202            node = skipTypeParentheses(node);
203
204            switch (node.kind) {
205                case SyntaxKind.VoidKeyword:
206                case SyntaxKind.UndefinedKeyword:
207                case SyntaxKind.NeverKeyword:
208                    return factory.createVoidZero();
209
210                case SyntaxKind.FunctionType:
211                case SyntaxKind.ConstructorType:
212                    return factory.createIdentifier("Function");
213
214                case SyntaxKind.ArrayType:
215                case SyntaxKind.TupleType:
216                    return factory.createIdentifier("Array");
217
218                case SyntaxKind.TypePredicate:
219                    return (node as TypePredicateNode).assertsModifier ?
220                        factory.createVoidZero() :
221                        factory.createIdentifier("Boolean");
222
223                case SyntaxKind.BooleanKeyword:
224                    return factory.createIdentifier("Boolean");
225
226                case SyntaxKind.TemplateLiteralType:
227                case SyntaxKind.StringKeyword:
228                    return factory.createIdentifier("String");
229
230                case SyntaxKind.ObjectKeyword:
231                    return factory.createIdentifier("Object");
232
233                case SyntaxKind.LiteralType:
234                    return serializeLiteralOfLiteralTypeNode((node as LiteralTypeNode).literal);
235
236                case SyntaxKind.NumberKeyword:
237                    return factory.createIdentifier("Number");
238
239                case SyntaxKind.BigIntKeyword:
240                    return getGlobalConstructor("BigInt", ScriptTarget.ES2020);
241
242                case SyntaxKind.SymbolKeyword:
243                    return getGlobalConstructor("Symbol", ScriptTarget.ES2015);
244
245                case SyntaxKind.TypeReference:
246                    return serializeTypeReferenceNode(node as TypeReferenceNode);
247
248                case SyntaxKind.IntersectionType:
249                    return serializeUnionOrIntersectionConstituents((node as UnionOrIntersectionTypeNode).types, /*isIntersection*/ true);
250
251                case SyntaxKind.UnionType:
252                    return serializeUnionOrIntersectionConstituents((node as UnionOrIntersectionTypeNode).types, /*isIntersection*/ false);
253
254                case SyntaxKind.ConditionalType:
255                    return serializeUnionOrIntersectionConstituents([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType], /*isIntersection*/ false);
256
257                case SyntaxKind.TypeOperator:
258                    if ((node as TypeOperatorNode).operator === SyntaxKind.ReadonlyKeyword) {
259                        return serializeTypeNode((node as TypeOperatorNode).type);
260                    }
261                    break;
262
263                case SyntaxKind.TypeQuery:
264                case SyntaxKind.IndexedAccessType:
265                case SyntaxKind.MappedType:
266                case SyntaxKind.TypeLiteral:
267                case SyntaxKind.AnyKeyword:
268                case SyntaxKind.UnknownKeyword:
269                case SyntaxKind.ThisType:
270                case SyntaxKind.ImportType:
271                    break;
272
273                // handle JSDoc types from an invalid parse
274                case SyntaxKind.JSDocAllType:
275                case SyntaxKind.JSDocUnknownType:
276                case SyntaxKind.JSDocFunctionType:
277                case SyntaxKind.JSDocVariadicType:
278                case SyntaxKind.JSDocNamepathType:
279                    break;
280
281                case SyntaxKind.JSDocNullableType:
282                case SyntaxKind.JSDocNonNullableType:
283                case SyntaxKind.JSDocOptionalType:
284                    return serializeTypeNode((node as JSDocNullableType | JSDocNonNullableType | JSDocOptionalType).type);
285
286                default:
287                    return Debug.failBadSyntaxKind(node);
288            }
289
290            return factory.createIdentifier("Object");
291        }
292
293        function serializeLiteralOfLiteralTypeNode(node: LiteralTypeNode["literal"]): SerializedTypeNode {
294            switch (node.kind) {
295                case SyntaxKind.StringLiteral:
296                case SyntaxKind.NoSubstitutionTemplateLiteral:
297                    return factory.createIdentifier("String");
298
299                case SyntaxKind.PrefixUnaryExpression: {
300                    const operand = (node as PrefixUnaryExpression).operand;
301                    switch (operand.kind) {
302                        case SyntaxKind.NumericLiteral:
303                        case SyntaxKind.BigIntLiteral:
304                            return serializeLiteralOfLiteralTypeNode(operand as NumericLiteral | BigIntLiteral);
305                        default:
306                            return Debug.failBadSyntaxKind(operand);
307                    }
308                }
309
310                case SyntaxKind.NumericLiteral:
311                    return factory.createIdentifier("Number");
312
313                case SyntaxKind.BigIntLiteral:
314                    return getGlobalConstructor("BigInt", ScriptTarget.ES2020);
315
316                case SyntaxKind.TrueKeyword:
317                case SyntaxKind.FalseKeyword:
318                    return factory.createIdentifier("Boolean");
319
320                case SyntaxKind.NullKeyword:
321                    return factory.createVoidZero();
322
323                default:
324                    return Debug.failBadSyntaxKind(node);
325            }
326        }
327
328        function serializeUnionOrIntersectionConstituents(types: readonly TypeNode[], isIntersection: boolean): SerializedTypeNode {
329            // Note when updating logic here also update `getEntityNameForDecoratorMetadata` in checker.ts so that aliases can be marked as referenced
330            let serializedType: SerializedTypeNode | undefined;
331            for (let typeNode of types) {
332                typeNode = skipTypeParentheses(typeNode);
333                if (typeNode.kind === SyntaxKind.NeverKeyword) {
334                    if (isIntersection) return factory.createVoidZero(); // Reduce to `never` in an intersection
335                    continue; // Elide `never` in a union
336                }
337
338                if (typeNode.kind === SyntaxKind.UnknownKeyword) {
339                    if (!isIntersection) return factory.createIdentifier("Object"); // Reduce to `unknown` in a union
340                    continue; // Elide `unknown` in an intersection
341                }
342
343                if (typeNode.kind === SyntaxKind.AnyKeyword) {
344                    return factory.createIdentifier("Object"); // Reduce to `any` in a union or intersection
345                }
346
347                if (!strictNullChecks && ((isLiteralTypeNode(typeNode) && typeNode.literal.kind === SyntaxKind.NullKeyword) || typeNode.kind === SyntaxKind.UndefinedKeyword)) {
348                    continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
349                }
350
351                const serializedConstituent = serializeTypeNode(typeNode);
352                if (isIdentifier(serializedConstituent) && serializedConstituent.escapedText === "Object") {
353                    // One of the individual is global object, return immediately
354                    return serializedConstituent;
355                }
356
357                // If there exists union that is not `void 0` expression, check if the the common type is identifier.
358                // anything more complex and we will just default to Object
359                if (serializedType) {
360                    // Different types
361                    if (!equateSerializedTypeNodes(serializedType, serializedConstituent)) {
362                        return factory.createIdentifier("Object");
363                    }
364                }
365                else {
366                    // Initialize the union type
367                    serializedType = serializedConstituent;
368                }
369            }
370
371            // If we were able to find common type, use it
372            return serializedType ?? (factory.createVoidZero()); // Fallback is only hit if all union constituents are null/undefined/never
373        }
374
375        function equateSerializedTypeNodes(left: Expression, right: Expression): boolean {
376            return (
377                // temp vars used in fallback
378                isGeneratedIdentifier(left) ? isGeneratedIdentifier(right) :
379
380                // entity names
381                isIdentifier(left) ? isIdentifier(right)
382                    && left.escapedText === right.escapedText :
383
384                isPropertyAccessExpression(left) ? isPropertyAccessExpression(right)
385                    && equateSerializedTypeNodes(left.expression, right.expression)
386                    && equateSerializedTypeNodes(left.name, right.name) :
387
388                // `void 0`
389                isVoidExpression(left) ? isVoidExpression(right)
390                    && isNumericLiteral(left.expression) && left.expression.text === "0"
391                    && isNumericLiteral(right.expression) && right.expression.text === "0" :
392
393                // `"undefined"` or `"function"` in `typeof` checks
394                isStringLiteral(left) ? isStringLiteral(right)
395                    && left.text === right.text :
396
397                // used in `typeof` checks for fallback
398                isTypeOfExpression(left) ? isTypeOfExpression(right)
399                    && equateSerializedTypeNodes(left.expression, right.expression) :
400
401                // parens in `typeof` checks with temps
402                isParenthesizedExpression(left) ? isParenthesizedExpression(right)
403                    && equateSerializedTypeNodes(left.expression, right.expression) :
404
405                // conditionals used in fallback
406                isConditionalExpression(left) ? isConditionalExpression(right)
407                    && equateSerializedTypeNodes(left.condition, right.condition)
408                    && equateSerializedTypeNodes(left.whenTrue, right.whenTrue)
409                    && equateSerializedTypeNodes(left.whenFalse, right.whenFalse) :
410
411                // logical binary and assignments used in fallback
412                isBinaryExpression(left) ? isBinaryExpression(right)
413                    && left.operatorToken.kind === right.operatorToken.kind
414                    && equateSerializedTypeNodes(left.left, right.left)
415                    && equateSerializedTypeNodes(left.right, right.right) :
416
417                false
418            );
419        }
420
421        /**
422         * Serializes a TypeReferenceNode to an appropriate JS constructor value for use with decorator type metadata.
423         * @param node The type reference node.
424         */
425        function serializeTypeReferenceNode(node: TypeReferenceNode): SerializedTypeNode {
426            const kind = resolver.getTypeReferenceSerializationKind(node.typeName, currentNameScope ?? currentLexicalScope);
427            switch (kind) {
428                case TypeReferenceSerializationKind.Unknown:
429                    // From conditional type type reference that cannot be resolved is Similar to any or unknown
430                    if (findAncestor(node, n => n.parent && isConditionalTypeNode(n.parent) && (n.parent.trueType === n || n.parent.falseType === n))) {
431                        return factory.createIdentifier("Object");
432                    }
433
434                    const serialized = serializeEntityNameAsExpressionFallback(node.typeName);
435                    const temp = factory.createTempVariable(hoistVariableDeclaration);
436                    return factory.createConditionalExpression(
437                        factory.createTypeCheck(factory.createAssignment(temp, serialized), "function"),
438                        /*questionToken*/ undefined,
439                        temp,
440                        /*colonToken*/ undefined,
441                        factory.createIdentifier("Object")
442                    );
443
444                case TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue:
445                    return serializeEntityNameAsExpression(node.typeName);
446
447                case TypeReferenceSerializationKind.VoidNullableOrNeverType:
448                    return factory.createVoidZero();
449
450                case TypeReferenceSerializationKind.BigIntLikeType:
451                    return getGlobalConstructor("BigInt", ScriptTarget.ES2020);
452
453                case TypeReferenceSerializationKind.BooleanType:
454                    return factory.createIdentifier("Boolean");
455
456                case TypeReferenceSerializationKind.NumberLikeType:
457                    return factory.createIdentifier("Number");
458
459                case TypeReferenceSerializationKind.StringLikeType:
460                    return factory.createIdentifier("String");
461
462                case TypeReferenceSerializationKind.ArrayLikeType:
463                    return factory.createIdentifier("Array");
464
465                case TypeReferenceSerializationKind.ESSymbolType:
466                    return getGlobalConstructor("Symbol", ScriptTarget.ES2015);
467
468                case TypeReferenceSerializationKind.TypeWithCallSignature:
469                    return factory.createIdentifier("Function");
470
471                case TypeReferenceSerializationKind.Promise:
472                    return factory.createIdentifier("Promise");
473
474                case TypeReferenceSerializationKind.ObjectType:
475                    return factory.createIdentifier("Object");
476
477                default:
478                    return Debug.assertNever(kind);
479            }
480        }
481
482        /**
483         * Produces an expression that results in `right` if `left` is not undefined at runtime:
484         *
485         * ```
486         * typeof left !== "undefined" && right
487         * ```
488         *
489         * We use `typeof L !== "undefined"` (rather than `L !== undefined`) since `L` may not be declared.
490         * It's acceptable for this expression to result in `false` at runtime, as the result is intended to be
491         * further checked by any containing expression.
492         */
493        function createCheckedValue(left: Expression, right: Expression) {
494            return factory.createLogicalAnd(
495                factory.createStrictInequality(factory.createTypeOfExpression(left), factory.createStringLiteral("undefined")),
496                right
497            );
498        }
499
500        /**
501         * Serializes an entity name which may not exist at runtime, but whose access shouldn't throw
502         * @param node The entity name to serialize.
503         */
504        function serializeEntityNameAsExpressionFallback(node: EntityName): BinaryExpression {
505            if (node.kind === SyntaxKind.Identifier) {
506                // A -> typeof A !== "undefined" && A
507                const copied = serializeEntityNameAsExpression(node);
508                return createCheckedValue(copied, copied);
509            }
510            if (node.left.kind === SyntaxKind.Identifier) {
511                // A.B -> typeof A !== "undefined" && A.B
512                return createCheckedValue(serializeEntityNameAsExpression(node.left), serializeEntityNameAsExpression(node));
513            }
514            // A.B.C -> typeof A !== "undefined" && (_a = A.B) !== void 0 && _a.C
515            const left = serializeEntityNameAsExpressionFallback(node.left);
516            const temp = factory.createTempVariable(hoistVariableDeclaration);
517            return factory.createLogicalAnd(
518                factory.createLogicalAnd(
519                    left.left,
520                    factory.createStrictInequality(factory.createAssignment(temp, left.right), factory.createVoidZero())
521                ),
522                factory.createPropertyAccessExpression(temp, node.right)
523            );
524        }
525
526        /**
527         * Serializes an entity name as an expression for decorator type metadata.
528         * @param node The entity name to serialize.
529         */
530        function serializeEntityNameAsExpression(node: EntityName): SerializedEntityName {
531            switch (node.kind) {
532                case SyntaxKind.Identifier:
533                    // Create a clone of the name with a new parent, and treat it as if it were
534                    // a source tree node for the purposes of the checker.
535                    const name = setParent(setTextRange(parseNodeFactory.cloneNode(node), node), node.parent);
536                    name.original = undefined;
537                    setParent(name, getParseTreeNode(currentLexicalScope)); // ensure the parent is set to a parse tree node.
538                    return name;
539
540                case SyntaxKind.QualifiedName:
541                    return serializeQualifiedNameAsExpression(node);
542            }
543        }
544
545        /**
546         * Serializes an qualified name as an expression for decorator type metadata.
547         * @param node The qualified name to serialize.
548         */
549        function serializeQualifiedNameAsExpression(node: QualifiedName): SerializedEntityName {
550            return factory.createPropertyAccessExpression(serializeEntityNameAsExpression(node.left), node.right) as PropertyAccessEntityNameExpression;
551        }
552
553        function getGlobalConstructorWithFallback(name: string) {
554            return factory.createConditionalExpression(
555                factory.createTypeCheck(factory.createIdentifier(name), "function"),
556                /*questionToken*/ undefined,
557                factory.createIdentifier(name),
558                /*colonToken*/ undefined,
559                factory.createIdentifier("Object")
560            );
561        }
562
563        function getGlobalConstructor(name: string, minLanguageVersion: ScriptTarget): SerializedTypeNode {
564            return languageVersion < minLanguageVersion ?
565                getGlobalConstructorWithFallback(name) :
566                factory.createIdentifier(name);
567        }
568    }
569}