mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
This is mostly to kick off conversation, i think we should go with a modified version of the implemented approach that i'll describe here. The playground currently serves two roles. The primary one we think about is for verifying compiler output. We use it for this sometimes, and developers frequently use it for this, including to send us repros if they have a potential bug. The second mode is to help developers learn about React. Part of that includes learning how to use React correctly — where it's helpful to see feedback about problematic code — and also to understand what kind of tools we provide compared to other frameworks, to make an informed choice about what tools they want to use. Currently we primarily think about the first role, but I think we should emphasize the second more. In this PR i'm doing the worst of both: enabling all the validations used by both the compiler and the linter by default. This means that code that would actually compile can fail with validations, which isn't great. What I think we should actually do is compile twice, one in "compilation" mode and once in "linter" mode, and combine the results as follows: * If "compilation" mode succeeds, show the compiled output _and_ any linter errors. * If "compilation" mode fails, show only the compilation mode failures. We should also distinguish which case it is when we show errors: "Compilation succeeded", "Compilation succeeded with linter errors", "Compilation failed". This lets developers continue to verify compiler output, while also turning the playground into a much more useful tool for learning React. Thoughts? --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33777). * #33981 * __->__ #33777
220 lines
6.0 KiB
TypeScript
220 lines
6.0 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import {fromZodError} from 'zod-validation-error';
|
|
import {CompilerError} from '../CompilerError';
|
|
import {
|
|
CompilationMode,
|
|
defaultOptions,
|
|
parsePluginOptions,
|
|
PluginOptions,
|
|
} from '../Entrypoint';
|
|
import {EnvironmentConfig} from '..';
|
|
import {
|
|
EnvironmentConfigSchema,
|
|
PartialEnvironmentConfig,
|
|
} from '../HIR/Environment';
|
|
import {Err, Ok, Result} from './Result';
|
|
import {hasOwnProperty} from './utils';
|
|
|
|
function tryParseTestPragmaValue(val: string): Result<unknown, unknown> {
|
|
try {
|
|
let parsedVal: unknown;
|
|
const stringMatch = /^"([^"]*)"$/.exec(val);
|
|
if (stringMatch && stringMatch.length > 1) {
|
|
parsedVal = stringMatch[1];
|
|
} else {
|
|
parsedVal = JSON.parse(val);
|
|
}
|
|
return Ok(parsedVal);
|
|
} catch (e) {
|
|
return Err(e);
|
|
}
|
|
}
|
|
|
|
const testComplexConfigDefaults: PartialEnvironmentConfig = {
|
|
validateNoCapitalizedCalls: [],
|
|
enableChangeDetectionForDebugging: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: '$structuralCheck',
|
|
},
|
|
enableEmitFreeze: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: 'makeReadOnly',
|
|
},
|
|
enableEmitInstrumentForget: {
|
|
fn: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: 'useRenderCounter',
|
|
},
|
|
gating: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: 'shouldInstrument',
|
|
},
|
|
globalGating: 'DEV',
|
|
},
|
|
enableEmitHookGuards: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: '$dispatcherGuard',
|
|
},
|
|
inlineJsxTransform: {
|
|
elementSymbol: 'react.transitional.element',
|
|
globalDevVar: 'DEV',
|
|
},
|
|
lowerContextAccess: {
|
|
source: 'react-compiler-runtime',
|
|
importSpecifierName: 'useContext_withSelector',
|
|
},
|
|
inferEffectDependencies: [
|
|
{
|
|
function: {
|
|
source: 'react',
|
|
importSpecifierName: 'useEffect',
|
|
},
|
|
autodepsIndex: 1,
|
|
},
|
|
{
|
|
function: {
|
|
source: 'shared-runtime',
|
|
importSpecifierName: 'useSpecialEffect',
|
|
},
|
|
autodepsIndex: 2,
|
|
},
|
|
{
|
|
function: {
|
|
source: 'useEffectWrapper',
|
|
importSpecifierName: 'default',
|
|
},
|
|
autodepsIndex: 1,
|
|
},
|
|
],
|
|
};
|
|
|
|
function* splitPragma(
|
|
pragma: string,
|
|
): Generator<{key: string; value: string | null}> {
|
|
for (const entry of pragma.split('@')) {
|
|
const keyVal = entry.trim();
|
|
const valIdx = keyVal.indexOf(':');
|
|
if (valIdx === -1) {
|
|
yield {key: keyVal.split(' ', 1)[0], value: null};
|
|
} else {
|
|
yield {key: keyVal.slice(0, valIdx), value: keyVal.slice(valIdx + 1)};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For snap test fixtures and playground only.
|
|
*/
|
|
function parseConfigPragmaEnvironmentForTest(
|
|
pragma: string,
|
|
defaultConfig: PartialEnvironmentConfig,
|
|
): EnvironmentConfig {
|
|
// throw early if the defaults are invalid
|
|
EnvironmentConfigSchema.parse(defaultConfig);
|
|
|
|
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
|
|
defaultConfig;
|
|
|
|
for (const {key, value: val} of splitPragma(pragma)) {
|
|
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
|
|
continue;
|
|
}
|
|
const isSet = val == null || val === 'true';
|
|
if (isSet && key in testComplexConfigDefaults) {
|
|
maybeConfig[key] = testComplexConfigDefaults[key];
|
|
} else if (isSet) {
|
|
maybeConfig[key] = true;
|
|
} else if (val === 'false') {
|
|
maybeConfig[key] = false;
|
|
} else if (val) {
|
|
const parsedVal = tryParseTestPragmaValue(val).unwrap();
|
|
if (key === 'customMacros' && typeof parsedVal === 'string') {
|
|
const valSplit = parsedVal.split('.');
|
|
const props = [];
|
|
for (const elt of valSplit.slice(1)) {
|
|
if (elt === '*') {
|
|
props.push({type: 'wildcard'});
|
|
} else if (elt.length > 0) {
|
|
props.push({type: 'name', name: elt});
|
|
}
|
|
}
|
|
maybeConfig[key] = [[valSplit[0], props]];
|
|
continue;
|
|
}
|
|
maybeConfig[key] = parsedVal;
|
|
}
|
|
}
|
|
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
|
|
if (config.success) {
|
|
/**
|
|
* Unless explicitly enabled, do not insert HMR handling code
|
|
* in test fixtures or playground to reduce visual noise.
|
|
*/
|
|
if (config.data.enableResetCacheOnSourceFileChanges == null) {
|
|
config.data.enableResetCacheOnSourceFileChanges = false;
|
|
}
|
|
return config.data;
|
|
}
|
|
CompilerError.invariant(false, {
|
|
reason: 'Internal error, could not parse config from pragma string',
|
|
description: `${fromZodError(config.error)}`,
|
|
loc: null,
|
|
suggestions: null,
|
|
});
|
|
}
|
|
|
|
const testComplexPluginOptionDefaults: Partial<PluginOptions> = {
|
|
gating: {
|
|
source: 'ReactForgetFeatureFlag',
|
|
importSpecifierName: 'isForgetEnabled_Fixtures',
|
|
},
|
|
};
|
|
export function parseConfigPragmaForTests(
|
|
pragma: string,
|
|
defaults: {
|
|
compilationMode: CompilationMode;
|
|
environment?: PartialEnvironmentConfig;
|
|
},
|
|
): PluginOptions {
|
|
const environment = parseConfigPragmaEnvironmentForTest(
|
|
pragma,
|
|
defaults.environment ?? {},
|
|
);
|
|
const options: Record<keyof PluginOptions, unknown> = {
|
|
...defaultOptions,
|
|
panicThreshold: 'all_errors',
|
|
compilationMode: defaults.compilationMode,
|
|
environment,
|
|
};
|
|
for (const {key, value: val} of splitPragma(pragma)) {
|
|
if (!hasOwnProperty(defaultOptions, key)) {
|
|
continue;
|
|
}
|
|
const isSet = val == null || val === 'true';
|
|
if (isSet && key in testComplexPluginOptionDefaults) {
|
|
options[key] = testComplexPluginOptionDefaults[key];
|
|
} else if (isSet) {
|
|
options[key] = true;
|
|
} else if (val === 'false') {
|
|
options[key] = false;
|
|
} else if (val != null) {
|
|
const parsedVal = tryParseTestPragmaValue(val).unwrap();
|
|
if (key === 'target' && parsedVal === 'donotuse_meta_internal') {
|
|
options[key] = {
|
|
kind: parsedVal,
|
|
runtimeModule: 'react',
|
|
};
|
|
} else {
|
|
options[key] = parsedVal;
|
|
}
|
|
}
|
|
}
|
|
return parsePluginOptions(options);
|
|
}
|