Skip to content
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
@@ -0,0 +1,147 @@
/**
* 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);
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ const tests: CompilerTestCases = {
}
`,
},
// ===========================================
// Tests for mayContainReactCode heuristic
// 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] Skips files with only lowercase utility functions',
filename: 'utils.ts',
// This mutates an argument, which would be flagged in a component/hook,
// but this file is skipped because there are no React-like function names
code: normalizeIndent`
function helper(obj) {
obj.key = 'value';
return obj;
}
`,
},
{
name: '[Heuristic] Skips lowercase arrow functions even with mutations',
filename: 'helpers.ts',
// Would be flagged if compiled, but skipped due to lowercase name
code: normalizeIndent`
const processData = (input) => {
input.modified = true;
return input;
};
`,
},
],
invalid: [
{
Expand All @@ -68,6 +97,101 @@ const tests: CompilerTestCases = {
},
],
},
// ===========================================
// Tests for mayContainReactCode heuristic
// Files that SHOULD be compiled (have React-like function names)
// These contain violations to prove compilation happens.
// ===========================================
{
name: '[Heuristic] Compiles PascalCase function declaration - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
function MyComponent({a}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic] Compiles PascalCase arrow function - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
const MyComponent = ({a}) => {
a.key = 'value';
return <div />;
};
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic] Compiles PascalCase function expression - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
const MyComponent = function({a}) {
a.key = 'value';
return <div />;
};
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic] Compiles exported function declaration - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
export function MyComponent({a}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic] Compiles exported arrow function - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
export const MyComponent = ({a}) => {
a.key = 'value';
return <div />;
};
`,
errors: [
{
message: /Modifying component props/,
},
],
},
{
name: '[Heuristic] Compiles default exported function - detects prop mutation',
filename: 'component.tsx',
code: normalizeIndent`
export default function MyComponent({a}) {
a.key = 'value';
return <div />;
}
`,
errors: [
{
message: /Modifying component props/,
},
],
},
],
};

Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin-react-hooks/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ process.env.NODE_ENV = 'development';
module.exports = {
setupFiles: [require.resolve('../../scripts/jest/setupEnvironment.js')],
moduleFileExtensions: ['ts', 'js', 'json'],
moduleNameMapper: {
'^babel-plugin-react-compiler$':
'<rootDir>/../../compiler/packages/babel-plugin-react-compiler/dist/index.js',
},
};
Loading
Loading