Skip to content

[compiler][playground][tests] Standardize more pragmas #33146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { c as _c } from "react/compiler-runtime"; // 
        @compilationMode(all)
        @compilationMode:"all"
function nonReactFn() {
  const $ = _c(1);
  let t0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @compilationMode(infer)
// @compilationMode:"infer"
function nonReactFn() {
  return {};
}
4 changes: 2 additions & 2 deletions compiler/apps/playground/__tests__/e2e/page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function useFoo(propVal: {+baz: number}) {
},
{
name: 'compilationMode-infer',
input: `// @compilationMode(infer)
input: `// @compilationMode:"infer"
function nonReactFn() {
return {};
}
Expand All @@ -101,7 +101,7 @@ function nonReactFn() {
},
{
name: 'compilationMode-all',
input: `// @compilationMode(all)
input: `// @compilationMode:"all"
function nonReactFn() {
return {};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ export type PluginOptions = {
* provided rules will skip compilation. To disable this feature (never bailout of compilation
* even if the default ESLint is suppressed), pass an empty array.
*/
eslintSuppressionRules?: Array<string> | null | undefined;
eslintSuppressionRules: Array<string> | null | undefined;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already spreading defaultOptions when parsing PluginOptions, which would set eslintSuppressionRules and sources if they were undefined in the Partial<PluginOptions> parameter. This type change should be a no-op


flowSuppressions: boolean;
/*
* Ignore 'use no forget' annotations. Helpful during testing but should not be used in production.
*/
ignoreUseNoForget: boolean;

sources?: Array<string> | ((filename: string) => boolean) | null;
sources: Array<string> | ((filename: string) => boolean) | null;

/**
* The compiler has customized support for react-native-reanimated, intended as a temporary workaround.
Expand Down
138 changes: 71 additions & 67 deletions compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {CompilerError} from '../CompilerError';
import {
CompilationMode,
defaultOptions,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
} from '../Entrypoint';
Expand All @@ -19,14 +18,24 @@ 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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed for hookPattern strings which contain escape character sequences

if (stringMatch && stringMatch.length > 1) {
parsedVal = stringMatch[1];
} else {
parsedVal = JSON.parse(val);
}
return Ok(parsedVal);
} catch (e) {
return Err(e);
}
}

/**
* For test fixtures and playground only.
*
* Pragmas are straightforward to parse for boolean options (`:true` and
* `:false`). These are 'enabled' config values for non-boolean configs (i.e.
* what is used when parsing `:true`).
*/
const testComplexConfigDefaults: PartialEnvironmentConfig = {
validateNoCapitalizedCalls: [],
enableChangeDetectionForDebugging: {
Expand Down Expand Up @@ -84,34 +93,37 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
},
],
};

/**
* For snap test fixtures and playground only.
*/
function parseConfigPragmaEnvironmentForTest(
pragma: string,
): EnvironmentConfig {
const maybeConfig: any = {};
// Get the defaults to programmatically check for boolean properties
const defaultConfig = EnvironmentConfigSchema.parse({});
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {};

for (const token of pragma.split(' ')) {
if (!token.startsWith('@')) {
continue;
}
const keyVal = token.slice(1);
let [key, val = undefined] = keyVal.split(':');
const valIdx = keyVal.indexOf(':');
const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx);
const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1);
const isSet = val === undefined || val === 'true';

if (isSet && key in testComplexConfigDefaults) {
maybeConfig[key] =
testComplexConfigDefaults[key as keyof PartialEnvironmentConfig];
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
continue;
}

if (key === 'customMacros' && val) {
const valSplit = val.split('.');
if (valSplit.length > 0) {
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 === '*') {
Expand All @@ -121,21 +133,9 @@ function parseConfigPragmaEnvironmentForTest(
}
}
maybeConfig[key] = [[valSplit[0], props]];
continue;
}
continue;
}

if (
key !== 'enableResetCacheOnSourceFileChanges' &&
typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean'
) {
// skip parsing non-boolean properties
continue;
}
if (val === undefined || val === 'true') {
maybeConfig[key] = true;
} else {
maybeConfig[key] = false;
maybeConfig[key] = parsedVal;
}
}
const config = EnvironmentConfigSchema.safeParse(maybeConfig);
Expand All @@ -156,51 +156,55 @@ function parseConfigPragmaEnvironmentForTest(
suggestions: null,
});
}

