[compiler] Playground qol: shared compilation option directives with tests (#32012)
- Adds @compilationMode(all|infer|syntax|annotation) and @panicMode(none) directives. This is now shared with our test infra - Playground still defaults to `infer` mode while tests default to `all` mode - See added fixture tests
mofeiZ committed
Jan 9, 2025 at 12:38 UTC
d16fe4be5b9b5eee0cfb0f602ad62d6b0842d253
7 files changed
+154
-96
compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt
new
+13
@@ -0,0 +1,13 @@
1
+import { c as _c } from "react/compiler-runtime"; //
2
+ @compilationMode(all)
3
+function nonReactFn() {
4
+ const $ = _c(1);
5
+ let t0;
6
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
7
+ t0 = {};
8
+ $[0] = t0;
9
+ } else {
10
+ t0 = $[0];
11
+ }
12
+ return t0;
13
+}
\ No newline at end of file
compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt
new
+4
@@ -0,0 +1,4 @@
1
+// @compilationMode(infer)
2
+function nonReactFn() {
3
+ return {};
4
+}
\ No newline at end of file
compiler/apps/playground/__tests__/e2e/page.spec.ts
+18
@@ -79,6 +79,24 @@ function Foo() {
79
// @flow
80
function useFoo(propVal: {+baz: number}) {
81
return <div>{(propVal.baz as number)}</div>;
82
+}
83
+ `,
84
+ noFormat: true,
85
+ },
86
+ {
87
+ name: 'compilationMode-infer',
88
+ input: `// @compilationMode(infer)
89
+function nonReactFn() {
90
+ return {};
91
+}
92
+ `,
93
+ noFormat: true,
94
+ },
95
+ {
96
+ name: 'compilationMode-all',
97
+ input: `// @compilationMode(all)
98
+function nonReactFn() {
99
+ return {};
100
}
101
`,
102
noFormat: true,
compiler/apps/playground/components/Editor/EditorImpl.tsx
+53
-55
@@ -20,7 +20,6 @@ import BabelPluginReactCompiler, {
20
CompilerPipelineValue,
21
parsePluginOptions,
22
} from 'babel-plugin-react-compiler/src';
23
-import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment';
23
import clsx from 'clsx';
24
import invariant from 'invariant';
25
import {useSnackbar} from 'notistack';
@@ -69,23 +68,14 @@ function parseInput(
68
function invokeCompiler(
69
source: string,
70
language: 'flow' | 'typescript',
72
- environment: EnvironmentConfig,
73
- logIR: (pipelineValue: CompilerPipelineValue) => void,
71
+ options: PluginOptions,
72
): CompilerTransformOutput {
75
- const opts: PluginOptions = parsePluginOptions({
76
- logger: {
77
- debugLogIRs: logIR,
78
- logEvent: () => {},
79
- },
80
- environment,
81
- panicThreshold: 'all_errors',
82
- });
73
const ast = parseInput(source, language);
74
let result = transformFromAstSync(ast, source, {
75
filename: '_playgroundFile.js',
76
highlightCode: false,
77
retainLines: true,
88
- plugins: [[BabelPluginReactCompiler, opts]],
78
+ plugins: [[BabelPluginReactCompiler, options]],
79
ast: true,
80
sourceType: 'module',
81
configFile: false,
@@ -171,51 +161,59 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
161
try {
162
// Extract the first line to quickly check for custom test directives
163
const pragma = source.substring(0, source.indexOf('\n'));
174
- const config = parseConfigPragmaForTests(pragma);
175
-
176
- transformOutput = invokeCompiler(
177
- source,
178
- language,
179
- {...config, customHooks: new Map([...COMMON_HOOKS])},
180
- result => {
181
- switch (result.kind) {
182
- case 'ast': {
183
- break;
184
- }
185
- case 'hir': {
186
- upsert({
187
- kind: 'hir',
188
- fnName: result.value.id,
189
- name: result.name,
190
- value: printFunctionWithOutlined(result.value),
191
- });
192
- break;
193
- }
194
- case 'reactive': {
195
- upsert({
196
- kind: 'reactive',
197
- fnName: result.value.id,
198
- name: result.name,
199
- value: printReactiveFunctionWithOutlined(result.value),
200
- });
201
- break;
202
- }
203
- case 'debug': {
204
- upsert({
205
- kind: 'debug',
206
- fnName: null,
207
- name: result.name,
208
- value: result.value,
209
- });
210
- break;
211
- }
212
- default: {
213
- const _: never = result;
214
- throw new Error(`Unhandled result ${result}`);
215
- }
164
+ const logIR = (result: CompilerPipelineValue): void => {
165
+ switch (result.kind) {
166
+ case 'ast': {
167
+ break;
168
+ }
169
+ case 'hir': {
170
+ upsert({
171
+ kind: 'hir',
172
+ fnName: result.value.id,
173
+ name: result.name,
174
+ value: printFunctionWithOutlined(result.value),
175
+ });
176
+ break;
177
+ }
178
+ case 'reactive': {
179
+ upsert({
180
+ kind: 'reactive',
181
+ fnName: result.value.id,
182
+ name: result.name,
183
+ value: printReactiveFunctionWithOutlined(result.value),
184
+ });
185
+ break;
186
}
187
+ case 'debug': {
188
+ upsert({
189
+ kind: 'debug',
190
+ fnName: null,
191
+ name: result.name,
192
+ value: result.value,
193
+ });
194
+ break;
195
+ }
196
+ default: {
197
+ const _: never = result;
198
+ throw new Error(`Unhandled result ${result}`);
199
+ }
200
+ }
201
+ };
202
+ const parsedOptions = parseConfigPragmaForTests(pragma, {
203
+ compilationMode: 'infer',
204
+ });
205
+ const opts: PluginOptions = parsePluginOptions({
206
+ ...parsedOptions,
207
+ environment: {
208
+ ...parsedOptions.environment,
209
+ customHooks: new Map([...COMMON_HOOKS]),
210
},
218
- );
211
+ logger: {
212
+ debugLogIRs: logIR,
213
+ logEvent: () => {},
214
+ },
215
+ });
216
+ transformOutput = invokeCompiler(source, language, opts);
217
} catch (err) {
218
/**
219
* error might be an invariant violation or other runtime error
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+52
-2
@@ -9,7 +9,13 @@ import * as t from '@babel/types';
9
import {ZodError, z} from 'zod';
10
import {fromZodError} from 'zod-validation-error';
11
import {CompilerError} from '../CompilerError';
12
-import {Logger} from '../Entrypoint';
12
+import {
13
+ CompilationMode,
14
+ Logger,
15
+ PanicThresholdOptions,
16
+ parsePluginOptions,
17
+ PluginOptions,
18
+} from '../Entrypoint';
19
import {Err, Ok, Result} from '../Utils/Result';
20
import {
21
DEFAULT_GLOBALS,
@@ -683,7 +689,9 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
689
/**
690
* For snap test fixtures and playground only.
691
*/
686
-export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
692
+function parseConfigPragmaEnvironmentForTest(
693
+ pragma: string,
694
+): EnvironmentConfig {
695
const maybeConfig: any = {};
696
// Get the defaults to programmatically check for boolean properties
697
const defaultConfig = EnvironmentConfigSchema.parse({});
@@ -749,6 +757,48 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
757
suggestions: null,
758
});
759
}
760
+export function parseConfigPragmaForTests(
761
+ pragma: string,
762
+ defaults: {
763
+ compilationMode: CompilationMode;
764
+ },
765
+): PluginOptions {
766
+ const environment = parseConfigPragmaEnvironmentForTest(pragma);
767
+ let compilationMode: CompilationMode = defaults.compilationMode;
768
+ let panicThreshold: PanicThresholdOptions = 'all_errors';
769
+ for (const token of pragma.split(' ')) {
770
+ if (!token.startsWith('@')) {
771
+ continue;
772
+ }
773
+ switch (token) {
774
+ case '@compilationMode(annotation)': {
775
+ compilationMode = 'annotation';
776
+ break;
777
+ }
778
+ case '@compilationMode(infer)': {
779
+ compilationMode = 'infer';
780
+ break;
781
+ }
782
+ case '@compilationMode(all)': {
783
+ compilationMode = 'all';
784
+ break;
785
+ }
786
+ case '@compilationMode(syntax)': {
787
+ compilationMode = 'syntax';
788
+ break;
789
+ }
790
+ case '@panicThreshold(none)': {
791
+ panicThreshold = 'none';
792
+ break;
793
+ }
794
+ }
795
+ }
796
+ return parsePluginOptions({
797
+ environment,
798
+ compilationMode,
799
+ panicThreshold,
800
+ });
801
+}
802
803
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
804
compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts
+11
-5
@@ -6,6 +6,7 @@
6
*/
7
8
import {parseConfigPragmaForTests, validateEnvironmentConfig} from '..';
9
+import {defaultOptions} from '../Entrypoint';
10
11
describe('parseConfigPragmaForTests()', () => {
12
it('parses flags in various forms', () => {
@@ -19,13 +20,18 @@ describe('parseConfigPragmaForTests()', () => {
20
21
const config = parseConfigPragmaForTests(
22
'@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false',
23
+ {compilationMode: defaultOptions.compilationMode},
24
);
25
expect(config).toEqual({
24
- ...defaultConfig,
25
- enableUseTypeAnnotations: true,
26
- validateNoSetStateInPassiveEffects: true,
27
- validateNoSetStateInRender: false,
28
- enableResetCacheOnSourceFileChanges: false,
26
+ ...defaultOptions,
27
+ panicThreshold: 'all_errors',
28
+ environment: {
29
+ ...defaultOptions.environment,
30
+ enableUseTypeAnnotations: true,
31
+ validateNoSetStateInPassiveEffects: true,
32
+ validateNoSetStateInRender: false,
33
+ enableResetCacheOnSourceFileChanges: false,
34
+ },
35
});
36
});
37
});
compiler/packages/snap/src/compiler.ts
+3
-34
@@ -11,12 +11,9 @@ import {transformFromAstSync} from '@babel/core';
11
import * as BabelParser from '@babel/parser';
12
import {NodePath} from '@babel/traverse';
13
import * as t from '@babel/types';
14
-import assert from 'assert';
14
import type {
16
- CompilationMode,
15
Logger,
16
LoggerEvent,
19
- PanicThresholdOptions,
17
PluginOptions,
18
CompilerReactTarget,
19
CompilerPipelineValue,
@@ -51,31 +48,13 @@ function makePluginOptions(
48
ValueKindEnum: typeof ValueKind,
49
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
50
let gating = null;
54
- let compilationMode: CompilationMode = 'all';
55
- let panicThreshold: PanicThresholdOptions = 'all_errors';
51
let hookPattern: string | null = null;
52
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
53
let validatePreserveExistingMemoizationGuarantees = false;
54
let customMacros: null | Array<Macro> = null;
55
let validateBlocklistedImports = null;
61
- let enableFire = false;
56
let target: CompilerReactTarget = '19';
57
64
- if (firstLine.indexOf('@compilationMode(annotation)') !== -1) {
65
- assert(
66
- compilationMode === 'all',
67
- 'Cannot set @compilationMode(..) more than once',
68
- );
69
- compilationMode = 'annotation';
70
- }
71
- if (firstLine.indexOf('@compilationMode(infer)') !== -1) {
72
- assert(
73
- compilationMode === 'all',
74
- 'Cannot set @compilationMode(..) more than once',
75
- );
76
- compilationMode = 'infer';
77
- }
78
-
58
if (firstLine.includes('@gating')) {
59
gating = {
60
source: 'ReactForgetFeatureFlag',
@@ -96,10 +75,6 @@ function makePluginOptions(
75
}
76
}
77
99
- if (firstLine.includes('@panicThreshold(none)')) {
100
- panicThreshold = 'none';
101
- }
102
-
78
let eslintSuppressionRules: Array<string> | null = null;
79
const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec(
80
firstLine,
@@ -130,10 +105,6 @@ function makePluginOptions(
105
validatePreserveExistingMemoizationGuarantees = true;
106
}
107
133
- if (firstLine.includes('@enableFire')) {
134
- enableFire = true;
135
- }
136
-
108
const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine);
109
if (
110
hookPatternMatch &&
@@ -199,10 +170,11 @@ function makePluginOptions(
170
debugLogIRs: debugIRLogger,
171
};
172
202
- const config = parseConfigPragmaFn(firstLine);
173
+ const config = parseConfigPragmaFn(firstLine, {compilationMode: 'all'});
174
const options = {
175
+ ...config,
176
environment: {
205
- ...config,
177
+ ...config.environment,
178
moduleTypeProvider: makeSharedRuntimeTypeProvider({
179
EffectEnum,
180
ValueKindEnum,
@@ -212,12 +184,9 @@ function makePluginOptions(
184
hookPattern,
185
validatePreserveExistingMemoizationGuarantees,
186
validateBlocklistedImports,
215
- enableFire,
187
},
217
- compilationMode,
188
logger,
189
gating,
220
- panicThreshold,
190
noEmit: false,
191
eslintSuppressionRules,
192
flowSuppressions,