Collect bailouts in BuildHIR::lowerExpression
Mostly mechanical changes to lowerExpression to push an error to HIRBuilder rather than invariant.
Lauren Tan committed
Jan 20, 2023 at 11:47 UTC
b2983571f284c31f74a8c34d37db9e7bd492a5e2
3 files changed
+396
-160
compiler/forget/src/HIR/BuildHIR.ts
+258
-127
@@ -811,84 +811,140 @@ function lowerExpression(
811
const expr = exprPath as NodePath<t.ObjectExpression>;
812
const propertyPaths = expr.get("properties");
813
const properties: Map<string, Place> = new Map();
814
+ let hasError = false;
815
for (const propertyPath of propertyPaths) {
815
- todoInvariant(
816
- propertyPath.isObjectProperty(),
817
- "Handle object property spread"
818
- );
816
+ if (!propertyPath.isObjectProperty()) {
817
+ builder.pushError({
818
+ reason: "Handle object property spread",
819
+ severity: ErrorSeverity.Todo,
820
+ nodePath: propertyPath,
821
+ });
822
+ hasError = true;
823
+ continue;
824
+ }
825
const key = propertyPath.node.key;
820
- invariant(key.type === "Identifier", "Unexpected private name");
826
+ if (key.type !== "Identifier") {
827
+ builder.pushError({
828
+ reason: "Unexpected private name",
829
+ severity: ErrorSeverity.InvalidInput,
830
+ nodePath: propertyPath,
831
+ });
832
+ hasError = true;
833
+ continue;
834
+ }
835
const valuePath = propertyPath.get("value");
822
- todoInvariant(
823
- valuePath.isExpression(),
824
- "Handle non-expression object values"
825
- );
836
+ if (!valuePath.isExpression()) {
837
+ builder.pushError({
838
+ reason: "Handle non-expression object values",
839
+ severity: ErrorSeverity.Todo,
840
+ nodePath: valuePath,
841
+ });
842
+ hasError = true;
843
+ continue;
844
+ }
845
const value = lowerExpressionToPlace(builder, valuePath);
846
properties.set(key.name, value);
847
}
829
- return {
830
- kind: "ObjectExpression",
831
- properties,
832
- loc: exprLoc,
833
- };
848
+ return hasError
849
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
850
+ : {
851
+ kind: "ObjectExpression",
852
+ properties,
853
+ loc: exprLoc,
854
+ };
855
}
856
case "ArrayExpression": {
857
const expr = exprPath as NodePath<t.ArrayExpression>;
837
- const elements = expr.get("elements").map((element) => {
838
- todoInvariant(
839
- element.hasNode() && element.isExpression(),
840
- "todo: handle non-expression array elements"
841
- );
842
- return lowerExpressionToPlace(builder, element);
843
- });
844
- return {
845
- kind: "ArrayExpression",
846
- elements,
847
- loc: exprLoc,
848
- };
858
+ let hasError = false;
859
+ let elements: Place[] = [];
860
+ for (const element of expr.get("elements")) {
861
+ if (!element.hasNode() || !element.isExpression()) {
862
+ builder.pushError({
863
+ reason: "Handle non-expression array elements",
864
+ severity: ErrorSeverity.Todo,
865
+ nodePath: element,
866
+ });
867
+ hasError = true;
868
+ continue;
869
+ }
870
+ elements.push(lowerExpressionToPlace(builder, element));
871
+ }
872
+ return hasError
873
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
874
+ : {
875
+ kind: "ArrayExpression",
876
+ elements,
877
+ loc: exprLoc,
878
+ };
879
}
880
case "NewExpression": {
881
const expr = exprPath as NodePath<t.NewExpression>;
882
const calleePath = expr.get("callee");
853
- invariant(
854
- calleePath.isExpression(),
855
- "Call expressions only support callees that are expressions (v8 intrinsics not supported)"
856
- );
883
+ if (!calleePath.isExpression()) {
884
+ builder.pushError({
885
+ reason:
886
+ "Call expressions only support callees that are expressions (v8 intrinsics not supported)",
887
+ severity: ErrorSeverity.InvalidInput,
888
+ nodePath: calleePath,
889
+ });
890
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
891
+ }
892
const callee = lowerExpressionToPlace(builder, calleePath);
858
- const argPaths = expr.get("arguments");
859
- const args = argPaths.map((arg) => {
860
- todoInvariant(
861
- arg.isExpression(),
862
- "todo: support non-expression call arguments"
863
- );
864
- return lowerExpressionToPlace(builder, arg);
865
- });
866
- return {
867
- kind: "NewExpression",
868
- callee,
869
- args,
870
- loc: exprLoc,
871
- };
893
+ let args: Place[] = [];
894
+ let hasError = false;
895
+ for (const argPath of expr.get("arguments")) {
896
+ if (!argPath.isExpression()) {
897
+ builder.pushError({
898
+ reason: "Support non-expression arguments to NewExpression",
899
+ severity: ErrorSeverity.Todo,
900
+ nodePath: argPath,
901
+ });
902
+ hasError = true;
903
+ continue;
904
+ }
905
+ args.push(lowerExpressionToPlace(builder, argPath));
906
+ }
907
+
908
+ return hasError
909
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
910
+ : {
911
+ kind: "NewExpression",
912
+ callee,
913
+ args,
914
+ loc: exprLoc,
915
+ };
916
}
917
case "CallExpression": {
918
const expr = exprPath as NodePath<t.CallExpression>;
919
const calleePath = expr.get("callee");
876
- invariant(
877
- calleePath.isExpression(),
878
- "Call expressions only support callees that are expressions (v8 intrinsics not supported)"
879
- );
920
+ let hasError = false;
921
+ if (!calleePath.isExpression()) {
922
+ builder.pushError({
923
+ reason:
924
+ "Call expressions only support callees that are expressions (v8 intrinsics not supported)",
925
+ severity: ErrorSeverity.InvalidInput,
926
+ nodePath: calleePath,
927
+ });
928
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
929
+ }
930
if (calleePath.isMemberExpression()) {
931
const { object, property, value } = lowerMemberExpression(
932
builder,
933
calleePath
934
);
885
- const args = expr.get("arguments").map((arg) => {
886
- todoInvariant(
887
- arg.isExpression(),
888
- "todo: support non-expression call arguments"
889
- );
890
- return lowerExpressionToPlace(builder, arg);
891
- });
935
+ let args: Place[] = [];
936
+ for (const argPath of expr.get("arguments")) {
937
+ if (!argPath.isExpression()) {
938
+ builder.pushError({
939
+ reason: "Support non-expression arguments to CallExpression",
940
+ severity: ErrorSeverity.Todo,
941
+ nodePath: argPath,
942
+ });
943
+ hasError = true;
944
+ continue;
945
+ }
946
+ args.push(lowerExpressionToPlace(builder, argPath));
947
+ }
948
if (typeof property === "string") {
949
return {
950
kind: "PropertyCall",
@@ -908,28 +964,41 @@ function lowerExpression(
964
}
965
} else {
966
const callee = lowerExpressionToPlace(builder, calleePath);
911
- const args = expr.get("arguments").map((arg) => {
912
- todoInvariant(
913
- arg.isExpression(),
914
- "todo: support non-expression call arguments"
915
- );
916
- return lowerExpressionToPlace(builder, arg);
917
- });
918
- return {
919
- kind: "CallExpression",
920
- callee,
921
- args,
922
- loc: exprLoc,
923
- };
967
+ let args: Place[] = [];
968
+ for (const argPath of expr.get("arguments")) {
969
+ if (!argPath.isExpression()) {
970
+ builder.pushError({
971
+ reason: "Support non-expression arguments to CallExpression",
972
+ severity: ErrorSeverity.Todo,
973
+ nodePath: argPath,
974
+ });
975
+ hasError = true;
976
+ continue;
977
+ }
978
+ args.push(lowerExpressionToPlace(builder, argPath));
979
+ }
980
+ return hasError
981
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
982
+ : {
983
+ kind: "CallExpression",
984
+ callee,
985
+ args,
986
+ loc: exprLoc,
987
+ };
988
}
989
}
990
case "BinaryExpression": {
991
const expr = exprPath as NodePath<t.BinaryExpression>;
992
const leftPath = expr.get("left");
929
- invariant(
930
- leftPath.isExpression(),
931
- "Private names may not appear as the left hand side of a binary expression"
932
- );
993
+ if (!leftPath.isExpression()) {
994
+ builder.pushError({
995
+ reason:
996
+ "Private names may not appear as the left hand side of a binary expression",
997
+ severity: ErrorSeverity.InvalidInput,
998
+ nodePath: leftPath,
999
+ });
1000
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1001
+ }
1002
const left = lowerExpressionToPlace(builder, leftPath);
1003
const right = lowerExpressionToPlace(builder, expr.get("right"));
1004
const operator = expr.node.operator;
@@ -944,10 +1013,15 @@ function lowerExpression(
1013
case "LogicalExpression": {
1014
const expr = exprPath as NodePath<t.LogicalExpression>;
1015
const leftPath = expr.get("left");
947
- invariant(
948
- leftPath.isExpression(),
949
- "Private names may not appear as the left hand side of a binary expression"
950
- );
1016
+ if (!leftPath.isExpression()) {
1017
+ builder.pushError({
1018
+ reason:
1019
+ "Private names may not appear as the left hand side of a logical expression",
1020
+ severity: ErrorSeverity.InvalidInput,
1021
+ nodePath: leftPath,
1022
+ });
1023
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1024
+ }
1025
const operator = expr.node.operator;
1026
switch (operator) {
1027
case "||": {
@@ -1050,11 +1124,14 @@ function lowerExpression(
1124
"^=": "^",
1125
};
1126
const binaryOperator = operators[operator];
1053
- invariant(
1054
- binaryOperator != null,
1055
- `Unhandled assignment operator '${operator}'`
1056
- );
1057
-
1127
+ if (binaryOperator == null) {
1128
+ builder.pushError({
1129
+ reason: `Unhandled assignment operator '${operator}'`,
1130
+ severity: ErrorSeverity.InvalidInput,
1131
+ nodePath: expr.get("operator"),
1132
+ });
1133
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1134
+ }
1135
const left = expr.get("left");
1136
const leftNode = left.node;
1137
switch (leftNode.type) {
@@ -1086,10 +1163,19 @@ function lowerExpression(
1163
);
1164
// Extract the final property to be read from and re-assigned, eg 'c'
1165
const property = leftExpr.get("property");
1089
- invariant(
1090
- property.isIdentifier(),
1091
- "Assignment expression to dynamic properties is not yet supported"
1092
- );
1166
+ if (!property.isIdentifier()) {
1167
+ builder.pushError({
1168
+ reason:
1169
+ "Assignment expression to dynamic properties is not yet supported",
1170
+ severity: ErrorSeverity.Todo,
1171
+ nodePath: property,
1172
+ });
1173
+ return {
1174
+ kind: "UnsupportedNode",
1175
+ node: leftExpr.node,
1176
+ loc: leftExpr.node.loc ?? GeneratedSource,
1177
+ };
1178
+ }
1179
// Store the previous value to a temporary
1180
const previousValuePlace: Place = buildTemporaryPlace(
1181
builder,
@@ -1137,10 +1223,13 @@ function lowerExpression(
1223
};
1224
}
1225
default: {
1140
- invariant(
1141
- false,
1142
- "Assignment update expressions require the lvalue to be an identifier or member expression"
1143
- );
1226
+ builder.pushError({
1227
+ reason:
1228
+ "Assignment update expressions require the lvalue to be an identifier or member expression",
1229
+ severity: ErrorSeverity.InvalidInput,
1230
+ nodePath: expr,
1231
+ });
1232
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1233
}
1234
}
1235
}
@@ -1164,36 +1253,65 @@ function lowerExpression(
1253
.get("children")
1254
.map((child) => lowerJsxElement(builder, child));
1255
const props: Map<string, Place> = new Map();
1167
- opening.get("attributes").forEach((attribute) => {
1168
- todoInvariant(attribute.isJSXAttribute(), "handle spread attributes");
1256
+ let hasError = false;
1257
+ for (const attribute of opening.get("attributes")) {
1258
+ if (!attribute.isJSXAttribute()) {
1259
+ builder.pushError({
1260
+ reason: "Handle spread attributes",
1261
+ severity: ErrorSeverity.Todo,
1262
+ nodePath: attribute,
1263
+ });
1264
+ hasError = true;
1265
+ continue;
1266
+ }
1267
const name = attribute.get("name");
1170
- todoInvariant(
1171
- name.isJSXIdentifier(),
1172
- "handle non-identifier jsx attribute names"
1173
- );
1268
+ if (!name.isJSXIdentifier()) {
1269
+ builder.pushError({
1270
+ reason: "Handle non-identifier jsx attribute names",
1271
+ severity: ErrorSeverity.Todo,
1272
+ nodePath: name,
1273
+ });
1274
+ hasError = true;
1275
+ continue;
1276
+ }
1277
const valueExpr = attribute.get("value");
1278
let value;
1279
if (valueExpr.isJSXElement() || valueExpr.isStringLiteral()) {
1280
value = lowerExpressionToPlace(builder, valueExpr);
1281
} else {
1179
- todoInvariant(
1180
- valueExpr.isJSXExpressionContainer(),
1181
- "handle other non expr containers"
1182
- );
1282
+ if (!valueExpr.isJSXExpressionContainer()) {
1283
+ builder.pushError({
1284
+ reason: "Handle other non expr containers",
1285
+ severity: ErrorSeverity.Todo,
1286
+ nodePath: valueExpr,
1287
+ });
1288
+ hasError = true;
1289
+ continue;
1290
+ }
1291
const expression = valueExpr.get("expression");
1184
- todoInvariant(expression.isExpression(), "handle empty expressions");
1292
+ if (!expression.isExpression()) {
1293
+ builder.pushError({
1294
+ reason: "Handle empty expressions",
1295
+ severity: ErrorSeverity.Todo,
1296
+ nodePath: valueExpr,
1297
+ });
1298
+ hasError = true;
1299
+ continue;
1300
+ }
1301
value = lowerExpressionToPlace(builder, expression);
1302
}
1303
const prop: string = name.node.name;
1304
props.set(prop, value);
1189
- });
1190
- return {
1191
- kind: "JsxExpression",
1192
- tag,
1193
- props,
1194
- children,
1195
- loc: exprLoc,
1196
- };
1305
+ }
1306
+ return hasError
1307
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
1308
+ : {
1309
+ kind: "JsxExpression",
1310
+ tag,
1311
+ props,
1312
+ children,
1313
+ loc: exprLoc,
1314
+ };
1315
}
1316
case "JSXFragment": {
1317
const expr = exprPath as NodePath<t.JSXFragment>;
@@ -1228,27 +1346,40 @@ function lowerExpression(
1346
}
1347
loweredFunc = lowering.unwrap();
1348
1231
- const params: Array<string> = expr.get("params").map((p) => {
1232
- todoInvariant(p.isIdentifier(), "handle non identifier params");
1233
- return p.node.name;
1234
- });
1235
- return {
1236
- kind: "FunctionExpression",
1237
- name,
1238
- body,
1239
- params,
1240
- loweredFunc,
1241
- dependencies,
1242
- mutatedDeps: [],
1243
- loc: exprLoc,
1244
- };
1349
+ let hasError = false;
1350
+ const params: Array<string> = [];
1351
+ for (const p of expr.get("params")) {
1352
+ if (!p.isIdentifier()) {
1353
+ builder.pushError({
1354
+ reason: "Handle non identifier params",
1355
+ severity: ErrorSeverity.Todo,
1356
+ nodePath: p,
1357
+ });
1358
+ hasError = true;
1359
+ continue;
1360
+ }
1361
+ params.push(p.node.name);
1362
+ }
1363
+ return hasError
1364
+ ? { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }
1365
+ : {
1366
+ kind: "FunctionExpression",
1367
+ name,
1368
+ body,
1369
+ params,
1370
+ loweredFunc,
1371
+ dependencies,
1372
+ mutatedDeps: [],
1373
+ loc: exprLoc,
1374
+ };
1375
}
1376
default: {
1247
- todo(`lowerExpression(${exprNode.type})`);
1248
- // assertExhaustive(
1249
- // exprNode,
1250
- // `Unexpected expression kind '${exprNode.type}'`
1251
- // );
1377
+ builder.pushError({
1378
+ reason: `Unhandled expression type: ${exprPath.type}`,
1379
+ severity: ErrorSeverity.Todo,
1380
+ nodePath: exprPath,
1381
+ });
1382
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1383
}
1384
}
1385
}
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
+126
-33
@@ -11,6 +11,18 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
11
with (true) {
12
}
13
14
+ const g = { ...a };
15
+ const h = [...b];
16
+ new c(...args);
17
+ c(...args);
18
+ g["e"] += 1;
19
+
20
+ <Button {...args}></Button>;
21
+ <Button xlink:href="localhost:3000"></Button>;
22
+ <Button haha={1}></Button>;
23
+
24
+ const j = function bar([quz, qux], ...args) {};
25
+
26
for (; i < 3; i += 1) {
27
x.push(i);
28
}
@@ -77,58 +89,139 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
89
| ^
90
8 | }
91
9 |
80
- 10 | for (; i < 3; i += 1) {
92
+ 10 | const g = { ...a };
93
82
-[ReactForget] TodoError: Support non-variable initialization in for
94
+[ReactForget] TodoError: Handle object property spread
95
8 | }
96
9 |
85
-> 10 | for (; i < 3; i += 1) {
97
+> 10 | const g = { ...a };
98
+ | ^^^^
99
+ 11 | const h = [...b];
100
+ 12 | new c(...args);
101
+ 13 | c(...args);
102
+
103
+[ReactForget] TodoError: Handle non-expression array elements
104
+ 9 |
105
+ 10 | const g = { ...a };
106
+> 11 | const h = [...b];
107
+ | ^^^^
108
+ 12 | new c(...args);
109
+ 13 | c(...args);
110
+ 14 | g["e"] += 1;
111
+
112
+[ReactForget] TodoError: Support non-expression arguments to NewExpression
113
+ 10 | const g = { ...a };
114
+ 11 | const h = [...b];
115
+> 12 | new c(...args);
116
+ | ^^^^^^^
117
+ 13 | c(...args);
118
+ 14 | g["e"] += 1;
119
+ 15 |
120
+
121
+[ReactForget] TodoError: Support non-expression arguments to CallExpression
122
+ 11 | const h = [...b];
123
+ 12 | new c(...args);
124
+> 13 | c(...args);
125
+ | ^^^^^^^
126
+ 14 | g["e"] += 1;
127
+ 15 |
128
+ 16 | <Button {...args}></Button>;
129
+
130
+[ReactForget] TodoError: Assignment expression to dynamic properties is not yet supported
131
+ 12 | new c(...args);
132
+ 13 | c(...args);
133
+> 14 | g["e"] += 1;
134
+ | ^^^
135
+ 15 |
136
+ 16 | <Button {...args}></Button>;
137
+ 17 | <Button xlink:href="localhost:3000"></Button>;
138
+
139
+[ReactForget] TodoError: Handle spread attributes
140
+ 14 | g["e"] += 1;
141
+ 15 |
142
+> 16 | <Button {...args}></Button>;
143
+ | ^^^^^^^^^
144
+ 17 | <Button xlink:href="localhost:3000"></Button>;
145
+ 18 | <Button haha={1}></Button>;
146
+ 19 |
147
+
148
+[ReactForget] TodoError: Handle non-identifier jsx attribute names
149
+ 15 |
150
+ 16 | <Button {...args}></Button>;
151
+> 17 | <Button xlink:href="localhost:3000"></Button>;
152
+ | ^^^^^^^^^^
153
+ 18 | <Button haha={1}></Button>;
154
+ 19 |
155
+ 20 | const j = function bar([quz, qux], ...args) {};
156
+
157
+[ReactForget] TodoError: Handle non identifier params
158
+ 18 | <Button haha={1}></Button>;
159
+ 19 |
160
+> 20 | const j = function bar([quz, qux], ...args) {};
161
+ | ^^^^^^^^^^
162
+ 21 |
163
+ 22 | for (; i < 3; i += 1) {
164
+ 23 | x.push(i);
165
+
166
+[ReactForget] TodoError: Handle non identifier params
167
+ 18 | <Button haha={1}></Button>;
168
+ 19 |
169
+> 20 | const j = function bar([quz, qux], ...args) {};
170
+ | ^^^^^^^
171
+ 21 |
172
+ 22 | for (; i < 3; i += 1) {
173
+ 23 | x.push(i);
174
+
175
+[ReactForget] TodoError: Support non-variable initialization in for
176
+ 20 | const j = function bar([quz, qux], ...args) {};
177
+ 21 |
178
+> 22 | for (; i < 3; i += 1) {
179
| ^
87
- 11 | x.push(i);
88
- 12 | }
89
- 13 | for (; i < 3; ) {}
180
+ 23 | x.push(i);
181
+ 24 | }
182
+ 25 | for (; i < 3; ) {}
183
184
[ReactForget] TodoError: Support non-variable initialization in for
92
- 11 | x.push(i);
93
- 12 | }
94
-> 13 | for (; i < 3; ) {}
185
+ 23 | x.push(i);
186
+ 24 | }
187
+> 25 | for (; i < 3; ) {}
188
| ^^^^^^^^^^^^^^^^^^
96
- 14 | for (;;) {}
97
- 15 | }
98
- 16 |
189
+ 26 | for (;;) {}
190
+ 27 | }
191
+ 28 |
192
193
[ReactForget] TodoError: Handle empty for updater
101
- 11 | x.push(i);
102
- 12 | }
103
-> 13 | for (; i < 3; ) {}
194
+ 23 | x.push(i);
195
+ 24 | }
196
+> 25 | for (; i < 3; ) {}
197
| ^^^^^^^^^^^^^^^^^^
105
- 14 | for (;;) {}
106
- 15 | }
107
- 16 |
198
+ 26 | for (;;) {}
199
+ 27 | }
200
+ 28 |
201
202
[ReactForget] TodoError: Support non-variable initialization in for
110
- 12 | }
111
- 13 | for (; i < 3; ) {}
112
-> 14 | for (;;) {}
203
+ 24 | }
204
+ 25 | for (; i < 3; ) {}
205
+> 26 | for (;;) {}
206
| ^^^^^^^^^^^
114
- 15 | }
115
- 16 |
207
+ 27 | }
208
+ 28 |
209
210
[ReactForget] TodoError: Handle empty for updater
118
- 12 | }
119
- 13 | for (; i < 3; ) {}
120
-> 14 | for (;;) {}
211
+ 24 | }
212
+ 25 | for (; i < 3; ) {}
213
+> 26 | for (;;) {}
214
| ^^^^^^^^^^^
122
- 15 | }
123
- 16 |
215
+ 27 | }
216
+ 28 |
217
218
[ReactForget] TodoError: ForStatement without test
126
- 12 | }
127
- 13 | for (; i < 3; ) {}
128
-> 14 | for (;;) {}
219
+ 24 | }
220
+ 25 | for (; i < 3; ) {}
221
+> 26 | for (;;) {}
222
| ^^^^^^^^^^^
130
- 15 | }
131
- 16 |
223
+ 27 | }
224
+ 28 |
225
```
226
227
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js
+12
@@ -7,6 +7,18 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
7
with (true) {
8
}
9
10
+ const g = { ...a };
11
+ const h = [...b];
12
+ new c(...args);
13
+ c(...args);
14
+ g["e"] += 1;
15
+
16
+ <Button {...args}></Button>;
17
+ <Button xlink:href="localhost:3000"></Button>;
18
+ <Button haha={1}></Button>;
19
+
20
+ const j = function bar([quz, qux], ...args) {};
21
+
22
for (; i < 3; i += 1) {
23
x.push(i);
24
}