Skip to content

Commit 3328feb

Browse files
authored
Use 'static {}' for static fields when available and useDefineForClassFields is false (#47707)
1 parent ceee975 commit 3328feb

File tree

42 files changed

+882
-1027
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+882
-1027
lines changed

src/compiler/checker.ts

+1-26
Original file line numberDiff line numberDiff line change
@@ -28425,29 +28425,6 @@ namespace ts {
2842528425
grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right));
2842628426
}
2842728427

28428-
if (lexicallyScopedSymbol?.valueDeclaration && (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && !useDefineForClassFields)) {
28429-
const lexicalClass = getContainingClass(lexicallyScopedSymbol.valueDeclaration);
28430-
const parentStaticFieldInitializer = findAncestor(node, (n) => {
28431-
if (n === lexicalClass) return "quit";
28432-
if (isPropertyDeclaration(n.parent) && hasStaticModifier(n.parent) && n.parent.initializer === n && n.parent.parent === lexicalClass) {
28433-
return true;
28434-
}
28435-
return false;
28436-
});
28437-
if (parentStaticFieldInitializer) {
28438-
const parentStaticFieldInitializerSymbol = getSymbolOfNode(parentStaticFieldInitializer.parent);
28439-
Debug.assert(parentStaticFieldInitializerSymbol, "Initializer without declaration symbol");
28440-
const diagnostic = error(node,
28441-
Diagnostics.Property_0_may_not_be_used_in_a_static_property_s_initializer_in_the_same_class_when_target_is_esnext_and_useDefineForClassFields_is_false,
28442-
symbolName(lexicallyScopedSymbol));
28443-
addRelatedInfo(diagnostic,
28444-
createDiagnosticForNode(parentStaticFieldInitializer.parent,
28445-
Diagnostics.Initializer_for_property_0,
28446-
symbolName(parentStaticFieldInitializerSymbol))
28447-
);
28448-
}
28449-
}
28450-
2845128428
if (isAnyLike) {
2845228429
if (lexicallyScopedSymbol) {
2845328430
return isErrorType(apparentType) ? errorType : apparentType;
@@ -34656,9 +34633,7 @@ namespace ts {
3465634633
checkVariableLikeDeclaration(node);
3465734634

3465834635
setNodeLinksForPrivateIdentifierScope(node);
34659-
if (isPrivateIdentifier(node.name) && hasStaticModifier(node) && node.initializer && languageVersion === ScriptTarget.ESNext && !compilerOptions.useDefineForClassFields) {
34660-
error(node.initializer, Diagnostics.Static_fields_with_private_names_can_t_have_initializers_when_the_useDefineForClassFields_flag_is_not_specified_with_a_target_of_esnext_Consider_adding_the_useDefineForClassFields_flag);
34661-
}
34636+
3466234637
// property signatures already report "initializer not allowed in ambient context" elsewhere
3466334638
if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) {
3466434639
error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name));

src/compiler/diagnosticMessages.json

-8
Original file line numberDiff line numberDiff line change
@@ -3281,10 +3281,6 @@
32813281
"category": "Error",
32823282
"code": 2804
32833283
},
3284-
"Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag.": {
3285-
"category": "Error",
3286-
"code": 2805
3287-
},
32883284
"Private accessor was defined without a getter.": {
32893285
"category": "Error",
32903286
"code": 2806
@@ -3301,10 +3297,6 @@
33013297
"category": "Error",
33023298
"code": 2809
33033299
},
3304-
"Property '{0}' may not be used in a static property's initializer in the same class when 'target' is 'esnext' and 'useDefineForClassFields' is 'false'.": {
3305-
"category": "Error",
3306-
"code": 2810
3307-
},
33083300
"Initializer for property '{0}'": {
33093301
"category": "Error",
33103302
"code": 2811

src/compiler/transformers/classFields.ts

+78-20
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,13 @@ namespace ts {
135135

136136
const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ES2022;
137137

138+
// We need to transform `this` in a static initializer into a reference to the class
139+
// when targeting < ES2022 since the assignment will be moved outside of the class body.
140+
const shouldTransformThisInStaticInitializers = languageVersion < ScriptTarget.ES2022;
141+
138142
// We don't need to transform `super` property access when targeting ES5, ES3 because
139143
// the es2015 transformation handles those.
140-
const shouldTransformSuperInStaticInitializers = (languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields) && languageVersion >= ScriptTarget.ES2015;
141-
const shouldTransformThisInStaticInitializers = languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields;
144+
const shouldTransformSuperInStaticInitializers = shouldTransformThisInStaticInitializers && languageVersion >= ScriptTarget.ES2015;
142145

143146
const previousOnSubstituteNode = context.onSubstituteNode;
144147
context.onSubstituteNode = onSubstituteNode;
@@ -422,6 +425,11 @@ namespace ts {
422425

423426
if (isPrivateIdentifier(node.name)) {
424427
if (!shouldTransformPrivateElementsOrClassStaticBlocks) {
428+
if (isStatic(node)) {
429+
// static fields are left as is
430+
return visitEachChild(node, visitor, context);
431+
}
432+
425433
// Initializer is elided as the field is initialized in transformConstructor.
426434
return factory.updatePropertyDeclaration(
427435
node,
@@ -448,6 +456,28 @@ namespace ts {
448456
if (expr && !isSimpleInlineableExpression(expr)) {
449457
getPendingExpressions().push(expr);
450458
}
459+
460+
if (isStatic(node) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) {
461+
const initializerStatement = transformPropertyOrClassStaticBlock(node, factory.createThis());
462+
if (initializerStatement) {
463+
const staticBlock = factory.createClassStaticBlockDeclaration(
464+
/*decorators*/ undefined,
465+
/*modifiers*/ undefined,
466+
factory.createBlock([initializerStatement])
467+
);
468+
469+
setOriginalNode(staticBlock, node);
470+
setCommentRange(staticBlock, node);
471+
472+
// Set the comment range for the statement to an empty synthetic range
473+
// and drop synthetic comments from the statement to avoid printing them twice.
474+
setCommentRange(initializerStatement, { pos: -1, end: -1 });
475+
setSyntheticLeadingComments(initializerStatement, undefined);
476+
setSyntheticTrailingComments(initializerStatement, undefined);
477+
return staticBlock;
478+
}
479+
}
480+
451481
return undefined;
452482
}
453483

@@ -1006,8 +1036,6 @@ namespace ts {
10061036
enableSubstitutionForClassStaticThisOrSuperReference();
10071037
}
10081038

1009-
const staticProperties = getStaticPropertiesAndClassStaticBlock(node);
1010-
10111039
// If a class has private static fields, or a static field has a `this` or `super` reference,
10121040
// then we need to allocate a temp variable to hold on to that reference.
10131041
let pendingClassReferenceAssignment: BinaryExpression | undefined;
@@ -1047,6 +1075,7 @@ namespace ts {
10471075
// HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using
10481076
// a lexical declaration such as a LexicalDeclaration or a ClassDeclaration.
10491077

1078+
const staticProperties = getStaticPropertiesAndClassStaticBlock(node);
10501079
if (some(staticProperties)) {
10511080
addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node));
10521081
}
@@ -1102,7 +1131,7 @@ namespace ts {
11021131
transformClassMembers(node, isDerivedClass)
11031132
);
11041133

1105-
const hasTransformableStatics = some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(p.name)));
1134+
const hasTransformableStatics = shouldTransformPrivateElementsOrClassStaticBlocks && some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || isPrivateIdentifier(p.name));
11061135
if (hasTransformableStatics || some(pendingExpressions)) {
11071136
if (isDecoratedClassDeclaration) {
11081137
Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration.");
@@ -1156,6 +1185,7 @@ namespace ts {
11561185
}
11571186

11581187
function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
1188+
const members: ClassElement[] = [];
11591189
if (shouldTransformPrivateElementsOrClassStaticBlocks) {
11601190
// Declare private names.
11611191
for (const member of node.members) {
@@ -1169,12 +1199,26 @@ namespace ts {
11691199
}
11701200
}
11711201

1172-
const members: ClassElement[] = [];
11731202
const constructor = transformConstructor(node, isDerivedClass);
1203+
const visitedMembers = visitNodes(node.members, classElementVisitor, isClassElement);
1204+
11741205
if (constructor) {
11751206
members.push(constructor);
11761207
}
1177-
addRange(members, visitNodes(node.members, classElementVisitor, isClassElement));
1208+
1209+
if (!shouldTransformPrivateElementsOrClassStaticBlocks && some(pendingExpressions)) {
1210+
members.push(factory.createClassStaticBlockDeclaration(
1211+
/*decorators*/ undefined,
1212+
/*modifiers*/ undefined,
1213+
factory.createBlock([
1214+
factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))
1215+
])
1216+
));
1217+
pendingExpressions = undefined;
1218+
}
1219+
1220+
addRange(members, visitedMembers);
1221+
11781222
return setTextRange(factory.createNodeArray(members), /*location*/ node.members);
11791223
}
11801224

@@ -1374,27 +1418,41 @@ namespace ts {
13741418
*/
13751419
function addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) {
13761420
for (const property of properties) {
1377-
const expression = isClassStaticBlockDeclaration(property) ?
1378-
transformClassStaticBlockDeclaration(property) :
1379-
transformProperty(property, receiver);
1380-
if (!expression) {
1421+
if (isStatic(property) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) {
13811422
continue;
13821423
}
1383-
const statement = factory.createExpressionStatement(expression);
1384-
setSourceMapRange(statement, moveRangePastModifiers(property));
1385-
setCommentRange(statement, property);
1386-
setOriginalNode(statement, property);
13871424

1388-
// `setOriginalNode` *copies* the `emitNode` from `property`, so now both
1389-
// `statement` and `expression` have a copy of the synthesized comments.
1390-
// Drop the comments from expression to avoid printing them twice.
1391-
setSyntheticLeadingComments(expression, undefined);
1392-
setSyntheticTrailingComments(expression, undefined);
1425+
const statement = transformPropertyOrClassStaticBlock(property, receiver);
1426+
if (!statement) {
1427+
continue;
1428+
}
13931429

13941430
statements.push(statement);
13951431
}
13961432
}
13971433

1434+
function transformPropertyOrClassStaticBlock(property: PropertyDeclaration | ClassStaticBlockDeclaration, receiver: LeftHandSideExpression) {
1435+
const expression = isClassStaticBlockDeclaration(property) ?
1436+
transformClassStaticBlockDeclaration(property) :
1437+
transformProperty(property, receiver);
1438+
if (!expression) {
1439+
return undefined;
1440+
}
1441+
1442+
const statement = factory.createExpressionStatement(expression);
1443+
setSourceMapRange(statement, moveRangePastModifiers(property));
1444+
setCommentRange(statement, property);
1445+
setOriginalNode(statement, property);
1446+
1447+
// `setOriginalNode` *copies* the `emitNode` from `property`, so now both
1448+
// `statement` and `expression` have a copy of the synthesized comments.
1449+
// Drop the comments from expression to avoid printing them twice.
1450+
setSyntheticLeadingComments(expression, undefined);
1451+
setSyntheticTrailingComments(expression, undefined);
1452+
1453+
return statement;
1454+
}
1455+
13981456
/**
13991457
* Generates assignment expressions for property initializers.
14001458
*

tests/baselines/reference/computedPropertyName.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ class D {
5656
constructor() {
5757
this[_a] = 0; // Error
5858
}
59+
static { _a = onInit; }
5960
}
60-
_a = onInit;
6161
class E {
6262
[onInit]() { } // Error
6363
}

0 commit comments

Comments
 (0)