From 6178abe5bb98f4b4a9b213bdd0e9904bc9c6f3a2 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 10 Jun 2019 13:29:49 -0700 Subject: [PATCH 1/2] Revert "Revert "Move class property transformation into new transformer. (#30467)"" This reverts commit 53467ae4a4edb37c259b0b4e61972a90cd6c5587. --- src/compiler/binder.ts | 21 +- src/compiler/transformer.ts | 1 + src/compiler/transformers/classFields.ts | 491 +++++++++++++++++ src/compiler/transformers/ts.ts | 493 ++++++------------ src/compiler/transformers/utilities.ts | 80 +++ src/compiler/tsconfig.json | 1 + src/compiler/types.ts | 1 + ...tPrivateSymbolCausesVarDeclarationEmit2.js | 4 +- .../decoratorsOnComputedProperties.js | 145 +++--- .../reference/systemModuleTargetES6.js | 2 +- ...arationEmitUniqueSymbolPartialStatement.js | 2 +- 11 files changed, 807 insertions(+), 434 deletions(-) create mode 100644 src/compiler/transformers/classFields.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4540a4409d65b..660b19a587276 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3229,8 +3229,7 @@ namespace ts { // A ClassDeclaration is ES6 syntax. transformFlags = subtreeFlags | TransformFlags.AssertES2015; - // A class with a parameter property assignment, property initializer, computed property name, or decorator is - // TypeScript syntax. + // A class with a parameter property assignment or decorator is TypeScript syntax. // An exported declaration may be TypeScript syntax, but is handled by the visitor // for a namespace declaration. if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax) @@ -3247,8 +3246,7 @@ namespace ts { // A ClassExpression is ES6 syntax. let transformFlags = subtreeFlags | TransformFlags.AssertES2015; - // A class with a parameter property assignment, property initializer, or decorator is - // TypeScript syntax. + // A class with a parameter property assignment or decorator is TypeScript syntax. if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax || node.typeParameters) { transformFlags |= TransformFlags.AssertTypeScript; @@ -3338,7 +3336,6 @@ namespace ts { || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.typeParameters || node.type - || (node.name && isComputedPropertyName(node.name)) // While computed method names aren't typescript, the TS transform must visit them to emit property declarations correctly || !node.body) { transformFlags |= TransformFlags.AssertTypeScript; } @@ -3369,7 +3366,6 @@ namespace ts { if (node.decorators || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type - || (node.name && isComputedPropertyName(node.name)) // While computed accessor names aren't typescript, the TS transform must visit them to emit property declarations correctly || !node.body) { transformFlags |= TransformFlags.AssertTypeScript; } @@ -3384,12 +3380,15 @@ namespace ts { } function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) { - // A PropertyDeclaration is TypeScript syntax. - let transformFlags = subtreeFlags | TransformFlags.AssertTypeScript; + let transformFlags = subtreeFlags | TransformFlags.ContainsClassFields; + + // Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax. + if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type) { + transformFlags |= TransformFlags.AssertTypeScript; + } - // If the PropertyDeclaration has an initializer or a computed name, we need to inform its ancestor - // so that it handle the transformation. - if (node.initializer || isComputedPropertyName(node.name)) { + // Hoisted variables related to class properties should live within the TypeScript class wrapper. + if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) { transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax; } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 1c1df9bb0c210..d520571e6dfc4 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -44,6 +44,7 @@ namespace ts { addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory)); transformers.push(transformTypeScript); + transformers.push(transformClassFields); if (jsx === JsxEmit.React) { transformers.push(transformJsx); diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts new file mode 100644 index 0000000000000..575e1d1334696 --- /dev/null +++ b/src/compiler/transformers/classFields.ts @@ -0,0 +1,491 @@ +/*@internal*/ +namespace ts { + const enum ClassPropertySubstitutionFlags { + /** + * Enables substitutions for class expressions with static fields + * which have initializers that reference the class name. + */ + ClassAliases = 1 << 0, + } + /** + * Transforms ECMAScript Class Syntax. + * TypeScript parameter property syntax is transformed in the TypeScript transformer. + * For now, this transforms public field declarations using TypeScript class semantics + * (where the declarations get elided and initializers are transformed as assignments in the constructor). + * Eventually, this transform will change to the ECMAScript semantics (with Object.defineProperty). + */ + export function transformClassFields(context: TransformationContext) { + const { + hoistVariableDeclaration, + endLexicalEnvironment, + resumeLexicalEnvironment + } = context; + const resolver = context.getEmitResolver(); + + const previousOnSubstituteNode = context.onSubstituteNode; + context.onSubstituteNode = onSubstituteNode; + + let enabledSubstitutions: ClassPropertySubstitutionFlags; + + let classAliases: Identifier[]; + + /** + * Tracks what computed name expressions originating from elided names must be inlined + * at the next execution site, in document order + */ + let pendingExpressions: Expression[] | undefined; + + /** + * Tracks what computed name expression statements and static property initializers must be + * emitted at the next execution site, in document order (for decorated classes). + */ + let pendingStatements: Statement[] | undefined; + + return chainBundle(transformSourceFile); + + function transformSourceFile(node: SourceFile) { + if (node.isDeclarationFile) { + return node; + } + const visited = visitEachChild(node, visitor, context); + addEmitHelpers(visited, context.readEmitHelpers()); + return visited; + } + + function visitor(node: Node): VisitResult { + if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node; + + switch (node.kind) { + case SyntaxKind.ClassExpression: + return visitClassExpression(node as ClassExpression); + case SyntaxKind.ClassDeclaration: + return visitClassDeclaration(node as ClassDeclaration); + case SyntaxKind.VariableStatement: + return visitVariableStatement(node as VariableStatement); + } + return visitEachChild(node, visitor, context); + } + + /** + * Visits the members of a class that has fields. + * + * @param node The node to visit. + */ + function classElementVisitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.Constructor: + // Constructors for classes using class fields are transformed in + // `visitClassDeclaration` or `visitClassExpression`. + return undefined; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodDeclaration: + // Visit the name of the member (if it's a computed property name). + return visitEachChild(node, classElementVisitor, context); + + case SyntaxKind.PropertyDeclaration: + return visitPropertyDeclaration(node as PropertyDeclaration); + + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node as ComputedPropertyName); + + default: + return node; + } + } + + function visitVariableStatement(node: VariableStatement) { + const savedPendingStatements = pendingStatements; + pendingStatements = []; + + const visitedNode = visitEachChild(node, visitor, context); + const statement = some(pendingStatements) ? + [visitedNode, ...pendingStatements] : + visitedNode; + + pendingStatements = savedPendingStatements; + return statement; + } + + function visitComputedPropertyName(name: ComputedPropertyName) { + let node = visitEachChild(name, visitor, context); + if (some(pendingExpressions)) { + const expressions = pendingExpressions; + expressions.push(name.expression); + pendingExpressions = []; + node = updateComputedPropertyName( + node, + inlineExpressions(expressions) + ); + } + return node; + } + + function visitPropertyDeclaration(node: PropertyDeclaration) { + Debug.assert(!some(node.decorators)); + // Create a temporary variable to store a computed property name (if necessary). + // If it's not inlineable, then we emit an expression after the class which assigns + // the property name to the temporary variable. + const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer); + if (expr && !isSimpleInlineableExpression(expr)) { + (pendingExpressions || (pendingExpressions = [])).push(expr); + } + return undefined; + } + + function visitClassDeclaration(node: ClassDeclaration) { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + + const statements: Statement[] = [ + updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + transformClassMembers(node, isDerivedClass) + ) + ]; + + // Write any pending expressions from elided or moved computed property names + if (some(pendingExpressions)) { + statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!))); + } + pendingExpressions = savedPendingExpressions; + + // Emit static property assignment. Because classDeclaration is lexically evaluated, + // it is safe to emit static property assignment after classDeclaration + // From ES6 specification: + // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using + // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + if (some(staticProperties)) { + addInitializedPropertyStatements(statements, staticProperties, getInternalName(node)); + } + + return statements; + } + + function visitClassExpression(node: ClassExpression): Expression { + if (!forEach(node.members, isPropertyDeclaration)) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; + pendingExpressions = undefined; + + // If this class expression is a transformation of a decorated class declaration, + // then we want to output the pendingExpressions as statements, not as inlined + // expressions with the class statement. + // + // In this case, we use pendingStatements to produce the same output as the + // class declaration transformation. The VariableStatement visitor will insert + // these statements after the class expression variable statement. + const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node)); + + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const extendsClauseElement = getEffectiveBaseTypeNode(node); + const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); + + const classExpression = updateClassExpression( + node, + node.modifiers, + node.name, + node.typeParameters, + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node, isDerivedClass) + ); + + if (some(staticProperties) || some(pendingExpressions)) { + if (isDecoratedClassDeclaration) { + Debug.assertDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); + + // Write any pending expressions from elided or moved computed property names + if (pendingStatements && pendingExpressions && some(pendingExpressions)) { + pendingStatements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); + } + pendingExpressions = savedPendingExpressions; + + if (pendingStatements && some(staticProperties)) { + addInitializedPropertyStatements(pendingStatements, staticProperties, getInternalName(node)); + } + return classExpression; + } + else { + const expressions: Expression[] = []; + const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; + const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); + if (isClassWithConstructorReference) { + // record an alias as the class name is not in scope for statics. + enableSubstitutionForClassAliases(); + const alias = getSynthesizedClone(temp); + alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; + classAliases[getOriginalNodeId(node)] = alias; + } + + // To preserve the behavior of the old emitter, we explicitly indent + // the body of a class with static initializers. + setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); + expressions.push(startOnNewLine(createAssignment(temp, classExpression))); + // Add any pending expressions leftover from elided or relocated computed property names + addRange(expressions, map(pendingExpressions, startOnNewLine)); + addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); + expressions.push(startOnNewLine(temp)); + + pendingExpressions = savedPendingExpressions; + return inlineExpressions(expressions); + } + } + + pendingExpressions = savedPendingExpressions; + return classExpression; + } + + function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const members: ClassElement[] = []; + const constructor = transformConstructor(node, isDerivedClass); + if (constructor) { + members.push(constructor); + } + addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return setTextRange(createNodeArray(members), /*location*/ node.members); + } + + function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration); + const containsPropertyInitializer = forEach(node.members, isInitializedProperty); + if (!containsPropertyInitializer) { + return constructor; + } + const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context); + const body = transformConstructorBody(node, constructor, isDerivedClass); + if (!body) { + return undefined; + } + return startOnNewLine( + setOriginalNode( + setTextRange( + createConstructor( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameters, + body + ), + constructor || node + ), + constructor + ) + ); + } + + function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + const properties = getInitializedProperties(node, /*isStatic*/ false); + + // Only generate synthetic constructor when there are property initializers to move. + if (!constructor && !some(properties)) { + return visitFunctionBody(/*node*/ undefined, visitor, context); + } + + resumeLexicalEnvironment(); + + let indexOfFirstStatement = 0; + let statements: Statement[] = []; + + if (!constructor && isDerivedClass) { + // Add a synthetic `super` call: + // + // super(...arguments); + // + statements.push( + createExpressionStatement( + createCall( + createSuper(), + /*typeArguments*/ undefined, + [createSpread(createIdentifier("arguments"))] + ) + ) + ); + } + + if (constructor) { + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); + } + + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + addInitializedPropertyStatements(statements, properties, createThis()); + + // Add existing statements, skipping the initial super call. + if (constructor) { + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); + } + + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + + return setTextRange( + createBlock( + setTextRange( + createNodeArray(statements), + /*location*/ constructor ? constructor.body!.statements : node.members + ), + /*multiLine*/ true + ), + /*location*/ constructor ? constructor.body : undefined + ); + } + + /** + * Generates assignment statements for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { + for (const property of properties) { + const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + setOriginalNode(statement, property); + statements.push(statement); + } + } + + /** + * Generates assignment expressions for property initializers. + * + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of properties) { + const expression = transformInitializedProperty(property, receiver); + startOnNewLine(expression); + setSourceMapRange(expression, moveRangePastModifiers(property)); + setCommentRange(expression, property); + setOriginalNode(expression, property); + expressions.push(expression); + } + + return expressions; + } + + /** + * Transforms a property initializer into an assignment statement. + * + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { + // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) + const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) + ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) + : property.name; + const initializer = visitNode(property.initializer!, visitor, isExpression); + const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); + + return createAssignment(memberAccess, initializer); + } + + function enableSubstitutionForClassAliases() { + if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) { + enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases; + + // We need to enable substitutions for identifiers. This allows us to + // substitute class names inside of a class declaration. + context.enableSubstitution(SyntaxKind.Identifier); + + // Keep track of class aliases. + classAliases = []; + } + } + + /** + * Hooks node substitutions. + * + * @param hint The context for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(hint: EmitHint, node: Node) { + node = previousOnSubstituteNode(hint, node); + if (hint === EmitHint.Expression) { + return substituteExpression(node as Expression); + } + return node; + } + + function substituteExpression(node: Expression) { + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node as Identifier); + } + return node; + } + + function substituteExpressionIdentifier(node: Identifier): Expression { + return trySubstituteClassAlias(node) || node; + } + + function trySubstituteClassAlias(node: Identifier): Expression | undefined { + if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + // Also, when emitting statics for class expressions, we must substitute a class alias for + // constructor references in static property initializers. + const declaration = resolver.getReferencedValueDeclaration(node); + if (declaration) { + const classAlias = classAliases[declaration.id!]; // TODO: GH#18217 + if (classAlias) { + const clone = getSynthesizedClone(classAlias); + setSourceMapRange(clone, node); + setCommentRange(clone, node); + return clone; + } + } + } + } + + return undefined; + } + + + /** + * If the name is a computed property, this function transforms it, then either returns an expression which caches the + * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations + * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) + */ + function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined { + if (isComputedPropertyName(name)) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + const inlinable = isSimpleInlineableExpression(innerExpression); + const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left); + if (!alreadyTransformed && !inlinable && shouldHoist) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + return createAssignment(generatedName, expression); + } + return (inlinable || isIdentifier(innerExpression)) ? undefined : expression; + } + } + } + +} diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index a70fb820e7950..adfe16c73f692 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -64,6 +64,7 @@ namespace ts { let currentLexicalScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentNameScope: ClassDeclaration | undefined; let currentScopeFirstDeclarationsOfName: UnderscoreEscapedMap | undefined; + let currentClassHasParameterProperties: boolean | undefined; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -83,12 +84,6 @@ namespace ts { */ let applicableSubstitutions: TypeScriptSubstitutionFlags; - /** - * Tracks what computed name expressions originating from elided names must be inlined - * at the next execution site, in document order - */ - let pendingExpressions: Expression[] | undefined; - return transformSourceFileOrBundle; function transformSourceFileOrBundle(node: SourceFile | Bundle) { @@ -136,6 +131,7 @@ namespace ts { const savedCurrentScope = currentLexicalScope; const savedCurrentNameScope = currentNameScope; const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName; + const savedCurrentClassHasParameterProperties = currentClassHasParameterProperties; // Handle state changes before visiting a node. onBeforeVisitNode(node); @@ -149,6 +145,7 @@ namespace ts { currentLexicalScope = savedCurrentScope; currentNameScope = savedCurrentNameScope; + currentClassHasParameterProperties = savedCurrentClassHasParameterProperties; return visited; } @@ -321,6 +318,9 @@ namespace ts { return undefined; case SyntaxKind.PropertyDeclaration: + // Property declarations are not TypeScript syntax, but they must be visited + // for the decorator transformation. + return visitPropertyDeclaration(node as PropertyDeclaration); case SyntaxKind.IndexSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: @@ -437,7 +437,6 @@ namespace ts { // - decorators // - optional `implements` heritage clause // - parameter property assignments in the constructor - // - property declarations // - index signatures // - method overload signatures return visitClassDeclaration(node); @@ -449,7 +448,6 @@ namespace ts { // - decorators // - optional `implements` heritage clause // - parameter property assignments in the constructor - // - property declarations // - index signatures // - method overload signatures return visitClassExpression(node); @@ -611,10 +609,6 @@ namespace ts { if (!isClassLikeDeclarationWithTypeScriptSyntax(node) && !(currentNamespace && hasModifier(node, ModifierFlags.Export))) { return visitEachChild(node, visitor, context); } - - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - const staticProperties = getInitializedProperties(node, /*isStatic*/ true); const facts = getClassFacts(node, staticProperties); @@ -624,25 +618,11 @@ namespace ts { const name = node.name || (facts & ClassFacts.NeedsName ? getGeneratedNameForNode(node) : undefined); const classStatement = facts & ClassFacts.HasConstructorDecorators - ? createClassDeclarationHeadWithDecorators(node, name, facts) + ? createClassDeclarationHeadWithDecorators(node, name) : createClassDeclarationHeadWithoutDecorators(node, name, facts); let statements: Statement[] = [classStatement]; - // Write any pending expressions from elided or moved computed property names - if (some(pendingExpressions)) { - statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!))); - } - pendingExpressions = savedPendingExpressions; - - // Emit static property assignment. Because classDeclaration is lexically evaluated, - // it is safe to emit static property assignment after classDeclaration - // From ES6 specification: - // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using - // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. - if (facts & ClassFacts.HasStaticInitializedProperties) { - addInitializedPropertyStatements(statements, staticProperties, facts & ClassFacts.UseImmediatelyInvokedFunctionExpression ? getInternalName(node) : getLocalName(node)); - } // Write any decorators of the node. addClassElementDecorationStatements(statements, node, /*isStatic*/ false); @@ -745,7 +725,7 @@ namespace ts { name, /*typeParameters*/ undefined, visitNodes(node.heritageClauses, visitor, isHeritageClause), - transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0) + transformClassMembers(node) ); // To better align with the old emitter, we should not emit a trailing source map @@ -765,7 +745,7 @@ namespace ts { * Transforms a decorated class declaration and appends the resulting statements. If * the class requires an alias to avoid issues with double-binding, the alias is returned. */ - function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined, facts: ClassFacts) { + function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined) { // When we emit an ES6 class that has a class decorator, we must tailor the // emit to certain specific cases. // @@ -860,7 +840,7 @@ namespace ts { // ${members} // } const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0); + const members = transformClassMembers(node); const classExpression = createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members); setOriginalNode(classExpression, node); setTextRange(classExpression, location); @@ -888,49 +868,19 @@ namespace ts { return visitEachChild(node, visitor, context); } - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - - const staticProperties = getInitializedProperties(node, /*isStatic*/ true); - const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = transformClassMembers(node, some(heritageClauses, c => c.token === SyntaxKind.ExtendsKeyword)); + const members = transformClassMembers(node); const classExpression = createClassExpression( /*modifiers*/ undefined, node.name, /*typeParameters*/ undefined, - heritageClauses, + node.heritageClauses, members ); setOriginalNode(classExpression, node); setTextRange(classExpression, node); - if (some(staticProperties) || some(pendingExpressions)) { - const expressions: Expression[] = []; - const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference; - const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference); - if (isClassWithConstructorReference) { - // record an alias as the class name is not in scope for statics. - enableSubstitutionForClassAliases(); - const alias = getSynthesizedClone(temp); - alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes; - classAliases[getOriginalNodeId(node)] = alias; - } - - // To preserve the behavior of the old emitter, we explicitly indent - // the body of a class with static initializers. - setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression)); - expressions.push(startOnNewLine(createAssignment(temp, classExpression))); - // Add any pending expressions leftover from elided or relocated computed property names - addRange(expressions, map(pendingExpressions, startOnNewLine)); - pendingExpressions = savedPendingExpressions; - addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp)); - expressions.push(startOnNewLine(temp)); - return inlineExpressions(expressions); - } - - pendingExpressions = savedPendingExpressions; return classExpression; } @@ -938,61 +888,81 @@ namespace ts { * Transforms the members of a class. * * @param node The current class. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. */ - function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + function transformClassMembers(node: ClassDeclaration | ClassExpression) { const members: ClassElement[] = []; - const constructor = transformConstructor(node, isDerivedClass); - if (constructor) { - members.push(constructor); - } - - addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); - return setTextRange(createNodeArray(members), /*location*/ node.members); - } - - /** - * Transforms (or creates) a constructor for a class. - * - * @param node The current class. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. - */ - function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { - // Check if we have property assignment inside class declaration. - // If there is a property assignment, we need to emit constructor whether users define it or not - // If there is no property assignment, we can omit constructor if users do not define it + const existingMembers = visitNodes(node.members, classElementVisitor, isClassElement); const constructor = getFirstConstructorWithBody(node); - const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); - const hasParameterPropertyAssignments = constructor && - constructor.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax && - forEach(constructor.parameters, isParameterWithPropertyAssignment); - - // If the class does not contain nodes that require a synthesized constructor, - // accept the current constructor if it exists. - if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { - return visitEachChild(constructor, visitor, context); - } - - const parameters = transformConstructorParameters(constructor); - const body = transformConstructorBody(node, constructor, isDerivedClass); + const parametersWithPropertyAssignments = + constructor && hasTypeScriptClassSyntax(constructor) + ? filter(constructor.parameters, isParameterPropertyDeclaration) + : undefined; + if (some(parametersWithPropertyAssignments) && constructor) { + currentClassHasParameterProperties = true; + + // Create property declarations for constructor parameter properties. + addRange( + members, + parametersWithPropertyAssignments.map(param => + createProperty( + /*decorators*/ undefined, + /*modifiers*/ undefined, + param.name, + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ) + ) + ); - // constructor(${parameters}) { - // ${body} - // } - return startOnNewLine( - setOriginalNode( - setTextRange( - createConstructor( + const parameters = transformConstructorParameters(constructor); + const body = transformConstructorBody(node.members, constructor, parametersWithPropertyAssignments); + members.push(startOnNewLine( + setOriginalNode( + setTextRange( + createConstructor( /*decorators*/ undefined, /*modifiers*/ undefined, - parameters, - body + parameters, + body + ), + constructor ), - constructor || node - ), - constructor - ) - ); + constructor + ) + )); + addRange( + members, + visitNodes( + existingMembers, + member => { + if (isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) { + const updated = updateProperty( + member, + member.decorators, + member.modifiers, + member.name, + member.questionToken, + member.type, + /*initializer*/ undefined + ); + setCommentRange(updated, node); + setSourceMapRange(updated, node); + return updated; + } + return member; + }, + isClassElement + ) + ); + } + else { + if (constructor) { + members.push(visitEachChild(constructor, visitor, context)); + } + addRange(members, existingMembers); + } + return setTextRange(createNodeArray(members), /*location*/ node.members); } /** @@ -1001,7 +971,7 @@ namespace ts { * * @param constructor The constructor declaration. */ - function transformConstructorParameters(constructor: ConstructorDeclaration | undefined) { + function transformConstructorParameters(constructor: ConstructorDeclaration) { // The ES2015 spec specifies in 14.5.14. Runtime Semantics: ClassDefinitionEvaluation: // If constructor is empty, then // If ClassHeritag_eopt is present and protoParent is not null, then @@ -1022,70 +992,54 @@ namespace ts { } /** - * Transforms (or creates) a constructor body for a class with parameter property - * assignments or instance property initializers. + * Transforms (or creates) a constructor body for a class with parameter property assignments. * * @param node The current class. * @param constructor The current class constructor. - * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'. */ - function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) { + function transformConstructorBody(members: NodeArray, constructor: ConstructorDeclaration, propertyAssignments: ReadonlyArray) { let statements: Statement[] = []; let indexOfFirstStatement = 0; resumeLexicalEnvironment(); - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements); - - // Add parameters with property assignments. Transforms this: - // - // constructor (public x, public y) { - // } - // - // Into this: - // - // constructor (x, y) { - // this.x = x; - // this.y = y; - // } - // - const propertyAssignments = getParametersWithPropertyAssignments(constructor); - addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); - } - else if (isDerivedClass) { - // Add a synthetic `super` call: - // - // super(...arguments); - // - statements.push( - createExpressionStatement( - createCall( - createSuper(), - /*typeArguments*/ undefined, - [createSpread(createIdentifier("arguments"))] - ) - ) - ); - } + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); - // Add the property initializers. Transforms this: + // Add parameters with property assignments. Transforms this: // - // public x = 1; + // constructor (public x, public y) { + // } // // Into this: // - // constructor() { - // this.x = 1; + // constructor (x, y) { + // this.x = x; + // this.y = y; // } // - const properties = getInitializedProperties(node, /*isStatic*/ false); - addInitializedPropertyStatements(statements, properties, createThis()); + addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); + + // Get property initializers. + const classBodyProperties = members.filter(member => isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) as PropertyDeclaration[]; + addRange(statements, classBodyProperties.map( + prop => { + const name = prop.name; + const lhs = (!isComputedPropertyName(name) || isSimpleInlineableExpression(name.expression)) ? + createMemberAccessForPropertyName(createThis(), name, prop) : + createElementAccess(createThis(), getGeneratedNameForNode(name)); + const initializerNode = createExpressionStatement( + createAssignment(lhs, prop.initializer!) + ); + setOriginalNode(initializerNode, prop); + setTextRange(initializerNode, prop); + setCommentRange(initializerNode, prop); + setSourceMapRange(initializerNode, prop); + return initializerNode; + } + )); - if (constructor) { - // The class already had a constructor, so we should add the existing statements, skipping the initial super call. - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); - } + // Add the existing statements, skipping the initial super call. + addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); // End the lexical environment. statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); @@ -1093,7 +1047,7 @@ namespace ts { createBlock( setTextRange( createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members + /*location*/ constructor ? constructor.body!.statements : members ), /*multiLine*/ true ), @@ -1101,61 +1055,16 @@ namespace ts { ); } - /** - * Adds super call and preceding prologue directives into the list of statements. - * - * @param ctor The constructor node. - * @returns index of the statement that follows super call - */ - function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[]): number { - if (ctor.body) { - const statements = ctor.body.statements; - // add prologue directives to the list (if any) - const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor); - if (index === statements.length) { - // list contains nothing but prologue directives (or empty) - exit - return index; - } - - const statement = statements[index]; - if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { - result.push(visitNode(statement, visitor, isStatement)); - return index + 1; - } - - return index; - } - - return 0; - } - - /** - * Gets all parameters of a constructor that should be transformed into property assignments. - * - * @param node The constructor node. - */ - function getParametersWithPropertyAssignments(node: ConstructorDeclaration): ReadonlyArray { - return filter(node.parameters, isParameterWithPropertyAssignment); - } - - /** - * Determines whether a parameter should be transformed into a property assignment. - * - * @param parameter The parameter node. - */ - function isParameterWithPropertyAssignment(parameter: ParameterDeclaration) { - return hasModifier(parameter, ModifierFlags.ParameterPropertyModifier) - && isIdentifier(parameter.name); - } - /** * Transforms a parameter into a property assignment statement. * * @param node The parameter declaration. */ - function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { - Debug.assert(isIdentifier(node.name)); - const name = node.name as Identifier; + function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) { + const name = node.name; + if (!isIdentifier(name)) { + return undefined; + } const propertyName = getMutableClone(name); setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); @@ -1184,99 +1093,6 @@ namespace ts { ); } - /** - * Gets all property declarations with initializers on either the static or instance side of a class. - * - * @param node The class node. - * @param isStatic A value indicating whether to get properties from the static or instance side of the class. - */ - function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray { - return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty); - } - - /** - * Gets a value indicating whether a class element is a static property declaration with an initializer. - * - * @param member The class element node. - */ - function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration { - return isInitializedProperty(member, /*isStatic*/ true); - } - - /** - * Gets a value indicating whether a class element is an instance property declaration with an initializer. - * - * @param member The class element node. - */ - function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration { - return isInitializedProperty(member, /*isStatic*/ false); - } - - /** - * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. - * - * @param member The class element node. - * @param isStatic A value indicating whether the member should be a static or instance member. - */ - function isInitializedProperty(member: ClassElement, isStatic: boolean) { - return member.kind === SyntaxKind.PropertyDeclaration - && isStatic === hasModifier(member, ModifierFlags.Static) - && (member).initializer !== undefined; - } - - /** - * Generates assignment statements for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) { - for (const property of properties) { - const statement = createExpressionStatement(transformInitializedProperty(property, receiver)); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); - statements.push(statement); - } - } - - /** - * Generates assignment expressions for property initializers. - * - * @param properties An array of property declarations to transform. - * @param receiver The receiver on which each property should be assigned. - */ - function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) { - const expressions: Expression[] = []; - for (const property of properties) { - const expression = transformInitializedProperty(property, receiver); - startOnNewLine(expression); - setSourceMapRange(expression, moveRangePastModifiers(property)); - setCommentRange(expression, property); - setOriginalNode(expression, property); - expressions.push(expression); - } - - return expressions; - } - - /** - * Transforms a property initializer into an assignment statement. - * - * @param property The property declaration. - * @param receiver The object receiving the property assignment. - */ - function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) { - // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name) - const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression) - ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name)) - : property.name; - const initializer = visitNode(property.initializer!, visitor, isExpression); - const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName); - - return createAssignment(memberAccess, initializer); - } - /** * Gets either the static or instance members of a class that are decorated, or have * parameters that are decorated. @@ -2144,16 +1960,6 @@ namespace ts { : createIdentifier("BigInt"); } - /** - * A simple inlinable expression is an expression which can be copied into multiple locations - * without risk of repeating any sideeffects and whose value could not possibly change between - * any such locations - */ - function isSimpleInlineableExpression(expression: Expression) { - return !isIdentifier(expression) && isSimpleCopiableExpression(expression) || - isWellKnownSymbolSyntactically(expression); - } - /** * Gets an expression that represents a property name. For a computed property, a * name is generated for the node. @@ -2175,26 +1981,6 @@ namespace ts { } } - /** - * If the name is a computed property, this function transforms it, then either returns an expression which caches the - * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations - * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator) - * @param omitSimple Should expressions with no observable side-effects be elided? (ie, the expression is not hoisted for a decorator or initializer and is a literal) - */ - function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean, omitSimple: boolean): Expression | undefined { - if (isComputedPropertyName(name)) { - const expression = visitNode(name.expression, visitor, isExpression); - const innerExpression = skipPartiallyEmittedExpressions(expression); - const inlinable = isSimpleInlineableExpression(innerExpression); - if (!inlinable && shouldHoist) { - const generatedName = getGeneratedNameForNode(name); - hoistVariableDeclaration(generatedName); - return createAssignment(generatedName, expression); - } - return (omitSimple && (inlinable || isIdentifier(innerExpression))) ? undefined : expression; - } - } - /** * Visits the property name of a class element, for use when emitting property * initializers. For a computed property on a node with decorators, a temporary @@ -2204,18 +1990,20 @@ namespace ts { */ function visitPropertyNameOfClassElement(member: ClassElement): PropertyName { const name = member.name!; - let expr = getPropertyNameExpressionIfNeeded(name, some(member.decorators), /*omitSimple*/ false); - if (expr) { // expr only exists if `name` is a computed property name - // Inline any pending expressions from previous elided or relocated computed property name expressions in order to preserve execution order - if (some(pendingExpressions)) { - expr = inlineExpressions([...pendingExpressions, expr]); - pendingExpressions.length = 0; + // Computed property names need to be transformed into a hoisted variable when they are used more than once. + // The names are used more than once when: + // - the property is non-static and its initializer is moved to the constructor (when there are parameter property assignments). + // - the property has a decorator. + if (isComputedPropertyName(name) && ((!hasStaticModifier(member) && currentClassHasParameterProperties) || some(member.decorators))) { + const expression = visitNode(name.expression, visitor, isExpression); + const innerExpression = skipPartiallyEmittedExpressions(expression); + if (!isSimpleInlineableExpression(innerExpression)) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + return updateComputedPropertyName(name, createAssignment(generatedName, expression)); } - return updateComputedPropertyName(name as ComputedPropertyName, expr); - } - else { - return name; } + return visitNode(name, visitor, isPropertyName); } /** @@ -2261,12 +2049,23 @@ namespace ts { return !nodeIsMissing(node.body); } - function visitPropertyDeclaration(node: PropertyDeclaration): undefined { - const expr = getPropertyNameExpressionIfNeeded(node.name, some(node.decorators) || !!node.initializer, /*omitSimple*/ true); - if (expr && !isSimpleInlineableExpression(expr)) { - (pendingExpressions || (pendingExpressions = [])).push(expr); + function visitPropertyDeclaration(node: PropertyDeclaration) { + const updated = updateProperty( + node, + /*decorators*/ undefined, + visitNodes(node.modifiers, visitor, isModifier), + visitPropertyNameOfClassElement(node), + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + visitNode(node.initializer, visitor) + ); + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setSourceMapRange(updated, moveRangePastDecorators(node)); } - return undefined; + return updated; } function visitConstructor(node: ConstructorDeclaration) { diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 09239d7765fd9..f5f8a298a490b 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -240,6 +240,47 @@ namespace ts { isIdentifier(expression); } + /** + * A simple inlinable expression is an expression which can be copied into multiple locations + * without risk of repeating any sideeffects and whose value could not possibly change between + * any such locations + */ + export function isSimpleInlineableExpression(expression: Expression) { + return !isIdentifier(expression) && isSimpleCopiableExpression(expression) || + isWellKnownSymbolSyntactically(expression); + } + + /** + * Adds super call and preceding prologue directives into the list of statements. + * + * @param ctor The constructor node. + * @param result The list of statements. + * @param visitor The visitor to apply to each node added to the result array. + * @returns index of the statement that follows super call + */ + export function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number { + if (ctor.body) { + const statements = ctor.body.statements; + // add prologue directives to the list (if any) + const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor); + if (index === statements.length) { + // list contains nothing but prologue directives (or empty) - exit + return index; + } + + const statement = statements[index]; + if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { + result.push(visitNode(statement, visitor, isStatement)); + return index + 1; + } + + return index; + } + + return 0; + } + + /** * @param input Template string input strings * @param args Names which need to be made file-level unique @@ -255,4 +296,43 @@ namespace ts { return result; }; } + + /** + * Gets all property declarations with initializers on either the static or instance side of a class. + * + * @param node The class node. + * @param isStatic A value indicating whether to get properties from the static or instance side of the class. + */ + export function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray { + return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty); + } + + /** + * Gets a value indicating whether a class element is a static property declaration with an initializer. + * + * @param member The class element node. + */ + export function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return isInitializedProperty(member) && hasStaticModifier(member); + } + + /** + * Gets a value indicating whether a class element is an instance property declaration with an initializer. + * + * @param member The class element node. + */ + export function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return isInitializedProperty(member) && !hasStaticModifier(member); + } + + /** + * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. + * + * @param member The class element node. + * @param isStatic A value indicating whether the member should be a static or instance member. + */ + export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } { + return member.kind === SyntaxKind.PropertyDeclaration + && (member).initializer !== undefined; + } } \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index fdc0ff2969f7d..f5e16b0d127db 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -30,6 +30,7 @@ "transformers/utilities.ts", "transformers/destructuring.ts", "transformers/ts.ts", + "transformers/classFields.ts", "transformers/es2017.ts", "transformers/es2018.ts", "transformers/es2019.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a7ee1c5745fb8..31fb4984507fe 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5197,6 +5197,7 @@ namespace ts { ContainsYield = 1 << 17, ContainsHoistedDeclarationOrCompletion = 1 << 18, ContainsDynamicImport = 1 << 19, + ContainsClassFields = 1 << 20, // Please leave this as 1 << 29. // It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system. diff --git a/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js b/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js index ab986aba1e88d..970590c136b41 100644 --- a/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js +++ b/tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js @@ -34,8 +34,8 @@ var C = /** @class */ (function () { } return C; }()); -_a = a_1.x; exports.C = C; +_a = a_1.x; //// [c.js] "use strict"; var __extends = (this && this.__extends) || (function () { @@ -64,8 +64,8 @@ var D = /** @class */ (function (_super) { } return D; }(b_1.C)); -_a = a_1.x; exports.D = D; +_a = a_1.x; //// [a.d.ts] diff --git a/tests/baselines/reference/decoratorsOnComputedProperties.js b/tests/baselines/reference/decoratorsOnComputedProperties.js index 2af6b460c0518..3f0444b310aaa 100644 --- a/tests/baselines/reference/decoratorsOnComputedProperties.js +++ b/tests/baselines/reference/decoratorsOnComputedProperties.js @@ -196,7 +196,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; -var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21; +var _a, _b, _c, _d; +var _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21; function x(o, k) { } let i = 0; function foo() { return ++i + ""; } @@ -209,11 +210,11 @@ class A { this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_b] = null; - this[_d] = null; + this[_f] = null; + this[_h] = null; } } -foo(), _a = foo(), _b = foo(), _c = fieldNameB, _d = fieldNameC; +foo(), _e = foo(), _f = foo(), _g = fieldNameB, _h = fieldNameC; __decorate([ x ], A.prototype, "property", void 0); @@ -228,42 +229,42 @@ __decorate([ ], A.prototype, Symbol.iterator, void 0); __decorate([ x -], A.prototype, _a, void 0); +], A.prototype, _e, void 0); __decorate([ x -], A.prototype, _b, void 0); +], A.prototype, _f, void 0); __decorate([ x -], A.prototype, _c, void 0); +], A.prototype, _g, void 0); __decorate([ x -], A.prototype, _d, void 0); -void (_j = class B { +], A.prototype, _h, void 0); +void (_a = class B { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_f] = null; - this[_h] = null; + this[_k] = null; + this[_m] = null; } }, foo(), - _e = foo(), - _f = foo(), - _g = fieldNameB, - _h = fieldNameC, - _j); + _j = foo(), + _k = foo(), + _l = fieldNameB, + _m = fieldNameC, + _a); class C { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_l] = null; - this[_o] = null; + this[_p] = null; + this[_r] = null; } - [(foo(), _k = foo(), _l = foo(), _m = fieldNameB, _o = fieldNameC, "some" + "method")]() { } + [(foo(), _o = foo(), _p = foo(), _q = fieldNameB, _r = fieldNameC, "some" + "method")]() { } } __decorate([ x @@ -279,26 +280,26 @@ __decorate([ ], C.prototype, Symbol.iterator, void 0); __decorate([ x -], C.prototype, _k, void 0); +], C.prototype, _o, void 0); __decorate([ x -], C.prototype, _l, void 0); +], C.prototype, _p, void 0); __decorate([ x -], C.prototype, _m, void 0); +], C.prototype, _q, void 0); __decorate([ x -], C.prototype, _o, void 0); +], C.prototype, _r, void 0); void class D { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_q] = null; - this[_s] = null; + this[_t] = null; + this[_v] = null; } - [(foo(), _p = foo(), _q = foo(), _r = fieldNameB, _s = fieldNameC, "some" + "method")]() { } + [(foo(), _s = foo(), _t = foo(), _u = fieldNameB, _v = fieldNameC, "some" + "method")]() { } }; class E { constructor() { @@ -306,12 +307,12 @@ class E { this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_u] = null; - this[_w] = null; + this[_x] = null; + this[_z] = null; } - [(foo(), _t = foo(), _u = foo(), "some" + "method")]() { } + [(foo(), _w = foo(), _x = foo(), "some" + "method")]() { } } -_v = fieldNameB, _w = fieldNameC; +_y = fieldNameB, _z = fieldNameC; __decorate([ x ], E.prototype, "property", void 0); @@ -326,43 +327,43 @@ __decorate([ ], E.prototype, Symbol.iterator, void 0); __decorate([ x -], E.prototype, _t, void 0); +], E.prototype, _w, void 0); __decorate([ x -], E.prototype, _u, void 0); +], E.prototype, _x, void 0); __decorate([ x -], E.prototype, _v, void 0); +], E.prototype, _y, void 0); __decorate([ x -], E.prototype, _w, void 0); -void (_1 = class F { +], E.prototype, _z, void 0); +void (_b = class F { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_y] = null; - this[_0] = null; + this[_1] = null; + this[_3] = null; } - [(foo(), _x = foo(), _y = foo(), "some" + "method")]() { } + [(foo(), _0 = foo(), _1 = foo(), "some" + "method")]() { } }, - _z = fieldNameB, - _0 = fieldNameC, - _1); + _2 = fieldNameB, + _3 = fieldNameC, + _b); class G { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_3] = null; this[_5] = null; + this[_7] = null; } - [(foo(), _2 = foo(), _3 = foo(), "some" + "method")]() { } - [(_4 = fieldNameB, "some" + "method2")]() { } + [(foo(), _4 = foo(), _5 = foo(), "some" + "method")]() { } + [(_6 = fieldNameB, "some" + "method2")]() { } } -_5 = fieldNameC; +_7 = fieldNameC; __decorate([ x ], G.prototype, "property", void 0); @@ -377,43 +378,43 @@ __decorate([ ], G.prototype, Symbol.iterator, void 0); __decorate([ x -], G.prototype, _2, void 0); +], G.prototype, _4, void 0); __decorate([ x -], G.prototype, _3, void 0); +], G.prototype, _5, void 0); __decorate([ x -], G.prototype, _4, void 0); +], G.prototype, _6, void 0); __decorate([ x -], G.prototype, _5, void 0); -void (_10 = class H { +], G.prototype, _7, void 0); +void (_c = class H { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_7] = null; this[_9] = null; + this[_11] = null; } - [(foo(), _6 = foo(), _7 = foo(), "some" + "method")]() { } - [(_8 = fieldNameB, "some" + "method2")]() { } + [(foo(), _8 = foo(), _9 = foo(), "some" + "method")]() { } + [(_10 = fieldNameB, "some" + "method2")]() { } }, - _9 = fieldNameC, - _10); + _11 = fieldNameC, + _c); class I { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_12] = null; - this[_15] = null; + this[_13] = null; + this[_16] = null; } - [(foo(), _11 = foo(), _12 = foo(), _13 = "some" + "method")]() { } - [(_14 = fieldNameB, "some" + "method2")]() { } + [(foo(), _12 = foo(), _13 = foo(), _14 = "some" + "method")]() { } + [(_15 = fieldNameB, "some" + "method2")]() { } } -_15 = fieldNameC; +_16 = fieldNameC; __decorate([ x ], I.prototype, "property", void 0); @@ -426,32 +427,32 @@ __decorate([ __decorate([ x ], I.prototype, Symbol.iterator, void 0); -__decorate([ - x -], I.prototype, _11, void 0); __decorate([ x ], I.prototype, _12, void 0); __decorate([ x -], I.prototype, _13, null); +], I.prototype, _13, void 0); __decorate([ x -], I.prototype, _14, void 0); +], I.prototype, _14, null); __decorate([ x ], I.prototype, _15, void 0); -void (_21 = class J { +__decorate([ + x +], I.prototype, _16, void 0); +void (_d = class J { constructor() { this["property2"] = 2; this[Symbol.iterator] = null; this["property4"] = 2; this[Symbol.match] = null; - this[_17] = null; - this[_20] = null; + this[_18] = null; + this[_21] = null; } - [(foo(), _16 = foo(), _17 = foo(), _18 = "some" + "method")]() { } - [(_19 = fieldNameB, "some" + "method2")]() { } + [(foo(), _17 = foo(), _18 = foo(), _19 = "some" + "method")]() { } + [(_20 = fieldNameB, "some" + "method2")]() { } }, - _20 = fieldNameC, - _21); + _21 = fieldNameC, + _d); diff --git a/tests/baselines/reference/systemModuleTargetES6.js b/tests/baselines/reference/systemModuleTargetES6.js index 943e57ff742ac..0df2835683e82 100644 --- a/tests/baselines/reference/systemModuleTargetES6.js +++ b/tests/baselines/reference/systemModuleTargetES6.js @@ -35,8 +35,8 @@ System.register([], function (exports_1, context_1) { MyClass2 = class MyClass2 { static getInstance() { return MyClass2.value; } }; - MyClass2.value = 42; exports_1("MyClass2", MyClass2); + MyClass2.value = 42; } }; }); diff --git a/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js b/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js index 21871ff230472..872008368c682 100644 --- a/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js +++ b/tests/baselines/reference/variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.js @@ -16,8 +16,8 @@ var Foo = /** @class */ (function () { } return Foo; }()); -_a = key; exports.Foo = Foo; +_a = key; //// [variableDeclarationDeclarationEmitUniqueSymbolPartialStatement.d.ts] From 1479823ffa74ebd5cb48ef4b4d087bb827c1b349 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 10 Jun 2019 11:10:11 -0700 Subject: [PATCH 2/2] Fix emit issues --- src/compiler/factory.ts | 12 + src/compiler/transformers/classFields.ts | 34 +- src/compiler/transformers/es2015.ts | 2 +- src/compiler/transformers/ts.ts | 307 ++++++------------ .../reference/instanceMemberInitialization.js | 19 +- .../instanceMemberInitialization.symbols | 22 ++ .../instanceMemberInitialization.types | 17 + .../instanceMemberInitialization.ts | 11 +- 8 files changed, 211 insertions(+), 213 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 517ebacb89dfb..3159f63654fd4 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3118,6 +3118,18 @@ namespace ts { return node.emitNode; } + /** + * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. + * @internal + */ + export function removeAllComments(node: T): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags |= EmitFlags.NoComments; + emitNode.leadingComments = undefined; + emitNode.trailingComments = undefined; + return node; + } + export function setTextRange(range: T, location: TextRange | undefined): T { if (location) { range.pos = location.pos; diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 575e1d1334696..f213f679b9cd8 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -90,8 +90,11 @@ namespace ts { case SyntaxKind.ComputedPropertyName: return visitComputedPropertyName(node as ComputedPropertyName); - default: + case SyntaxKind.SemicolonClassElement: return node; + + default: + return visitor(node); } } @@ -138,8 +141,9 @@ namespace ts { if (!forEach(node.members, isPropertyDeclaration)) { return visitEachChild(node, visitor, context); } + const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; + pendingExpressions = undefined!; const extendsClauseElement = getEffectiveBaseTypeNode(node); const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword); @@ -147,19 +151,20 @@ namespace ts { const statements: Statement[] = [ updateClassDeclaration( node, - node.decorators, + /*decorators*/ undefined, node.modifiers, node.name, - node.typeParameters, - node.heritageClauses, + /*typeParameters*/ undefined, + visitNodes(node.heritageClauses, visitor, isHeritageClause), transformClassMembers(node, isDerivedClass) ) ]; // Write any pending expressions from elided or moved computed property names if (some(pendingExpressions)) { - statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!))); + statements.push(createExpressionStatement(inlineExpressions(pendingExpressions))); } + pendingExpressions = savedPendingExpressions; // Emit static property assignment. Because classDeclaration is lexically evaluated, @@ -199,7 +204,7 @@ namespace ts { node, node.modifiers, node.name, - node.typeParameters, + /*typeParameters*/ undefined, visitNodes(node.heritageClauses, visitor, isHeritageClause), transformClassMembers(node, isDerivedClass) ); @@ -329,6 +334,21 @@ namespace ts { // this.x = 1; // } // + if (constructor && constructor.body) { + let parameterPropertyDeclarationCount = 0; + for (let i = indexOfFirstStatement; i < constructor.body.statements.length; i++) { + if (isParameterPropertyDeclaration(getOriginalNode(constructor.body.statements[i]))) { + parameterPropertyDeclarationCount++; + } + else { + break; + } + } + if (parameterPropertyDeclarationCount > 0) { + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, parameterPropertyDeclarationCount)); + indexOfFirstStatement += parameterPropertyDeclarationCount; + } + } addInitializedPropertyStatements(statements, properties, createThis()); // Add existing statements, skipping the initial super call. diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 9849d72f14a08..88b4b29196c46 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1567,7 +1567,7 @@ namespace ts { break; default: - Debug.failBadSyntaxKind(node); + Debug.failBadSyntaxKind(member, currentSourceFile && currentSourceFile.fileName); break; } } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index adfe16c73f692..db4c1459e5a5d 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -312,10 +312,7 @@ namespace ts { function classElementVisitorWorker(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.Constructor: - // TypeScript constructors are transformed in `visitClassDeclaration`. - // We elide them here as `visitorWorker` checks transform flags, which could - // erronously include an ES6 constructor without TypeScript syntax. - return undefined; + return visitConstructor(node as ConstructorDeclaration); case SyntaxKind.PropertyDeclaration: // Property declarations are not TypeScript syntax, but they must be visited @@ -609,6 +606,7 @@ namespace ts { if (!isClassLikeDeclarationWithTypeScriptSyntax(node) && !(currentNamespace && hasModifier(node, ModifierFlags.Export))) { return visitEachChild(node, visitor, context); } + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); const facts = getClassFacts(node, staticProperties); @@ -735,6 +733,7 @@ namespace ts { emitFlags |= EmitFlags.NoTrailingSourceMap; } + aggregateTransformFlags(classDeclaration); setTextRange(classDeclaration, node); setOriginalNode(classDeclaration, node); setEmitFlags(classDeclaration, emitFlags); @@ -842,6 +841,7 @@ namespace ts { const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); const members = transformClassMembers(node); const classExpression = createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members); + aggregateTransformFlags(classExpression); setOriginalNode(classExpression, node); setTextRange(classExpression, location); @@ -868,16 +868,15 @@ namespace ts { return visitEachChild(node, visitor, context); } - const members = transformClassMembers(node); - const classExpression = createClassExpression( /*modifiers*/ undefined, node.name, /*typeParameters*/ undefined, - node.heritageClauses, - members + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node) ); + aggregateTransformFlags(classExpression); setOriginalNode(classExpression, node); setTextRange(classExpression, node); @@ -891,207 +890,28 @@ namespace ts { */ function transformClassMembers(node: ClassDeclaration | ClassExpression) { const members: ClassElement[] = []; - const existingMembers = visitNodes(node.members, classElementVisitor, isClassElement); const constructor = getFirstConstructorWithBody(node); - const parametersWithPropertyAssignments = - constructor && hasTypeScriptClassSyntax(constructor) - ? filter(constructor.parameters, isParameterPropertyDeclaration) - : undefined; - if (some(parametersWithPropertyAssignments) && constructor) { - currentClassHasParameterProperties = true; - - // Create property declarations for constructor parameter properties. - addRange( - members, - parametersWithPropertyAssignments.map(param => - createProperty( + const parametersWithPropertyAssignments = constructor && + filter(constructor.parameters, isParameterPropertyDeclaration); + + if (parametersWithPropertyAssignments) { + for (const parameter of parametersWithPropertyAssignments) { + if (isIdentifier(parameter.name)) { + members.push(aggregateTransformFlags(createProperty( /*decorators*/ undefined, /*modifiers*/ undefined, - param.name, + parameter.name, /*questionOrExclamationToken*/ undefined, /*type*/ undefined, - /*initializer*/ undefined - ) - ) - ); - - const parameters = transformConstructorParameters(constructor); - const body = transformConstructorBody(node.members, constructor, parametersWithPropertyAssignments); - members.push(startOnNewLine( - setOriginalNode( - setTextRange( - createConstructor( - /*decorators*/ undefined, - /*modifiers*/ undefined, - parameters, - body - ), - constructor - ), - constructor - ) - )); - addRange( - members, - visitNodes( - existingMembers, - member => { - if (isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) { - const updated = updateProperty( - member, - member.decorators, - member.modifiers, - member.name, - member.questionToken, - member.type, - /*initializer*/ undefined - ); - setCommentRange(updated, node); - setSourceMapRange(updated, node); - return updated; - } - return member; - }, - isClassElement - ) - ); - } - else { - if (constructor) { - members.push(visitEachChild(constructor, visitor, context)); + /*initializer*/ undefined))); + } } - addRange(members, existingMembers); } - return setTextRange(createNodeArray(members), /*location*/ node.members); - } - - /** - * Transforms (or creates) the parameters for the constructor of a class with - * parameter property assignments or instance property initializers. - * - * @param constructor The constructor declaration. - */ - function transformConstructorParameters(constructor: ConstructorDeclaration) { - // The ES2015 spec specifies in 14.5.14. Runtime Semantics: ClassDefinitionEvaluation: - // If constructor is empty, then - // If ClassHeritag_eopt is present and protoParent is not null, then - // Let constructor be the result of parsing the source text - // constructor(...args) { super (...args);} - // using the syntactic grammar with the goal symbol MethodDefinition[~Yield]. - // Else, - // Let constructor be the result of parsing the source text - // constructor( ){ } - // using the syntactic grammar with the goal symbol MethodDefinition[~Yield]. - // - // While we could emit the '...args' rest parameter, certain later tools in the pipeline might - // downlevel the '...args' portion less efficiently by naively copying the contents of 'arguments' to an array. - // Instead, we'll avoid using a rest parameter and spread into the super call as - // 'super(...arguments)' instead of 'super(...args)', as you can see in "transformConstructorBody". - return visitParameterList(constructor && constructor.parameters, visitor, context) - || []; - } - - /** - * Transforms (or creates) a constructor body for a class with parameter property assignments. - * - * @param node The current class. - * @param constructor The current class constructor. - */ - function transformConstructorBody(members: NodeArray, constructor: ConstructorDeclaration, propertyAssignments: ReadonlyArray) { - let statements: Statement[] = []; - let indexOfFirstStatement = 0; - - resumeLexicalEnvironment(); - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); - - // Add parameters with property assignments. Transforms this: - // - // constructor (public x, public y) { - // } - // - // Into this: - // - // constructor (x, y) { - // this.x = x; - // this.y = y; - // } - // - addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); - - // Get property initializers. - const classBodyProperties = members.filter(member => isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) as PropertyDeclaration[]; - addRange(statements, classBodyProperties.map( - prop => { - const name = prop.name; - const lhs = (!isComputedPropertyName(name) || isSimpleInlineableExpression(name.expression)) ? - createMemberAccessForPropertyName(createThis(), name, prop) : - createElementAccess(createThis(), getGeneratedNameForNode(name)); - const initializerNode = createExpressionStatement( - createAssignment(lhs, prop.initializer!) - ); - setOriginalNode(initializerNode, prop); - setTextRange(initializerNode, prop); - setCommentRange(initializerNode, prop); - setSourceMapRange(initializerNode, prop); - return initializerNode; - } - )); - - // Add the existing statements, skipping the initial super call. - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); - - // End the lexical environment. - statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); - return setTextRange( - createBlock( - setTextRange( - createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : members - ), - /*multiLine*/ true - ), - /*location*/ constructor ? constructor.body : undefined - ); + addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return setTextRange(createNodeArray(members), /*location*/ node.members); } - /** - * Transforms a parameter into a property assignment statement. - * - * @param node The parameter declaration. - */ - function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) { - const name = node.name; - if (!isIdentifier(name)) { - return undefined; - } - const propertyName = getMutableClone(name); - setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); - - const localName = getMutableClone(name); - setEmitFlags(localName, EmitFlags.NoComments); - - return startOnNewLine( - setEmitFlags( - setTextRange( - createExpressionStatement( - createAssignment( - setTextRange( - createPropertyAccess( - createThis(), - propertyName - ), - node.name - ), - localName - ) - ), - moveRangePos(node, -1) - ), - EmitFlags.NoComments - ) - ); - } /** * Gets either the static or instance members of a class that are decorated, or have @@ -2045,7 +1865,7 @@ namespace ts { * * @param node The declaration node. */ - function shouldEmitFunctionLikeDeclaration(node: FunctionLikeDeclaration) { + function shouldEmitFunctionLikeDeclaration(node: T): node is T & { body: NonNullable } { return !nodeIsMissing(node.body); } @@ -2075,10 +1895,90 @@ namespace ts { return updateConstructor( node, - visitNodes(node.decorators, visitor, isDecorator), - visitNodes(node.modifiers, visitor, isModifier), + /*decorators*/ undefined, + /*modifiers*/ undefined, visitParameterList(node.parameters, visitor, context), - visitFunctionBody(node.body, visitor, context) + transformConstructorBody(node.body, node) + ); + } + + function transformConstructorBody(body: Block, constructor: ConstructorDeclaration) { + const parametersWithPropertyAssignments = constructor && + filter(constructor.parameters, isParameterPropertyDeclaration); + if (!some(parametersWithPropertyAssignments)) { + return visitFunctionBody(body, visitor, context); + } + + let statements: Statement[] = []; + let indexOfFirstStatement = 0; + + resumeLexicalEnvironment(); + + indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor); + + // Add parameters with property assignments. Transforms this: + // + // constructor (public x, public y) { + // } + // + // Into this: + // + // constructor (x, y) { + // this.x = x; + // this.y = y; + // } + // + addRange(statements, map(parametersWithPropertyAssignments, transformParameterWithPropertyAssignment)); + + // Add the existing statements, skipping the initial super call. + addRange(statements, visitNodes(body.statements, visitor, isStatement, indexOfFirstStatement)); + + // End the lexical environment. + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + const block = createBlock(setTextRange(createNodeArray(statements), body.statements), /*multiLine*/ true); + setTextRange(block, /*location*/ body); + setOriginalNode(block, body); + return block; + } + + /** + * Transforms a parameter into a property assignment statement. + * + * @param node The parameter declaration. + */ + function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) { + const name = node.name; + if (!isIdentifier(name)) { + return undefined; + } + + const propertyName = getMutableClone(name); + setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); + + const localName = getMutableClone(name); + setEmitFlags(localName, EmitFlags.NoComments); + + return startOnNewLine( + removeAllComments( + setTextRange( + setOriginalNode( + createExpressionStatement( + createAssignment( + setTextRange( + createPropertyAccess( + createThis(), + propertyName + ), + node.name + ), + localName + ) + ), + node + ), + moveRangePos(node, -1) + ) + ) ); } @@ -2217,6 +2117,7 @@ namespace ts { if (parameterIsThisKeyword(node)) { return undefined; } + const updated = updateParameter( node, /*decorators*/ undefined, diff --git a/tests/baselines/reference/instanceMemberInitialization.js b/tests/baselines/reference/instanceMemberInitialization.js index f153090d719f7..0437eaa27a04c 100644 --- a/tests/baselines/reference/instanceMemberInitialization.js +++ b/tests/baselines/reference/instanceMemberInitialization.js @@ -6,7 +6,16 @@ class C { var c = new C(); c.x = 3; var c2 = new C(); -var r = c.x === c2.x; +var r = c.x === c2.x; + +// #31792 + + + +class MyMap { + constructor(private readonly Map_: { new(): any }) {} + private readonly store = new this.Map_(); +} //// [instanceMemberInitialization.js] var C = /** @class */ (function () { @@ -19,3 +28,11 @@ var c = new C(); c.x = 3; var c2 = new C(); var r = c.x === c2.x; +// #31792 +var MyMap = /** @class */ (function () { + function MyMap(Map_) { + this.Map_ = Map_; + this.store = new this.Map_(); + } + return MyMap; +}()); diff --git a/tests/baselines/reference/instanceMemberInitialization.symbols b/tests/baselines/reference/instanceMemberInitialization.symbols index 4211713375901..045b2427e2983 100644 --- a/tests/baselines/reference/instanceMemberInitialization.symbols +++ b/tests/baselines/reference/instanceMemberInitialization.symbols @@ -28,3 +28,25 @@ var r = c.x === c2.x; >c2 : Symbol(c2, Decl(instanceMemberInitialization.ts, 6, 3)) >x : Symbol(C.x, Decl(instanceMemberInitialization.ts, 0, 9)) +// #31792 + + + +class MyMap { +>MyMap : Symbol(MyMap, Decl(instanceMemberInitialization.ts, 7, 21)) +>K : Symbol(K, Decl(instanceMemberInitialization.ts, 13, 12)) +>V : Symbol(V, Decl(instanceMemberInitialization.ts, 13, 14)) + + constructor(private readonly Map_: { new(): any }) {} +>Map_ : Symbol(MyMap.Map_, Decl(instanceMemberInitialization.ts, 14, 16)) +>K : Symbol(K, Decl(instanceMemberInitialization.ts, 14, 45)) +>V : Symbol(V, Decl(instanceMemberInitialization.ts, 14, 47)) + + private readonly store = new this.Map_(); +>store : Symbol(MyMap.store, Decl(instanceMemberInitialization.ts, 14, 63)) +>this.Map_ : Symbol(MyMap.Map_, Decl(instanceMemberInitialization.ts, 14, 16)) +>this : Symbol(MyMap, Decl(instanceMemberInitialization.ts, 7, 21)) +>Map_ : Symbol(MyMap.Map_, Decl(instanceMemberInitialization.ts, 14, 16)) +>K : Symbol(K, Decl(instanceMemberInitialization.ts, 13, 12)) +>V : Symbol(V, Decl(instanceMemberInitialization.ts, 13, 14)) +} diff --git a/tests/baselines/reference/instanceMemberInitialization.types b/tests/baselines/reference/instanceMemberInitialization.types index ca97ccac3336b..e440f3c4bfd37 100644 --- a/tests/baselines/reference/instanceMemberInitialization.types +++ b/tests/baselines/reference/instanceMemberInitialization.types @@ -34,3 +34,20 @@ var r = c.x === c2.x; >c2 : C >x : number +// #31792 + + + +class MyMap { +>MyMap : MyMap + + constructor(private readonly Map_: { new(): any }) {} +>Map_ : new () => any + + private readonly store = new this.Map_(); +>store : any +>new this.Map_() : any +>this.Map_ : new () => any +>this : this +>Map_ : new () => any +} diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/instanceMemberInitialization.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/instanceMemberInitialization.ts index 6240da8078bd7..04b44a6835097 100644 --- a/tests/cases/conformance/classes/propertyMemberDeclarations/instanceMemberInitialization.ts +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/instanceMemberInitialization.ts @@ -5,4 +5,13 @@ class C { var c = new C(); c.x = 3; var c2 = new C(); -var r = c.x === c2.x; \ No newline at end of file +var r = c.x === c2.x; + +// #31792 + + + +class MyMap { + constructor(private readonly Map_: { new(): any }) {} + private readonly store = new this.Map_(); +} \ No newline at end of file