[compiler][be] Move test pragma to separate file (#33145)
`Environment.ts` is getting complex so let's separate test / playground parsing logic from it --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33145). * #33146 * __->__ #33145
mofeiZ committed
May 8, 2025 at 11:24 UTC
b629a865fb18b899d251bf5c3c5ca2580d222720
6 files changed
+211
-199
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+2
-195
@@ -9,15 +9,7 @@ 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 {
13
- CompilationMode,
14
- defaultOptions,
15
- Logger,
16
- PanicThresholdOptions,
17
- parsePluginOptions,
18
- PluginOptions,
19
- ProgramContext,
20
-} from '../Entrypoint';
12
+import {Logger, ProgramContext} from '../Entrypoint';
13
import {Err, Ok, Result} from '../Utils/Result';
14
import {
15
DEFAULT_GLOBALS,
@@ -158,7 +150,7 @@ export type Hook = z.infer<typeof HookSchema>;
150
* missing some recursive Object / Function shapeIds
151
*/
152
161
-const EnvironmentConfigSchema = z.object({
153
+export const EnvironmentConfigSchema = z.object({
154
customHooks: z.map(z.string(), HookSchema).default(new Map()),
155
156
/**
@@ -640,191 +632,6 @@ const EnvironmentConfigSchema = z.object({
632
633
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
634
643
-/**
644
- * For test fixtures and playground only.
645
- *
646
- * Pragmas are straightforward to parse for boolean options (`:true` and
647
- * `:false`). These are 'enabled' config values for non-boolean configs (i.e.
648
- * what is used when parsing `:true`).
649
- */
650
-const testComplexConfigDefaults: PartialEnvironmentConfig = {
651
- validateNoCapitalizedCalls: [],
652
- enableChangeDetectionForDebugging: {
653
- source: 'react-compiler-runtime',
654
- importSpecifierName: '$structuralCheck',
655
- },
656
- enableEmitFreeze: {
657
- source: 'react-compiler-runtime',
658
- importSpecifierName: 'makeReadOnly',
659
- },
660
- enableEmitInstrumentForget: {
661
- fn: {
662
- source: 'react-compiler-runtime',
663
- importSpecifierName: 'useRenderCounter',
664
- },
665
- gating: {
666
- source: 'react-compiler-runtime',
667
- importSpecifierName: 'shouldInstrument',
668
- },
669
- globalGating: 'DEV',
670
- },
671
- enableEmitHookGuards: {
672
- source: 'react-compiler-runtime',
673
- importSpecifierName: '$dispatcherGuard',
674
- },
675
- inlineJsxTransform: {
676
- elementSymbol: 'react.transitional.element',
677
- globalDevVar: 'DEV',
678
- },
679
- lowerContextAccess: {
680
- source: 'react-compiler-runtime',
681
- importSpecifierName: 'useContext_withSelector',
682
- },
683
- inferEffectDependencies: [
684
- {
685
- function: {
686
- source: 'react',
687
- importSpecifierName: 'useEffect',
688
- },
689
- numRequiredArgs: 1,
690
- },
691
- {
692
- function: {
693
- source: 'shared-runtime',
694
- importSpecifierName: 'useSpecialEffect',
695
- },
696
- numRequiredArgs: 2,
697
- },
698
- {
699
- function: {
700
- source: 'useEffectWrapper',
701
- importSpecifierName: 'default',
702
- },
703
- numRequiredArgs: 1,
704
- },
705
- ],
706
-};
707
-
708
-/**
709
- * For snap test fixtures and playground only.
710
- */
711
-function parseConfigPragmaEnvironmentForTest(
712
- pragma: string,
713
-): EnvironmentConfig {
714
- const maybeConfig: any = {};
715
- // Get the defaults to programmatically check for boolean properties
716
- const defaultConfig = EnvironmentConfigSchema.parse({});
717
-
718
- for (const token of pragma.split(' ')) {
719
- if (!token.startsWith('@')) {
720
- continue;
721
- }
722
- const keyVal = token.slice(1);
723
- let [key, val = undefined] = keyVal.split(':');
724
- const isSet = val === undefined || val === 'true';
725
-
726
- if (isSet && key in testComplexConfigDefaults) {
727
- maybeConfig[key] =
728
- testComplexConfigDefaults[key as keyof PartialEnvironmentConfig];
729
- continue;
730
- }
731
-
732
- if (key === 'customMacros' && val) {
733
- const valSplit = val.split('.');
734
- if (valSplit.length > 0) {
735
- const props = [];
736
- for (const elt of valSplit.slice(1)) {
737
- if (elt === '*') {
738
- props.push({type: 'wildcard'});
739
- } else if (elt.length > 0) {
740
- props.push({type: 'name', name: elt});
741
- }
742
- }
743
- maybeConfig[key] = [[valSplit[0], props]];
744
- }
745
- continue;
746
- }
747
-
748
- if (
749
- key !== 'enableResetCacheOnSourceFileChanges' &&
750
- typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
751
- ) {
752
- // skip parsing non-boolean properties
753
- continue;
754
- }
755
- if (val === undefined || val === 'true') {
756
- maybeConfig[key] = true;
757
- } else {
758
- maybeConfig[key] = false;
759
- }
760
- }
761
- const config = EnvironmentConfigSchema.safeParse(maybeConfig);
762
- if (config.success) {
763
- /**
764
- * Unless explicitly enabled, do not insert HMR handling code
765
- * in test fixtures or playground to reduce visual noise.
766
- */
767
- if (config.data.enableResetCacheOnSourceFileChanges == null) {
768
- config.data.enableResetCacheOnSourceFileChanges = false;
769
- }
770
- return config.data;
771
- }
772
- CompilerError.invariant(false, {
773
- reason: 'Internal error, could not parse config from pragma string',
774
- description: `${fromZodError(config.error)}`,
775
- loc: null,
776
- suggestions: null,
777
- });
778
-}
779
-export function parseConfigPragmaForTests(
780
- pragma: string,
781
- defaults: {
782
- compilationMode: CompilationMode;
783
- },
784
-): PluginOptions {
785
- const environment = parseConfigPragmaEnvironmentForTest(pragma);
786
- let compilationMode: CompilationMode = defaults.compilationMode;
787
- let panicThreshold: PanicThresholdOptions = 'all_errors';
788
- let noEmit: boolean = defaultOptions.noEmit;
789
- for (const token of pragma.split(' ')) {
790
- if (!token.startsWith('@')) {
791
- continue;
792
- }
793
- switch (token) {
794
- case '@compilationMode(annotation)': {
795
- compilationMode = 'annotation';
796
- break;
797
- }
798
- case '@compilationMode(infer)': {
799
- compilationMode = 'infer';
800
- break;
801
- }
802
- case '@compilationMode(all)': {
803
- compilationMode = 'all';
804
- break;
805
- }
806
- case '@compilationMode(syntax)': {
807
- compilationMode = 'syntax';
808
- break;
809
- }
810
- case '@panicThreshold(none)': {
811
- panicThreshold = 'none';
812
- break;
813
- }
814
- case '@noEmit': {
815
- noEmit = true;
816
- break;
817
- }
818
- }
819
- }
820
- return parsePluginOptions({
821
- environment,
822
- compilationMode,
823
- panicThreshold,
824
- noEmit,
825
- });
826
-}
827
-
635
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;
636
637
export type ReactFunctionType = 'Component' | 'Hook' | 'Other';
compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts
-1
@@ -17,7 +17,6 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR';
17
export {computeDominatorTree, computePostDominatorTree} from './Dominator';
18
export {
19
Environment,
20
- parseConfigPragmaForTests,
20
validateEnvironmentConfig,
21
type EnvironmentConfig,
22
type ExternalFunction,
compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts
new
+206
@@ -0,0 +1,206 @@
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
+
8
+import {fromZodError} from 'zod-validation-error';
9
+import {CompilerError} from '../CompilerError';
10
+import {
11
+ CompilationMode,
12
+ defaultOptions,
13
+ PanicThresholdOptions,
14
+ parsePluginOptions,
15
+ PluginOptions,
16
+} from '../Entrypoint';
17
+import {EnvironmentConfig} from '..';
18
+import {
19
+ EnvironmentConfigSchema,
20
+ PartialEnvironmentConfig,
21
+} from '../HIR/Environment';
22
+
23
+/**
24
+ * For test fixtures and playground only.
25
+ *
26
+ * Pragmas are straightforward to parse for boolean options (`:true` and
27
+ * `:false`). These are 'enabled' config values for non-boolean configs (i.e.
28
+ * what is used when parsing `:true`).
29
+ */
30
+const testComplexConfigDefaults: PartialEnvironmentConfig = {
31
+ validateNoCapitalizedCalls: [],
32
+ enableChangeDetectionForDebugging: {
33
+ source: 'react-compiler-runtime',
34
+ importSpecifierName: '$structuralCheck',
35
+ },
36
+ enableEmitFreeze: {
37
+ source: 'react-compiler-runtime',
38
+ importSpecifierName: 'makeReadOnly',
39
+ },
40
+ enableEmitInstrumentForget: {
41
+ fn: {
42
+ source: 'react-compiler-runtime',
43
+ importSpecifierName: 'useRenderCounter',
44
+ },
45
+ gating: {
46
+ source: 'react-compiler-runtime',
47
+ importSpecifierName: 'shouldInstrument',
48
+ },
49
+ globalGating: 'DEV',
50
+ },
51
+ enableEmitHookGuards: {
52
+ source: 'react-compiler-runtime',
53
+ importSpecifierName: '$dispatcherGuard',
54
+ },
55
+ inlineJsxTransform: {
56
+ elementSymbol: 'react.transitional.element',
57
+ globalDevVar: 'DEV',
58
+ },
59
+ lowerContextAccess: {
60
+ source: 'react-compiler-runtime',
61
+ importSpecifierName: 'useContext_withSelector',
62
+ },
63
+ inferEffectDependencies: [
64
+ {
65
+ function: {
66
+ source: 'react',
67
+ importSpecifierName: 'useEffect',
68
+ },
69
+ numRequiredArgs: 1,
70
+ },
71
+ {
72
+ function: {
73
+ source: 'shared-runtime',
74
+ importSpecifierName: 'useSpecialEffect',
75
+ },
76
+ numRequiredArgs: 2,
77
+ },
78
+ {
79
+ function: {
80
+ source: 'useEffectWrapper',
81
+ importSpecifierName: 'default',
82
+ },
83
+ numRequiredArgs: 1,
84
+ },
85
+ ],
86
+};
87
+
88
+/**
89
+ * For snap test fixtures and playground only.
90
+ */
91
+function parseConfigPragmaEnvironmentForTest(
92
+ pragma: string,
93
+): EnvironmentConfig {
94
+ const maybeConfig: any = {};
95
+ // Get the defaults to programmatically check for boolean properties
96
+ const defaultConfig = EnvironmentConfigSchema.parse({});
97
+
98
+ for (const token of pragma.split(' ')) {
99
+ if (!token.startsWith('@')) {
100
+ continue;
101
+ }
102
+ const keyVal = token.slice(1);
103
+ let [key, val = undefined] = keyVal.split(':');
104
+ const isSet = val === undefined || val === 'true';
105
+
106
+ if (isSet && key in testComplexConfigDefaults) {
107
+ maybeConfig[key] =
108
+ testComplexConfigDefaults[key as keyof PartialEnvironmentConfig];
109
+ continue;
110
+ }
111
+
112
+ if (key === 'customMacros' && val) {
113
+ const valSplit = val.split('.');
114
+ if (valSplit.length > 0) {
115
+ const props = [];
116
+ for (const elt of valSplit.slice(1)) {
117
+ if (elt === '*') {
118
+ props.push({type: 'wildcard'});
119
+ } else if (elt.length > 0) {
120
+ props.push({type: 'name', name: elt});
121
+ }
122
+ }
123
+ maybeConfig[key] = [[valSplit[0], props]];
124
+ }
125
+ continue;
126
+ }
127
+
128
+ if (
129
+ key !== 'enableResetCacheOnSourceFileChanges' &&
130
+ typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
131
+ ) {
132
+ // skip parsing non-boolean properties
133
+ continue;
134
+ }
135
+ if (val === undefined || val === 'true') {
136
+ maybeConfig[key] = true;
137
+ } else {
138
+ maybeConfig[key] = false;
139
+ }
140
+ }
141
+ const config = EnvironmentConfigSchema.safeParse(maybeConfig);
142
+ if (config.success) {
143
+ /**
144
+ * Unless explicitly enabled, do not insert HMR handling code
145
+ * in test fixtures or playground to reduce visual noise.
146
+ */
147
+ if (config.data.enableResetCacheOnSourceFileChanges == null) {
148
+ config.data.enableResetCacheOnSourceFileChanges = false;
149
+ }
150
+ return config.data;
151
+ }
152
+ CompilerError.invariant(false, {
153
+ reason: 'Internal error, could not parse config from pragma string',
154
+ description: `${fromZodError(config.error)}`,
155
+ loc: null,
156
+ suggestions: null,
157
+ });
158
+}
159
+export function parseConfigPragmaForTests(
160
+ pragma: string,
161
+ defaults: {
162
+ compilationMode: CompilationMode;
163
+ },
164
+): PluginOptions {
165
+ const environment = parseConfigPragmaEnvironmentForTest(pragma);
166
+ let compilationMode: CompilationMode = defaults.compilationMode;
167
+ let panicThreshold: PanicThresholdOptions = 'all_errors';
168
+ let noEmit: boolean = defaultOptions.noEmit;
169
+ for (const token of pragma.split(' ')) {
170
+ if (!token.startsWith('@')) {
171
+ continue;
172
+ }
173
+ switch (token) {
174
+ case '@compilationMode(annotation)': {
175
+ compilationMode = 'annotation';
176
+ break;
177
+ }
178
+ case '@compilationMode(infer)': {
179
+ compilationMode = 'infer';
180
+ break;
181
+ }
182
+ case '@compilationMode(all)': {
183
+ compilationMode = 'all';
184
+ break;
185
+ }
186
+ case '@compilationMode(syntax)': {
187
+ compilationMode = 'syntax';
188
+ break;
189
+ }
190
+ case '@panicThreshold(none)': {
191
+ panicThreshold = 'none';
192
+ break;
193
+ }
194
+ case '@noEmit': {
195
+ noEmit = true;
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ return parsePluginOptions({
201
+ environment,
202
+ compilationMode,
203
+ panicThreshold,
204
+ noEmit,
205
+ });
206
+}
compiler/packages/babel-plugin-react-compiler/src/index.ts
+1
-1
@@ -30,7 +30,6 @@ export {
30
export {
31
Effect,
32
ValueKind,
33
- parseConfigPragmaForTests,
33
printHIR,
34
printFunctionWithOutlined,
35
validateEnvironmentConfig,
@@ -43,6 +42,7 @@ export {
42
printReactiveFunction,
43
printReactiveFunctionWithOutlined,
44
} from './ReactiveScopes';
45
+export {parseConfigPragmaForTests} from './Utils/TestUtils';
46
declare global {
47
let __DEV__: boolean | null | undefined;
48
}
compiler/packages/snap/src/compiler.ts
+1
-1
@@ -19,10 +19,10 @@ import type {
19
CompilerPipelineValue,
20
} from 'babel-plugin-react-compiler/src/Entrypoint';
21
import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR';
22
+import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
23
import type {
24
Macro,
25
MacroMethod,
25
- parseConfigPragmaForTests as ParseConfigPragma,
26
} from 'babel-plugin-react-compiler/src/HIR/Environment';
27
import * as HermesParser from 'hermes-parser';
28
import invariant from 'invariant';
compiler/packages/snap/src/runner-worker.ts
+1
-1
@@ -7,7 +7,7 @@
7
8
import {codeFrameColumns} from '@babel/code-frame';
9
import type {PluginObj} from '@babel/core';
10
-import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment';
10
+import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
11
import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR';
12
import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction';
13
import {TransformResult, transformFixtureInput} from './compiler';