@samitouri / QOS-React / commits / 36c04348d7

[DevTools] Make Functions Clickable to Jump to Definition (#30769)

Currently you can jump to definition of a function by right clicking through the context menu. However, it's pretty difficult to discover. This makes the functions clickable to jump to definition - like links. This uses the same styling as we do for links (which are btw only clickable if they're not editable). Including cursor: pointer. I added a background on hover which follows the same pattern as the owners list. I also dropped the ƒ prefix when displaying functions. This is a cute short cut and there's precedence in how Chrome prints functions in the console *if* the function's toString would've had a function prefix like if it was a function declaration or expression. It does not do this for arrow functions or object methods. Elsewhere in the JS ecosystem this isn't really used anywhere. It invites more questions than it answers. The parenthesis and curlies are enough. There's no ambiguity here since strings have quotations. It looks better with just its object method form. Keeping it simple seems best. To my eyes this flows better because I'm used to looking at function syntax but not weird "f"s. Before: <img width="433" alt="Screenshot 2024-08-20 at 11 55 09 PM" src="https://github.com/user-attachments/assets/9dd50da6-598f-4291-9e24-1cdc7200dc9e"> After: <img width="388" alt="Screenshot 2024-08-20 at 11 46 01 PM" src="https://github.com/user-attachments/assets/dd988e14-412e-4deb-8c8c-26a54be8331f"> After (Hover): <img width="389" alt="Screenshot 2024-08-20 at 11 46 31 PM" src="https://github.com/user-attachments/assets/6fb4ebed-5dc1-448a-8e4d-b6d4f3903329">

