Compare commits

...

1 Commits

Author SHA1 Message Date
Mofei Zhang
2b817b78bc [compiler] Playground qol: shared compilation option directives with tests
- 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
2025-01-09 12:29:03 -05:00
7 changed files with 154 additions and 96 deletions

View File

@@ -0,0 +1,13 @@
import { c as _c } from "react/compiler-runtime"; // 
        @compilationMode(all)
function nonReactFn() {
  const $ = _c(1);
  let t0;
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    t0 = {};
    $[0] = t0;
  } else {
    t0 = $[0];
  }
  return t0;
}

View File

@@ -0,0 +1,4 @@
// @compilationMode(infer)
function nonReactFn() {
  return {};
}

View File

@@ -79,6 +79,24 @@ function Foo() {
// @flow // @flow
function useFoo(propVal: {+baz: number}) { function useFoo(propVal: {+baz: number}) {
return <div>{(propVal.baz as number)}</div>; return <div>{(propVal.baz as number)}</div>;
}
`,
noFormat: true,
},
{
name: 'compilationMode-infer',
input: `// @compilationMode(infer)
function nonReactFn() {
return {};
}
`,
noFormat: true,
},
{
name: 'compilationMode-all',
input: `// @compilationMode(all)
function nonReactFn() {
return {};
} }
`, `,
noFormat: true, noFormat: true,

View File

@@ -20,7 +20,6 @@ import BabelPluginReactCompiler, {
CompilerPipelineValue, CompilerPipelineValue,
parsePluginOptions, parsePluginOptions,
} from 'babel-plugin-react-compiler/src'; } from 'babel-plugin-react-compiler/src';
import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment';
import clsx from 'clsx'; import clsx from 'clsx';
import invariant from 'invariant'; import invariant from 'invariant';
import {useSnackbar} from 'notistack'; import {useSnackbar} from 'notistack';
@@ -69,23 +68,14 @@ function parseInput(
function invokeCompiler( function invokeCompiler(
source: string, source: string,
language: 'flow' | 'typescript', language: 'flow' | 'typescript',
environment: EnvironmentConfig, options: PluginOptions,
logIR: (pipelineValue: CompilerPipelineValue) => void,
): CompilerTransformOutput { ): CompilerTransformOutput {
const opts: PluginOptions = parsePluginOptions({
logger: {
debugLogIRs: logIR,
logEvent: () => {},
},
environment,
panicThreshold: 'all_errors',
});
const ast = parseInput(source, language); const ast = parseInput(source, language);
let result = transformFromAstSync(ast, source, { let result = transformFromAstSync(ast, source, {
filename: '_playgroundFile.js', filename: '_playgroundFile.js',
highlightCode: false, highlightCode: false,
retainLines: true, retainLines: true,
plugins: [[BabelPluginReactCompiler, opts]], plugins: [[BabelPluginReactCompiler, options]],
ast: true, ast: true,
sourceType: 'module', sourceType: 'module',
configFile: false, configFile: false,
@@ -171,51 +161,59 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
try { try {
// Extract the first line to quickly check for custom test directives // Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n')); const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragmaForTests(pragma); const logIR = (result: CompilerPipelineValue): void => {
switch (result.kind) {
transformOutput = invokeCompiler( case 'ast': {
source, break;
language,
{...config, customHooks: new Map([...COMMON_HOOKS])},
result => {
switch (result.kind) {
case 'ast': {
break;
}
case 'hir': {
upsert({
kind: 'hir',
fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName: null,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
} }
case 'hir': {
upsert({
kind: 'hir',
fnName: result.value.id,
name: result.name,
value: printFunctionWithOutlined(result.value),
});
break;
}
case 'reactive': {
upsert({
kind: 'reactive',
fnName: result.value.id,
name: result.name,
value: printReactiveFunctionWithOutlined(result.value),
});
break;
}
case 'debug': {
upsert({
kind: 'debug',
fnName: null,
name: result.name,
value: result.value,
});
break;
}
default: {
const _: never = result;
throw new Error(`Unhandled result ${result}`);
}
}
};
const parsedOptions = parseConfigPragmaForTests(pragma, {
compilationMode: 'infer',
});
const opts: PluginOptions = parsePluginOptions({
...parsedOptions,
environment: {
...parsedOptions.environment,
customHooks: new Map([...COMMON_HOOKS]),
}, },
); logger: {
debugLogIRs: logIR,
logEvent: () => {},
},
});
transformOutput = invokeCompiler(source, language, opts);
} catch (err) { } catch (err) {
/** /**
* error might be an invariant violation or other runtime error * error might be an invariant violation or other runtime error

View File

@@ -9,7 +9,13 @@ import * as t from '@babel/types';
import {ZodError, z} from 'zod'; import {ZodError, z} from 'zod';
import {fromZodError} from 'zod-validation-error'; import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError'; import {CompilerError} from '../CompilerError';
import {Logger} from '../Entrypoint'; import {
CompilationMode,
Logger,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result'; import {Err, Ok, Result} from '../Utils/Result';
import { import {
DEFAULT_GLOBALS, DEFAULT_GLOBALS,
@@ -683,7 +689,9 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
/** /**
* For snap test fixtures and playground only. * For snap test fixtures and playground only.
*/ */
export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig { function parseConfigPragmaEnvironmentForTest(
pragma: string,
): EnvironmentConfig {
const maybeConfig: any = {}; const maybeConfig: any = {};
// Get the defaults to programmatically check for boolean properties // Get the defaults to programmatically check for boolean properties
const defaultConfig = EnvironmentConfigSchema.parse({}); const defaultConfig = EnvironmentConfigSchema.parse({});
@@ -749,6 +757,48 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
suggestions: null, suggestions: null,
}); });
} }
export function parseConfigPragmaForTests(
pragma: string,
defaults: {
compilationMode: CompilationMode;
},
): PluginOptions {
const environment = parseConfigPragmaEnvironmentForTest(pragma);
let compilationMode: CompilationMode = defaults.compilationMode;
let panicThreshold: PanicThresholdOptions = 'all_errors';
for (const token of pragma.split(' ')) {
if (!token.startsWith('@')) {
continue;
}
switch (token) {
case '@compilationMode(annotation)': {
compilationMode = 'annotation';
break;
}
case '@compilationMode(infer)': {
compilationMode = 'infer';
break;
}
case '@compilationMode(all)': {
compilationMode = 'all';
break;
}
case '@compilationMode(syntax)': {
compilationMode = 'syntax';
break;
}
case '@panicThreshold(none)': {
panicThreshold = 'none';
break;
}
}
}
return parsePluginOptions({
environment,
compilationMode,
panicThreshold,
});
}
export type PartialEnvironmentConfig = Partial<EnvironmentConfig>; export type PartialEnvironmentConfig = Partial<EnvironmentConfig>;

View File

@@ -6,6 +6,7 @@
*/ */
import {parseConfigPragmaForTests, validateEnvironmentConfig} from '..'; import {parseConfigPragmaForTests, validateEnvironmentConfig} from '..';
import {defaultOptions} from '../Entrypoint';
describe('parseConfigPragmaForTests()', () => { describe('parseConfigPragmaForTests()', () => {
it('parses flags in various forms', () => { it('parses flags in various forms', () => {
@@ -19,13 +20,18 @@ describe('parseConfigPragmaForTests()', () => {
const config = parseConfigPragmaForTests( const config = parseConfigPragmaForTests(
'@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false', '@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false',
{compilationMode: defaultOptions.compilationMode},
); );
expect(config).toEqual({ expect(config).toEqual({
...defaultConfig, ...defaultOptions,
enableUseTypeAnnotations: true, panicThreshold: 'all_errors',
validateNoSetStateInPassiveEffects: true, environment: {
validateNoSetStateInRender: false, ...defaultOptions.environment,
enableResetCacheOnSourceFileChanges: false, enableUseTypeAnnotations: true,
validateNoSetStateInPassiveEffects: true,
validateNoSetStateInRender: false,
enableResetCacheOnSourceFileChanges: false,
},
}); });
}); });
}); });

View File

@@ -11,12 +11,9 @@ import {transformFromAstSync} from '@babel/core';
import * as BabelParser from '@babel/parser'; import * as BabelParser from '@babel/parser';
import {NodePath} from '@babel/traverse'; import {NodePath} from '@babel/traverse';
import * as t from '@babel/types'; import * as t from '@babel/types';
import assert from 'assert';
import type { import type {
CompilationMode,
Logger, Logger,
LoggerEvent, LoggerEvent,
PanicThresholdOptions,
PluginOptions, PluginOptions,
CompilerReactTarget, CompilerReactTarget,
CompilerPipelineValue, CompilerPipelineValue,
@@ -51,31 +48,13 @@ function makePluginOptions(
ValueKindEnum: typeof ValueKind, ValueKindEnum: typeof ValueKind,
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
let gating = null; let gating = null;
let compilationMode: CompilationMode = 'all';
let panicThreshold: PanicThresholdOptions = 'all_errors';
let hookPattern: string | null = null; let hookPattern: string | null = null;
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
let validatePreserveExistingMemoizationGuarantees = false; let validatePreserveExistingMemoizationGuarantees = false;
let customMacros: null | Array<Macro> = null; let customMacros: null | Array<Macro> = null;
let validateBlocklistedImports = null; let validateBlocklistedImports = null;
let enableFire = false;
let target: CompilerReactTarget = '19'; let target: CompilerReactTarget = '19';
if (firstLine.indexOf('@compilationMode(annotation)') !== -1) {
assert(
compilationMode === 'all',
'Cannot set @compilationMode(..) more than once',
);
compilationMode = 'annotation';
}
if (firstLine.indexOf('@compilationMode(infer)') !== -1) {
assert(
compilationMode === 'all',
'Cannot set @compilationMode(..) more than once',
);
compilationMode = 'infer';
}
if (firstLine.includes('@gating')) { if (firstLine.includes('@gating')) {
gating = { gating = {
source: 'ReactForgetFeatureFlag', source: 'ReactForgetFeatureFlag',
@@ -96,10 +75,6 @@ function makePluginOptions(
} }
} }
if (firstLine.includes('@panicThreshold(none)')) {
panicThreshold = 'none';
}
let eslintSuppressionRules: Array<string> | null = null; let eslintSuppressionRules: Array<string> | null = null;
const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec( const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec(
firstLine, firstLine,
@@ -130,10 +105,6 @@ function makePluginOptions(
validatePreserveExistingMemoizationGuarantees = true; validatePreserveExistingMemoizationGuarantees = true;
} }
if (firstLine.includes('@enableFire')) {
enableFire = true;
}
const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine);
if ( if (
hookPatternMatch && hookPatternMatch &&
@@ -199,10 +170,11 @@ function makePluginOptions(
debugLogIRs: debugIRLogger, debugLogIRs: debugIRLogger,
}; };
const config = parseConfigPragmaFn(firstLine); const config = parseConfigPragmaFn(firstLine, {compilationMode: 'all'});
const options = { const options = {
...config,
environment: { environment: {
...config, ...config.environment,
moduleTypeProvider: makeSharedRuntimeTypeProvider({ moduleTypeProvider: makeSharedRuntimeTypeProvider({
EffectEnum, EffectEnum,
ValueKindEnum, ValueKindEnum,
@@ -212,12 +184,9 @@ function makePluginOptions(
hookPattern, hookPattern,
validatePreserveExistingMemoizationGuarantees, validatePreserveExistingMemoizationGuarantees,
validateBlocklistedImports, validateBlocklistedImports,
enableFire,
}, },
compilationMode,
logger, logger,
gating, gating,
panicThreshold,
noEmit: false, noEmit: false,
eslintSuppressionRules, eslintSuppressionRules,
flowSuppressions, flowSuppressions,