@samitouri / QOS-React-1 / commits / 1ad0460693

[sprout] Add shared runtime, improve error reporting

--- I added ~20 more tests to Sprout to get more of a feel for what the test framework would need to support all fixtures. I'm relatively confident that the approach outlined in [the original workplace post](https://fburl.com/workplace/ftu8woch) works to migrate almost all existing fixture tests to sprout (TLDR: use shared functions when possible, otherwise write helper functions in-file). Add `shared-runtime` file to reduce the amount of overhead needed to set up a fixture test. - All generalizable functions and values should be added here (e.g. `shallowCopy`, `deepCopy`, `sum`). - Editor integration is set up through a custom tsconfig, so importing from `shared-runtime` should just work (with hover annotations, click-to-definition, etc). <img width="682" alt="Screenshot 2023-08-17 at 11 09 47 AM" src="https://github.com/facebook/react-forget/assets/34200447/78c3dff9-ba10-4057-b3f6-2fa842d19b1d"> --- Tested new test fixtures added to sprout by adding them to `testfilter.txt` and running with `--verbose --filter`. Note that there are no unexpected exceptions, and all logs + returned values match. ``` feifei0@feifei0-mbp babel-plugin-react-forget % yarn sprout:build && yarn sprout --verbose --filter yarn run v1.22.19 $ yarn workspace sprout run build $ rimraf dist && tsc --build ✨ Done in 2.07s. yarn run v1.22.19 $ node ../sprout/dist/main.js --verbose --filter PASS console-readonly ok {"a":1,"b":2} [ "{ a: 1, b: 2 }", "{ a: 1, b: 2 }", "{ a: 1, b: 2 }", "{ a: 1, b: 2 }", "{ a: 1, b: 2 }" ] PASS constant-propagate-global-phis-constant ok <div>global string 0</div> PASS constant-propagate-global-phis ok <div>global string 1</div> PASS dce-loop ok 10 PASS destructure-capture-global ok {"a":"value 1","someGlobal":{}} PASS destructuring-mixed-scope-and-local-variables-with-default ok {"media":null,"allUrls":["url1","url2","url3"],"onClick":"[[ function params=1 ]]"} PASS holey-array-expr ok [null,"global string 0",{"a":1,"b":2}] PASS infer-global-object ok {"primitiveVal1":2,"primitiveVal2":null,"primitiveVal3":null} PASS infer-phi-primitive ok 1 PASS infer-types-through-type-cast.flow ok 4 PASS issue933-disjoint-set-infinite-loop ok [2] PASS jsx-tag-evaluation-order-non-global ok <div>StaticText1<div>StaticText2</div></div> PASS jsx-tag-evaluation-order ok <div>StaticText1string value 1<div>StaticText2</div></div> PASS method-call ok 4 PASS mutable-lifetime-loops ok {"a":{"value":6},"b":{"value":5},"c":{"value":4},"d":{"value":6}} PASS mutable-lifetime-with-aliasing ok {"b":[{}],"value":[{"c":{},"value":"[[ cyclic ref *0 ]]"},null]} PASS update-expression-in-sequence ok [4,2,3,4] PASS update-expression-on-function-parameter ok [4,1,4,2,4,3,1,4,4] PASS update-expression ok {"x":1,"y":1,"z":2} PASS useMemo-multiple-if-else ok 2 20 Tests, 20 Passed, 0 Failed ✨ Done in 4.11s. ```

