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}