@samitouri / QOS-React / commits / d16fe4be5b

[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,