main
ts 171 lines 4.87 KB
Raw
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/v4';
9 import {CompilerError} from '../CompilerError';
10 import {
11 CompilationMode,
12 defaultOptions,
13 parsePluginOptions,
14 PluginOptions,
15 } from '../Entrypoint';
16 import {EnvironmentConfig} from '..';
17 import {GeneratedSource} from '../HIR/HIR';
18 import {
19 EnvironmentConfigSchema,
20 PartialEnvironmentConfig,
21 } from '../HIR/Environment';
22 import {Err, Ok, Result} from './Result';
23 import {hasOwnProperty} from './utils';
24
25 function tryParseTestPragmaValue(val: string): Result<unknown, unknown> {
26 try {
27 let parsedVal: unknown;
28 const stringMatch = /^"([^"]*)"$/.exec(val);
29 if (stringMatch && stringMatch.length > 1) {
30 parsedVal = stringMatch[1];
31 } else {
32 parsedVal = JSON.parse(val);
33 }
34 return Ok(parsedVal);
35 } catch (e) {
36 return Err(e);
37 }
38 }
39
40 const testComplexConfigDefaults: PartialEnvironmentConfig = {
41 validateNoCapitalizedCalls: [],
42 enableEmitInstrumentForget: {
43 fn: {
44 source: 'react-compiler-runtime',
45 importSpecifierName: 'useRenderCounter',
46 },
47 gating: {
48 source: 'react-compiler-runtime',
49 importSpecifierName: 'shouldInstrument',
50 },
51 globalGating: 'DEV',
52 },
53 enableEmitHookGuards: {
54 source: 'react-compiler-runtime',
55 importSpecifierName: '$dispatcherGuard',
56 },
57 };
58
59 function* splitPragma(
60 pragma: string,
61 ): Generator<{key: string; value: string | null}> {
62 for (const entry of pragma.split('@')) {
63 const keyVal = entry.trim();
64 const valIdx = keyVal.indexOf(':');
65 if (valIdx === -1) {
66 yield {key: keyVal.split(' ', 1)[0], value: null};
67 } else {
68 yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)};
69 }
70 }
71 }
72
73 /**
74 * For snap test fixtures and playground only.
75 */
76 function parseConfigPragmaEnvironmentForTest(
77 pragma: string,
78 defaultConfig: PartialEnvironmentConfig,
79 ): EnvironmentConfig {
80 // throw early if the defaults are invalid
81 EnvironmentConfigSchema.parse(defaultConfig);
82
83 const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
84 defaultConfig;
85
86 for (const {key, value: val} of splitPragma(pragma)) {
87 if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
88 continue;
89 }
90 const isSet = val == null || val === 'true';
91 if (isSet && key in testComplexConfigDefaults) {
92 maybeConfig[key] = testComplexConfigDefaults[key];
93 } else if (isSet) {
94 maybeConfig[key] = true;
95 } else if (val === 'false') {
96 maybeConfig[key] = false;
97 } else if (val) {
98 const parsedVal = tryParseTestPragmaValue(val).unwrap();
99 if (key === 'customMacros' && typeof parsedVal === 'string') {
100 maybeConfig[key] = [parsedVal.split('.')[0]];
101 continue;
102 }
103 maybeConfig[key] = parsedVal;
104 }
105 }
106 const config = EnvironmentConfigSchema.safeParse(maybeConfig);
107 if (config.success) {
108 /**
109 * Unless explicitly enabled, do not insert HMR handling code
110 * in test fixtures or playground to reduce visual noise.
111 */
112 if (config.data.enableResetCacheOnSourceFileChanges == null) {
113 config.data.enableResetCacheOnSourceFileChanges = false;
114 }
115 return config.data;
116 }
117 CompilerError.invariant(false, {
118 reason: 'Internal error, could not parse config from pragma string',
119 description: `${fromZodError(config.error)}`,
120 loc: GeneratedSource,
121 });
122 }
123
124 const testComplexPluginOptionDefaults: PluginOptions = {
125 gating: {
126 source: 'ReactForgetFeatureFlag',
127 importSpecifierName: 'isForgetEnabled_Fixtures',
128 },
129 };
130 export function parseConfigPragmaForTests(
131 pragma: string,
132 defaults: {
133 compilationMode: CompilationMode;
134 environment?: PartialEnvironmentConfig;
135 },
136 ): PluginOptions {
137 const environment = parseConfigPragmaEnvironmentForTest(
138 pragma,
139 defaults.environment ?? {},
140 );
141 const options: Record<keyof PluginOptions, unknown> = {
142 ...defaultOptions,
143 panicThreshold: 'all_errors',
144 compilationMode: defaults.compilationMode,
145 environment,
146 };
147 for (const {key, value: val} of splitPragma(pragma)) {
148 if (!hasOwnProperty(defaultOptions, key)) {
149 continue;
150 }
151 const isSet = val == null || val === 'true';
152 if (isSet && key in testComplexPluginOptionDefaults) {
153 options[key] = testComplexPluginOptionDefaults[key];
154 } else if (isSet) {
155 options[key] = true;
156 } else if (val === 'false') {
157 options[key] = false;
158 } else if (val != null) {
159 const parsedVal = tryParseTestPragmaValue(val).unwrap();
160 if (key === 'target' && parsedVal === 'donotuse_meta_internal') {
161 options[key] = {
162 kind: parsedVal,
163 runtimeModule: 'react',
164 };
165 } else {
166 options[key] = parsedVal;
167 }
168 }
169 }
170 return parsePluginOptions(options);
171 }