Skip to content

Commit

Permalink
Transform decorators that reference private names into a 'static {}' …
Browse files Browse the repository at this point in the history
…block
  • Loading branch information
rbuckton committed Jul 27, 2022
1 parent 8d0c72d commit ecaf38f
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 22 deletions.
7 changes: 5 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
95 changes: 79 additions & 16 deletions src/compiler/transformers/legacyDecorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,46 @@ namespace ts {
function visitClassDeclaration(node: ClassDeclaration): VisitResult<Statement> {
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.
*
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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);

Expand All @@ -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) {
Expand Down
7 changes: 3 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit ecaf38f

Please sign in to comment.