From e84cb2f1bfeef2f6799d1a0763514302237c2e3b Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:15:28 +0000 Subject: [PATCH] fix(react/display-name): handle merged type+value context symbols (#19608) ## Summary - scan all symbol redeclarations in `react/display-name` when detecting components/context objects - keep existing displayName assignment checks, but avoid missing value declarations hidden behind TS type redeclarations - add a regression test for `interface` + `const` sharing the same context name Fixes #19607 --- .../src/rules/react/display_name.rs | 44 ++++++++++++++----- .../src/snapshots/react_display_name.snap | 9 ++++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/oxc_linter/src/rules/react/display_name.rs b/crates/oxc_linter/src/rules/react/display_name.rs index bc6aac030dd2c..f4d9b7a6adc06 100644 --- a/crates/oxc_linter/src/rules/react/display_name.rs +++ b/crates/oxc_linter/src/rules/react/display_name.rs @@ -151,17 +151,20 @@ impl Rule for DisplayName { // Phase 1: Iterate symbols to find components with declarations for symbol_id in ctx.scoping().symbol_ids() { - let declaration = ctx.scoping().symbol_declaration(symbol_id); - let decl_node = ctx.nodes().get_node(declaration); + let component_info = + ctx.scoping().symbol_declarations(symbol_id).find_map(|declaration| { + let decl_node = ctx.nodes().get_node(declaration); + is_react_component_node( + decl_node, + ctx, + &mut version_cache, + ignore_transpiler_name, + check_context_objects, + ) + }); // First check if the declaration itself is a component - if let Some(component_info) = is_react_component_node( - decl_node, - ctx, - &mut version_cache, - ignore_transpiler_name, - check_context_objects, - ) { + if let Some(component_info) = component_info { // If component has a name and ignoreTranspilerName is false and it's not a context, // the name itself is considered a valid displayName if component_info.name.is_some() @@ -178,9 +181,13 @@ impl Rule for DisplayName { } else if check_context_objects { // If the declaration isn't a component, check if any write references assign createContext() // This handles: var Hello; Hello = createContext(); - if let Some(component_info) = - check_context_assignment_references(symbol_id, decl_node, ctx) - { + let context_assignment_info = + ctx.scoping().symbol_declarations(symbol_id).find_map(|declaration| { + let decl_node = ctx.nodes().get_node(declaration); + check_context_assignment_references(symbol_id, decl_node, ctx) + }); + + if let Some(component_info) = context_assignment_info { // Check if this symbol has a displayName assignment if !has_display_name_via_semantic(symbol_id, component_info.name.as_ref(), ctx) { @@ -2151,6 +2158,19 @@ fn test() { " import { createContext } from 'react'; + interface PostHogGroupContext { + value: string; + } + + const PostHogGroupContext = createContext(null); + ", + Some(serde_json::json!([{ "checkContextObjects": true }])), + None, + ), + ( + " + import { createContext } from 'react'; + var Hello; Hello = createContext(); ", diff --git a/crates/oxc_linter/src/snapshots/react_display_name.snap b/crates/oxc_linter/src/snapshots/react_display_name.snap index 95e2036977b26..dab2575559405 100644 --- a/crates/oxc_linter/src/snapshots/react_display_name.snap +++ b/crates/oxc_linter/src/snapshots/react_display_name.snap @@ -343,6 +343,15 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Add a `displayName` property to the context. + ⚠ eslint-plugin-react(display-name): Context definition is missing display name. + ╭─[display_name.tsx:8:27] + 7 │ + 8 │ const PostHogGroupContext = createContext(null); + · ─────────────────── + 9 │ + ╰──── + help: Add a `displayName` property to the context. + ⚠ eslint-plugin-react(display-name): Context definition is missing display name. ╭─[display_name.tsx:5:21] 4 │ var Hello;