Skip to content

Commit

Permalink
[compiler] Playground qol: shared compilation option directives with …
Browse files Browse the repository at this point in the history
…tests (facebook#32012)

- 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
  • Loading branch information
mofeiZ authored Jan 9, 2025
1 parent 8932ca3 commit d16fe4b
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @compilationMode(infer)
function nonReactFn() {
  return {};
}
18 changes: 18 additions & 0 deletions compiler/apps/playground/__tests__/e2e/page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ function Foo() {
// @flow
function useFoo(propVal: {+baz: number}) {
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,
Expand Down
108 changes: 53 additions & 55 deletions compiler/apps/playground/components/Editor/EditorImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import BabelPluginReactCompiler, {
CompilerPipelineValue,
parsePluginOptions,
} from 'babel-plugin-react-compiler/src';
import {type EnvironmentConfig} from 'babel-plugin-react-compiler/src/HIR/Environment';
import clsx from 'clsx';
import invariant from 'invariant';
import {useSnackbar} from 'notistack';
Expand Down Expand Up @@ -69,23 +68,14 @@ function parseInput(
function invokeCompiler(
source: string,
language: 'flow' | 'typescript',
environment: EnvironmentConfig,
logIR: (pipelineValue: CompilerPipelineValue) => void,
options: PluginOptions,
): CompilerTransformOutput {
const opts: PluginOptions = parsePluginOptions({
logger: {
debugLogIRs: logIR,
logEvent: () => {},
},
environment,
panicThreshold: 'all_errors',
});
const ast = parseInput(source, language);
let result = transformFromAstSync(ast, source, {
filename: '_playgroundFile.js',
highlightCode: false,
retainLines: true,
plugins: [[BabelPluginReactCompiler, opts]],
plugins: [[BabelPluginReactCompiler, options]],
ast: true,
sourceType: 'module',
configFile: false,
Expand Down Expand Up @@ -171,51 +161,59 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] {
try {
// Extract the first line to quickly check for custom test directives
const pragma = source.substring(0, source.indexOf('\n'));
const config = parseConfigPragmaForTests(pragma);

transformOutput = invokeCompiler(
source,
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}`);
}
const logIR = (result: CompilerPipelineValue): void => {
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}`);
}
}
};
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) {
/**
* error might be an invariant violation or other runtime error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import * as t from '@babel/types';
import {ZodError, z} from 'zod';
import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {Logger} from '../Entrypoint';
import {
CompilationMode,
Logger,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
DEFAULT_GLOBALS,
Expand Down Expand Up @@ -683,7 +689,9 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
/**
* For snap test fixtures and playground only.
*/
export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
function parseConfigPragmaEnvironmentForTest(
pragma: string,
): EnvironmentConfig {
const maybeConfig: any = {};
// Get the defaults to programmatically check for boolean properties
const defaultConfig = EnvironmentConfigSchema.parse({});
Expand Down Expand Up @@ -749,6 +757,48 @@ export function parseConfigPragmaForTests(pragma: string): EnvironmentConfig {
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>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import {parseConfigPragmaForTests, validateEnvironmentConfig} from '..';
import {defaultOptions} from '../Entrypoint';

describe('parseConfigPragmaForTests()', () => {
it('parses flags in various forms', () => {
Expand All @@ -19,13 +20,18 @@ describe('parseConfigPragmaForTests()', () => {

const config = parseConfigPragmaForTests(
'@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false',
{compilationMode: defaultOptions.compilationMode},
);
expect(config).toEqual({
...defaultConfig,
enableUseTypeAnnotations: true,
validateNoSetStateInPassiveEffects: true,
validateNoSetStateInRender: false,
enableResetCacheOnSourceFileChanges: false,
...defaultOptions,
panicThreshold: 'all_errors',
environment: {
...defaultOptions.environment,
enableUseTypeAnnotations: true,
validateNoSetStateInPassiveEffects: true,
validateNoSetStateInRender: false,
enableResetCacheOnSourceFileChanges: false,
},
});
});
});
37 changes: 3 additions & 34 deletions compiler/packages/snap/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import {transformFromAstSync} from '@babel/core';
import * as BabelParser from '@babel/parser';
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import assert from 'assert';
import type {
CompilationMode,
Logger,
LoggerEvent,
PanicThresholdOptions,
PluginOptions,
CompilerReactTarget,
CompilerPipelineValue,
Expand Down Expand Up @@ -51,31 +48,13 @@ function makePluginOptions(
ValueKindEnum: typeof ValueKind,
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
let gating = null;
let compilationMode: CompilationMode = 'all';
let panicThreshold: PanicThresholdOptions = 'all_errors';
let hookPattern: string | null = null;
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
let validatePreserveExistingMemoizationGuarantees = false;
let customMacros: null | Array<Macro> = null;
let validateBlocklistedImports = null;
let enableFire = false;
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')) {
gating = {
source: 'ReactForgetFeatureFlag',
Expand All @@ -96,10 +75,6 @@ function makePluginOptions(
}
}

if (firstLine.includes('@panicThreshold(none)')) {
panicThreshold = 'none';
}

let eslintSuppressionRules: Array<string> | null = null;
const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec(
firstLine,
Expand Down Expand Up @@ -130,10 +105,6 @@ function makePluginOptions(
validatePreserveExistingMemoizationGuarantees = true;
}

if (firstLine.includes('@enableFire')) {
enableFire = true;
}

const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine);
if (
hookPatternMatch &&
Expand Down Expand Up @@ -199,10 +170,11 @@ function makePluginOptions(
debugLogIRs: debugIRLogger,
};

const config = parseConfigPragmaFn(firstLine);
const config = parseConfigPragmaFn(firstLine, {compilationMode: 'all'});
const options = {
...config,
environment: {
...config,
...config.environment,
moduleTypeProvider: makeSharedRuntimeTypeProvider({
EffectEnum,
ValueKindEnum,
Expand All @@ -212,12 +184,9 @@ function makePluginOptions(
hookPattern,
validatePreserveExistingMemoizationGuarantees,
validateBlocklistedImports,
enableFire,
},
compilationMode,
logger,
gating,
panicThreshold,
noEmit: false,
eslintSuppressionRules,
flowSuppressions,
Expand Down

0 comments on commit d16fe4b

Please sign in to comment.