@samitouri / QOS-React / commits / b629a865fb

[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';