[compiler] snap fails if nothing compiled, unless @expectNothingCompiled (#35615)

A few times an agent has constructed fixtures that are silently skipped
because the component has no jsx or hook calls. This PR updates snap to
ensure that for each fixture either:

1) There are at least one compile success/failure *and* the
`@expectNothingCompiled` pragma is missing
2) OR there are zero success/failures *and* the `@expectNothingCompiled`
pragma is present

This ensures we are intentional about fixtures that are expected not to
have compilation, and know if that expectation breaks.
This commit is contained in:
Joseph Savona
2026-01-23 10:38:40 -08:00
committed by GitHub
parent 03613cd68c
commit 2c8725fdfd
59 changed files with 116 additions and 81 deletions

View File

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

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
class Component {
_renderMessage = () => {
const Message = () => {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @customOptOutDirectives:["use todo memo"]
// @expectNothingCompiled @customOptOutDirectives:["use todo memo"]
function Component() {
'use todo memo';
return <div>hello world!</div>;
@@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @customOptOutDirectives:["use todo memo"]
// @expectNothingCompiled @customOptOutDirectives:["use todo memo"]
function Component() {
"use todo memo";
return <div>hello world!</div>;

View File

@@ -1,4 +1,4 @@
// @customOptOutDirectives:["use todo memo"]
// @expectNothingCompiled @customOptOutDirectives:["use todo memo"]
function Component() {
'use todo memo';
return <div>hello world!</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
// @expectNothingCompiled @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
@@ -37,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
// @expectNothingCompiled @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
// @expectNothingCompiled @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating
// @expectNothingCompiled @gating
import {isForgetEnabled_Fixtures} from 'ReactForgetFeatureFlag';
export default 42;
@@ -12,7 +12,7 @@ export default 42;
## Code
```javascript
// @gating
// @expectNothingCompiled @gating
import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag";
export default 42;

View File

@@ -1,4 +1,4 @@
// @gating
// @expectNothingCompiled @gating
import {isForgetEnabled_Fixtures} from 'ReactForgetFeatureFlag';
export default 42;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;
@@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
import {useIdentity, identity} from 'shared-runtime';
function Component(fakeProps: number) {
@@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
import { useIdentity, identity } from "shared-runtime";
function Component(fakeProps: number) {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
import {useIdentity, identity} from 'shared-runtime';
function Component(fakeProps: number) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const result = f(props);
function helper() {
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const result = f(props);
function helper() {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const result = f(props);
function helper() {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const ignore = <foo />;
return {foo: f(props)};
@@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const ignore = <foo />;
return { foo: f(props) };

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
function Component(props) {
const ignore = <foo />;
return {foo: f(props)};

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This component is skipped bc it doesn't call any hooks or
// use JSX:
function Component(props) {
@@ -14,7 +14,7 @@ function Component(props) {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This component is skipped bc it doesn't call any hooks or
// use JSX:
function Component(props) {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This component is skipped bc it doesn't call any hooks or
// use JSX:
function Component(props) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Regression test for some internal code.
// This shows how the "callback rule" is more relaxed,
// and doesn't kick in unless we're confident we're in
@@ -20,7 +20,7 @@ function makeListener(instance) {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Regression test for some internal code.
// This shows how the "callback rule" is more relaxed,
// and doesn't kick in unless we're confident we're in

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Regression test for some internal code.
// This shows how the "callback rule" is more relaxed,
// and doesn't kick in unless we're confident we're in

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can call hooks.
function createHook() {
return function useHook() {
@@ -16,7 +16,7 @@ function createHook() {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can call hooks.
function createHook() {
return function useHook() {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can call hooks.
function createHook() {
return function useHook() {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can use hooks.
function createHook() {
return function useHookWithHook() {
@@ -15,7 +15,7 @@ function createHook() {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can use hooks.
function createHook() {
return function useHookWithHook() {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because hooks can use hooks.
function createHook() {
return function useHookWithHook() {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because components can use hooks.
function createComponentWithHook() {
return function ComponentWithHook() {
@@ -15,7 +15,7 @@ function createComponentWithHook() {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because components can use hooks.
function createComponentWithHook() {
return function ComponentWithHook() {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// Valid because components can use hooks.
function createComponentWithHook() {
return function ComponentWithHook() {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @expectNothingCompiled
// Valid because they're not matching use[A-Z].
fooState();
_use();
@@ -15,6 +16,7 @@ jest.useFakeTimer();
## Code
```javascript
// @expectNothingCompiled
// Valid because they're not matching use[A-Z].
fooState();
_use();

View File

@@ -1,3 +1,4 @@
// @expectNothingCompiled
// Valid because they're not matching use[A-Z].
fooState();
_use();

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @expectNothingCompiled
// Valid because classes can call functions.
// We don't consider these to be hooks.
class C {
@@ -16,6 +17,7 @@ class C {
## Code
```javascript
// @expectNothingCompiled
// Valid because classes can call functions.
// We don't consider these to be hooks.
class C {

View File

@@ -1,3 +1,4 @@
// @expectNothingCompiled
// Valid because classes can call functions.
// We don't consider these to be hooks.
class C {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
unknownFunction(function (foo, bar) {
@@ -16,7 +16,7 @@ unknownFunction(function (foo, bar) {
## Code
```javascript
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
unknownFunction(function (foo, bar) {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @expectNothingCompiled @compilationMode:"infer"
// This is valid because "use"-prefixed functions called in
// unnamed function arguments are not assumed to be hooks.
unknownFunction(function (foo, bar) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Invalid because it's dangerous.
@@ -22,7 +22,7 @@ useCustomHook();
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Invalid because it's dangerous.

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Invalid because it's dangerous.

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// This is a false positive (it's valid) that unfortunately
@@ -20,7 +20,7 @@ class Foo extends Component {
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// This is a false positive (it's valid) that unfortunately

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// This is a false positive (it's valid) that unfortunately

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Technically this is a false positive.
@@ -23,7 +23,7 @@ const browserHistory = useBasename(createHistory)({
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Technically this is a false positive.

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
// Technically this is a false positive.

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
@@ -16,7 +16,7 @@
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithHook extends React.Component {
@@ -16,7 +16,7 @@ class ClassComponentWithHook extends React.Component {
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithHook extends React.Component {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithHook extends React.Component {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithFeatureFlag extends React.Component {
@@ -18,7 +18,7 @@ class ClassComponentWithFeatureFlag extends React.Component {
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithFeatureFlag extends React.Component {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class ClassComponentWithFeatureFlag extends React.Component {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
@@ -16,7 +16,7 @@
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class C {
@@ -17,7 +17,7 @@ class C {
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class C {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
class C {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
@@ -16,7 +16,7 @@
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {
@@ -16,7 +16,7 @@
## Code
```javascript
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -1,4 +1,4 @@
// @skip
// @expectNothingCompiled @skip
// Passed but should have failed
(class {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @expectNothingCompiled
import {c as useMemoCache} from 'react/compiler-runtime';
function Component(props) {
@@ -26,6 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @expectNothingCompiled
import { c as useMemoCache } from "react/compiler-runtime";
function Component(props) {

View File

@@ -1,3 +1,4 @@
// @expectNothingCompiled
import {c as useMemoCache} from 'react/compiler-runtime';
function Component(props) {

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @expectNothingCompiled
function Component() {
'use no forget';
return <div>Hello World</div>;
@@ -18,6 +19,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @expectNothingCompiled
function Component() {
"use no forget";
return <div>Hello World</div>;

View File

@@ -1,3 +1,4 @@
// @expectNothingCompiled
function Component() {
'use no forget';
return <div>Hello World</div>;

View File

@@ -2,6 +2,7 @@
## Input
```javascript
// @expectNothingCompiled
function Component(props) {
'use no memo';
let x = [props.foo];
@@ -19,6 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @expectNothingCompiled
function Component(props) {
"use no memo";
let x = [props.foo];

View File

@@ -1,3 +1,4 @@
// @expectNothingCompiled
function Component(props) {
'use no memo';
let x = [props.foo];

View File

@@ -52,7 +52,11 @@ function makePluginOptions(
EffectEnum: typeof Effect,
ValueKindEnum: typeof ValueKind,
ValueReasonEnum: typeof ValueReason,
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
): {
options: PluginOptions;
loggerTestOnly: boolean;
logs: Array<{filename: string | null; event: LoggerEvent}>;
} {
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
let validatePreserveExistingMemoizationGuarantees = false;
let target: CompilerReactTarget = '19';
@@ -69,13 +73,12 @@ function makePluginOptions(
validatePreserveExistingMemoizationGuarantees = true;
}
const loggerTestOnly = firstLine.includes('@loggerTestOnly');
const logs: Array<{filename: string | null; event: LoggerEvent}> = [];
const logger: Logger = {
logEvent: firstLine.includes('@loggerTestOnly')
? (filename, event) => {
logs.push({filename, event});
}
: () => {},
logEvent: (filename, event) => {
logs.push({filename, event});
},
debugLogIRs: debugIRLogger,
};
@@ -96,7 +99,7 @@ function makePluginOptions(
enableReanimatedCheck: false,
target,
};
return [options, logs];
return {options, loggerTestOnly, logs};
}
export function parseInput(
@@ -245,7 +248,7 @@ export async function transformFixtureInput(
/**
* Get Forget compiled code
*/
const [options, logs] = makePluginOptions(
const {options, loggerTestOnly, logs} = makePluginOptions(
firstLine,
parseConfigPragmaFn,
debugIRLogger,
@@ -342,7 +345,7 @@ export async function transformFixtureInput(
}
const forgetOutput = await format(forgetCode, language);
let formattedLogs = null;
if (logs.length !== 0) {
if (loggerTestOnly && logs.length !== 0) {
formattedLogs = logs
.map(({event}) => {
return JSON.stringify(event, (key, value) => {
@@ -358,6 +361,23 @@ export async function transformFixtureInput(
})
.join('\n');
}
const expectNothingCompiled =
firstLine.indexOf('@expectNothingCompiled') !== -1;
const successFailures = logs.filter(
log =>
log.event.kind === 'CompileSuccess' || log.event.kind === 'CompileError',
);
if (successFailures.length === 0 && !expectNothingCompiled) {
return {
kind: 'err',
msg: 'No success/failure events, add `// @expectNothingCompiled` to the first line if this is expected',
};
} else if (successFailures.length !== 0 && expectNothingCompiled) {
return {
kind: 'err',
msg: 'Expected nothing to be compiled (from `// @expectNothingCompiled`), but some functions compiled or errored',
};
}
return {
kind: 'ok',
value: {