From ecaf38f6d45f90a72baea0515c74ff8e3f521940 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 27 Jul 2022 18:24:04 -0400 Subject: [PATCH] Transform decorators that reference private names into a 'static {}' block --- src/compiler/factory/nodeFactory.ts | 7 +- src/compiler/transformers/legacyDecorators.ts | 95 +++++++++++++++---- src/compiler/types.ts | 7 +- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 76a8c405d722b..71a9a80c510ac 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -2331,7 +2331,7 @@ namespace ts { propagateChildFlags(node.expression) | (isIdentifier(node.name) ? propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name)); + propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); if (isSuperKeyword(expression)) { // super method calls require a lexical 'this' // super method calls require 'super' hoisting in ES2017 and ES2018 async functions and async generators @@ -2366,7 +2366,7 @@ namespace ts { propagateChildFlags(node.questionDotToken) | (isIdentifier(node.name) ? propagateIdentifierNameFlags(node.name) : - propagateChildFlags(node.name)); + propagateChildFlags(node.name) | TransformFlags.ContainsPrivateIdentifierInExpression); return node; } @@ -2851,6 +2851,9 @@ namespace ts { else if (isLogicalOrCoalescingAssignmentOperator(operatorKind)) { node.transformFlags |= TransformFlags.ContainsES2021; } + if (operatorKind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { + node.transformFlags |= TransformFlags.ContainsPrivateIdentifierInExpression; + } return node; } diff --git a/src/compiler/transformers/legacyDecorators.ts b/src/compiler/transformers/legacyDecorators.ts index 43a5b1f7a05fe..ecd3a0560bcd4 100644 --- a/src/compiler/transformers/legacyDecorators.ts +++ b/src/compiler/transformers/legacyDecorators.ts @@ -68,26 +68,46 @@ namespace ts { function visitClassDeclaration(node: ClassDeclaration): VisitResult { if (!(classOrConstructorParameterIsDecorated(node) || childIsDecorated(node))) return visitEachChild(node, visitor, context); - const classStatement = hasDecorators(node) ? + const statements = hasDecorators(node) ? createClassDeclarationHeadWithDecorators(node, node.name) : createClassDeclarationHeadWithoutDecorators(node, node.name); - const statements: Statement[] = [classStatement]; - - // Write any decorators of the node. - addClassElementDecorationStatements(statements, node, /*isStatic*/ false); - addClassElementDecorationStatements(statements, node, /*isStatic*/ true); - addConstructorDecorationStatement(statements, node); - if (statements.length > 1) { // Add a DeclarationMarker as a marker for the end of the declaration statements.push(factory.createEndOfDeclarationMarker(node)); - setEmitFlags(classStatement, getEmitFlags(classStatement) | EmitFlags.HasEndOfDeclarationMarker); + setEmitFlags(statements[0], getEmitFlags(statements[0]) | EmitFlags.HasEndOfDeclarationMarker); } return singleOrMany(statements); } + function containsDecoratedClassElementWithPrivateFieldAccess(node: ClassDeclaration) { + for (const member of node.members) { + if (!canHaveDecorators(member)) continue; + const decorators = getAllDecoratorsOfClassElement(member, node); + if (decorators?.decorators) { + for (const decorator of decorators.decorators) { + if (decorator.transformFlags & TransformFlags.ContainsPrivateIdentifierInExpression) { + return true; + } + } + } + if (decorators?.parameters) { + for (const parameterDecorators of decorators.parameters) { + if (parameterDecorators) { + for (const decorator of parameterDecorators) { + if (decorator.transformFlags & TransformFlags.ContainsPrivateIdentifierInExpression) { + return true; + } + } + } + } + } + } + + return false; + } + /** * Transforms a non-decorated class declaration. * @@ -99,14 +119,33 @@ namespace ts { // ${members} // } - return factory.updateClassDeclaration( + const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier); + const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); + let members = visitNodes(node.members, visitor, isClassElement); + + let decorationStatements: Statement[] | undefined = []; + addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ false); + addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ true); + if (containsDecoratedClassElementWithPrivateFieldAccess(node)) { + members = setTextRange(factory.createNodeArray([ + ...members, + factory.createClassStaticBlockDeclaration( + factory.createBlock(decorationStatements, /*multiLine*/ true) + ) + ]), members); + decorationStatements = undefined; + } + + const updated = factory.updateClassDeclaration( node, - visitNodes(node.modifiers, modifierVisitor, isModifier), + modifiers, name, /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, visitor, isHeritageClause), - visitNodes(node.members, visitor, isClassElement) + heritageClauses, + members ); + + return addRange([updated], decorationStatements); } /** @@ -213,8 +252,28 @@ namespace ts { // ${members} // } const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); - const members = visitNodes(node.members, visitor, isClassElement); - const classExpression = factory.createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members); + let members = visitNodes(node.members, visitor, isClassElement); + + let decorationStatements: Statement[] | undefined = []; + addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ false); + addClassElementDecorationStatements(decorationStatements, node, /*isStatic*/ true); + if (containsDecoratedClassElementWithPrivateFieldAccess(node)) { + members = setTextRange(factory.createNodeArray([ + ...members, + factory.createClassStaticBlockDeclaration( + factory.createBlock(decorationStatements, /*multiLine*/ true) + ) + ]), members); + decorationStatements = undefined; + } + + const classExpression = factory.createClassExpression( + /*modifiers*/ undefined, + name, + /*typeParameters*/ undefined, + heritageClauses, + members); + setOriginalNode(classExpression, node); setTextRange(classExpression, location); @@ -234,7 +293,11 @@ namespace ts { setOriginalNode(statement, node); setTextRange(statement, location); setCommentRange(statement, node); - return statement; + + const statements: Statement[] = [statement]; + addRange(statements, decorationStatements); + addConstructorDecorationStatement(statements, node); + return statements; } function visitClassExpression(node: ClassExpression) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f93f36c369157..fbfdfb42e3024 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -7041,10 +7041,9 @@ namespace ts { ContainsPossibleTopLevelAwait = 1 << 26, ContainsLexicalSuper = 1 << 27, ContainsUpdateExpressionForIdentifier = 1 << 28, - // 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. - // It is a good reminder of how much room we have left - HasComputedFlags = 1 << 29, // Transform flags have been computed. + ContainsPrivateIdentifierInExpression = 1 << 29, + + HasComputedFlags = 1 << 31, // Transform flags have been computed. // Assertions // - Bitmasks that are used to assert facts about the syntax of a node and its subtree.