@@ -37,6 +37,8 @@ public final class RewriteClassMembers implements NodeTraversal.ScopedCallback,
37
37
private final SynthesizeExplicitConstructors ctorCreator ;
38
38
private final Deque <ClassRecord > classStack ;
39
39
40
+ private static final String COMP_FIELD_VAR = "$jscomp$compfield$" ;
41
+
40
42
public RewriteClassMembers (AbstractCompiler compiler ) {
41
43
this .compiler = compiler ;
42
44
this .astFactory = compiler .createAstFactory ();
@@ -61,22 +63,9 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
61
63
|| scriptFeatures .contains (Feature .CLASS_STATIC_BLOCK );
62
64
case CLASS :
63
65
Node classNameNode = NodeUtil .getNameNode (n );
64
-
65
- if (classNameNode == null ) {
66
- t .report (
67
- n , TranspilationUtil .CANNOT_CONVERT_YET , "Anonymous classes with ES2022 features" );
68
- return false ;
69
- }
70
-
71
- @ Nullable Node classInsertionPoint = getStatementDeclaringClass (n , classNameNode );
72
-
73
- if (classInsertionPoint == null ) {
74
- t .report (
75
- n ,
76
- TranspilationUtil .CANNOT_CONVERT_YET ,
77
- "Class in a non-extractable location with ES2022 features" );
78
- return false ;
79
- }
66
+ checkState (classNameNode != null , "Class missing a name: %s" , n );
67
+ Node classInsertionPoint = getStatementDeclaringClass (n , classNameNode );
68
+ checkState (classInsertionPoint != null , "Class was not extracted: %s" , n );
80
69
81
70
if (!n .getFirstChild ().isEmpty ()
82
71
&& !classNameNode .matchesQualifiedName (n .getFirstChild ())) {
@@ -89,14 +78,10 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
89
78
classStack .push (new ClassRecord (n , classNameNode .getQualifiedName (), classInsertionPoint ));
90
79
break ;
91
80
case COMPUTED_FIELD_DEF :
81
+ checkState (!classStack .isEmpty ());
92
82
if (NodeUtil .canBeSideEffected (n .getFirstChild ())) {
93
- t .report (
94
- n ,
95
- TranspilationUtil .CANNOT_CONVERT_YET ,
96
- "Class contains computed field with possible side effects" );
97
- return false ;
83
+ classStack .peek ().hasCompFieldWithSideEffect = true ;
98
84
}
99
- checkState (!classStack .isEmpty ());
100
85
classStack .peek ().enterField (n );
101
86
break ;
102
87
case MEMBER_FIELD_DEF :
@@ -128,6 +113,17 @@ public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
128
113
record .potentiallyRecordNameInRhs (n );
129
114
}
130
115
break ;
116
+ case COMPUTED_PROP :
117
+ checkState (!classStack .isEmpty ());
118
+ if (classStack .peek ().hasCompFieldWithSideEffect ) {
119
+ t .report (
120
+ n ,
121
+ TranspilationUtil .CANNOT_CONVERT_YET ,
122
+ "Class contains computed field with side effect and computed function" );
123
+ classStack .peek ().cannotConvert = true ;
124
+ break ;
125
+ }
126
+ break ;
131
127
default :
132
128
break ;
133
129
}
@@ -183,11 +179,54 @@ private void visitClass(NodeTraversal t) {
183
179
if (currClassRecord .cannotConvert ) {
184
180
return ;
185
181
}
186
-
187
182
rewriteInstanceMembers (t , currClassRecord );
188
183
rewriteStaticMembers (t , currClassRecord );
189
184
}
190
185
186
+ /**
187
+ * Extracts the expression in the LHS of a computed field to not disturb possible side effects and
188
+ * allow for this and super to be used in the LHS of a computed field in certain cases
189
+ *
190
+ * <p>E.g.
191
+ *
192
+ * <pre>
193
+ * class Foo {
194
+ * [bar('str')] = 4;
195
+ * }
196
+ * </pre>
197
+ *
198
+ * becomes
199
+ *
200
+ * <pre>
201
+ * var $jscomp$computedfield$0;
202
+ * class Foo {
203
+ * [$jscomp$computedfield$0] = 4;
204
+ * }
205
+ * $jscomp$computedfield$0 = bar('str');
206
+ * </pre>
207
+ */
208
+ private void extractExpressionFromCompField (
209
+ NodeTraversal t , ClassRecord record , Node memberField ) {
210
+ checkArgument (memberField .isComputedFieldDef (), memberField );
211
+ checkState (
212
+ !memberField .getFirstChild ().isName ()
213
+ || !memberField .getFirstChild ().getString ().startsWith (COMP_FIELD_VAR ),
214
+ memberField );
215
+ Node compExpression = memberField .getFirstChild ().detach ();
216
+ Node compFieldVar =
217
+ astFactory .createSingleVarNameDeclaration (
218
+ COMP_FIELD_VAR + compiler .getUniqueIdSupplier ().getUniqueId (t .getInput ()));
219
+ Node compFieldName = compFieldVar .getFirstChild ();
220
+ memberField .addChildToFront (compFieldName .cloneNode ());
221
+ compFieldVar .insertBefore (record .insertionPointBeforeClass );
222
+ compFieldVar .srcrefTreeIfMissing (record .classNode );
223
+ Node exprResult =
224
+ astFactory .exprResult (astFactory .createAssign (compFieldName .cloneNode (), compExpression ));
225
+ exprResult .insertAfter (record .insertionPointAfterClass );
226
+ record .insertionPointAfterClass = exprResult ;
227
+ exprResult .srcrefTreeIfMissing (record .classNode );
228
+ }
229
+
191
230
/** Rewrites and moves all instance fields */
192
231
private void rewriteInstanceMembers (NodeTraversal t , ClassRecord record ) {
193
232
Deque <Node > instanceMembers = record .instanceMembers ;
@@ -218,6 +257,10 @@ private void rewriteInstanceMembers(NodeTraversal t, ClassRecord record) {
218
257
}
219
258
220
259
Node thisNode = astFactory .createThisForEs6ClassMember (instanceMember );
260
+ if (instanceMember .isComputedFieldDef ()
261
+ && NodeUtil .canBeSideEffected (instanceMember .getFirstChild ())) {
262
+ extractExpressionFromCompField (t , record , instanceMember );
263
+ }
221
264
Node transpiledNode =
222
265
instanceMember .isMemberFieldDef ()
223
266
? convNonCompFieldToGetProp (thisNode , instanceMember .detach ())
@@ -256,12 +299,15 @@ private void rewriteStaticMembers(NodeTraversal t, ClassRecord record) {
256
299
transpiledNode = convNonCompFieldToGetProp (nameToUse , staticMember .detach ());
257
300
break ;
258
301
case COMPUTED_FIELD_DEF :
302
+ if (NodeUtil .canBeSideEffected (staticMember .getFirstChild ())) {
303
+ extractExpressionFromCompField (t , record , staticMember );
304
+ }
259
305
transpiledNode = convCompFieldToGetElem (nameToUse , staticMember .detach ());
260
306
break ;
261
307
default :
262
308
throw new IllegalStateException (String .valueOf (staticMember ));
263
309
}
264
- transpiledNode .insertAfter (record .classInsertionPoint );
310
+ transpiledNode .insertAfter (record .insertionPointAfterClass );
265
311
t .reportCodeChange ();
266
312
}
267
313
}
@@ -361,6 +407,11 @@ private Node findInitialInstanceInsertionPoint(Node ctorBlock) {
361
407
* Accumulates information about different classes while going down the AST in shouldTraverse()
362
408
*/
363
409
private static final class ClassRecord {
410
+
411
+ // Keeps track of if there are any computed fields with side effects in the class to throw
412
+ // an error if any computed functions are encountered because it requires a unique way to
413
+ // transpile to preserve the behavior of the side effects
414
+ boolean hasCompFieldWithSideEffect ;
364
415
// During traversal, contains the current member being traversed. After traversal, always null
365
416
@ Nullable Node currentMember ;
366
417
boolean cannotConvert ;
@@ -378,12 +429,18 @@ private static final class ClassRecord {
378
429
379
430
final Node classNode ;
380
431
final String classNameString ;
381
- final Node classInsertionPoint ;
432
+
433
+ // Keeps track of the statement declaring the class
434
+ final Node insertionPointBeforeClass ;
435
+
436
+ // Keeps track of the last statement inserted after the class
437
+ Node insertionPointAfterClass ;
382
438
383
439
ClassRecord (Node classNode , String classNameString , Node classInsertionPoint ) {
384
440
this .classNode = classNode ;
385
441
this .classNameString = classNameString ;
386
- this .classInsertionPoint = classInsertionPoint ;
442
+ this .insertionPointBeforeClass = classInsertionPoint ;
443
+ this .insertionPointAfterClass = classInsertionPoint ;
387
444
}
388
445
389
446
void enterField (Node field ) {
0 commit comments