From 13a80c4c8adec03f1a7e9a6b29df4ac5fe0c7de3 Mon Sep 17 00:00:00 2001 From: Terry Tao Date: Sat, 20 Dec 2025 23:50:22 -0500 Subject: [PATCH] DevTools: Fix nested HOC name extraction extractHOCNames unwraps displayName strings like: withFoo(withBar(Component)) The previous implementation used a global RegExp and relied on RegExp.exec() while mutating the input string. Because global RegExp instances keep state via lastIndex, nested wrappers could stop unwrapping early and only record the outermost wrapper. Use an anchored, non-global RegExp so each unwrap re-parses the updated name from the beginning, avoiding lastIndex drift. Add unit tests covering nested wrappers, a single wrapper, and no-wrapper cases. Test Plan: node ./scripts/jest/jest-cli.js --project devtools --build \ --runTestsByPath packages/react-devtools-shared/src/__tests__/extractHOCNames-test.js --- .../src/__tests__/extractHOCNames-test.js | 33 +++++++++++++++++++ .../src/backend/views/utils.js | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 packages/react-devtools-shared/src/__tests__/extractHOCNames-test.js diff --git a/packages/react-devtools-shared/src/__tests__/extractHOCNames-test.js b/packages/react-devtools-shared/src/__tests__/extractHOCNames-test.js new file mode 100644 index 000000000000..b5f8ac29c6ba --- /dev/null +++ b/packages/react-devtools-shared/src/__tests__/extractHOCNames-test.js @@ -0,0 +1,33 @@ +/** + * 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. + * + * @flow + */ + +import {extractHOCNames} from 'react-devtools-shared/src/backend/views/utils'; + +describe('extractHOCNames', () => { + it('should extract nested HOC names in order', () => { + expect(extractHOCNames('Memo(Forget(Foo))')).toEqual({ + baseComponentName: 'Foo', + hocNames: ['Memo', 'Forget'], + }); + }); + + it('should handle a single HOC', () => { + expect(extractHOCNames('Memo(Foo)')).toEqual({ + baseComponentName: 'Foo', + hocNames: ['Memo'], + }); + }); + + it('should return the original name when there are no HOCs', () => { + expect(extractHOCNames('Foo')).toEqual({ + baseComponentName: 'Foo', + hocNames: [], + }); + }); +}); diff --git a/packages/react-devtools-shared/src/backend/views/utils.js b/packages/react-devtools-shared/src/backend/views/utils.js index a73c8094edb9..397cab5137b8 100644 --- a/packages/react-devtools-shared/src/backend/views/utils.js +++ b/packages/react-devtools-shared/src/backend/views/utils.js @@ -148,7 +148,7 @@ export function extractHOCNames(displayName: string): { } { if (!displayName) return {baseComponentName: '', hocNames: []}; - const hocRegex = /([A-Z][a-zA-Z0-9]*?)\((.*)\)/g; + const hocRegex = /^([A-Z][a-zA-Z0-9]*?)\((.*)\)$/; const hocNames: string[] = []; let baseComponentName = displayName; let match;