@samitouri / QOS-React / commits / 51497c8c59
Fork 0

The library for web and native user interfaces.

Basic ForInStatement support

First pass of ForInStatement support. This mostly just copies our handling of ForOfStatement, but the next PR updates to use a different instruction instead of `NextIterableOf`.

Joe Savona committed Sep 11, 2023 at 15:56 UTC 51497c8c59d008983a40609158f75dc793093730
24 files changed +582 -18
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+83 -1
@@ -910,6 +910,89 @@ function lowerStatement(
910 );
911 return;
912 }
913 + case "ForInStatement": {
914 + const stmt = stmtPath as NodePath<t.ForInStatement>;
915 + const continuationBlock = builder.reserve("block");
916 + const initBlock = builder.reserve("loop");
917 +
918 + const loopBlock = builder.enter("block", (_blockId) => {
919 + return builder.loop(label, initBlock.id, continuationBlock.id, () => {
920 + const body = stmt.get("body");
921 + lowerStatement(builder, body);
922 + return {
923 + kind: "goto",
924 + block: initBlock.id,
925 + variant: GotoVariant.Continue,
926 + id: makeInstructionId(0),
927 + loc: body.node.loc ?? GeneratedSource,
928 + };
929 + });
930 + });
931 +
932 + const loc = stmt.node.loc ?? GeneratedSource;
933 + const value = lowerExpressionToTemporary(builder, stmt.get("right"));
934 + builder.terminateWithContinuation(
935 + {
936 + kind: "for-in",
937 + loc,
938 + init: initBlock.id,
939 + loop: loopBlock,
940 + fallthrough: continuationBlock.id,
941 + id: makeInstructionId(0),
942 + },
943 + initBlock
944 + );
945 +
946 + // The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
947 + // right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
948 + // instructions when we handle other syntax like Patterns)
949 + const left = stmt.get("left");
950 + const leftLoc = left.node.loc ?? GeneratedSource;
951 + let test: Place;
952 + if (left.isVariableDeclaration()) {
953 + const declarations = left.get("declarations");
954 + CompilerError.invariant(declarations.length === 1, {
955 + reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
956 + description: null,
957 + loc: left.node.loc ?? null,
958 + suggestions: null,
959 + });
960 + const id = declarations[0].get("id");
961 + const nextIterableOf = lowerValueToTemporary(builder, {
962 + kind: "NextIterableOf", // TODO: change this to reflect for-in semantics (returns immutable keys, does not modify collection)
963 + loc: leftLoc,
964 + value,
965 + });
966 + const assign = lowerAssignment(
967 + builder,
968 + leftLoc,
969 + InstructionKind.Let,
970 + id,
971 + nextIterableOf
972 + );
973 + test = lowerValueToTemporary(builder, assign);
974 + } else {
975 + builder.errors.push({
976 + reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
977 + severity: ErrorSeverity.Todo,
978 + loc: left.node.loc ?? null,
979 + suggestions: null,
980 + });
981 + return;
982 + }
983 + builder.terminateWithContinuation(
984 + {
985 + id: makeInstructionId(0),
986 + kind: "branch",
987 + test,
988 + consequent: loopBlock,
989 + alternate: continuationBlock.id,
990 + loc: stmt.node.loc ?? GeneratedSource,
991 + },
992 + continuationBlock
993 + );
994 + return;
995 + }
996 case "DebuggerStatement": {
997 const stmt = stmtPath as NodePath<t.DebuggerStatement>;
998 const loc = stmt.node.loc ?? GeneratedSource;
@@ -1028,7 +1111,6 @@ function lowerStatement(
1111
1112 return;
1113 }
1031 - case "ForInStatement":
1114 case "ClassDeclaration":
1115 case "DeclareClass":
1116 case "DeclareExportAllDeclaration":
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+17
@@ -137,6 +137,7 @@ export type ReactiveTerminal =
137 | ReactiveWhileTerminal
138 | ReactiveForTerminal
139 | ReactiveForOfTerminal
140 + | ReactiveForInTerminal
141 | ReactiveIfTerminal
142 | ReactiveLabelTerminal
143 | ReactiveTryTerminal;
@@ -198,6 +199,12 @@ export type ReactiveForOfTerminal = {
199 loop: ReactiveBlock;
200 id: InstructionId;
201 };
202 +export type ReactiveForInTerminal = {
203 + kind: "for-in";
204 + init: ReactiveValue;
205 + loop: ReactiveBlock;
206 + id: InstructionId;
207 +};
208 export type ReactiveIfTerminal = {
209 kind: "if";
210 test: Place;
@@ -281,6 +288,7 @@ export type Terminal =
288 | SwitchTerminal
289 | ForTerminal
290 | ForOfTerminal
291 + | ForInTerminal
292 | DoWhileTerminal
293 | WhileTerminal
294 | LogicalTerminal
@@ -409,6 +417,15 @@ export type ForOfTerminal = {
417 id: InstructionId;
418 };
419
420 +export type ForInTerminal = {
421 + kind: "for-in";
422 + loc: SourceLocation;
423 + init: BlockId;
424 + loop: BlockId;
425 + fallthrough: BlockId;
426 + id: InstructionId;
427 +};
428 +
429 export type LogicalTerminal = {
430 kind: "logical";
431 operator: t.LogicalExpression["operator"];
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+2 -4
@@ -745,10 +745,8 @@ export function reversePostorderBlocks(func: HIR): void {
745 visit(terminal.test);
746 break;
747 }
748 - case "for": {
749 - visit(terminal.init);
750 - break;
751 - }
748 + case "for":
749 + case "for-in":
750 case "for-of": {
751 visit(terminal.init);
752 break;
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+5
@@ -97,6 +97,7 @@ export function printMixedHIR(
97 case "unsupported":
98 case "goto":
99 case "do-while":
100 + case "for-in":
101 case "for-of": {
102 const terminal = printTerminal(value);
103 if (Array.isArray(terminal)) {
@@ -218,6 +219,10 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
219 value = `[${terminal.id}] ForOf init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
220 break;
221 }
222 + case "for-in": {
223 + value = `[${terminal.id}] ForIn init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
224 + break;
225 + }
226 case "label": {
227 value = `[${terminal.id}] Label block=bb${terminal.block} fallthrough=${
228 terminal.fallthrough ? `bb${terminal.fallthrough}` : ""
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+24
@@ -708,6 +708,19 @@ export function mapTerminalSuccessors(
708 id: makeInstructionId(0),
709 };
710 }
711 + case "for-in": {
712 + const init = fn(terminal.init);
713 + const loop = fn(terminal.loop);
714 + const fallthrough = fn(terminal.fallthrough);
715 + return {
716 + kind: "for-in",
717 + loc: terminal.loc,
718 + init,
719 + loop,
720 + fallthrough,
721 + id: makeInstructionId(0),
722 + };
723 + }
724 case "label": {
725 const block = fn(terminal.block);
726 const fallthrough =
@@ -787,6 +800,7 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
800 case "try":
801 case "do-while":
802 case "for-of":
803 + case "for-in":
804 case "for":
805 case "if":
806 case "label":
@@ -832,6 +846,10 @@ export function mapOptionalFallthroughs(
846 const _: BlockId = terminal.fallthrough;
847 break;
848 }
849 + case "for-in": {
850 + const _: BlockId = terminal.fallthrough;
851 + break;
852 + }
853 case "for": {
854 const _: BlockId = terminal.fallthrough;
855 break;
@@ -946,6 +964,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
964 yield terminal.init;
965 break;
966 }
967 + case "for-in": {
968 + yield terminal.init;
969 + break;
970 + }
971 case "label": {
972 yield terminal.block;
973 break;
@@ -1020,6 +1042,7 @@ export function mapTerminalOperands(
1042 case "while":
1043 case "for":
1044 case "for-of":
1045 + case "for-in":
1046 case "goto":
1047 case "unsupported": {
1048 // no-op
@@ -1070,6 +1093,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1093 case "while":
1094 case "for":
1095 case "for-of":
1096 + case "for-in":
1097 case "goto":
1098 case "unsupported": {
1099 // no-op
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+75
@@ -499,6 +499,81 @@ class Driver {
499 }
500 break;
501 }
502 + case "for-in": {
503 + const loopId =
504 + !this.cx.isScheduled(terminal.loop) &&
505 + terminal.loop !== terminal.fallthrough
506 + ? terminal.loop
507 + : null;
508 +
509 + const fallthroughId =
510 + terminal.fallthrough !== null &&
511 + !this.cx.isScheduled(terminal.fallthrough)
512 + ? terminal.fallthrough
513 + : null;
514 +
515 + const scheduleId = this.cx.scheduleLoop(
516 + terminal.fallthrough,
517 + terminal.init,
518 + terminal.loop
519 + );
520 + scheduleIds.push(scheduleId);
521 +
522 + const init = this.visitValueBlock(terminal.init, terminal.loc);
523 + const initBlock = this.cx.ir.blocks.get(init.block)!;
524 + let initValue = init.value;
525 + if (initValue.kind === "SequenceExpression") {
526 + const last = initBlock.instructions.at(-1)!;
527 + initValue.instructions.push(last);
528 + initValue.value = {
529 + kind: "Primitive",
530 + value: undefined,
531 + loc: terminal.loc,
532 + };
533 + } else {
534 + initValue = {
535 + kind: "SequenceExpression",
536 + instructions: [initBlock.instructions.at(-1)!],
537 + id: terminal.id,
538 + loc: terminal.loc,
539 + value: {
540 + kind: "Primitive",
541 + value: undefined,
542 + loc: terminal.loc,
543 + },
544 + };
545 + }
546 +
547 + let loopBody: ReactiveBlock;
548 + if (loopId) {
549 + loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
550 + } else {
551 + const break_ = this.visitBreak(terminal.loop, null);
552 + CompilerError.invariant(break_ !== null, {
553 + reason: "If loop body is already scheduled it must be a break",
554 + description: null,
555 + loc: null,
556 + suggestions: null,
557 + });
558 + loopBody = [break_];
559 + }
560 +
561 + this.cx.unscheduleAll(scheduleIds);
562 + blockValue.push({
563 + kind: "terminal",
564 + terminal: {
565 + kind: "for-in",
566 + init: initValue,
567 + loop: loopBody,
568 + id: terminal.id,
569 + },
570 + label: fallthroughId,
571 + });
572 + if (fallthroughId !== null) {
573 + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
574 + }
575 + break;
576 + }
577 case "branch": {
578 let consequent: ReactiveBlock | null = null;
579 if (this.cx.isScheduled(terminal.consequent)) {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+22 -9
@@ -403,6 +403,7 @@ function codegenTerminal(
403 codegenBlock(cx, terminal.loop)
404 );
405 }
406 + case "for-in":
407 case "for-of": {
408 CompilerError.invariant(terminal.init.kind === "SequenceExpression", {
409 reason: `Expected a sequence expression init for ForOf`,
@@ -467,15 +468,27 @@ function codegenTerminal(
468 `Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`
469 );
470 }
470 - return t.forOfStatement(
471 - // Special handling here since we only want the VariableDeclarators without any inits
472 - // This needs to be updated when we handle non-trivial ForOf inits
473 - createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
474 - t.variableDeclarator(lval, null),
475 - ]),
476 - codegenInstructionValue(cx, iterableCollection.value),
477 - codegenBlock(cx, terminal.loop)
478 - );
471 + if (terminal.kind === "for-of") {
472 + return t.forOfStatement(
473 + // Special handling here since we only want the VariableDeclarators without any inits
474 + // This needs to be updated when we handle non-trivial ForOf inits
475 + createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
476 + t.variableDeclarator(lval, null),
477 + ]),
478 + codegenInstructionValue(cx, iterableCollection.value),
479 + codegenBlock(cx, terminal.loop)
480 + );
481 + } else {
482 + return t.forInStatement(
483 + // Special handling here since we only want the VariableDeclarators without any inits
484 + // This needs to be updated when we handle non-trivial ForOf inits
485 + createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
486 + t.variableDeclarator(lval, null),
487 + ]),
488 + codegenInstructionValue(cx, iterableCollection.value),
489 + codegenBlock(cx, terminal.loop)
490 + );
491 + }
492 }
493 case "if": {
494 const test = codegenPlace(cx, terminal.test);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+2 -1
@@ -49,7 +49,8 @@ class Transform extends ReactiveFunctionTransform<boolean> {
49 case "do-while":
50 case "while":
51 case "for":
52 - case "for-of": {
52 + case "for-of":
53 + case "for-in": {
54 this.traverseTerminal(stmt, true);
55 break;
56 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
@@ -262,6 +262,14 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
262 writer.writeLine("}");
263 break;
264 }
265 + case "for-in": {
266 + writer.writeLine(`[${terminal.id}] for-in (`);
267 + printReactiveValue(writer, terminal.init);
268 + writer.writeLine(") {");
269 + printReactiveInstructions(writer, terminal.loop);
270 + writer.writeLine("}");
271 + break;
272 + }
273 case "throw": {
274 writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
275 break;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+7
@@ -612,6 +612,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
612 });
613 break;
614 }
615 + case "for-in": {
616 + this.visitReactiveValue(context, terminal.id, terminal.init);
617 + context.enterConditional(() => {
618 + this.visitBlock(terminal.loop, context);
619 + });
620 + break;
621 + }
622 case "do-while": {
623 this.visitBlock(terminal.loop, context);
624 context.enterConditional(() => {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+9
@@ -117,6 +117,11 @@ export class ReactiveFunctionVisitor<TState = void> {
117 this.visitBlock(terminal.loop, state);
118 break;
119 }
120 + case "for-in": {
121 + this.visitValue(terminal.id, terminal.init, state);
122 + this.visitBlock(terminal.loop, state);
123 + break;
124 + }
125 case "do-while": {
126 this.visitBlock(terminal.loop, state);
127 this.visitValue(terminal.id, terminal.test, state);
@@ -342,6 +347,10 @@ export function mapTerminalBlocks(
347 terminal.loop = fn(terminal.loop);
348 break;
349 }
350 + case "for-in": {
351 + terminal.loop = fn(terminal.loop);
352 + break;
353 + }
354 case "do-while":
355 case "while": {
356 terminal.loop = fn(terminal.loop);
compiler/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+5 -1
@@ -352,7 +352,11 @@ export function leaveSSA(fn: HIRFunction): void {
352 const loop = fn.body.blocks.get(terminal.loop)!;
353 pushPhis(loop);
354 }
355 - if (terminal.kind === "for" || terminal.kind === "for-of") {
355 + if (
356 + terminal.kind === "for" ||
357 + terminal.kind === "for-of" ||
358 + terminal.kind === "for-in"
359 + ) {
360 const init = fn.body.blocks.get(terminal.init)!;
361 pushPhis(init);
362
compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts
+2
@@ -139,6 +139,7 @@ function printTerminalLabel(terminal: Terminal): string {
139 buffer.push("Sequence");
140 break;
141 }
142 + case "for-in":
143 case "try":
144 case "maybe-throw":
145 case "unsupported": {
@@ -248,6 +249,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
249 buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
250 break;
251 }
252 + case "for-in":
253 case "try":
254 case "maybe-throw":
255 case "throw":
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
-2
@@ -107,8 +107,6 @@ let moduleLocal = false;
107
108 [ReactForget] Todo: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement (40:40)
109
110 -[ReactForget] Todo: (BuildHIR::lowerStatement) Handle ForInStatement statements (43:44)
111 -
110 [ReactForget] Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (49:49)
111
112 [ReactForget] Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (50:50)
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-body-always-returns.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + for (const x in props.value) {
7 + return x;
8 + }
9 + return null;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ value: { a: "A!" } }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + for (const x in props.value) {
24 + return x;
25 + }
26 + return null;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ value: { a: "A!" } }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-body-always-returns.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + for (const x in props.value) {
3 + return x;
4 + }
5 + return null;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ value: { a: "A!" } }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-break.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + const object = { ...props.value };
8 + for (const y in object) {
9 + if (y === "break") {
10 + break;
11 + }
12 + x = object[y];
13 + }
14 + return x;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + // should return 'a'
20 + params: [{ a: "a", break: null, c: "C!" }],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { unstable_useMemoCache as useMemoCache } from "react";
29 +function Component(props) {
30 + const $ = useMemoCache(4);
31 + const c_0 = $[0] !== props.value;
32 + let x;
33 + if (c_0) {
34 + const c_2 = $[2] !== props.value;
35 + let t0;
36 + if (c_2) {
37 + t0 = { ...props.value };
38 + $[2] = props.value;
39 + $[3] = t0;
40 + } else {
41 + t0 = $[3];
42 + }
43 + const object = t0;
44 + for (const y in object) {
45 + if (y === "break") {
46 + break;
47 + }
48 +
49 + x = object[y];
50 + }
51 + $[0] = props.value;
52 + $[1] = x;
53 + } else {
54 + x = $[1];
55 + }
56 + return x;
57 +}
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + // should return 'a'
62 + params: [{ a: "a", break: null, c: "C!" }],
63 +};
64 +
65 +```
66 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-break.js new
+17
@@ -0,0 +1,17 @@
1 +function Component(props) {
2 + let x;
3 + const object = { ...props.value };
4 + for (const y in object) {
5 + if (y === "break") {
6 + break;
7 + }
8 + x = object[y];
9 + }
10 + return x;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + // should return 'a'
16 + params: [{ a: "a", break: null, c: "C!" }],
17 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + const object = { ...props.value };
8 + for (const y in object) {
9 + if (y === "continue") {
10 + continue;
11 + }
12 + x = object[y];
13 + }
14 + return x;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 +function Component(props) {
29 + const $ = useMemoCache(3);
30 + let x;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + const c_1 = $[1] !== props.value;
33 + let t0;
34 + if (c_1) {
35 + t0 = { ...props.value };
36 + $[1] = props.value;
37 + $[2] = t0;
38 + } else {
39 + t0 = $[2];
40 + }
41 + const object = t0;
42 + for (const y in object) {
43 + if (y === "continue") {
44 + continue;
45 + }
46 +
47 + x = object[y];
48 + }
49 + $[0] = x;
50 + } else {
51 + x = $[0];
52 + }
53 + return x;
54 +}
55 +
56 +export const FIXTURE_ENTRYPOINT = {
57 + fn: Component,
58 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
59 +};
60 +
61 +```
62 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.js new
+16
@@ -0,0 +1,16 @@
1 +function Component(props) {
2 + let x;
3 + const object = { ...props.value };
4 + for (const y in object) {
5 + if (y === "continue") {
6 + continue;
7 + }
8 + x = object[y];
9 + }
10 + return x;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-empty-body.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + for (const y in props.value) {
8 + }
9 + return x;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + let x;
24 + for (const y in props.value) {
25 + }
26 + return x;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-empty-body.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + let x;
3 + for (const y in props.value) {
4 + }
5 + return x;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.expect.md new
+56
@@ -0,0 +1,56 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let items = [];
7 + for (const key in props) {
8 + items.push(<div key={key}>{key}</div>);
9 + }
10 + return <div>{items}</div>;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ hello: null, world: undefined, "!": true }],
16 +};
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +import { unstable_useMemoCache as useMemoCache } from "react";
24 +function Component(props) {
25 + const $ = useMemoCache(4);
26 + const c_0 = $[0] !== props;
27 + let items;
28 + if (c_0) {
29 + items = [];
30 + for (const key in props) {
31 + items.push(<div key={key}>{key}</div>);
32 + }
33 + $[0] = props;
34 + $[1] = items;
35 + } else {
36 + items = $[1];
37 + }
38 + const c_2 = $[2] !== items;
39 + let t0;
40 + if (c_2) {
41 + t0 = <div>{items}</div>;
42 + $[2] = items;
43 + $[3] = t0;
44 + } else {
45 + t0 = $[3];
46 + }
47 + return t0;
48 +}
49 +
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: Component,
52 + params: [{ hello: null, world: undefined, "!": true }],
53 +};
54 +
55 +```
56 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.js new
+12
@@ -0,0 +1,12 @@
1 +function Component(props) {
2 + let items = [];
3 + for (const key in props) {
4 + items.push(<div key={key}>{key}</div>);
5 + }
6 + return <div>{items}</div>;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: Component,
11 + params: [{ hello: null, world: undefined, "!": true }],
12 +};