Sebastian Markbåge committed Aug 22, 2024 at 12:35 UTC 36c04348d7c6179bac4e7f27af823a67289432f9
5 files changed +70 -27
packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
+14 -14
@@ -697,8 +697,8 @@ describe('InspectedElement', () => {
697 expect(inspectedElement.props).toMatchInlineSnapshot(`
698 {
699 "anonymous_fn": Dehydrated {
700 - "preview_short": ƒ () {},
701 - "preview_long": ƒ () {},
700 + "preview_short": () => {},
701 + "preview_long": () => {},
702 },
703 "array_buffer": Dehydrated {
704 "preview_short": ArrayBuffer(3),
@@ -715,8 +715,8 @@ describe('InspectedElement', () => {
715 "preview_long": 123n,
716 },
717 "bound_fn": Dehydrated {
718 - "preview_short": ƒ bound exampleFunction() {},
719 - "preview_long": ƒ bound exampleFunction() {},
718 + "preview_short": bound exampleFunction() {},
719 + "preview_long": bound exampleFunction() {},
720 },
721 "data_view": Dehydrated {
722 "preview_short": DataView(3),
@@ -727,8 +727,8 @@ describe('InspectedElement', () => {
727 "preview_long": Tue Dec 31 2019 23:42:42 GMT+0000 (Coordinated Universal Time),
728 },
729 "fn": Dehydrated {
730 - "preview_short": ƒ exampleFunction() {},
731 - "preview_long": ƒ exampleFunction() {},
730 + "preview_short": exampleFunction() {},
731 + "preview_long": exampleFunction() {},
732 },
733 "html_element": Dehydrated {
734 "preview_short": <div />,
@@ -778,8 +778,8 @@ describe('InspectedElement', () => {
778 "Symbol(name)": "hello",
779 },
780 "proxy": Dehydrated {
781 - "preview_short": ƒ () {},
782 - "preview_long": ƒ () {},
781 + "preview_short": () => {},
782 + "preview_long": () => {},
783 },
784 "react_element": Dehydrated {
785 "preview_short": <span />,
@@ -2018,16 +2018,16 @@ describe('InspectedElement', () => {
2018 {
2019 "proxy": {
2020 "$$typeof": Dehydrated {
2021 - "preview_short": ƒ () {},
2022 - "preview_long": ƒ () {},
2021 + "preview_short": () => {},
2022 + "preview_long": () => {},
2023 },
2024 "Symbol(Symbol.iterator)": Dehydrated {
2025 - "preview_short": ƒ () {},
2026 - "preview_long": ƒ () {},
2025 + "preview_short": () => {},
2026 + "preview_long": () => {},
2027 },
2028 "constructor": Dehydrated {
2029 - "preview_short": ƒ () {},
2030 - "preview_long": ƒ () {},
2029 + "preview_short": () => {},
2030 + "preview_long": () => {},
2031 },
2032 },
2033 }
packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js
+6 -6
@@ -212,8 +212,8 @@ describe('InspectedElementContext', () => {
212 expect(inspectedElement.props).toMatchInlineSnapshot(`
213 {
214 "anonymous_fn": Dehydrated {
215 - "preview_short": ƒ () {},
216 - "preview_long": ƒ () {},
215 + "preview_short": () => {},
216 + "preview_long": () => {},
217 },
218 "array_buffer": Dehydrated {
219 "preview_short": ArrayBuffer(3),
@@ -230,8 +230,8 @@ describe('InspectedElementContext', () => {
230 "preview_long": 123n,
231 },
232 "bound_fn": Dehydrated {
233 - "preview_short": ƒ bound exampleFunction() {},
234 - "preview_long": ƒ bound exampleFunction() {},
233 + "preview_short": bound exampleFunction() {},
234 + "preview_long": bound exampleFunction() {},
235 },
236 "data_view": Dehydrated {
237 "preview_short": DataView(3),
@@ -242,8 +242,8 @@ describe('InspectedElementContext', () => {
242 "preview_long": Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time),
243 },
244 "fn": Dehydrated {
245 - "preview_short": ƒ exampleFunction() {},
246 - "preview_long": ƒ exampleFunction() {},
245 + "preview_short": exampleFunction() {},
246 + "preview_long": exampleFunction() {},
247 },
248 "html_element": Dehydrated {
249 "preview_short": <div />,
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.css
+8
@@ -34,8 +34,16 @@
34 overflow: hidden;
35 text-overflow: ellipsis;
36 flex: 1;
37 + cursor: pointer;
38 + border-radius: 0.125rem;
39 + padding: 0px 2px;
40 }
41
42 +.Link:hover {
43 + background-color: var(--color-background-hover);
44 +}
45 +
46 +
47 .ExpandCollapseToggleSpacer {
48 flex: 0 0 1rem;
49 width: 1rem;
packages/react-devtools-shared/src/devtools/views/Components/KeyValue.js
+38 -4
@@ -27,7 +27,9 @@ import isArray from 'react-devtools-shared/src/isArray';
27 import {InspectedElementContext} from './InspectedElementContext';
28 import {PROTOCOLS_SUPPORTED_AS_LINKS_IN_KEY_VALUE} from './constants';
29 import KeyValueContextMenuContainer from './KeyValueContextMenuContainer';
30 +import {ContextMenuContext} from '../context';
31
32 +import type {ContextMenuContextType} from '../context';
33 import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
34 import type {Element} from 'react-devtools-shared/src/frontend/types';
35 import type {Element as ReactElement} from 'react';
@@ -91,6 +93,8 @@ export default function KeyValue({
93 const contextMenuTriggerRef = useRef(null);
94
95 const {inspectPaths} = useContext(InspectedElementContext);
96 + const {viewAttributeSourceFunction} =
97 + useContext<ContextMenuContextType>(ContextMenuContext);
98
99 let isInspectable = false;
100 let isReadOnlyBasedOnMetadata = false;
@@ -268,8 +272,8 @@ export default function KeyValue({
272 <KeyValueContextMenuContainer
273 key="root"
274 anchorElementRef={contextMenuTriggerRef}
271 - attributeSourceCanBeInspected={pathIsFunction}
272 - canBeCopiedToClipboard={!pathIsFunction}
275 + attributeSourceCanBeInspected={false}
276 + canBeCopiedToClipboard={true}
277 store={store}
278 bridge={bridge}
279 id={id}
@@ -305,6 +309,36 @@ export default function KeyValue({
309 </div>
310 </KeyValueContextMenuContainer>
311 );
312 + } else if (pathIsFunction && viewAttributeSourceFunction != null) {
313 + children = (
314 + <KeyValueContextMenuContainer
315 + key="root"
316 + anchorElementRef={contextMenuTriggerRef}
317 + attributeSourceCanBeInspected={true}
318 + canBeCopiedToClipboard={false}
319 + store={store}
320 + bridge={bridge}
321 + id={id}
322 + path={fullPath}>
323 + <div
324 + data-testname="KeyValue"
325 + className={styles.Item}
326 + hidden={hidden}
327 + ref={contextMenuTriggerRef}
328 + style={style}>
329 + <div className={styles.ExpandCollapseToggleSpacer} />
330 + {renderedName}
331 + <div className={styles.AfterName}>:</div>
332 + <span
333 + className={styles.Link}
334 + onClick={() => {
335 + viewAttributeSourceFunction(id, fullPath);
336 + }}>
337 + {getMetaValueLabel(value)}
338 + </span>
339 + </div>
340 + </KeyValueContextMenuContainer>
341 + );
342 } else if (
343 hasOwnProperty.call(value, meta.type) &&
344 !hasOwnProperty.call(value, meta.unserializable)
@@ -313,8 +347,8 @@ export default function KeyValue({
347 <KeyValueContextMenuContainer
348 key="root"
349 anchorElementRef={contextMenuTriggerRef}
316 - attributeSourceCanBeInspected={pathIsFunction}
317 - canBeCopiedToClipboard={!pathIsFunction}
350 + attributeSourceCanBeInspected={false}
351 + canBeCopiedToClipboard={true}
352 store={store}
353 bridge={bridge}
354 id={id}
packages/react-devtools-shared/src/utils.js
+4 -3
@@ -843,9 +843,10 @@ export function formatDataForPreview(
843 case 'html_element':
844 return `<${truncateForDisplay(data.tagName.toLowerCase())} />`;
845 case 'function':
846 - return truncateForDisplay(
847 - `ƒ ${typeof data.name === 'function' ? '' : data.name}() {}`,
848 - );
846 + if (typeof data.name === 'function' || data.name === '') {
847 + return '() => {}';
848 + }
849 + return `${truncateForDisplay(data.name)}() {}`;
850 case 'string':
851 return `"${data}"`;
852 case 'bigint':