From aac80524dce934e1b89c16e7d224af99a63c4904 Mon Sep 17 00:00:00 2001 From: Dmitrii Troitskii Date: Sat, 21 Feb 2026 17:14:38 +0000 Subject: [PATCH] [compiler] Surface all lint diagnostics instead of stopping at first validation error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In lint mode (ESLint plugin), the compilation pipeline would stop at the first validation error. For example, if `validateHooksUsage` found an issue, subsequent validations like `validateNoCapitalizedCalls` would never run, hiding additional lint errors from the user. This changes the pipeline to use `env.logErrors()` instead of `.unwrap()` for early validations (hooks usage and capitalized calls) when in lint mode. These validations are purely diagnostic — they don't modify the HIR — so it's safe to continue the pipeline after logging their errors. This fixes the issue where files with multiple types of lint violations would only show the first category of error. Fixes #35394 Fixes #31545 --- .../src/Entrypoint/Pipeline.ts | 12 +++- .../src/HIR/Environment.ts | 2 +- .../__tests__/PluginTest-test.ts | 2 +- .../__tests__/Repro35394-test.ts | 63 +++++++++++++++++++ 4 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 compiler/packages/eslint-plugin-react-compiler/__tests__/Repro35394-test.ts diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 90651818c7..4a6fdeacf5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -196,10 +196,18 @@ function runWithEnvironment( if (env.enableValidations) { if (env.config.validateHooksUsage) { - validateHooksUsage(hir).unwrap(); + if (env.outputMode === 'lint') { + env.logErrors(validateHooksUsage(hir)); + } else { + validateHooksUsage(hir).unwrap(); + } } if (env.config.validateNoCapitalizedCalls) { - validateNoCapitalizedCalls(hir).unwrap(); + if (env.outputMode === 'lint') { + env.logErrors(validateNoCapitalizedCalls(hir)); + } else { + validateNoCapitalizedCalls(hir).unwrap(); + } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 80caee2caf..79269d25a7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -696,7 +696,7 @@ export class Environment { return this.#scope; } - logErrors(errors: Result): void { + logErrors(errors: Result): void { if (errors.isOk() || this.logger == null) { return; } diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts index e9fe8e001b..eeec23048d 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts @@ -57,7 +57,6 @@ testRule('plugin-recommended', TestRecommendedRules, { ], invalid: [ { - // TODO: actually return multiple diagnostics in this case name: 'Multiple diagnostic kinds from the same function are surfaced', code: normalizeIndent` import Child from './Child'; @@ -70,6 +69,7 @@ testRule('plugin-recommended', TestRecommendedRules, { `, errors: [ makeTestCaseError('Hooks must always be called in a consistent order'), + makeTestCaseError('Capitalized functions are reserved for components'), ], }, { diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/Repro35394-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/Repro35394-test.ts new file mode 100644 index 0000000000..6d6d9d909e --- /dev/null +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/Repro35394-test.ts @@ -0,0 +1,63 @@ +/** + * 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 {normalizeIndent, TestRecommendedRules, testRule} from './shared-utils'; + +testRule('repro-35394', TestRecommendedRules, { + valid: [], + invalid: [ + { + name: '#35394 repro - multiple violations with useState in nested component', + code: normalizeIndent` + import {useState, useEffect} from 'react'; + export default function App() { + const [foo, setFoo] = useState(false); + + useEffect(() => { + setFoo(!foo); + }, []); + + const ViolateStaticComponents = () => { + const [counter, setCounter] = useState(0); + return

Hello, World!

; + }; + + return ( + <> + + + ); + } + `, + errors: 3, + }, + { + name: 'Without nested useState - should show 2 errors', + code: normalizeIndent` + import {useState, useEffect} from 'react'; + export default function App() { + const [foo, setFoo] = useState(false); + + useEffect(() => { + setFoo(!foo); + }, []); + + const ViolateStaticComponents = () => { + return

Hello, World!

; + }; + + return ( + <> + + + ); + } + `, + errors: 2, + }, + ], +});