/** * 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
; } `, 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
; } `, 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
; } `, 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);