const testComplexPluginOptionDefaults: Partial<PluginOptions> = {
gating: {
source: 'ReactForgetFeatureFlag',
importSpecifierName: 'isForgetEnabled_Fixtures',
},
};
export function parseConfigPragmaForTests(
pragma: string,
defaults: {
compilationMode: CompilationMode;
},
): PluginOptions {
const environment = parseConfigPragmaEnvironmentForTest(pragma);
let compilationMode: CompilationMode = defaults.compilationMode;
let panicThreshold: PanicThresholdOptions = 'all_errors';
let noEmit: boolean = defaultOptions.noEmit;
const options: Record<keyof PluginOptions, unknown> = {
...defaultOptions,
panicThreshold: 'all_errors',
compilationMode: defaults.compilationMode,
environment,
};
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;
}
case '@noEmit': {
noEmit = true;
break;
const keyVal = token.slice(1);
const idx = keyVal.indexOf(':');
const key = idx === -1 ? keyVal : keyVal.slice(0, idx);
const val = idx === -1 ? undefined : keyVal.slice(idx + 1);
if (!hasOwnProperty(defaultOptions, key)) {
continue;
}
const isSet = val === undefined || 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({
environment,
compilationMode,
panicThreshold,
noEmit,
});
return parsePluginOptions(options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @compilationMode(infer)
// @compilationMode:"infer"
const Test = () => <div />;

export const FIXTURE_ENTRYPOINT = {
Expand All @@ -15,7 +15,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code

```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer)
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer"
const Test = () => {
const $ = _c(1);
let t0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @compilationMode(infer)
// @compilationMode:"infer"
const Test = () => <div />;

export const FIXTURE_ENTRYPOINT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @compilationMode(infer)
// @compilationMode:"infer"
class Component {
_renderMessage = () => {
const Message = () => {
Expand All @@ -22,7 +22,7 @@ class Component {
## Code

```javascript
// @compilationMode(infer)
// @compilationMode:"infer"
class Component {
_renderMessage = () => {
const Message = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @compilationMode(infer)
// @compilationMode:"infer"
class Component {
_renderMessage = () => {
const Message = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableEmitInstrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode:"annotation"

function Bar(props) {
'use forget';
Expand All @@ -24,7 +24,7 @@ function Foo(props) {

```javascript
import { shouldInstrument, useRenderCounter } from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation"

function Bar(props) {
"use forget";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableEmitInstrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode:"annotation"

function Bar(props) {
'use forget';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @flow @compilationMode(infer)
// @flow @compilationMode:"infer"
export default component Foo(bar: number) {
return <Bar bar={bar} />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @flow @compilationMode(infer)
// @flow @compilationMode:"infer"
export default component Foo(bar: number) {
return <Bar bar={bar} />;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @enableEmitInstrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode:"annotation"

import {identity} from 'shared-runtime';

Expand Down Expand Up @@ -35,7 +35,7 @@ import {
shouldInstrument as _shouldInstrument3,
useRenderCounter,
} from "react-compiler-runtime";
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation)
import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation"

import { identity } from "shared-runtime";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @enableEmitInstrumentForget @compilationMode(annotation)
// @enableEmitInstrumentForget @compilationMode:"annotation"

import {identity} from 'shared-runtime';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Input

```javascript
// @eslintSuppressionRules(my-app/react-rule)
// @eslintSuppressionRules:["my-app","react-rule"]

/* eslint-disable my-app/react-rule */
function lowercasecomponent() {
Expand All @@ -19,7 +19,7 @@ function lowercasecomponent() {
## Error

```
1 | // @eslintSuppressionRules(my-app/react-rule)
1 | // @eslintSuppressionRules:["my-app","react-rule"]
2 |
> 3 | /* eslint-disable my-app/react-rule */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @eslintSuppressionRules(my-app/react-rule)
// @eslintSuppressionRules:["my-app","react-rule"]

/* eslint-disable my-app/react-rule */
function lowercasecomponent() {
Expand Down
Loading
Loading