1/*@internal*/ 2namespace ts { 3 const enum ClassPropertySubstitutionFlags { 4 /** 5 * Enables substitutions for class expressions with static fields 6 * which have initializers that reference the class name. 7 */ 8 ClassAliases = 1 << 0, 9 } 10 11 const enum PrivateIdentifierPlacement { 12 InstanceField 13 } 14 15 type PrivateIdentifierInfo = PrivateIdentifierInstanceField; 16 17 interface PrivateIdentifierInstanceField { 18 placement: PrivateIdentifierPlacement.InstanceField; 19 weakMapName: Identifier; 20 } 21 22 /** 23 * A mapping of private names to information needed for transformation. 24 */ 25 type PrivateIdentifierEnvironment = UnderscoreEscapedMap<PrivateIdentifierInfo>; 26 27 /** 28 * Transforms ECMAScript Class Syntax. 29 * TypeScript parameter property syntax is transformed in the TypeScript transformer. 30 * For now, this transforms public field declarations using TypeScript class semantics, 31 * where declarations are elided and initializers are transformed as assignments in the constructor. 32 * When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty. 33 */ 34 export function transformClassFields(context: TransformationContext) { 35 const { 36 factory, 37 hoistVariableDeclaration, 38 endLexicalEnvironment, 39 resumeLexicalEnvironment 40 } = context; 41 const resolver = context.getEmitResolver(); 42 const compilerOptions = context.getCompilerOptions(); 43 const languageVersion = getEmitScriptTarget(compilerOptions); 44 45 const shouldTransformPrivateFields = languageVersion < ScriptTarget.ESNext; 46 47 const previousOnSubstituteNode = context.onSubstituteNode; 48 context.onSubstituteNode = onSubstituteNode; 49 50 let enabledSubstitutions: ClassPropertySubstitutionFlags; 51 52 let classAliases: Identifier[]; 53 54 /** 55 * Tracks what computed name expressions originating from elided names must be inlined 56 * at the next execution site, in document order 57 */ 58 let pendingExpressions: Expression[] | undefined; 59 60 /** 61 * Tracks what computed name expression statements and static property initializers must be 62 * emitted at the next execution site, in document order (for decorated classes). 63 */ 64 let pendingStatements: Statement[] | undefined; 65 66 const privateIdentifierEnvironmentStack: (PrivateIdentifierEnvironment | undefined)[] = []; 67 let currentPrivateIdentifierEnvironment: PrivateIdentifierEnvironment | undefined; 68 69 return chainBundle(context, transformSourceFile); 70 71 function transformSourceFile(node: SourceFile) { 72 const options = context.getCompilerOptions(); 73 if (node.isDeclarationFile 74 || options.useDefineForClassFields && options.target === ScriptTarget.ESNext) { 75 return node; 76 } 77 const visited = visitEachChild(node, visitor, context); 78 addEmitHelpers(visited, context.readEmitHelpers()); 79 return visited; 80 } 81 82 function visitor(node: Node): VisitResult<Node> { 83 if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node; 84 85 switch (node.kind) { 86 case SyntaxKind.ClassExpression: 87 case SyntaxKind.ClassDeclaration: 88 return visitClassLike(node as ClassLikeDeclaration); 89 case SyntaxKind.PropertyDeclaration: 90 return visitPropertyDeclaration(node as PropertyDeclaration); 91 case SyntaxKind.VariableStatement: 92 return visitVariableStatement(node as VariableStatement); 93 case SyntaxKind.PropertyAccessExpression: 94 return visitPropertyAccessExpression(node as PropertyAccessExpression); 95 case SyntaxKind.PrefixUnaryExpression: 96 return visitPrefixUnaryExpression(node as PrefixUnaryExpression); 97 case SyntaxKind.PostfixUnaryExpression: 98 return visitPostfixUnaryExpression(node as PostfixUnaryExpression, /*valueIsDiscarded*/ false); 99 case SyntaxKind.CallExpression: 100 return visitCallExpression(node as CallExpression); 101 case SyntaxKind.BinaryExpression: 102 return visitBinaryExpression(node as BinaryExpression); 103 case SyntaxKind.PrivateIdentifier: 104 return visitPrivateIdentifier(node as PrivateIdentifier); 105 case SyntaxKind.ExpressionStatement: 106 return visitExpressionStatement(node as ExpressionStatement); 107 case SyntaxKind.ForStatement: 108 return visitForStatement(node as ForStatement); 109 case SyntaxKind.TaggedTemplateExpression: 110 return visitTaggedTemplateExpression(node as TaggedTemplateExpression); 111 } 112 return visitEachChild(node, visitor, context); 113 } 114 115 function visitorDestructuringTarget(node: Node): VisitResult<Node> { 116 switch (node.kind) { 117 case SyntaxKind.ObjectLiteralExpression: 118 case SyntaxKind.ArrayLiteralExpression: 119 return visitAssignmentPattern(node as AssignmentPattern); 120 default: 121 return visitor(node); 122 } 123 } 124 125 /** 126 * If we visit a private name, this means it is an undeclared private name. 127 * Replace it with an empty identifier to indicate a problem with the code. 128 */ 129 function visitPrivateIdentifier(node: PrivateIdentifier) { 130 if (!shouldTransformPrivateFields) { 131 return node; 132 } 133 return setOriginalNode(factory.createIdentifier(""), node); 134 } 135 136 /** 137 * Visits the members of a class that has fields. 138 * 139 * @param node The node to visit. 140 */ 141 function classElementVisitor(node: Node): VisitResult<Node> { 142 switch (node.kind) { 143 case SyntaxKind.Constructor: 144 // Constructors for classes using class fields are transformed in 145 // `visitClassDeclaration` or `visitClassExpression`. 146 return undefined; 147 148 case SyntaxKind.GetAccessor: 149 case SyntaxKind.SetAccessor: 150 case SyntaxKind.MethodDeclaration: 151 // Visit the name of the member (if it's a computed property name). 152 return visitEachChild(node, classElementVisitor, context); 153 154 case SyntaxKind.PropertyDeclaration: 155 return visitPropertyDeclaration(node as PropertyDeclaration); 156 157 case SyntaxKind.ComputedPropertyName: 158 return visitComputedPropertyName(node as ComputedPropertyName); 159 160 case SyntaxKind.SemicolonClassElement: 161 return node; 162 163 default: 164 return visitor(node); 165 } 166 } 167 168 function visitVariableStatement(node: VariableStatement) { 169 const savedPendingStatements = pendingStatements; 170 pendingStatements = []; 171 172 const visitedNode = visitEachChild(node, visitor, context); 173 const statement = some(pendingStatements) ? 174 [visitedNode, ...pendingStatements] : 175 visitedNode; 176 177 pendingStatements = savedPendingStatements; 178 return statement; 179 } 180 181 function visitComputedPropertyName(name: ComputedPropertyName) { 182 let node = visitEachChild(name, visitor, context); 183 if (some(pendingExpressions)) { 184 const expressions = pendingExpressions; 185 expressions.push(node.expression); 186 pendingExpressions = []; 187 node = factory.updateComputedPropertyName( 188 node, 189 factory.inlineExpressions(expressions) 190 ); 191 } 192 return node; 193 } 194 195 function visitPropertyDeclaration(node: PropertyDeclaration) { 196 Debug.assert(!some(node.decorators)); 197 if (!shouldTransformPrivateFields && isPrivateIdentifier(node.name)) { 198 // Initializer is elided as the field is initialized in transformConstructor. 199 return factory.updatePropertyDeclaration( 200 node, 201 /*decorators*/ undefined, 202 visitNodes(node.modifiers, visitor, isModifier), 203 node.name, 204 /*questionOrExclamationToken*/ undefined, 205 /*type*/ undefined, 206 /*initializer*/ undefined 207 ); 208 } 209 // Create a temporary variable to store a computed property name (if necessary). 210 // If it's not inlineable, then we emit an expression after the class which assigns 211 // the property name to the temporary variable. 212 const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer || !!context.getCompilerOptions().useDefineForClassFields); 213 if (expr && !isSimpleInlineableExpression(expr)) { 214 getPendingExpressions().push(expr); 215 } 216 return undefined; 217 } 218 219 function createPrivateIdentifierAccess(info: PrivateIdentifierInfo, receiver: Expression): Expression { 220 receiver = visitNode(receiver, visitor, isExpression); 221 switch (info.placement) { 222 case PrivateIdentifierPlacement.InstanceField: 223 return context.getEmitHelperFactory().createClassPrivateFieldGetHelper( 224 nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver), 225 info.weakMapName 226 ); 227 default: return Debug.fail("Unexpected private identifier placement"); 228 } 229 } 230 231 function visitPropertyAccessExpression(node: PropertyAccessExpression) { 232 if (shouldTransformPrivateFields && isPrivateIdentifier(node.name)) { 233 const privateIdentifierInfo = accessPrivateIdentifier(node.name); 234 if (privateIdentifierInfo) { 235 return setOriginalNode( 236 createPrivateIdentifierAccess(privateIdentifierInfo, node.expression), 237 node 238 ); 239 } 240 } 241 return visitEachChild(node, visitor, context); 242 } 243 244 function visitPrefixUnaryExpression(node: PrefixUnaryExpression) { 245 if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.operand)) { 246 const operator = node.operator === SyntaxKind.PlusPlusToken ? 247 SyntaxKind.PlusToken : node.operator === SyntaxKind.MinusMinusToken ? 248 SyntaxKind.MinusToken : undefined; 249 let info: PrivateIdentifierInfo | undefined; 250 if (operator && (info = accessPrivateIdentifier(node.operand.name))) { 251 const receiver = visitNode(node.operand.expression, visitor, isExpression); 252 const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); 253 254 const existingValue = factory.createPrefixUnaryExpression(SyntaxKind.PlusToken, createPrivateIdentifierAccess(info, readExpression)); 255 256 return setOriginalNode( 257 createPrivateIdentifierAssignment( 258 info, 259 initializeExpression || readExpression, 260 factory.createBinaryExpression(existingValue, operator, factory.createNumericLiteral(1)), 261 SyntaxKind.EqualsToken 262 ), 263 node 264 ); 265 } 266 } 267 return visitEachChild(node, visitor, context); 268 } 269 270 function visitPostfixUnaryExpression(node: PostfixUnaryExpression, valueIsDiscarded: boolean) { 271 if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.operand)) { 272 const operator = node.operator === SyntaxKind.PlusPlusToken ? 273 SyntaxKind.PlusToken : node.operator === SyntaxKind.MinusMinusToken ? 274 SyntaxKind.MinusToken : undefined; 275 let info: PrivateIdentifierInfo | undefined; 276 if (operator && (info = accessPrivateIdentifier(node.operand.name))) { 277 const receiver = visitNode(node.operand.expression, visitor, isExpression); 278 const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); 279 280 const existingValue = factory.createPrefixUnaryExpression(SyntaxKind.PlusToken, createPrivateIdentifierAccess(info, readExpression)); 281 282 // Create a temporary variable to store the value returned by the expression. 283 const returnValue = valueIsDiscarded ? undefined : factory.createTempVariable(hoistVariableDeclaration); 284 285 return setOriginalNode( 286 factory.inlineExpressions(compact<Expression>([ 287 createPrivateIdentifierAssignment( 288 info, 289 initializeExpression || readExpression, 290 factory.createBinaryExpression( 291 returnValue ? factory.createAssignment(returnValue, existingValue) : existingValue, 292 operator, 293 factory.createNumericLiteral(1) 294 ), 295 SyntaxKind.EqualsToken 296 ), 297 returnValue 298 ])), 299 node 300 ); 301 } 302 } 303 return visitEachChild(node, visitor, context); 304 } 305 306 function visitForStatement(node: ForStatement) { 307 if (node.incrementor && isPostfixUnaryExpression(node.incrementor)) { 308 return factory.updateForStatement( 309 node, 310 visitNode(node.initializer, visitor, isForInitializer), 311 visitNode(node.condition, visitor, isExpression), 312 visitPostfixUnaryExpression(node.incrementor, /*valueIsDiscarded*/ true), 313 visitNode(node.statement, visitor, isStatement) 314 ); 315 } 316 return visitEachChild(node, visitor, context); 317 } 318 319 function visitExpressionStatement(node: ExpressionStatement) { 320 if (isPostfixUnaryExpression(node.expression)) { 321 return factory.updateExpressionStatement(node, visitPostfixUnaryExpression(node.expression, /*valueIsDiscarded*/ true)); 322 } 323 return visitEachChild(node, visitor, context); 324 } 325 326 function createCopiableReceiverExpr(receiver: Expression): { readExpression: Expression; initializeExpression: Expression | undefined } { 327 const clone = nodeIsSynthesized(receiver) ? receiver : factory.cloneNode(receiver); 328 if (isSimpleInlineableExpression(receiver)) { 329 return { readExpression: clone, initializeExpression: undefined }; 330 } 331 const readExpression = factory.createTempVariable(hoistVariableDeclaration); 332 const initializeExpression = factory.createAssignment(readExpression, clone); 333 return { readExpression, initializeExpression }; 334 } 335 336 function visitCallExpression(node: CallExpression) { 337 if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.expression)) { 338 // Transform call expressions of private names to properly bind the `this` parameter. 339 const { thisArg, target } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion); 340 if (isCallChain(node)) { 341 return factory.updateCallChain( 342 node, 343 factory.createPropertyAccessChain(visitNode(target, visitor), node.questionDotToken, "call"), 344 /*questionDotToken*/ undefined, 345 /*typeArguments*/ undefined, 346 [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] 347 ); 348 } 349 return factory.updateCallExpression( 350 node, 351 factory.createPropertyAccessExpression(visitNode(target, visitor), "call"), 352 /*typeArguments*/ undefined, 353 [visitNode(thisArg, visitor, isExpression), ...visitNodes(node.arguments, visitor, isExpression)] 354 ); 355 } 356 return visitEachChild(node, visitor, context); 357 } 358 359 function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { 360 if (shouldTransformPrivateFields && isPrivateIdentifierPropertyAccessExpression(node.tag)) { 361 // Bind the `this` correctly for tagged template literals when the tag is a private identifier property access. 362 const { thisArg, target } = factory.createCallBinding(node.tag, hoistVariableDeclaration, languageVersion); 363 return factory.updateTaggedTemplateExpression( 364 node, 365 factory.createCallExpression( 366 factory.createPropertyAccessExpression(visitNode(target, visitor), "bind"), 367 /*typeArguments*/ undefined, 368 [visitNode(thisArg, visitor, isExpression)] 369 ), 370 /*typeArguments*/ undefined, 371 visitNode(node.template, visitor, isTemplateLiteral) 372 ); 373 } 374 return visitEachChild(node, visitor, context); 375 } 376 377 function visitBinaryExpression(node: BinaryExpression) { 378 if (shouldTransformPrivateFields) { 379 if (isDestructuringAssignment(node)) { 380 const savedPendingExpressions = pendingExpressions; 381 pendingExpressions = undefined!; 382 node = factory.updateBinaryExpression( 383 node, 384 visitNode(node.left, visitorDestructuringTarget), 385 node.operatorToken, 386 visitNode(node.right, visitor) 387 ); 388 const expr = some(pendingExpressions) ? 389 factory.inlineExpressions(compact([...pendingExpressions!, node])) : 390 node; 391 pendingExpressions = savedPendingExpressions; 392 return expr; 393 } 394 if (isAssignmentExpression(node) && isPrivateIdentifierPropertyAccessExpression(node.left)) { 395 const info = accessPrivateIdentifier(node.left.name); 396 if (info) { 397 return setOriginalNode( 398 createPrivateIdentifierAssignment(info, node.left.expression, node.right, node.operatorToken.kind), 399 node 400 ); 401 } 402 } 403 } 404 return visitEachChild(node, visitor, context); 405 } 406 407 function createPrivateIdentifierAssignment(info: PrivateIdentifierInfo, receiver: Expression, right: Expression, operator: AssignmentOperator) { 408 switch (info.placement) { 409 case PrivateIdentifierPlacement.InstanceField: { 410 return createPrivateIdentifierInstanceFieldAssignment(info, receiver, right, operator); 411 } 412 default: return Debug.fail("Unexpected private identifier placement"); 413 } 414 } 415 416 function createPrivateIdentifierInstanceFieldAssignment(info: PrivateIdentifierInstanceField, receiver: Expression, right: Expression, operator: AssignmentOperator) { 417 receiver = visitNode(receiver, visitor, isExpression); 418 right = visitNode(right, visitor, isExpression); 419 if (isCompoundAssignment(operator)) { 420 const { readExpression, initializeExpression } = createCopiableReceiverExpr(receiver); 421 return context.getEmitHelperFactory().createClassPrivateFieldSetHelper( 422 initializeExpression || readExpression, 423 info.weakMapName, 424 factory.createBinaryExpression( 425 context.getEmitHelperFactory().createClassPrivateFieldGetHelper(readExpression, info.weakMapName), 426 getNonAssignmentOperatorForCompoundAssignment(operator), 427 right 428 ) 429 ); 430 } 431 else { 432 return context.getEmitHelperFactory().createClassPrivateFieldSetHelper(receiver, info.weakMapName, right); 433 } 434 } 435 436 /** 437 * Set up the environment for a class. 438 */ 439 function visitClassLike(node: ClassLikeDeclaration) { 440 const savedPendingExpressions = pendingExpressions; 441 pendingExpressions = undefined; 442 if (shouldTransformPrivateFields) { 443 startPrivateIdentifierEnvironment(); 444 } 445 446 //TODO StructDeclaration 类型暂未处理 447 const result = isClassDeclaration(node) 448 ? visitClassDeclaration(node) 449 : isClassExpression(node) 450 ? visitClassExpression(node) 451 : []; 452 453 if (shouldTransformPrivateFields) { 454 endPrivateIdentifierEnvironment(); 455 } 456 pendingExpressions = savedPendingExpressions; 457 return result; 458 } 459 460 function doesClassElementNeedTransform(node: ClassElement) { 461 return isPropertyDeclaration(node) || (shouldTransformPrivateFields && node.name && isPrivateIdentifier(node.name)); 462 } 463 464 function visitClassDeclaration(node: ClassDeclaration) { 465 if (!forEach(node.members, doesClassElementNeedTransform)) { 466 return visitEachChild(node, visitor, context); 467 } 468 469 const extendsClauseElement = getEffectiveBaseTypeNode(node); 470 const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); 471 472 const statements: Statement[] = [ 473 factory.updateClassDeclaration( 474 node, 475 /*decorators*/ undefined, 476 node.modifiers, 477 node.name, 478 /*typeParameters*/ undefined, 479 visitNodes(node.heritageClauses, visitor, isHeritageClause), 480 transformClassMembers(node, isDerivedClass) 481 ) 482 ]; 483 484 // Write any pending expressions from elided or moved computed property names 485 if (some(pendingExpressions)) { 486 statements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); 487 } 488 489 // Emit static property assignment. Because classDeclaration is lexically evaluated, 490 // it is safe to emit static property assignment after classDeclaration 491 // From ES6 specification: 492 // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using 493 // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. 494 const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); 495 if (some(staticProperties)) { 496 addPropertyStatements(statements, staticProperties, factory.getInternalName(node)); 497 } 498 499 return statements; 500 } 501 502 function visitClassExpression(node: ClassExpression): Expression { 503 if (!forEach(node.members, doesClassElementNeedTransform)) { 504 return visitEachChild(node, visitor, context); 505 } 506 507 // If this class expression is a transformation of a decorated class declaration, 508 // then we want to output the pendingExpressions as statements, not as inlined 509 // expressions with the class statement. 510 // 511 // In this case, we use pendingStatements to produce the same output as the 512 // class declaration transformation. The VariableStatement visitor will insert 513 // these statements after the class expression variable statement. 514 const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node)); 515 516 const staticProperties = getProperties(node, /*requireInitializer*/ true, /*isStatic*/ true); 517 const extendsClauseElement = getEffectiveBaseTypeNode(node); 518 const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); 519 520 const classExpression = factory.updateClassExpression( 521 node, 522 visitNodes(node.decorators, visitor, isDecorator), 523 node.modifiers, 524 node.name, 525 /*typeParameters*/ undefined, 526 visitNodes(node.heritageClauses, visitor, isHeritageClause), 527 transformClassMembers(node, isDerivedClass) 528 ); 529 530 if (some(staticProperties) || some(pendingExpressions)) { 531 if (isDecoratedClassDeclaration) { 532 Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); 533 534 // Write any pending expressions from elided or moved computed property names 535 if (pendingStatements && pendingExpressions && some(pendingExpressions)) { 536 pendingStatements.push(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))); 537 } 538 539 if (pendingStatements && some(staticProperties)) { 540 addPropertyStatements(pendingStatements, staticProperties, factory.getInternalName(node)); 541 } 542 return classExpression; 543 } 544 else { 545 const expressions: Expression[] = []; 546 const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; 547 const temp = factory.createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); 548 if (isClassWithConstructorReference) { 549 // record an alias as the class name is not in scope for statics. 550 enableSubstitutionForClassAliases(); 551 const alias = factory.cloneNode(temp) as GeneratedIdentifier; 552 alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; 553 classAliases[getOriginalNodeId(node)] = alias; 554 } 555 556 // To preserve the behavior of the old emitter, we explicitly indent 557 // the body of a class with static initializers. 558 setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); 559 expressions.push(startOnNewLine(factory.createAssignment(temp, classExpression))); 560 // Add any pending expressions leftover from elided or relocated computed property names 561 addRange(expressions, map(pendingExpressions, startOnNewLine)); 562 addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); 563 expressions.push(startOnNewLine(temp)); 564 565 return factory.inlineExpressions(expressions); 566 } 567 } 568 569 return classExpression; 570 } 571 572 function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { 573 if (shouldTransformPrivateFields) { 574 // Declare private names. 575 for (const member of node.members) { 576 if (isPrivateIdentifierPropertyDeclaration(member)) { 577 addPrivateIdentifierToEnvironment(member.name); 578 } 579 } 580 } 581 582 const members: ClassElement[] = []; 583 const constructor = transformConstructor(node, isDerivedClass); 584 if (constructor) { 585 members.push(constructor); 586 } 587 addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); 588 return setTextRange(factory.createNodeArray(members), /*location*/ node.members); 589 } 590 591 function isPropertyDeclarationThatRequiresConstructorStatement(member: ClassElement): member is PropertyDeclaration { 592 if (!isPropertyDeclaration(member) || hasStaticModifier(member) || hasSyntacticModifier(getOriginalNode(member), ModifierFlags.Abstract)) { 593 return false; 594 } 595 if (context.getCompilerOptions().useDefineForClassFields) { 596 // If we are using define semantics and targeting ESNext or higher, 597 // then we don't need to transform any class properties. 598 return languageVersion < ScriptTarget.ESNext; 599 } 600 return isInitializedProperty(member) || shouldTransformPrivateFields && isPrivateIdentifierPropertyDeclaration(member); 601 } 602 603 function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { 604 const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); 605 const properties = node.members.filter(isPropertyDeclarationThatRequiresConstructorStatement); 606 if (!some(properties)) { 607 return constructor; 608 } 609 const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); 610 const body = transformConstructorBody(node, constructor, isDerivedClass); 611 if (!body) { 612 return undefined; 613 } 614 return startOnNewLine( 615 setOriginalNode( 616 setTextRange( 617 factory.createConstructorDeclaration( 618 /*decorators*/ undefined, 619 /*modifiers*/ undefined, 620 parameters ?? [], 621 body 622 ), 623 constructor || node 624 ), 625 constructor 626 ) 627 ); 628 } 629 630 function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { 631 const useDefineForClassFields = context.getCompilerOptions().useDefineForClassFields; 632 let properties = getProperties(node, /*requireInitializer*/ false, /*isStatic*/ false); 633 if (!useDefineForClassFields) { 634 properties = filter(properties, property => !!property.initializer || isPrivateIdentifier(property.name)); 635 } 636 637 638 // Only generate synthetic constructor when there are property initializers to move. 639 if (!constructor && !some(properties)) { 640 return visitFunctionBody(/*node*/ undefined, visitor, context); 641 } 642 643 resumeLexicalEnvironment(); 644 645 let indexOfFirstStatement = 0; 646 let statements: Statement[] = []; 647 648 if (!constructor && isDerivedClass) { 649 // Add a synthetic `super` call: 650 // 651 // super(...arguments); 652 // 653 statements.push( 654 factory.createExpressionStatement( 655 factory.createCallExpression( 656 factory.createSuper(), 657 /*typeArguments*/ undefined, 658 [factory.createSpreadElement(factory.createIdentifier("arguments"))] 659 ) 660 ) 661 ); 662 } 663 664 if (constructor) { 665 indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor); 666 } 667 // Add the property initializers. Transforms this: 668 // 669 // public x = 1; 670 // 671 // Into this: 672 // 673 // constructor() { 674 // this.x = 1; 675 // } 676 // 677 if (constructor?.body) { 678 let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); 679 if (afterParameterProperties === -1) { 680 afterParameterProperties = constructor.body.statements.length; 681 } 682 if (afterParameterProperties > indexOfFirstStatement) { 683 if (!useDefineForClassFields) { 684 addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); 685 } 686 indexOfFirstStatement = afterParameterProperties; 687 } 688 } 689 addPropertyStatements(statements, properties, factory.createThis()); 690 691 // Add existing statements, skipping the initial super call. 692 if (constructor) { 693 addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); 694 } 695 696 statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); 697 698 return setTextRange( 699 factory.createBlock( 700 setTextRange( 701 factory.createNodeArray(statements), 702 /*location*/ constructor ? constructor.body!.statements : node.members 703 ), 704 /*multiLine*/ true 705 ), 706 /*location*/ constructor ? constructor.body : undefined 707 ); 708 } 709 710 /** 711 * Generates assignment statements for property initializers. 712 * 713 * @param properties An array of property declarations to transform. 714 * @param receiver The receiver on which each property should be assigned. 715 */ 716 function addPropertyStatements(statements: Statement[], properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { 717 for (const property of properties) { 718 const expression = transformProperty(property, receiver); 719 if (!expression) { 720 continue; 721 } 722 const statement = factory.createExpressionStatement(expression); 723 setSourceMapRange(statement, moveRangePastModifiers(property)); 724 setCommentRange(statement, property); 725 setOriginalNode(statement, property); 726 statements.push(statement); 727 } 728 } 729 730 /** 731 * Generates assignment expressions for property initializers. 732 * 733 * @param properties An array of property declarations to transform. 734 * @param receiver The receiver on which each property should be assigned. 735 */ 736 function generateInitializedPropertyExpressions(properties: readonly PropertyDeclaration[], receiver: LeftHandSideExpression) { 737 const expressions: Expression[] = []; 738 for (const property of properties) { 739 const expression = transformProperty(property, receiver); 740 if (!expression) { 741 continue; 742 } 743 startOnNewLine(expression); 744 setSourceMapRange(expression, moveRangePastModifiers(property)); 745 setCommentRange(expression, property); 746 setOriginalNode(expression, property); 747 expressions.push(expression); 748 } 749 750 return expressions; 751 } 752 753 /** 754 * Transforms a property initializer into an assignment statement. 755 * 756 * @param property The property declaration. 757 * @param receiver The object receiving the property assignment. 758 */ 759 function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { 760 // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) 761 const emitAssignment = !context.getCompilerOptions().useDefineForClassFields; 762 const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) 763 ? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name)) 764 : property.name; 765 766 if (shouldTransformPrivateFields && isPrivateIdentifier(propertyName)) { 767 const privateIdentifierInfo = accessPrivateIdentifier(propertyName); 768 if (privateIdentifierInfo) { 769 switch (privateIdentifierInfo.placement) { 770 case PrivateIdentifierPlacement.InstanceField: { 771 return createPrivateInstanceFieldInitializer( 772 receiver, 773 visitNode(property.initializer, visitor, isExpression), 774 privateIdentifierInfo.weakMapName 775 ); 776 } 777 } 778 } 779 else { 780 Debug.fail("Undeclared private name for property declaration."); 781 } 782 } 783 if (isPrivateIdentifier(propertyName) && !property.initializer) { 784 return undefined; 785 } 786 787 if (isPrivateIdentifier(propertyName) && !property.initializer) { 788 return undefined; 789 } 790 791 const propertyOriginalNode = getOriginalNode(property); 792 if (hasSyntacticModifier(propertyOriginalNode, ModifierFlags.Abstract)) { 793 return undefined; 794 } 795 const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero() 796 : isParameterPropertyDeclaration(propertyOriginalNode, propertyOriginalNode.parent) && isIdentifier(propertyName) ? propertyName 797 : factory.createVoidZero(); 798 799 if (emitAssignment || isPrivateIdentifier(propertyName)) { 800 const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName); 801 return factory.createAssignment(memberAccess, initializer); 802 } 803 else { 804 const name = isComputedPropertyName(propertyName) ? propertyName.expression 805 : isIdentifier(propertyName) ? factory.createStringLiteral(unescapeLeadingUnderscores(propertyName.escapedText)) 806 : propertyName; 807 const descriptor = factory.createPropertyDescriptor({ value: initializer, configurable: true, writable: true, enumerable: true }); 808 return factory.createObjectDefinePropertyCall(receiver, name, descriptor); 809 } 810 } 811 812 function enableSubstitutionForClassAliases() { 813 if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { 814 enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; 815 816 // We need to enable substitutions for identifiers. This allows us to 817 // substitute class names inside of a class declaration. 818 context.enableSubstitution(SyntaxKind.Identifier); 819 820 // Keep track of class aliases. 821 classAliases = []; 822 } 823 } 824 825 /** 826 * Hooks node substitutions. 827 * 828 * @param hint The context for the emitter. 829 * @param node The node to substitute. 830 */ 831 function onSubstituteNode(hint: EmitHint, node: Node) { 832 node = previousOnSubstituteNode(hint, node); 833 if (hint === EmitHint.Expression) { 834 return substituteExpression(node as Expression); 835 } 836 return node; 837 } 838 839 function substituteExpression(node: Expression) { 840 switch (node.kind) { 841 case SyntaxKind.Identifier: 842 return substituteExpressionIdentifier(node as Identifier); 843 } 844 return node; 845 } 846 847 function substituteExpressionIdentifier(node: Identifier): Expression { 848 return trySubstituteClassAlias(node) || node; 849 } 850 851 function trySubstituteClassAlias(node: Identifier): Expression | undefined { 852 if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { 853 if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { 854 // Due to the emit for class decorators, any reference to the class from inside of the class body 855 // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind 856 // behavior of class names in ES6. 857 // Also, when emitting statics for class expressions, we must substitute a class alias for 858 // constructor references in static property initializers. 859 const declaration = resolver.getReferencedValueDeclaration(node); 860 if (declaration) { 861 const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 862 if (classAlias) { 863 const clone = factory.cloneNode(classAlias); 864 setSourceMapRange(clone, node); 865 setCommentRange(clone, node); 866 return clone; 867 } 868 } 869 } 870 } 871 872 return undefined; 873 } 874 875 876 /** 877 * If the name is a computed property, this function transforms it, then either returns an expression which caches the 878 * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations 879 * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) 880 */ 881 function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { 882 if (isComputedPropertyName(name)) { 883 const expression = visitNode(name.expression, visitor, isExpression); 884 const innerExpression = skipPartiallyEmittedExpressions(expression); 885 const inlinable = isSimpleInlineableExpression(innerExpression); 886 const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); 887 if (!alreadyTransformed && !inlinable && shouldHoist) { 888 const generatedName = factory.getGeneratedNameForNode(name); 889 hoistVariableDeclaration(generatedName); 890 return factory.createAssignment(generatedName, expression); 891 } 892 return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; 893 } 894 } 895 896 function startPrivateIdentifierEnvironment() { 897 privateIdentifierEnvironmentStack.push(currentPrivateIdentifierEnvironment); 898 currentPrivateIdentifierEnvironment = undefined; 899 } 900 901 function endPrivateIdentifierEnvironment() { 902 currentPrivateIdentifierEnvironment = privateIdentifierEnvironmentStack.pop(); 903 } 904 905 function getPrivateIdentifierEnvironment() { 906 return currentPrivateIdentifierEnvironment || (currentPrivateIdentifierEnvironment = new Map()); 907 } 908 909 function getPendingExpressions() { 910 return pendingExpressions || (pendingExpressions = []); 911 } 912 913 function addPrivateIdentifierToEnvironment(name: PrivateIdentifier) { 914 const text = getTextOfPropertyName(name) as string; 915 const weakMapName = factory.createUniqueName("_" + text.substring(1), GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.ReservedInNestedScopes); 916 hoistVariableDeclaration(weakMapName); 917 getPrivateIdentifierEnvironment().set(name.escapedText, { placement: PrivateIdentifierPlacement.InstanceField, weakMapName }); 918 getPendingExpressions().push( 919 factory.createAssignment( 920 weakMapName, 921 factory.createNewExpression( 922 factory.createIdentifier("WeakMap"), 923 /*typeArguments*/ undefined, 924 [] 925 ) 926 ) 927 ); 928 } 929 930 function accessPrivateIdentifier(name: PrivateIdentifier) { 931 if (currentPrivateIdentifierEnvironment) { 932 const info = currentPrivateIdentifierEnvironment.get(name.escapedText); 933 if (info) { 934 return info; 935 } 936 } 937 for (let i = privateIdentifierEnvironmentStack.length - 1; i >= 0; --i) { 938 const env = privateIdentifierEnvironmentStack[i]; 939 if (!env) { 940 continue; 941 } 942 const info = env.get(name.escapedText); 943 if (info) { 944 return info; 945 } 946 } 947 return undefined; 948 } 949 950 951 function wrapPrivateIdentifierForDestructuringTarget(node: PrivateIdentifierPropertyAccessExpression) { 952 const parameter = factory.getGeneratedNameForNode(node); 953 const info = accessPrivateIdentifier(node.name); 954 if (!info) { 955 return visitEachChild(node, visitor, context); 956 } 957 let receiver = node.expression; 958 // We cannot copy `this` or `super` into the function because they will be bound 959 // differently inside the function. 960 if (isThisProperty(node) || isSuperProperty(node) || !isSimpleCopiableExpression(node.expression)) { 961 receiver = factory.createTempVariable(hoistVariableDeclaration, /*reservedInNestedScopes*/ true); 962 getPendingExpressions().push(factory.createBinaryExpression(receiver, SyntaxKind.EqualsToken, node.expression)); 963 } 964 return factory.createPropertyAccessExpression( 965 // Explicit parens required because of v8 regression (https://bugs.chromium.org/p/v8/issues/detail?id=9560) 966 factory.createParenthesizedExpression( 967 factory.createObjectLiteralExpression([ 968 factory.createSetAccessorDeclaration( 969 /*decorators*/ undefined, 970 /*modifiers*/ undefined, 971 "value", 972 [factory.createParameterDeclaration( 973 /*decorators*/ undefined, 974 /*modifiers*/ undefined, 975 /*dotDotDotToken*/ undefined, 976 parameter, 977 /*questionToken*/ undefined, 978 /*type*/ undefined, 979 /*initializer*/ undefined 980 )], 981 factory.createBlock( 982 [factory.createExpressionStatement( 983 createPrivateIdentifierAssignment( 984 info, 985 receiver, 986 parameter, 987 SyntaxKind.EqualsToken 988 ) 989 )] 990 ) 991 ) 992 ]) 993 ), 994 "value" 995 ); 996 } 997 998 function visitArrayAssignmentTarget(node: BindingOrAssignmentElement) { 999 const target = getTargetOfBindingOrAssignmentElement(node); 1000 if (target && isPrivateIdentifierPropertyAccessExpression(target)) { 1001 const wrapped = wrapPrivateIdentifierForDestructuringTarget(target); 1002 if (isAssignmentExpression(node)) { 1003 return factory.updateBinaryExpression( 1004 node, 1005 wrapped, 1006 node.operatorToken, 1007 visitNode(node.right, visitor, isExpression) 1008 ); 1009 } 1010 else if (isSpreadElement(node)) { 1011 return factory.updateSpreadElement(node, wrapped); 1012 } 1013 else { 1014 return wrapped; 1015 } 1016 } 1017 return visitNode(node, visitorDestructuringTarget); 1018 } 1019 1020 function visitObjectAssignmentTarget(node: ObjectLiteralElementLike) { 1021 if (isPropertyAssignment(node)) { 1022 const target = getTargetOfBindingOrAssignmentElement(node); 1023 if (target && isPrivateIdentifierPropertyAccessExpression(target)) { 1024 const initializer = getInitializerOfBindingOrAssignmentElement(node); 1025 const wrapped = wrapPrivateIdentifierForDestructuringTarget(target); 1026 return factory.updatePropertyAssignment( 1027 node, 1028 visitNode(node.name, visitor), 1029 initializer ? factory.createAssignment(wrapped, visitNode(initializer, visitor)) : wrapped, 1030 ); 1031 } 1032 return factory.updatePropertyAssignment( 1033 node, 1034 visitNode(node.name, visitor), 1035 visitNode(node.initializer, visitorDestructuringTarget) 1036 ); 1037 } 1038 return visitNode(node, visitor); 1039 } 1040 1041 1042 function visitAssignmentPattern(node: AssignmentPattern) { 1043 if (isArrayLiteralExpression(node)) { 1044 // Transforms private names in destructuring assignment array bindings. 1045 // 1046 // Source: 1047 // ([ this.#myProp ] = [ "hello" ]); 1048 // 1049 // Transformation: 1050 // [ { set value(x) { this.#myProp = x; } }.value ] = [ "hello" ]; 1051 return factory.updateArrayLiteralExpression( 1052 node, 1053 visitNodes(node.elements, visitArrayAssignmentTarget, isExpression) 1054 ); 1055 } 1056 else { 1057 // Transforms private names in destructuring assignment object bindings. 1058 // 1059 // Source: 1060 // ({ stringProperty: this.#myProp } = { stringProperty: "hello" }); 1061 // 1062 // Transformation: 1063 // ({ stringProperty: { set value(x) { this.#myProp = x; } }.value }) = { stringProperty: "hello" }; 1064 return factory.updateObjectLiteralExpression( 1065 node, 1066 visitNodes(node.properties, visitObjectAssignmentTarget, isObjectLiteralElementLike) 1067 ); 1068 } 1069 } 1070 } 1071 1072 function createPrivateInstanceFieldInitializer(receiver: LeftHandSideExpression, initializer: Expression | undefined, weakMapName: Identifier) { 1073 return factory.createCallExpression( 1074 factory.createPropertyAccessExpression(weakMapName, "set"), 1075 /*typeArguments*/ undefined, 1076 [receiver, initializer || factory.createVoidZero()] 1077 ); 1078 } 1079} 1080