Mofei Zhang committed Aug 17, 2023 at 13:28 UTC 1ad0460693e61646c05d7a214be823ebcf7b6c3a
50 files changed +1143 -461
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/console-readonly.expect.md
+18 -2
@@ -2,8 +2,10 @@
2 ## Input
3
4 ```javascript
5 +import { shallowCopy } from "shared-runtime";
6 +
7 function Component(props) {
6 - const x = makeObject(props);
8 + const x = shallowCopy(props);
9 // These calls should view x as readonly and be grouped outside of the reactive scope for x:
10 console.log(x);
11 console.info(x);
@@ -14,18 +16,26 @@ function Component(props) {
16 return x;
17 }
18
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: Component,
21 + params: [{ a: 1, b: 2 }],
22 + isComponent: false,
23 +};
24 +
25 ```
26
27 ## Code
28
29 ```javascript
30 import { unstable_useMemoCache as useMemoCache } from "react";
31 +import { shallowCopy } from "shared-runtime";
32 +
33 function Component(props) {
34 const $ = useMemoCache(2);
35 const c_0 = $[0] !== props;
36 let t0;
37 if (c_0) {
28 - t0 = makeObject(props);
38 + t0 = shallowCopy(props);
39 $[0] = props;
40 $[1] = t0;
41 } else {
@@ -42,5 +52,11 @@ function Component(props) {
52 return x;
53 }
54
55 +export const FIXTURE_ENTRYPOINT = {
56 + fn: Component,
57 + params: [{ a: 1, b: 2 }],
58 + isComponent: false,
59 +};
60 +
61 ```
62
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/console-readonly.js
+9 -1
@@ -1,5 +1,7 @@
1 +import { shallowCopy } from "shared-runtime";
2 +
3 function Component(props) {
2 - const x = makeObject(props);
4 + const x = shallowCopy(props);
5 // These calls should view x as readonly and be grouped outside of the reactive scope for x:
6 console.log(x);
7 console.info(x);
@@ -9,3 +11,9 @@ function Component(props) {
11 console.table(x);
12 return x;
13 }
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Component,
17 + params: [{ a: 1, b: 2 }],
18 + isComponent: false,
19 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-propagate-global-phis-constant.expect.md
+28 -4
@@ -2,26 +2,44 @@
2 ## Input
3
4 ```javascript
5 +import { CONST_STRING0, Text } from "shared-runtime";
6 +function useFoo() {
7 + "use no forget";
8 + return { tab: CONST_STRING0 };
9 +}
10 +
11 function Test() {
12 const { tab } = useFoo();
7 - const currentTab = tab === WAT ? WAT : WAT;
13 + const currentTab = tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING0;
14
9 - return <Foo value={currentTab} />;
15 + return <Text value={currentTab} />;
16 }
17
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: Test,
20 + params: [],
21 + isComponent: true,
22 +};
23 +
24 ```
25
26 ## Code
27
28 ```javascript
29 import { unstable_useMemoCache as useMemoCache } from "react";
30 +import { CONST_STRING0, Text } from "shared-runtime";
31 +function useFoo() {
32 + "use no forget";
33 + return { tab: CONST_STRING0 };
34 +}
35 +
36 function Test() {
37 const $ = useMemoCache(1);
38 const { tab } = useFoo();
21 - tab === WAT ? WAT : WAT;
39 + tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING0;
40 let t0;
41 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
24 - t0 = <Foo value={WAT} />;
42 + t0 = <Text value={CONST_STRING0} />;
43 $[0] = t0;
44 } else {
45 t0 = $[0];
@@ -29,5 +47,11 @@ function Test() {
47 return t0;
48 }
49
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: Test,
52 + params: [],
53 + isComponent: true,
54 +};
55 +
56 ```
57
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-propagate-global-phis-constant.js
+14 -2
@@ -1,6 +1,18 @@
1 +import { CONST_STRING0, Text } from "shared-runtime";
2 +function useFoo() {
3 + "use no forget";
4 + return { tab: CONST_STRING0 };
5 +}
6 +
7 function Test() {
8 const { tab } = useFoo();
3 - const currentTab = tab === WAT ? WAT : WAT;
9 + const currentTab = tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING0;
10
5 - return <Foo value={currentTab} />;
11 + return <Text value={currentTab} />;
12 }
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Test,
16 + params: [],
17 + isComponent: true,
18 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-propagate-global-phis.expect.md
+30 -4
@@ -2,27 +2,47 @@
2 ## Input
3
4 ```javascript
5 +import { CONST_STRING0, CONST_STRING1, Text } from "shared-runtime";
6 +
7 +function useFoo() {
8 + "use no forget";
9 + return { tab: CONST_STRING1 };
10 +}
11 +
12 function Test() {
13 const { tab } = useFoo();
7 - const currentTab = tab === WAT ? WAT : BAR;
14 + const currentTab = tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING1;
15
9 - return <Foo value={currentTab} />;
16 + return <Text value={currentTab} />;
17 }
18
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: Test,
21 + params: [],
22 + isComponent: true,
23 +};
24 +
25 ```
26
27 ## Code
28
29 ```javascript
30 import { unstable_useMemoCache as useMemoCache } from "react";
31 +import { CONST_STRING0, CONST_STRING1, Text } from "shared-runtime";
32 +
33 +function useFoo() {
34 + "use no forget";
35 + return { tab: CONST_STRING1 };
36 +}
37 +
38 function Test() {
39 const $ = useMemoCache(2);
40 const { tab } = useFoo();
21 - const currentTab = tab === WAT ? WAT : BAR;
41 + const currentTab = tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING1;
42 const c_0 = $[0] !== currentTab;
43 let t0;
44 if (c_0) {
25 - t0 = <Foo value={currentTab} />;
45 + t0 = <Text value={currentTab} />;
46 $[0] = currentTab;
47 $[1] = t0;
48 } else {
@@ -31,5 +51,11 @@ function Test() {
51 return t0;
52 }
53
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: Test,
56 + params: [],
57 + isComponent: true,
58 +};
59 +
60 ```
61
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/constant-propagate-global-phis.js
+15 -2
@@ -1,6 +1,19 @@
1 +import { CONST_STRING0, CONST_STRING1, Text } from "shared-runtime";
2 +
3 +function useFoo() {
4 + "use no forget";
5 + return { tab: CONST_STRING1 };
6 +}
7 +
8 function Test() {
9 const { tab } = useFoo();
3 - const currentTab = tab === WAT ? WAT : BAR;
10 + const currentTab = tab === CONST_STRING0 ? CONST_STRING0 : CONST_STRING1;
11
5 - return <Foo value={currentTab} />;
12 + return <Text value={currentTab} />;
13 }
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: Test,
17 + params: [],
18 + isComponent: true,
19 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-loop.expect.md
+4 -4
@@ -14,8 +14,8 @@ function foo(props) {
14
15 export const FIXTURE_ENTRYPOINT = {
16 fn: foo,
17 - params: ["TodoAdd"],
18 - isComponent: "TodoAdd",
17 + params: [{ max: 10 }],
18 + isComponent: false,
19 };
20
21 ```
@@ -33,8 +33,8 @@ function foo(props) {
33
34 export const FIXTURE_ENTRYPOINT = {
35 fn: foo,
36 - params: ["TodoAdd"],
37 - isComponent: "TodoAdd",
36 + params: [{ max: 10 }],
37 + isComponent: false,
38 };
39
40 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/dce-loop.js
+2 -2
@@ -10,6 +10,6 @@ function foo(props) {
10
11 export const FIXTURE_ENTRYPOINT = {
12 fn: foo,
13 - params: ["TodoAdd"],
14 - isComponent: "TodoAdd",
13 + params: [{ max: 10 }],
14 + isComponent: false,
15 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-capture-global.expect.md
+12
@@ -8,6 +8,12 @@ function component(a) {
8 return x;
9 }
10
11 +export const FIXTURE_ENTRYPOINT = {
12 + fn: component,
13 + params: ["value 1"],
14 + isComponent: false,
15 +};
16 +
17 ```
18
19 ## Code
@@ -30,5 +36,11 @@ function component(a) {
36 return x;
37 }
38
39 +export const FIXTURE_ENTRYPOINT = {
40 + fn: component,
41 + params: ["value 1"],
42 + isComponent: false,
43 +};
44 +
45 ```
46
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructure-capture-global.js
+6
@@ -3,3 +3,9 @@ function component(a) {
3 let x = { a, someGlobal };
4 return x;
5 }
6 +
7 +export const FIXTURE_ENTRYPOINT = {
8 + fn: component,
9 + params: ["value 1"],
10 + isComponent: false,
11 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md
+81 -35
@@ -2,6 +2,16 @@
2 ## Input
3
4 ```javascript
5 +import { Stringify, graphql } from "shared-runtime";
6 +
7 +function useFragment(_arg1, _arg2) {
8 + "use no forget";
9 + return {
10 + urls: ["url1", "url2", "url3"],
11 + comments: ["comment1"],
12 + };
13 +}
14 +
15 function Component(props) {
16 const post = useFragment(graphql`...`, props.post);
17 const allUrls = [];
@@ -20,84 +30,120 @@ function Component(props) {
30 console.log(comments.length);
31 };
32 allUrls.push(...urls);
23 - return <Media media={media} onClick={onClick} />;
33 + return <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
34 }
35
36 +export const FIXTURE_ENTRYPOINT = {
37 + fn: Component,
38 + params: [{ post: {} }],
39 + isComponent: true,
40 +};
41 +
42 ```
43
44 ## Code
45
46 ```javascript
47 import { unstable_useMemoCache as useMemoCache } from "react";
48 +import { Stringify, graphql } from "shared-runtime";
49 +
50 +function useFragment(_arg1, _arg2) {
51 + "use no forget";
52 + return {
53 + urls: ["url1", "url2", "url3"],
54 + comments: ["comment1"],
55 + };
56 +}
57 +
58 function Component(props) {
33 - const $ = useMemoCache(12);
59 + const $ = useMemoCache(16);
60 const post = useFragment(graphql`...`, props.post);
61 const c_0 = $[0] !== post;
62 let media;
63 + let allUrls;
64 let onClick;
65 if (c_0) {
39 - const allUrls = [];
66 + allUrls = [];
67
41 - const { media: t0, comments: t2, urls: t81 } = post;
42 - const c_3 = $[3] !== t0;
68 + const { media: t0, comments: t2, urls: t4 } = post;
69 + const c_4 = $[4] !== t0;
70 let t1;
44 - if (c_3) {
71 + if (c_4) {
72 t1 = t0 === undefined ? null : t0;
46 - $[3] = t0;
47 - $[4] = t1;
73 + $[4] = t0;
74 + $[5] = t1;
75 } else {
49 - t1 = $[4];
76 + t1 = $[5];
77 }
78 media = t1;
52 - const c_5 = $[5] !== t2;
79 + const c_6 = $[6] !== t2;
80 let t3;
54 - if (c_5) {
81 + if (c_6) {
82 t3 = t2 === undefined ? [] : t2;
56 - $[5] = t2;
57 - $[6] = t3;
83 + $[6] = t2;
84 + $[7] = t3;
85 } else {
59 - t3 = $[6];
86 + t3 = $[7];
87 }
88 const comments = t3;
62 - const urls = t81 === undefined ? [] : t81;
63 - const c_7 = $[7] !== comments.length;
64 - let t4;
65 - if (c_7) {
66 - t4 = (e) => {
89 + const c_8 = $[8] !== t4;
90 + let t5;
91 + if (c_8) {
92 + t5 = t4 === undefined ? [] : t4;
93 + $[8] = t4;
94 + $[9] = t5;
95 + } else {
96 + t5 = $[9];
97 + }
98 + const urls = t5;
99 + const c_10 = $[10] !== comments.length;
100 + let t6;
101 + if (c_10) {
102 + t6 = (e) => {
103 if (!comments.length) {
104 return;
105 }
106
107 console.log(comments.length);
108 };
73 - $[7] = comments.length;
74 - $[8] = t4;
109 + $[10] = comments.length;
110 + $[11] = t6;
111 } else {
76 - t4 = $[8];
112 + t6 = $[11];
113 }
78 - onClick = t4;
114 + onClick = t6;
115
116 allUrls.push(...urls);
117 $[0] = post;
118 $[1] = media;
83 - $[2] = onClick;
119 + $[2] = allUrls;
120 + $[3] = onClick;
121 } else {
122 media = $[1];
86 - onClick = $[2];
123 + allUrls = $[2];
124 + onClick = $[3];
125 }
88 - const c_9 = $[9] !== media;
89 - const c_10 = $[10] !== onClick;
90 - let t5;
91 - if (c_9 || c_10) {
92 - t5 = <Media media={media} onClick={onClick} />;
93 - $[9] = media;
94 - $[10] = onClick;
95 - $[11] = t5;
126 + const c_12 = $[12] !== media;
127 + const c_13 = $[13] !== allUrls;
128 + const c_14 = $[14] !== onClick;
129 + let t7;
130 + if (c_12 || c_13 || c_14) {
131 + t7 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
132 + $[12] = media;
133 + $[13] = allUrls;
134 + $[14] = onClick;
135 + $[15] = t7;
136 } else {
97 - t5 = $[11];
137 + t7 = $[15];
138 }
99 - return t5;
139 + return t7;
140 }
141
142 +export const FIXTURE_ENTRYPOINT = {
143 + fn: Component,
144 + params: [{ post: {} }],
145 + isComponent: true,
146 +};
147 +
148 ```
149
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.js
+17 -1
@@ -1,3 +1,13 @@
1 +import { Stringify, graphql } from "shared-runtime";
2 +
3 +function useFragment(_arg1, _arg2) {
4 + "use no forget";
5 + return {
6 + urls: ["url1", "url2", "url3"],
7 + comments: ["comment1"],
8 + };
9 +}
10 +
11 function Component(props) {
12 const post = useFragment(graphql`...`, props.post);
13 const allUrls = [];
@@ -16,5 +26,11 @@ function Component(props) {
26 console.log(comments.length);
27 };
28 allUrls.push(...urls);
19 - return <Media media={media} onClick={onClick} />;
29 + return <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
30 }
31 +
32 +export const FIXTURE_ENTRYPOINT = {
33 + fn: Component,
34 + params: [{ post: {} }],
35 + isComponent: true,
36 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/holey-array-expr.expect.md
+18 -2
@@ -2,23 +2,33 @@
2 ## Input
3
4 ```javascript
5 +import { CONST_STRING0 } from "shared-runtime";
6 +
7 function t(props) {
6 - let x = [, foo, props];
8 + let x = [, CONST_STRING0, props];
9 return x;
10 }
11
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: t,
14 + params: [{ a: 1, b: 2 }],
15 + isComponent: false,
16 +};
17 +
18 ```
19
20 ## Code
21
22 ```javascript
23 import { unstable_useMemoCache as useMemoCache } from "react";
24 +import { CONST_STRING0 } from "shared-runtime";
25 +
26 function t(props) {
27 const $ = useMemoCache(2);
28 const c_0 = $[0] !== props;
29 let t0;
30 if (c_0) {
21 - t0 = [, foo, props];
31 + t0 = [, CONST_STRING0, props];
32 $[0] = props;
33 $[1] = t0;
34 } else {
@@ -28,5 +38,11 @@ function t(props) {
38 return x;
39 }
40
41 +export const FIXTURE_ENTRYPOINT = {
42 + fn: t,
43 + params: [{ a: 1, b: 2 }],
44 + isComponent: false,
45 +};
46 +
47 ```
48
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/holey-array-expr.js
+9 -1
@@ -1,4 +1,12 @@
1 +import { CONST_STRING0 } from "shared-runtime";
2 +
3 function t(props) {
2 - let x = [, foo, props];
4 + let x = [, CONST_STRING0, props];
5 return x;
6 }
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: t,
10 + params: [{ a: 1, b: 2 }],
11 + isComponent: false,
12 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-global-object.expect.md
+25 -8
@@ -2,33 +2,44 @@
2 ## Input
3
4 ```javascript
5 +import { identity, sum } from "shared-runtime";
6 +
7 // Check that we correctly resolve type and effect lookups on the javascript
8 // global object.
9 function Component(props) {
8 - let neverAliasedOrMutated = foo(props.b);
10 + let neverAliasedOrMutated = identity(props.b);
11 let primitiveVal1 = Math.max(props.a, neverAliasedOrMutated);
12 let primitiveVal2 = Infinity;
11 - let primitiveVal3 = globaThis.globalThis.NaN;
13 + let primitiveVal3 = globalThis.globalThis.NaN;
14
13 - // Even though we don't know the function signature of foo,
15 + // Even though we don't know the function signature of sum,
16 // we should be able to infer that it does not mutate its inputs.
15 - foo(primitiveVal1, primitiveVal2, primitiveVal3);
17 + sum(primitiveVal1, primitiveVal2, primitiveVal3);
18 return { primitiveVal1, primitiveVal2, primitiveVal3 };
19 }
20
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: Component,
23 + params: [{ a: 1, b: 2 }],
24 + isComponent: false,
25 +};
26 +
27 ```
28
29 ## Code
30
31 ```javascript
24 -import { unstable_useMemoCache as useMemoCache } from "react"; // Check that we correctly resolve type and effect lookups on the javascript
32 +import { unstable_useMemoCache as useMemoCache } from "react";
33 +import { identity, sum } from "shared-runtime";
34 +
35 +// Check that we correctly resolve type and effect lookups on the javascript
36 // global object.
37 function Component(props) {
38 const $ = useMemoCache(4);
39 const c_0 = $[0] !== props.b;
40 let t0;
41 if (c_0) {
31 - t0 = foo(props.b);
42 + t0 = identity(props.b);
43 $[0] = props.b;
44 $[1] = t0;
45 } else {
@@ -37,9 +48,9 @@ function Component(props) {
48 const neverAliasedOrMutated = t0;
49 const primitiveVal1 = Math.max(props.a, neverAliasedOrMutated);
50
40 - const primitiveVal3 = globaThis.globalThis.NaN;
51 + const primitiveVal3 = globalThis.globalThis.NaN;
52
42 - foo(primitiveVal1, Infinity, primitiveVal3);
53 + sum(primitiveVal1, Infinity, primitiveVal3);
54 const c_2 = $[2] !== primitiveVal1;
55 let t1;
56 if (c_2) {
@@ -52,5 +63,11 @@ function Component(props) {
63 return t1;
64 }
65
66 +export const FIXTURE_ENTRYPOINT = {
67 + fn: Component,
68 + params: [{ a: 1, b: 2 }],
69 + isComponent: false,
70 +};
71 +
72 ```
73
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-global-object.js
+12 -4
@@ -1,13 +1,21 @@
1 +import { identity, sum } from "shared-runtime";
2 +
3 // Check that we correctly resolve type and effect lookups on the javascript
4 // global object.
5 function Component(props) {
4 - let neverAliasedOrMutated = foo(props.b);
6 + let neverAliasedOrMutated = identity(props.b);
7 let primitiveVal1 = Math.max(props.a, neverAliasedOrMutated);
8 let primitiveVal2 = Infinity;
7 - let primitiveVal3 = globaThis.globalThis.NaN;
9 + let primitiveVal3 = globalThis.globalThis.NaN;
10
9 - // Even though we don't know the function signature of foo,
11 + // Even though we don't know the function signature of sum,
12 // we should be able to infer that it does not mutate its inputs.
11 - foo(primitiveVal1, primitiveVal2, primitiveVal3);
13 + sum(primitiveVal1, primitiveVal2, primitiveVal3);
14 return { primitiveVal1, primitiveVal2, primitiveVal3 };
15 }
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{ a: 1, b: 2 }],
20 + isComponent: false,
21 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-phi-primitive.expect.md
+4 -4
@@ -16,8 +16,8 @@ function foo(a, b) {
16
17 export const FIXTURE_ENTRYPOINT = {
18 fn: foo,
19 - params: ["TodoAdd"],
20 - isComponent: "TodoAdd",
19 + params: [true, false],
20 + isComponent: false,
21 };
22
23 ```
@@ -39,8 +39,8 @@ function foo(a, b) {
39
40 export const FIXTURE_ENTRYPOINT = {
41 fn: foo,
42 - params: ["TodoAdd"],
43 - isComponent: "TodoAdd",
42 + params: [true, false],
43 + isComponent: false,
44 };
45
46 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-phi-primitive.js
+2 -2
@@ -12,6 +12,6 @@ function foo(a, b) {
12
13 export const FIXTURE_ENTRYPOINT = {
14 fn: foo,
15 - params: ["TodoAdd"],
16 - isComponent: "TodoAdd",
15 + params: [true, false],
16 + isComponent: false,
17 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-types-through-type-cast.flow.expect.md
+19 -2
@@ -3,23 +3,40 @@
3
4 ```javascript
5 // @flow
6 +import {getNumber} from 'shared-runtime';
7 +
8 function Component(props) {
9 // We can infer that `x` is a primitive bc it is aliased to `y`,
10 // which is used in a binary expression
9 - const x = foo();
11 + const x = getNumber();
12 const y = (x: any);
13 y + 1;
14 return x;
15 }
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{}],
20 + isComponent: false,
21 +}
22 +
23 ```
24
25 ## Code
26
27 ```javascript
28 +import { getNumber } from "shared-runtime";
29 +
30 function Component(props) {
20 - const x = foo();
31 + const x = getNumber();
32 return x;
33 }
34
35 +export const FIXTURE_ENTRYPOINT = {
36 + fn: Component,
37 + params: [{}],
38 + isComponent: false,
39 +};
40 +
41 ```
42
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/infer-types-through-type-cast.flow.js
+10 -2
@@ -1,9 +1,17 @@
1 // @flow
2 +import {getNumber} from 'shared-runtime';
3 +
4 function Component(props) {
5 // We can infer that `x` is a primitive bc it is aliased to `y`,
6 // which is used in a binary expression
5 - const x = foo();
7 + const x = getNumber();
8 const y = (x: any);
9 y + 1;
10 return x;
9 -}
\ No newline at end of file
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{}],
16 + isComponent: false,
17 +}
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/issue933-disjoint-set-infinite-loop.expect.md
+30 -1
@@ -2,6 +2,14 @@
2 ## Input
3
4 ```javascript
5 +function makeObj() {
6 + "use no forget";
7 + const result = [];
8 + result.a = { b: 2 };
9 +
10 + return result;
11 +}
12 +
13 // This caused an infinite loop in the compiler
14 function MyApp(props) {
15 const y = makeObj();
@@ -11,12 +19,27 @@ function MyApp(props) {
19 return y;
20 }
21
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: MyApp,
24 + params: [],
25 + isComponent: false,
26 +};
27 +
28 ```
29
30 ## Code
31
32 ```javascript
19 -import { unstable_useMemoCache as useMemoCache } from "react"; // This caused an infinite loop in the compiler
33 +import { unstable_useMemoCache as useMemoCache } from "react";
34 +function makeObj() {
35 + "use no forget";
36 + const result = [];
37 + result.a = { b: 2 };
38 +
39 + return result;
40 +}
41 +
42 +// This caused an infinite loop in the compiler
43 function MyApp(props) {
44 const $ = useMemoCache(1);
45 let y;
@@ -32,5 +55,11 @@ function MyApp(props) {
55 return y;
56 }
57
58 +export const FIXTURE_ENTRYPOINT = {
59 + fn: MyApp,
60 + params: [],
61 + isComponent: false,
62 +};
63 +
64 ```
65
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/issue933-disjoint-set-infinite-loop.js
+14
@@ -1,3 +1,11 @@
1 +function makeObj() {
2 + "use no forget";
3 + const result = [];
4 + result.a = { b: 2 };
5 +
6 + return result;
7 +}
8 +
9 // This caused an infinite loop in the compiler
10 function MyApp(props) {
11 const y = makeObj();
@@ -6,3 +14,9 @@ function MyApp(props) {
14 y.push(tmp2);
15 return y;
16 }
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: MyApp,
20 + params: [],
21 + isComponent: false,
22 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.expect.md
+60 -32
@@ -2,85 +2,113 @@
2 ## Input
3
4 ```javascript
5 +import { StaticText1, StaticText2 } from "shared-runtime";
6 +
7 +function MaybeMutable() {
8 + return {};
9 +}
10 +function maybeMutate(x) {}
11 +
12 function Component(props) {
13 const maybeMutable = new MaybeMutable();
14 let Tag = props.component;
15 // NOTE: the order of evaluation in the lowering is incorrect:
16 // the jsx element's tag observes `Tag` after reassignment, but should observe
17 // it before the reassignment.
18 +
19 + // Currently, Forget preserves jsx whitespace in the source text.
20 + // prettier-ignore
21 return (
12 - <Tag>
13 - {((Tag = props.alternateComponent), maybeMutate(maybeMutable))}
14 - <Tag />
15 - </Tag>
22 + <Tag>{((Tag = props.alternateComponent), maybeMutate(maybeMutable))}<Tag /></Tag>
23 );
24 }
25
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: Component,
28 + params: [{ component: StaticText1, alternateComponent: StaticText2 }],
29 + isComponent: true,
30 +};
31 +
32 ```
33
34 ## Code
35
36 ```javascript
37 import { unstable_useMemoCache as useMemoCache } from "react";
38 +import { StaticText1, StaticText2 } from "shared-runtime";
39 +
40 +function MaybeMutable() {
41 + const $ = useMemoCache(1);
42 + let t0;
43 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
44 + t0 = {};
45 + $[0] = t0;
46 + } else {
47 + t0 = $[0];
48 + }
49 + return t0;
50 +}
51 +
52 +function maybeMutate(x) {}
53 +
54 function Component(props) {
26 - const $ = useMemoCache(13);
55 + const $ = useMemoCache(11);
56 const c_0 = $[0] !== props.component;
57 const c_1 = $[1] !== props.alternateComponent;
58 let Tag;
59 let T0;
60 let t1;
32 - let t2;
61 if (c_0 || c_1) {
62 const maybeMutable = new MaybeMutable();
63 Tag = props.component;
64
65 T0 = Tag;
38 - t1 = "\n ";
39 - t2 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable));
66 + t1 = ((Tag = props.alternateComponent), maybeMutate(maybeMutable));
67 $[0] = props.component;
68 $[1] = props.alternateComponent;
69 $[2] = Tag;
70 $[3] = T0;
71 $[4] = t1;
45 - $[5] = t2;
72 } else {
73 Tag = $[2];
74 T0 = $[3];
75 t1 = $[4];
50 - t2 = $[5];
76 }
52 - const c_6 = $[6] !== Tag;
53 - let t3;
54 - if (c_6) {
55 - t3 = <Tag />;
56 - $[6] = Tag;
57 - $[7] = t3;
77 + const c_5 = $[5] !== Tag;
78 + let t2;
79 + if (c_5) {
80 + t2 = <Tag />;
81 + $[5] = Tag;
82 + $[6] = t2;
83 } else {
59 - t3 = $[7];
84 + t2 = $[6];
85 }
61 - const c_8 = $[8] !== T0;
62 - const c_9 = $[9] !== t1;
63 - const c_10 = $[10] !== t2;
64 - const c_11 = $[11] !== t3;
65 - let t4;
66 - if (c_8 || c_9 || c_10 || c_11) {
67 - t4 = (
86 + const c_7 = $[7] !== T0;
87 + const c_8 = $[8] !== t1;
88 + const c_9 = $[9] !== t2;
89 + let t3;
90 + if (c_7 || c_8 || c_9) {
91 + t3 = (
92 <T0>
93 {t1}
94 {t2}
71 - {t3}
95 </T0>
96 );
74 - $[8] = T0;
75 - $[9] = t1;
76 - $[10] = t2;
77 - $[11] = t3;
78 - $[12] = t4;
97 + $[7] = T0;
98 + $[8] = t1;
99 + $[9] = t2;
100 + $[10] = t3;
101 } else {
80 - t4 = $[12];
102 + t3 = $[10];
103 }
82 - return t4;
104 + return t3;
105 }
106
107 +export const FIXTURE_ENTRYPOINT = {
108 + fn: Component,
109 + params: [{ component: StaticText1, alternateComponent: StaticText2 }],
110 + isComponent: true,
111 +};
112 +
113 ```
114
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order-non-global.js
+17 -4
@@ -1,13 +1,26 @@
1 +import { StaticText1, StaticText2 } from "shared-runtime";
2 +
3 +function MaybeMutable() {
4 + return {};
5 +}
6 +function maybeMutate(x) {}
7 +
8 function Component(props) {
9 const maybeMutable = new MaybeMutable();
10 let Tag = props.component;
11 // NOTE: the order of evaluation in the lowering is incorrect:
12 // the jsx element's tag observes `Tag` after reassignment, but should observe
13 // it before the reassignment.
14 +
15 + // Currently, Forget preserves jsx whitespace in the source text.
16 + // prettier-ignore
17 return (
8 - <Tag>
9 - {((Tag = props.alternateComponent), maybeMutate(maybeMutable))}
10 - <Tag />
11 - </Tag>
18 + <Tag>{((Tag = props.alternateComponent), maybeMutate(maybeMutable))}<Tag /></Tag>
19 );
20 }
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: Component,
24 + params: [{ component: StaticText1, alternateComponent: StaticText2 }],
25 + isComponent: true,
26 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order.expect.md
+24 -8
@@ -2,29 +2,39 @@
2 ## Input
3
4 ```javascript
5 +import { StaticText1, StaticText2 } from "shared-runtime";
6 +
7 function Component(props) {
6 - let Tag = View;
8 + let Tag = StaticText1;
9 +
10 + // Currently, Forget preserves jsx whitespace in the source text.
11 + // prettier-ignore
12 return (
8 - <Tag>
9 - {((Tag = HScroll), props.value)}
10 - <Tag />
11 - </Tag>
13 + <Tag>{((Tag = StaticText2), props.value)}<Tag /></Tag>
14 );
15 }
16
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{ value: "string value 1" }],
20 + isComponent: true,
21 +};
22 +
23 ```
24
25 ## Code
26
27 ```javascript
28 import { unstable_useMemoCache as useMemoCache } from "react";
29 +import { StaticText1, StaticText2 } from "shared-runtime";
30 +
31 function Component(props) {
32 const $ = useMemoCache(3);
33
34 const t1 = props.value;
35 let t0;
36 if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27 - t0 = <HScroll />;
37 + t0 = <StaticText2 />;
38 $[0] = t0;
39 } else {
40 t0 = $[0];
@@ -33,10 +43,10 @@ function Component(props) {
43 let t2;
44 if (c_1) {
45 t2 = (
36 - <View>
46 + <StaticText1>
47 {t1}
48 {t0}
39 - </View>
49 + </StaticText1>
50 );
51 $[1] = t1;
52 $[2] = t2;
@@ -46,5 +56,11 @@ function Component(props) {
56 return t2;
57 }
58
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + params: [{ value: "string value 1" }],
62 + isComponent: true,
63 +};
64 +
65 ```
66
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/jsx-tag-evaluation-order.js
+13 -5
@@ -1,9 +1,17 @@
1 +import { StaticText1, StaticText2 } from "shared-runtime";
2 +
3 function Component(props) {
2 - let Tag = View;
4 + let Tag = StaticText1;
5 +
6 + // Currently, Forget preserves jsx whitespace in the source text.
7 + // prettier-ignore
8 return (
4 - <Tag>
5 - {((Tag = HScroll), props.value)}
6 - <Tag />
7 - </Tag>
9 + <Tag>{((Tag = StaticText2), props.value)}<Tag /></Tag>
10 );
11 }
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ value: "string value 1" }],
16 + isComponent: true,
17 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/method-call.expect.md
+18 -2
@@ -2,9 +2,11 @@
2 ## Input
3
4 ```javascript
5 +import { addOne, shallowCopy } from "shared-runtime";
6 +
7 function foo(a, b, c) {
8 // Construct and freeze x
7 - const x = makeObject(a);
9 + const x = shallowCopy(a);
10 <div>{x}</div>;
11
12 // y should depend on `x` and `b`
@@ -12,18 +14,26 @@ function foo(a, b, c) {
14 return y;
15 }
16
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: foo,
19 + params: [{ foo: addOne }, 3],
20 + isComponent: false,
21 +};
22 +
23 ```
24
25 ## Code
26
27 ```javascript
28 import { unstable_useMemoCache as useMemoCache } from "react";
29 +import { addOne, shallowCopy } from "shared-runtime";
30 +
31 function foo(a, b, c) {
32 const $ = useMemoCache(5);
33 const c_0 = $[0] !== a;
34 let t0;
35 if (c_0) {
26 - t0 = makeObject(a);
36 + t0 = shallowCopy(a);
37 $[0] = a;
38 $[1] = t0;
39 } else {
@@ -45,5 +55,11 @@ function foo(a, b, c) {
55 return y;
56 }
57
58 +export const FIXTURE_ENTRYPOINT = {
59 + fn: foo,
60 + params: [{ foo: addOne }, 3],
61 + isComponent: false,
62 +};
63 +
64 ```
65
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/method-call.js
+9 -1
@@ -1,9 +1,17 @@
1 +import { addOne, shallowCopy } from "shared-runtime";
2 +
3 function foo(a, b, c) {
4 // Construct and freeze x
3 - const x = makeObject(a);
5 + const x = shallowCopy(a);
6 <div>{x}</div>;
7
8 // y should depend on `x` and `b`
9 const y = x.foo(b);
10 return y;
11 }
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: foo,
15 + params: [{ foo: addOne }, 3],
16 + isComponent: false,
17 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/mutable-lifetime-loops.expect.md
+88 -28
@@ -2,10 +2,21 @@
2 ## Input
3
4 ```javascript
5 -function mutate(x, y) {}
6 -function cond(x) {}
5 +function mutate(x, y) {
6 + "use no forget";
7 + if (x != null) {
8 + x.value = (x.value ?? 0) + 1;
9 + }
10 + if (y != null) {
11 + y.value = (y.value ?? 0) + 1;
12 + }
13 +}
14 +function cond(x) {
15 + "use no forget";
16 + return x.value > 5;
17 +}
18
8 -function Component(props) {
19 +function testFunction(props) {
20 let a = {};
21 let b = {};
22 let c = {};
@@ -35,43 +46,92 @@ function Component(props) {
46 }
47
48 mutate(d, null);
49 + return { a, b, c, d };
50 }
51
52 +export const FIXTURE_ENTRYPOINT = {
53 + fn: testFunction,
54 + params: [{}],
55 + isComponent: false,
56 +};
57 +
58 ```
59
60 ## Code
61
62 ```javascript
45 -function mutate(x, y) {}
46 -function cond(x) {}
47 -
48 -function Component(props) {
49 - let a = {};
50 - let b = {};
51 - let c = {};
52 - let d = {};
53 - while (true) {
54 - const z = a;
55 - a = b;
56 - b = c;
57 - c = d;
58 - d = z;
59 - mutate(a, b);
60 - if (cond(a)) {
61 - break;
62 - }
63 +import { unstable_useMemoCache as useMemoCache } from "react";
64 +function mutate(x, y) {
65 + "use no forget";
66 + if (x != null) {
67 + x.value = (x.value ?? 0) + 1;
68 }
64 - if (a) {
69 + if (y != null) {
70 + y.value = (y.value ?? 0) + 1;
71 }
66 - if (b) {
67 - }
68 - if (c) {
72 +}
73 +function cond(x) {
74 + "use no forget";
75 + return x.value > 5;
76 +}
77 +
78 +function testFunction(props) {
79 + const $ = useMemoCache(5);
80 + let a;
81 + let b;
82 + let c;
83 + let d;
84 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
85 + a = {};
86 + b = {};
87 + c = {};
88 + d = {};
89 + while (true) {
90 + const z = a;
91 + a = b;
92 + b = c;
93 + c = d;
94 + d = z;
95 + mutate(a, b);
96 + if (cond(a)) {
97 + break;
98 + }
99 + }
100 + if (a) {
101 + }
102 + if (b) {
103 + }
104 + if (c) {
105 + }
106 + if (d) {
107 + }
108 +
109 + mutate(d, null);
110 + $[0] = a;
111 + $[1] = b;
112 + $[2] = c;
113 + $[3] = d;
114 + } else {
115 + a = $[0];
116 + b = $[1];
117 + c = $[2];
118 + d = $[3];
119 }
70 - if (d) {
120 + let t0;
121 + if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
122 + t0 = { a, b, c, d };
123 + $[4] = t0;
124 + } else {
125 + t0 = $[4];
126 }
72 -
73 - mutate(d, null);
127 + return t0;
128 }
129
130 +export const FIXTURE_ENTRYPOINT = {
131 + fn: testFunction,
132 + params: [{}],
133 + isComponent: false,
134 +};
135 +
136 ```
137
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/mutable-lifetime-loops.js
+21 -3
@@ -1,7 +1,18 @@
1 -function mutate(x, y) {}
2 -function cond(x) {}
1 +function mutate(x, y) {
2 + "use no forget";
3 + if (x != null) {
4 + x.value = (x.value ?? 0) + 1;
5 + }
6 + if (y != null) {
7 + y.value = (y.value ?? 0) + 1;
8 + }
9 +}
10 +function cond(x) {
11 + "use no forget";
12 + return x.value > 5;
13 +}
14
4 -function Component(props) {
15 +function testFunction(props) {
16 let a = {};
17 let b = {};
18 let c = {};
@@ -31,4 +42,11 @@ function Component(props) {
42 }
43
44 mutate(d, null);
45 + return { a, b, c, d };
46 }
47 +
48 +export const FIXTURE_ENTRYPOINT = {
49 + fn: testFunction,
50 + params: [{}],
51 + isComponent: false,
52 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/mutable-lifetime-with-aliasing.expect.md
+60 -20
@@ -2,7 +2,16 @@
2 ## Input
3
4 ```javascript
5 -function mutate(x, y) {}
5 +function mutate(x, y) {
6 + "use no forget";
7 + if (!Array.isArray(x.value)) {
8 + x.value = [];
9 + }
10 + x.value.push(y);
11 + if (y != null) {
12 + y.value = x;
13 + }
14 +}
15
16 function Component(props) {
17 const a = {};
@@ -31,37 +40,68 @@ function Component(props) {
40
41 // could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
42 mutate(x, null);
43 + return x;
44 }
45
46 +export const FIXTURE_ENTRYPOINT = {
47 + fn: Component,
48 + params: [{}],
49 + isComponent: false,
50 +};
51 +
52 ```
53
54 ## Code
55
56 ```javascript
41 -function mutate(x, y) {}
57 +import { unstable_useMemoCache as useMemoCache } from "react";
58 +function mutate(x, y) {
59 + "use no forget";
60 + if (!Array.isArray(x.value)) {
61 + x.value = [];
62 + }
63 + x.value.push(y);
64 + if (y != null) {
65 + y.value = x;
66 + }
67 +}
68
69 function Component(props) {
44 - const a = {};
45 - const b = [a];
46 - const c = {};
47 - const d = { c };
70 + const $ = useMemoCache(1);
71 + let x;
72 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
73 + const a = {};
74 + const b = [a];
75 + const c = {};
76 + const d = { c };
77
49 - const x = {};
50 - x.b = b;
51 - const y = mutate(x, d);
52 - if (a) {
53 - }
54 - if (b) {
55 - }
56 - if (c) {
57 - }
58 - if (d) {
59 - }
60 - if (y) {
61 - }
78 + x = {};
79 + x.b = b;
80 + const y = mutate(x, d);
81 + if (a) {
82 + }
83 + if (b) {
84 + }
85 + if (c) {
86 + }
87 + if (d) {
88 + }
89 + if (y) {
90 + }
91
63 - mutate(x, null);
92 + mutate(x, null);
93 + $[0] = x;
94 + } else {
95 + x = $[0];
96 + }
97 + return x;
98 }
99
100 +export const FIXTURE_ENTRYPOINT = {
101 + fn: Component,
102 + params: [{}],
103 + isComponent: false,
104 +};
105 +
106 ```
107
\ No newline at end of file
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/mutable-lifetime-with-aliasing.js
+17 -1
@@ -1,4 +1,13 @@
1 -function mutate(x, y) {}
1 +function mutate(x, y) {
2 + "use no forget";
3 + if (!Array.isArray(x.value)) {
4 + x.value = [];
5 + }
6 + x.value.push(y);
7 + if (y != null) {
8 + y.value = x;
9 + }
10 +}
11
12 function Component(props) {
13 const a = {};
@@ -27,4 +36,11 @@ function Component(props) {
36
37 // could in theory mutate any of a/b/c/x/z, so the above should be inferred as mutable
38 mutate(x, null);
39 + return x;
40 }
41 +
42 +export const FIXTURE_ENTRYPOINT = {
43 + fn: Component,
44 + params: [{}],
45 + isComponent: false,
46 +};
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-in-sequence.expect.md
+5 -6
@@ -2,7 +2,6 @@
2 ## Input
3
4 ```javascript
5 -// @debug
5 function Component(props) {
6 let a = props.x;
7 let b;
@@ -16,8 +15,8 @@ function Component(props) {
15
16 export const FIXTURE_ENTRYPOINT = {
17 fn: Component,
19 - params: ["TodoAdd"],
20 - isComponent: "TodoAdd",
18 + params: [{ x: 2, cond: true }],
19 + isComponent: false,
20 };
21
22 ```
@@ -25,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
24 ## Code
25
26 ```javascript
28 -import { unstable_useMemoCache as useMemoCache } from "react"; // @debug
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 function Component(props) {
29 const $ = useMemoCache(5);
30 let a = props.x;
@@ -55,8 +54,8 @@ function Component(props) {
54
55 export const FIXTURE_ENTRYPOINT = {
56 fn: Component,
58 - params: ["TodoAdd"],
59 - isComponent: "TodoAdd",
57 + params: [{ x: 2, cond: true }],
58 + isComponent: false,
59 };
60
61 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-in-sequence.js
+2 -3
@@ -1,4 +1,3 @@
1 -// @debug
1 function Component(props) {
2 let a = props.x;
3 let b;
@@ -12,6 +11,6 @@ function Component(props) {
11
12 export const FIXTURE_ENTRYPOINT = {
13 fn: Component,
15 - params: ["TodoAdd"],
16 - isComponent: "TodoAdd",
14 + params: [{ x: 2, cond: true }],
15 + isComponent: false,
16 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.expect.md
+4 -4
@@ -15,8 +15,8 @@ function Component(a, [b], { c }) {
15
16 export const FIXTURE_ENTRYPOINT = {
17 fn: Component,
18 - params: ["TodoAdd"],
19 - isComponent: "TodoAdd",
18 + params: [2, [3], { c: 4 }],
19 + isComponent: false,
20 };
21
22 ```
@@ -65,8 +65,8 @@ function Component(a, t37, t38) {
65
66 export const FIXTURE_ENTRYPOINT = {
67 fn: Component,
68 - params: ["TodoAdd"],
69 - isComponent: "TodoAdd",
68 + params: [2, [3], { c: 4 }],
69 + isComponent: false,
70 };
71
72 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression-on-function-parameter.js
+2 -2
@@ -11,6 +11,6 @@ function Component(a, [b], { c }) {
11
12 export const FIXTURE_ENTRYPOINT = {
13 fn: Component,
14 - params: ["TodoAdd"],
15 - isComponent: "TodoAdd",
14 + params: [2, [3], { c: 4 }],
15 + isComponent: false,
16 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression.expect.md
+4 -4
@@ -11,8 +11,8 @@ function foo(props) {
11
12 export const FIXTURE_ENTRYPOINT = {
13 fn: foo,
14 - params: ["TodoAdd"],
15 - isComponent: "TodoAdd",
14 + params: [{ x: 1 }],
15 + isComponent: false,
16 };
17
18 ```
@@ -44,8 +44,8 @@ function foo(props) {
44
45 export const FIXTURE_ENTRYPOINT = {
46 fn: foo,
47 - params: ["TodoAdd"],
48 - isComponent: "TodoAdd",
47 + params: [{ x: 1 }],
48 + isComponent: false,
49 };
50
51 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/update-expression.js
+2 -2
@@ -7,6 +7,6 @@ function foo(props) {
7
8 export const FIXTURE_ENTRYPOINT = {
9 fn: foo,
10 - params: ["TodoAdd"],
11 - isComponent: "TodoAdd",
10 + params: [{ x: 1 }],
11 + isComponent: false,
12 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.expect.md
+8 -5
@@ -2,6 +2,8 @@
2 ## Input
3
4 ```javascript
5 +import { useMemo } from "react";
6 +
7 function Component(props) {
8 const x = useMemo(() => {
9 let y = [];
@@ -19,8 +21,8 @@ function Component(props) {
21
22 export const FIXTURE_ENTRYPOINT = {
23 fn: Component,
22 - params: ["TodoAdd"],
23 - isComponent: "TodoAdd",
24 + params: [{ a: 1, b: 2, cond2: false }],
25 + isComponent: true,
26 };
27
28 ```
@@ -28,7 +30,8 @@ export const FIXTURE_ENTRYPOINT = {
30 ## Code
31
32 ```javascript
31 -import { unstable_useMemoCache as useMemoCache } from "react";
33 +import { useMemo, unstable_useMemoCache as useMemoCache } from "react";
34 +
35 function Component(props) {
36 const $ = useMemoCache(3);
37 let t31 = undefined;
@@ -61,8 +64,8 @@ function Component(props) {
64
65 export const FIXTURE_ENTRYPOINT = {
66 fn: Component,
64 - params: ["TodoAdd"],
65 - isComponent: "TodoAdd",
67 + params: [{ a: 1, b: 2, cond2: false }],
68 + isComponent: true,
69 };
70
71 ```
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/useMemo-multiple-if-else.js
+4 -2
@@ -1,3 +1,5 @@
1 +import { useMemo } from "react";
2 +
3 function Component(props) {
4 const x = useMemo(() => {
5 let y = [];
@@ -15,6 +17,6 @@ function Component(props) {
17
18 export const FIXTURE_ENTRYPOINT = {
19 fn: Component,
18 - params: ["TodoAdd"],
19 - isComponent: "TodoAdd",
20 + params: [{ a: 1, b: 2, cond2: false }],
21 + isComponent: true,
22 };
compiler/forget/packages/babel-plugin-react-forget/src/__tests__/fixtures/tsconfig.json new
+20
@@ -0,0 +1,20 @@
1 +// Custom tsconfig for IDE integration of fixture files.
2 +// Note that noEmit is set, as we actually rely on snap and sprout
3 +// to transform these files.
4 +{
5 + "compilerOptions": {
6 + "allowJs": true,
7 + "noEmit": true,
8 + "noImplicitAny": false,
9 + "noUncheckedIndexedAccess": false,
10 + "noUnusedParameters": false,
11 + "useUnknownInCatchVariables": false,
12 + "noUnusedLocals": false,
13 + "baseUrl": ".",
14 + "paths": {
15 + // Editor integration for sprout shared runtime files
16 + "shared-runtime": ["../../../../sprout/src/shared-runtime.ts"]
17 + }
18 + },
19 + "include": ["./compiler/**/*.js"]
20 +}
compiler/forget/packages/babel-plugin-react-forget/tsconfig.json
+1 -1
@@ -19,6 +19,6 @@
19 "sourceMap": true,
20 "composite": true
21 },
22 - "exclude": ["node_modules"],
22 + "exclude": ["node_modules", "src/__tests__/fixtures"],
23 "include": ["src/**/*.ts"]
24 }
compiler/forget/packages/sprout/README.md
+68 -14
@@ -4,10 +4,44 @@ React Forget test framework that executes compiler fixtures.
4 Currently, Sprout runs each fixture with a known set of inputs and annotations. Sprout compares execution outputs (i.e. return values and console logs) of original source code and the corresponding Forget-transformed version.
5 We hope to add fuzzing capabilities to Sprout, synthesizing sets of program inputs based on type and/or effect annotations.
6
7 -Sprout is now enabled for all fixtures! If Sprout cannot execute your fixture due to some technical limitations, add your fixture to `packages/sprout/src/SproutTodoFilter.ts` with a comment explaining why.
7 +Sprout is now enabled for all fixtures! If Sprout cannot execute your fixture due to some technical limitations, add your fixture to [`SproutTodoFilter.ts`](./src/SproutTodoFilter.ts) with a comment explaining why.
8
9 +### Sprout CLI
10 +Sprout can be run from `forget/packages/babel-plugin-react-forget`. When adding new fixtures to Sprout, please check that fixture outputs look reasonable with `yarn sprout --filter --verbose`.
11
10 -### How to use Sprout
12 +```sh
13 +# rebuild when sprout or babel-plugin-react-forget changes
14 +$ yarn sprout:build
15 +
16 +# evaluate all test fixtures not listed in SproutTodoFilter
17 +$ yarn sprout
18 +
19 +# show all sprout options
20 +$ yarn sprout --help
21 +Options:
22 + --sync Run compiler in main thread.
23 + [boolean] [default: false]
24 + --filter Evaluate fixtures in filter mode.
25 + [boolean] [default: false]
26 + --verbose Print all fixture outputs and logs.
27 + [boolean] [default: false]
28 +```
29 +
30 +Sprout can be run in filter mode with `yarn sprout --filter`. Just like Snap, Sprout expects `babel-plugin-react-forget/testfilter.txt` be formatted as such.
31 +- *first line:* `// @only` or `// @skip`
32 +- *all other lines:* a test fixture name, i.e. the relative path from the compiler fixtures dir, without a `.js` or `.expect.md` extension.
33 +
34 +Example:
35 +```c
36 +// @only
37 +console-readonly
38 +constant-propagate-global-phis
39 +dce-loop
40 +```
41 +
42 +### Adding fixtures to Sprout
43 +
44 +#### 1. Annotate fixtures.
45 Each fixture test executed by Sprout needs to export const `FIXTURE_ENTRYPOINT` object with the following type signature.
46
47 ```js
@@ -15,8 +49,12 @@ type FixtureEntrypoint<T> = {
49 // function to be invoked
50 fn: ((...params: Array<T>) => any),
51 // params to pass to fn
52 + // (if `fn` is a react component, this should be an array
53 + // with exactly one element -- props)
54 params: Array<T>,
19 - // true if fn should be rendered as a React Component (i.e. returns jsx)
55 + // True if fn returns jsx.
56 + // False if fn returns non-jsx values that can be serialized.
57 + // (e.g. regular objects, sets, maps)
58 isComponent: boolean,
59 }
60 ```
@@ -34,9 +72,26 @@ export const FIXTURE_ENTRYPOINT = {
72 };
73 ```
74
37 -Additional notes:
38 -- If your fixture needs a helper function, the current solution is to define it in the same fixture file.
75 +#### 2. Import / define helper functions.
76
77 +- Prefer importing helper functions for readability and simplicity.
78 +- Fixtures that require helper functions with specific types or mutability can define their own within the same fixture file.
79 +
80 +```js
81 +// test.js
82 +import { addOne } from 'shared-runtime';
83 +
84 +function customHelper(val1, val2) {
85 + // This directive is important, as helper functions don't
86 + // always follow the rules of React.
87 + "use no forget";
88 + // ...
89 +}
90 +
91 +// ...
92 +```
93 +
94 +#### Notes
95 - If your fixture needs to import from an external module, we currently only support importing from `react` (see Milestones todo list).
96
97 - Any fixture can use React hooks, but they need to be first imported. We may later enforce that only `isComponent: true` fixtures can use React hooks.
@@ -46,16 +101,15 @@ Additional notes:
101
102 - If your fixture wants to export multiple functions to Sprout to run, please split up the fixture into multiple files (e.g. `test-case-1`, `test-case-2`, etc).
103
49 -- Sprout currently runs each fixture in an iife to prevent variable collisions, but it does not run fixtures in isolation. Please do not write to globals or mutate React library module state in fixtures.
104 +- Sprout currently runs each fixture in an iife to prevent variable collisions, but it does not run fixtures in isolation. Please do not mutate any external state in fixtures.
105 +
106 +- Sprout does not run fixtures listed in [`SproutTodoFilter.ts`](./src/SproutTodoFilter.ts), even in filter mode.
107
108 ### Milestones:
52 -- [x] Render fixtures with React runtime / `testing-library/react`.
53 -- [x] Make Sprout CLI -runnable and report results in process exit code.
54 - After this point:
55 - - Sprout can be enabled by default and added to the Github Actions pipeline.
56 - - `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`.
57 - - All new tests should provide a `FIXTURE_ENTRYPOINT`.
58 -- [ ] Annotate `FIXTURE_ENTRYPOINT` (fn entrypoint and params) for rest of fixtures (see `SproutTodoFilter.ts`).
59 -- [ ] Edit rest of fixtures to use shared functions or define their own helpers.
109 +- [✅] Render fixtures with React runtime / `testing-library/react`.
110 +- [✅] Make Sprout CLI -runnable and report results in process exit code.
111 +- [✅] Enable Sprout by default and run it in the Github Actions pipeline.
112 +- [🚧] Make all existing test fixtures Sprout compatible (see `SproutTodoFilter.ts`). This involves each fixture being annotated with `FIXTURE_ENTRYPOINT` and using shared functions and/or defining its own helpers.
113 + - 77 done, ~410 to go
114 - [ ] *(optional)* Store Sprout output as snapshot files. i.e. each fixture could have a `fixture.js`, `fixture.snap.md`, and `fixture.sprout.md`.
115 - [ ] Add support for `fbt`.
compiler/forget/packages/sprout/package.json
+2
@@ -19,6 +19,8 @@
19 "dependencies": {
20 "@babel/generator": "7.2.0",
21 "@babel/plugin-syntax-jsx": "^7.18.6",
22 + "@babel/preset-flow": "^7.7.4",
23 + "@babel/preset-typescript": "^7.18.6",
24 "@babel/types": "^7.19.0",
25 "@parcel/watcher": "^2.1.0",
26 "@testing-library/react": "^13.4.0",
compiler/forget/packages/sprout/src/SproutTodoFilter.ts
-39
@@ -43,7 +43,6 @@ const skipFilter = new Set([
43 "conditional-set-state-in-render",
44 "constant-computed",
45 "constant-propagation-phi",
46 - "dce-loop",
46 "debugger-memoized",
47 "debugger",
48 "declare-reassign-variable-in-closure",
@@ -90,7 +89,6 @@ const skipFilter = new Set([
89 "holey-array-pattern-dce",
90 "holey-array",
91 "independently-memoize-object-property",
93 - "infer-phi-primitive",
92 "inverted-if-else",
93 "inverted-if",
94 "jsx-empty-expression",
@@ -165,14 +163,10 @@ const skipFilter = new Set([
163 "unused-logical",
164 "unused-object-element-with-rest",
165 "unused-object-element",
168 - "update-expression-in-sequence",
169 - "update-expression-on-function-parameter",
170 - "update-expression",
166 "useMemo-inlining-block-return",
167 "useMemo-inverted-if",
168 "useMemo-labeled-statement-unconditional-return",
169 "useMemo-logical",
175 - "useMemo-multiple-if-else",
170 "useMemo-nested-ifs",
171 "useMemo-switch-no-fallthrough",
172 "useMemo-switch-return",
@@ -203,22 +197,6 @@ const skipFilter = new Set([
197 "ref-current-optional-field-no-added-to-dep",
198 "ref-current-write-not-added-to-dep",
199 "rewrite-phis-in-lambda-capture-context",
206 - "rules-of-hooks-0e2214abc294",
207 - "rules-of-hooks-1ff6c3fbbc94",
208 - "rules-of-hooks-23dc7fffde57",
209 - "rules-of-hooks-2bec02ac982b",
210 - "rules-of-hooks-2e405c78cb80",
211 - "rules-of-hooks-33a6e23edac1",
212 - "rules-of-hooks-347b0dae66f1",
213 - "rules-of-hooks-485bf041f55f",
214 - "rules-of-hooks-7e52f5eec669",
215 - "rules-of-hooks-844a496db20b",
216 - "rules-of-hooks-8f1c2c3f71c9",
217 - "rules-of-hooks-9d7879272ff6",
218 - "rules-of-hooks-c1e8c7f4c191",
219 - "rules-of-hooks-c5d1f3143c4c",
220 - "rules-of-hooks-cfdfe5572fc7",
221 - "rules-of-hooks-e5dd6caf4084",
200 "sketchy-code-exhaustive-deps",
201 "ssa-property-alias-mutate",
202 "ssa-property-mutate-2",
@@ -282,11 +260,8 @@ const skipFilter = new Set([
260 "computed-call-spread",
261 "computed-load-primitive-as-dependency",
262 "computed-store-alias",
285 - "console-readonly",
263 "constant-propagation-into-function-expressions",
264 "context-variable-reassigned-outside-of-lambda",
288 - "destructure-capture-global",
289 - "destructuring-mixed-scope-and-local-variables-with-default",
265 "destructuring-mixed-scope-declarations-and-locals",
266 "destructuring-property-inference",
267 "do-while-conditional-break",
@@ -297,28 +272,21 @@ const skipFilter = new Set([
272 "function-expression-maybe-mutates-hook-return-value",
273 "function-expression-with-store-to-parameter",
274 "global-jsx-tag-lowered-between-mutations",
300 - "holey-array-expr",
275 "hook-inside-logical-expression",
276 "immutable-hooks",
277 "inadvertent-mutability-readonly-class",
278 "inadvertent-mutability-readonly-lambda",
279 "infer-computed-delete",
306 - "infer-global-object",
280 "infer-property-delete",
308 - "infer-types-through-type-cast.flow",
281 "inner-memo-value-not-promoted-to-outer-scope-dynamic",
282 "inner-memo-value-not-promoted-to-outer-scope-static",
283 "issue852",
312 - "issue933-disjoint-set-infinite-loop",
284 "jsx-member-expression-tag-grouping",
285 "jsx-member-expression",
286 "jsx-spread",
316 - "jsx-tag-evaluation-order-non-global",
317 - "jsx-tag-evaluation-order",
287 "lambda-capture-returned-alias",
288 "method-call-computed",
289 "method-call-fn-call",
321 - "method-call",
290 "nested-optional-member-expr",
291 "nested-scopes-hook-call",
292 "new-spread",
@@ -377,7 +345,6 @@ const skipFilter = new Set([
345 "repro-reassign-to-variable-without-mutable-range",
346 "repro-scope-missing-mutable-range",
347 "repro",
380 - "rules-of-hooks-0592bd574811",
348 "simple",
349 "ssa-leave-case",
350 "ssa-property-alias-alias-mutate-if",
@@ -433,8 +400,6 @@ const skipFilter = new Set([
400 "interdependent",
401 "multi-arrow-expr-export-gating-test",
402 "multi-arrow-expr-gating-test",
436 - "mutable-lifetime-loops",
437 - "mutable-lifetime-with-aliasing",
403 "mutable-liverange-loop",
404 "sequence-expression",
405 "ssa-call-jsx-2",
@@ -474,10 +439,6 @@ const skipFilter = new Set([
439 "rules-of-hooks/rules-of-hooks-e66a744cffbe",
440 "rules-of-hooks/rules-of-hooks-eacfcaa6ef89",
441 "rules-of-hooks/rules-of-hooks-fe6042f7628b",
477 -
478 - // new fixtures added during rebase
479 - "constant-propagate-global-phis-constant",
480 - "constant-propagate-global-phis"
442 ]);
443
444 export default skipFilter;
compiler/forget/packages/sprout/src/runner-evaluator.ts
+98 -89
@@ -5,8 +5,9 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -// tslint:disable:no-unused-variable */
8 import { JSDOM } from "jsdom";
9 +import { toJSON } from "./shared-runtime";
10 +import util from "util";
11 const React = require("react");
12 const render = require("@testing-library/react").render;
13
@@ -33,105 +34,113 @@ const PLACEHOLDER_VALUE = Symbol();
34 params: Array<any>;
35 }) {
36 const result = props.fn(...props.params);
36 - const seen = new Map();
37
38 - return JSON.stringify(result, (_key, val) => {
39 - if (typeof val === "function") {
40 - return `[[ function params=${val.length} ]]`;
41 - } else if (typeof val === "object") {
42 - let id = seen.get(val);
43 - if (id != null) {
44 - return `[[ cyclic ref *${id} ]]`;
45 - } else if (val instanceof Map) {
46 - return {
47 - kind: "Map",
48 - value: Array.from(val.entries()), // or with spread: value: [...value]
49 - };
50 - } else if (val instanceof Set) {
51 - return {
52 - kind: "Set",
53 - value: Array.from(val.values()), // or with spread: value: [...value]
54 - };
55 - }
56 - seen.set(val, seen.size);
57 - }
58 - return val;
59 - });
38 + return toJSON(result);
39 };
40
41 export function doEval(source: string): EvaluatorResult {
42 "use strict";
43
65 - const originalConsoleLog = console.log.bind(console);
44 + const originalConsole = globalThis.console;
45 const logs: Array<string> = [];
67 - global.console.log = (...args: Array<any>) => {
68 - logs.push(`${args}`);
46 + const mockedLog = (...args: Array<any>) => {
47 + logs.push(`${args.map((arg) => util.inspect(arg))}`);
48 };
49
71 - // source needs to be evaluated in the same scope as invoke
72 - const evalResult: any = eval(`
73 - (() => {
74 - // Exports should be overwritten by source
75 - let exports = {
76 - FIXTURE_ENTRYPOINT: {
77 - fn: globalThis.placeholderFn,
78 - params: [],
79 - isComponent: false,
80 - },
81 - };
82 - let reachedInvoke = false;
83 - try {
84 - ${source}
85 - reachedInvoke = true;
86 - if (exports.FIXTURE_ENTRYPOINT == null || exports.FIXTURE_ENTRYPOINT.fn === globalThis.placeholderFn) {
87 - return {
88 - kind: "UnexpectedError",
89 - value: 'FIXTURE_ENTRYPOINT not exported!',
90 - };
91 - } else if (exports.FIXTURE_ENTRYPOINT.isComponent) {
92 - // try to run fixture as a react component
93 - const result = render(
94 - React.createElement(
95 - exports.FIXTURE_ENTRYPOINT.fn,
96 - exports.FIXTURE_ENTRYPOINT.params)
97 - ).container.innerHTML;
50 + (globalThis.console as any) = {
51 + info: mockedLog,
52 + log: mockedLog,
53 + warn: mockedLog,
54 + error: mockedLog,
55 + table: mockedLog,
56 + trace: () => {},
57 + };
58 + try {
59 + // source needs to be evaluated in the same scope as invoke
60 + const evalResult: any = eval(`
61 + (() => {
62 + // Exports should be overwritten by source
63 + let exports = {
64 + FIXTURE_ENTRYPOINT: {
65 + fn: globalThis.placeholderFn,
66 + params: [],
67 + isComponent: false,
68 + },
69 + };
70 + let reachedInvoke = false;
71 + try {
72 + ${source}
73 + reachedInvoke = true;
74 + if (exports.FIXTURE_ENTRYPOINT == null ||
75 + exports.FIXTURE_ENTRYPOINT.fn === globalThis.placeholderFn
76 + ) {
77 + return {
78 + kind: "UnexpectedError",
79 + value: 'FIXTURE_ENTRYPOINT not exported!',
80 + };
81 + } else if (
82 + typeof exports.FIXTURE_ENTRYPOINT.fn !== "function" ||
83 + !Array.isArray(exports.FIXTURE_ENTRYPOINT.params) ||
84 + typeof exports.FIXTURE_ENTRYPOINT.isComponent !== "boolean"
85 + ) {
86 + return {
87 + kind: "UnexpectedError",
88 + value: 'Bad shape for FIXTURE_ENTRYPOINT.',
89 + };
90 + } else if (exports.FIXTURE_ENTRYPOINT.isComponent) {
91 + // try to run fixture as a react component
92 + const result = render(
93 + React.createElement(
94 + exports.FIXTURE_ENTRYPOINT.fn,
95 + exports.FIXTURE_ENTRYPOINT.params[0])
96 + ).container.innerHTML;
97
99 - return {
100 - kind: "ok",
101 - value: result ?? 'null',
102 - };
103 - } else {
104 - const result = render(
105 - React.createElement(
106 - WrapperTestComponent,
107 - exports.FIXTURE_ENTRYPOINT
108 - )
109 - ).container.innerHTML;
98 + return {
99 + kind: "ok",
100 + value: result ?? 'null',
101 + };
102 + } else {
103 + const result = render(
104 + React.createElement(
105 + WrapperTestComponent,
106 + exports.FIXTURE_ENTRYPOINT
107 + )
108 + ).container.innerHTML;
109
111 - return {
112 - kind: "ok",
113 - value: result ?? 'null',
114 - };
115 - }
116 - } catch (e) {
117 - if (!reachedInvoke) {
118 - return {
119 - kind: "UnexpectedError",
120 - value: e.toString(),
121 - };
122 - } else {
123 - return {
124 - kind: "exception",
125 - value: e.stack,
126 - };
110 + return {
111 + kind: "ok",
112 + value: result ?? 'null',
113 + };
114 + }
115 + } catch (e) {
116 + if (!reachedInvoke) {
117 + return {
118 + kind: "UnexpectedError",
119 + value: e.message,
120 + };
121 + } else {
122 + return {
123 + kind: "exception",
124 + value: e.message,
125 + };
126 + }
127 }
128 - }
129 - })()`);
128 + })()`);
129
131 - globalThis.console.log = originalConsoleLog;
132 - const result = {
133 - ...evalResult,
134 - logs,
135 - };
136 - return result;
130 + const result = {
131 + ...evalResult,
132 + logs,
133 + };
134 + return result;
135 + } catch (e) {
136 + // syntax errors will cause the eval to throw and bubble up here
137 + return {
138 + kind: "UnexpectedError",
139 + value:
140 + "Unexpected error during eval, possible syntax error?\n" + e.message,
141 + logs,
142 + };
143 + } finally {
144 + globalThis.console = originalConsole;
145 + }
146 }
compiler/forget/packages/sprout/src/runner-worker.ts
+71 -40
@@ -12,9 +12,10 @@ import {
12 TestFixture,
13 transformFixtureInput,
14 } from "fixture-test-utils";
15 -import { transformFromAstSync } from "@babel/core";
15 +import { NodePath, PluginItem, transformFromAstSync } from "@babel/core";
16 import fs from "fs/promises";
17 import * as parser from "@babel/parser";
18 +import * as t from "@babel/types";
19 import { doEval, EvaluatorResult } from "./runner-evaluator";
20
21 const { runReactForgetBabelPlugin } = require(COMPILER_PATH) as {
@@ -44,11 +45,73 @@ type TransformResult =
45 value: string;
46 };
47
48 +function transformAST(
49 + ast: t.File,
50 + sourceCode: string,
51 + filename: string,
52 + language: "typescript" | "flow",
53 + transformJSX: boolean
54 +): string {
55 + // missing more transforms
56 + const presets: Array<PluginItem> = [
57 + language === "typescript"
58 + ? "@babel/preset-typescript"
59 + : "@babel/preset-flow",
60 + ];
61 +
62 + if (transformJSX) {
63 + presets.push({
64 + plugins: ["@babel/plugin-syntax-jsx"],
65 + });
66 + }
67 + presets.push(
68 + ["@babel/preset-react", { throwIfNamespace: false }],
69 + {
70 + plugins: ["@babel/plugin-transform-modules-commonjs"],
71 + },
72 + {
73 + plugins: [
74 + function BabelPluginRewriteRequirePath() {
75 + return {
76 + visitor: {
77 + CallExpression(path: NodePath<t.CallExpression>) {
78 + const { callee } = path.node;
79 + if (callee.type === "Identifier" && callee.name === "require") {
80 + const arg = path.node.arguments[0];
81 + if (arg.type === "StringLiteral") {
82 + // rewrite to use relative import
83 + if (arg.value === "shared-runtime") {
84 + arg.value = "./shared-runtime";
85 + }
86 + }
87 + }
88 + },
89 + },
90 + };
91 + },
92 + ],
93 + }
94 + );
95 + const transformResult = transformFromAstSync(ast, sourceCode, {
96 + presets,
97 + filename: filename,
98 + });
99 +
100 + const code = transformResult?.code;
101 + if (code == null) {
102 + throw new Error(
103 + `Expected custom transform to codegen successfully, got: ${transformResult}`
104 + );
105 + }
106 + return code;
107 +}
108 function transformFixtureForget(
109 input: string,
110 basename: string
111 ): TransformResult {
112 try {
113 + const language = parseLanguage(input.split("\n", 1)[0]);
114 +
115 const forgetResult = transformFixtureInput(
116 input,
117 basename,
@@ -63,27 +126,13 @@ function transformFixtureForget(
126 };
127 }
128
66 - // missing more transforms
67 - const transformResult = transformFromAstSync(
129 + const code = transformAST(
130 forgetResult.ast,
131 forgetResult.code,
70 - {
71 - presets: [
72 - ["@babel/preset-react", { throwIfNamespace: false }],
73 - {
74 - plugins: ["@babel/plugin-transform-modules-commonjs"],
75 - },
76 - ],
77 - }
132 + basename,
133 + language,
134 + false
135 );
79 -
80 - const code = transformResult?.code;
81 - if (code == null) {
82 - return {
83 - type: "UnexpectedError",
84 - value: `Expected custom transform to codegen successfully, got: ${transformResult}`,
85 - };
86 - }
136 return {
137 type: "Ok",
138 value: code,
@@ -91,7 +140,7 @@ function transformFixtureForget(
140 } catch (e) {
141 return {
142 type: "UnexpectedError",
94 - value: e.message,
143 + value: "Error in Forget transform pipeline: " + e.message,
144 };
145 }
146 }
@@ -108,25 +157,7 @@ function transformFixtureNoForget(
157 sourceType: "module",
158 });
159
111 - const transformResult = transformFromAstSync(ast, input, {
112 - presets: [
113 - {
114 - plugins: ["@babel/plugin-syntax-jsx"],
115 - },
116 - ["@babel/preset-react", { throwIfNamespace: false }],
117 - {
118 - plugins: ["@babel/plugin-transform-modules-commonjs"],
119 - },
120 - ],
121 - });
122 -
123 - const code = transformResult?.code;
124 - if (code == null) {
125 - return {
126 - type: "UnexpectedError",
127 - value: `Expected custom transform to codegen successfully, got: ${transformResult}`,
128 - };
129 - }
160 + const code = transformAST(ast, input, basename, language, true);
161 return {
162 type: "Ok",
163 value: code,
@@ -134,7 +165,7 @@ function transformFixtureNoForget(
165 } catch (e) {
166 return {
167 type: "UnexpectedError",
137 - value: e.message,
168 + value: "Error in non-Forget transform pipeline: " + e.message,
169 };
170 }
171 }
compiler/forget/packages/sprout/src/runner.ts
+4 -4
@@ -44,7 +44,7 @@ const opts: RunnerOptions = yargs
44 .boolean("sync")
45 .describe(
46 "sync",
47 - "Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false."
47 + "Run compiler in main thread (instead of using worker threads)."
48 )
49 .default("sync", false)
50 .boolean("filter")
@@ -54,7 +54,7 @@ const opts: RunnerOptions = yargs
54 )
55 .default("filter", false)
56 .boolean("verbose")
57 - .describe("verbose", "Print results of passing fixtures.")
57 + .describe("verbose", "Print all fixture outputs and logs.")
58 .default("verbose", false)
59 .help("help")
60 .strict()
@@ -148,10 +148,10 @@ function reportResults(
148 console.log(
149 chalk.red("Difference in forget and non-forget results.") +
150 `\nExpected result: ${JSON.stringify(
151 - forgetResult,
151 + nonForgetResult,
152 undefined,
153 2
154 - )}\nFound: ${JSON.stringify(nonForgetResult, undefined, 2)}`
154 + )}\nFound: ${JSON.stringify(forgetResult, undefined, 2)}`
155 );
156 }
157 }
compiler/forget/packages/sprout/src/shared-runtime.ts new
+111
@@ -0,0 +1,111 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +import React from "react";
8 +
9 +/**
10 + * This file is meant for use by `runner-evaluator` and fixture tests.
11 + *
12 + * Any fixture test can import constants or functions exported here.
13 + * However, the import path must be the relative path from `runner-evaluator`
14 + * (which calls `eval` on each fixture) to this file.
15 + *
16 + * ```js
17 + * // test.js
18 + * import {CONST_STRING0} from './shared-runtime';
19 + *
20 + * // ...
21 + * ```
22 + */
23 +
24 +export const CONST_STRING0 = "global string 0";
25 +export const CONST_STRING1 = "global string 1";
26 +export const CONST_STRING2 = "global string 2";
27 +
28 +export const CONST_NUMBER0 = 0;
29 +export const CONST_NUMBER1 = 1;
30 +export const CONST_NUMBER2 = 2;
31 +
32 +export const CONST_TRUE = true;
33 +export const CONST_FALSE = false;
34 +
35 +export function graphql(value: string): string {
36 + return value;
37 +}
38 +
39 +export function identity<T>(x: T): T {
40 + return x;
41 +}
42 +
43 +export function getNumber(): number {
44 + return 4;
45 +}
46 +
47 +/**
48 + * Functions that do not mutate their parameters
49 + */
50 +export function shallowCopy(obj: Object): object {
51 + return Object.assign({}, obj);
52 +}
53 +
54 +export function makeObject_Primitives(): object {
55 + return { a: 0, b: "value1", c: true };
56 +}
57 +
58 +export function addOne(value: number): number {
59 + return value + 1;
60 +}
61 +
62 +export function sum(...args: Array<number>): number {
63 + return args.reduce((result, arg) => result + arg, 0);
64 +}
65 +
66 +/**
67 + * React Components
68 + */
69 +export function Text(props: { value: string; children: any }) {
70 + return React.createElement("div", null, props.value, props.children);
71 +}
72 +
73 +export function StaticText1(props: { children: any }) {
74 + return React.createElement("div", null, "StaticText1", props.children);
75 +}
76 +
77 +export function StaticText2(props: { children: any }) {
78 + return React.createElement("div", null, "StaticText2", props.children);
79 +}
80 +
81 +export function Stringify(props: any) {
82 + return toJSON(props);
83 +}
84 +
85 +// helper functions
86 +export function toJSON(value: any) {
87 + const seen = new Map();
88 +
89 + return JSON.stringify(value, (_key: string, val: any) => {
90 + if (typeof val === "function") {
91 + return `[[ function params=${val.length} ]]`;
92 + } else if (typeof val === "object") {
93 + let id = seen.get(val);
94 + if (id != null) {
95 + return `[[ cyclic ref *${id} ]]`;
96 + } else if (val instanceof Map) {
97 + return {
98 + kind: "Map",
99 + value: Array.from(val.entries()),
100 + };
101 + } else if (val instanceof Set) {
102 + return {
103 + kind: "Set",
104 + value: Array.from(val.values()),
105 + };
106 + }
107 + seen.set(val, seen.size);
108 + }
109 + return val;
110 + });
111 +}
compiler/forget/yarn.lock
+31 -59
@@ -74,28 +74,7 @@
74 semver "^5.4.1"
75 source-map "^0.5.0"
76
77 -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.1":
78 - version "7.22.10"
79 - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35"
80 - integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
81 - dependencies:
82 - "@ampproject/remapping" "^2.2.0"
83 - "@babel/code-frame" "^7.22.10"
84 - "@babel/generator" "^7.22.10"
85 - "@babel/helper-compilation-targets" "^7.22.10"
86 - "@babel/helper-module-transforms" "^7.22.9"
87 - "@babel/helpers" "^7.22.10"
88 - "@babel/parser" "^7.22.10"
89 - "@babel/template" "^7.22.5"
90 - "@babel/traverse" "^7.22.10"
91 - "@babel/types" "^7.22.10"
92 - convert-source-map "^1.7.0"
93 - debug "^4.1.0"
94 - gensync "^1.0.0-beta.2"
95 - json5 "^2.2.2"
96 - semver "^6.3.1"
97 -
98 -"@babel/core@^7.2.0":
77 +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.1", "@babel/core@^7.2.0":
78 version "7.22.10"
79 resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35"
80 integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
@@ -209,17 +188,6 @@
188 lru-cache "^5.1.1"
189 semver "^6.3.1"
190
212 -"@babel/helper-compilation-targets@^7.22.9":
213 - version "7.22.9"
214 - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892"
215 - integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==
216 - dependencies:
217 - "@babel/compat-data" "^7.22.9"
218 - "@babel/helper-validator-option" "^7.22.5"
219 - browserslist "^4.21.9"
220 - lru-cache "^5.1.1"
221 - semver "^6.3.1"
222 -
191 "@babel/helper-create-class-features-plugin@^7.18.6":
192 version "7.22.9"
193 resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236"
@@ -461,6 +429,11 @@
429 resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629"
430 integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==
431
432 +"@babel/helper-plugin-utils@^7.22.5":
433 + version "7.22.5"
434 + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
435 + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
436 +
437 "@babel/helper-remap-async-to-generator@^7.18.9":
438 version "7.18.9"
439 resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
@@ -632,15 +605,6 @@
605 "@babel/traverse" "^7.22.10"
606 "@babel/types" "^7.22.10"
607
635 -"@babel/helpers@^7.22.6":
636 - version "7.22.6"
637 - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd"
638 - integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==
639 - dependencies:
640 - "@babel/template" "^7.22.5"
641 - "@babel/traverse" "^7.22.10"
642 - "@babel/types" "^7.22.10"
643 -
608 "@babel/highlight@^7.18.6":
609 version "7.18.6"
610 resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@@ -799,6 +763,13 @@
763 dependencies:
764 "@babel/helper-plugin-utils" "^7.20.2"
765
766 +"@babel/plugin-syntax-flow@^7.22.5":
767 + version "7.22.5"
768 + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.22.5.tgz#163b820b9e7696ce134df3ee716d9c0c98035859"
769 + integrity sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==
770 + dependencies:
771 + "@babel/helper-plugin-utils" "^7.22.5"
772 +
773 "@babel/plugin-syntax-import-assertions@^7.20.0":
774 version "7.20.0"
775 resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4"
@@ -1053,6 +1024,14 @@
1024 "@babel/helper-plugin-utils" "^7.20.2"
1025 "@babel/plugin-syntax-flow" "^7.18.6"
1026
1027 +"@babel/plugin-transform-flow-strip-types@^7.22.5":
1028 + version "7.22.5"
1029 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz#0bb17110c7bf5b35a60754b2f00c58302381dee2"
1030 + integrity sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==
1031 + dependencies:
1032 + "@babel/helper-plugin-utils" "^7.22.5"
1033 + "@babel/plugin-syntax-flow" "^7.22.5"
1034 +
1035 "@babel/plugin-transform-for-of@^7.21.5":
1036 version "7.21.5"
1037 resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"
@@ -1462,6 +1441,15 @@
1441 core-js-compat "^3.30.2"
1442 semver "^6.3.0"
1443
1444 +"@babel/preset-flow@^7.7.4":
1445 + version "7.22.5"
1446 + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.22.5.tgz#876f24ab6b38bd79703a93f32020ca2162312784"
1447 + integrity sha512-ta2qZ+LSiGCrP5pgcGt8xMnnkXQrq8Sa4Ulhy06BOlF5QbLw9q5hIx7bn5MrsvyTGAfh6kTOo07Q+Pfld/8Y5Q==
1448 + dependencies:
1449 + "@babel/helper-plugin-utils" "^7.22.5"
1450 + "@babel/helper-validator-option" "^7.22.5"
1451 + "@babel/plugin-transform-flow-strip-types" "^7.22.5"
1452 +
1453 "@babel/preset-modules@^0.1.5":
1454 version "0.1.5"
1455 resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
@@ -1598,23 +1586,7 @@
1586 globals "^11.1.0"
1587 lodash "^4.17.10"
1588
1601 -"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.1", "@babel/traverse@^7.22.10", "@babel/traverse@^7.7.2":
1602 - version "7.22.10"
1603 - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa"
1604 - integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==
1605 - dependencies:
1606 - "@babel/code-frame" "^7.22.10"
1607 - "@babel/generator" "^7.22.10"
1608 - "@babel/helper-environment-visitor" "^7.22.5"
1609 - "@babel/helper-function-name" "^7.22.5"
1610 - "@babel/helper-hoist-variables" "^7.22.5"
1611 - "@babel/helper-split-export-declaration" "^7.22.6"
1612 - "@babel/parser" "^7.22.10"
1613 - "@babel/types" "^7.22.10"
1614 - debug "^4.1.0"
1615 - globals "^11.1.0"
1616 -
1617 -"@babel/traverse@^7.2.0", "@babel/traverse@^7.22.10":
1589 +"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.2.0", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.1", "@babel/traverse@^7.22.10", "@babel/traverse@^7.7.2":
1590 version "7.22.10"
1591 resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa"
1592 integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==