From 7bb66d178298a40a8f36ebb16fe39ebc5d98ecd1 Mon Sep 17 00:00:00 2001 From: MorikawaSouma Date: Wed, 18 Mar 2026 12:54:26 +0800 Subject: [PATCH] fix(eslint-plugin-react-hooks): support Unicode component and hook names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rules-of-hooks rule previously only recognized ASCII uppercase letters (A-Z) for component names and hook names, causing false positives for components/hooks with Unicode names like "ÄndraVärde". This change: - Updates isComponentName() to use toUpperCase() comparison - Updates isHookName() to support Unicode uppercase letters - Updates isHook() PascalCase namespace check to support Unicode The toUpperCase()/toLowerCase() comparison approach correctly identifies uppercase letters across all Unicode scripts, not just Latin A-Z. Fixes #31446 --- .../src/rules/RulesOfHooks.ts | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts index ca82c99e2f55..3489436c44fb 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts @@ -23,11 +23,25 @@ import CodePathAnalyzer from '../code-path-analysis/code-path-analyzer'; import {getAdditionalEffectHooksFromSettings} from '../shared/Utils'; /** - * Catch all identifiers that begin with "use" followed by an uppercase Latin + * Catch all identifiers that begin with "use" followed by an uppercase * character to exclude identifiers like "user". + * + * We use toUpperCase() comparison to support Unicode letters, not just A-Z. */ function isHookName(s: string): boolean { - return s === 'use' || /^use[A-Z0-9]/.test(s); + if (s === 'use') { + return true; + } + if (s.length < 4 || !s.startsWith('use')) { + return false; + } + const fourthChar = s[3]; + // Check if it's a digit or an uppercase letter (including Unicode) + const isDigit = fourthChar >= '0' && fourthChar <= '9'; + const isUpperCaseLetter = + fourthChar === fourthChar.toUpperCase() && + fourthChar !== fourthChar.toLowerCase(); + return isDigit || isUpperCaseLetter; } /** @@ -43,8 +57,15 @@ function isHook(node: Node): boolean { isHook(node.property) ) { const obj = node.object; - const isPascalCaseNameSpace = /^[A-Z].*/; - return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name); + // Check if namespace starts with uppercase letter (including Unicode) + if (obj.type === 'Identifier') { + const firstChar = obj.name[0]; + return ( + firstChar === firstChar.toUpperCase() && + firstChar !== firstChar.toLowerCase() + ); + } + return false; } else { return false; } @@ -53,9 +74,17 @@ function isHook(node: Node): boolean { /** * Checks if the node is a React component name. React component names must * always start with an uppercase letter. + * + * We use toUpperCase() comparison to support Unicode letters, not just A-Z. + * This allows component names like "ÄndraVärde" to be recognized as valid + * React components. */ function isComponentName(node: Node): boolean { - return node.type === 'Identifier' && /^[A-Z]/.test(node.name); + return ( + node.type === 'Identifier' && + node.name[0] === node.name[0].toUpperCase() && + node.name[0] !== node.name[0].toLowerCase() + ); } function isReactFunction(node: Node, functionName: string): boolean {