Files
react/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleFlow-test.ts
Joseph Savona 03ee29da2f [eslint-plugin-react-hooks] Skip compilation for non-React files (#35589)
Add a fast heuristic to detect whether a file may contain React
components or hooks before running the full compiler. This avoids the
overhead of Babel AST parsing and compilation for utility files, config
files, and other non-React code.

The heuristic uses ESLint's already-parsed AST to check for functions
with React-like names at module scope:
- Capitalized functions: MyComponent, Button, App
- Hook pattern functions: useEffect, useState, useMyCustomHook

Files without matching function names are skipped and return an empty
result, which is cached to avoid re-checking for subsequent rules.

Also adds test coverage for the heuristic edge cases.
2026-01-21 12:49:15 -08:00

148 lines
4.2 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 {RuleTester} from 'eslint';
import {allRules} from '../src/shared/ReactCompiler';
const ESLintTesterV8 = require('eslint-v8').RuleTester;
/**
* A string template tag that removes padding from the left side of multi-line strings
* @param {Array} strings array of code strings (only one expected)
*/
function normalizeIndent(strings: TemplateStringsArray): string {
const codeLines = strings[0]?.split('\n') ?? [];
const leftPadding = codeLines[1]?.match(/\s+/)![0] ?? '';
return codeLines.map(line => line.slice(leftPadding.length)).join('\n');
}
type CompilerTestCases = {
valid: RuleTester.ValidTestCase[];
invalid: RuleTester.InvalidTestCase[];
};
const tests: CompilerTestCases = {
valid: [
// ===========================================
// Tests for mayContainReactCode heuristic with Flow syntax
// Files that should be SKIPPED (no React-like function names)
// These contain code that WOULD trigger errors if compiled,
// but since the heuristic skips them, no errors are reported.
// ===========================================
{
name: '[Heuristic/Flow] Skips files with only lowercase utility functions',
filename: 'utils.js',
code: normalizeIndent`
function helper(obj) {
obj.key = 'value';
return obj;
}
`,
},
{
name: '[Heuristic/Flow] Skips lowercase arrow functions even with mutations',
filename: 'helpers.js',
code: normalizeIndent`
const processData = (input) => {
input.modified = true;
return input;
};
`,
},
],
invalid: [
// ===========================================
// Tests for mayContainReactCode heuristic with Flow component/hook syntax
// These use Flow's component/hook declarations which should be detected
// ===========================================
{
name: '[Heuristic/Flow] Compiles Flow component declaration - detects prop mutation',
filename: 'component.js',
code: normalizeIndent`
component MyComponent(a: {key: string}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic/Flow] Compiles exported Flow component declaration - detects prop mutation',
filename: 'component.js',
code: normalizeIndent`
export component MyComponent(a: {key: string}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic/Flow] Compiles default exported Flow component declaration - detects prop mutation',
filename: 'component.js',
code: normalizeIndent`
export default component MyComponent(a: {key: string}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic/Flow] Compiles Flow hook declaration - detects argument mutation',
filename: 'hooks.js',
code: normalizeIndent`
hook useMyHook(a: {key: string}) {
a.key = 'value';
return a;
}
`,
errors: [
{
message: /Modifying component props or hook arguments/,
},
],
},
{
name: '[Heuristic/Flow] Compiles exported Flow hook declaration - detects argument mutation',
filename: 'hooks.js',
code: normalizeIndent`
export hook useMyHook(a: {key: string}) {
a.key = 'value';
return a;
}
`,
errors: [
{
message: /Modifying component props or hook arguments/,
},
],
},
],
};
const eslintTester = new ESLintTesterV8({
parser: require.resolve('hermes-eslint'),
parserOptions: {
sourceType: 'module',
enableExperimentalComponentSyntax: true,
},
});
eslintTester.run('react-compiler', allRules['immutability'].rule, tests);