Skip to content

Commit

Permalink
[DevTools] Make Functions Clickable to Jump to Definition (#30769)
Browse files Browse the repository at this point in the history
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:/user-attachments/assets/9dd50da6-598f-4291-9e24-1cdc7200dc9e">


After:
<img width="388" alt="Screenshot 2024-08-20 at 11 46 01 PM"
src="https:/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:/user-attachments/assets/6fb4ebed-5dc1-448a-8e4d-b6d4f3903329">
  • Loading branch information
sebmarkbage authored Aug 22, 2024
1 parent 97e2ce6 commit 36c0434
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -697,8 +697,8 @@ describe('InspectedElement', () => {
expect(inspectedElement.props).toMatchInlineSnapshot(`
{
"anonymous_fn": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
"array_buffer": Dehydrated {
"preview_short": ArrayBuffer(3),
Expand All @@ -715,8 +715,8 @@ describe('InspectedElement', () => {
"preview_long": 123n,
},
"bound_fn": Dehydrated {
"preview_short": ƒ bound exampleFunction() {},
"preview_long": ƒ bound exampleFunction() {},
"preview_short": bound exampleFunction() {},
"preview_long": bound exampleFunction() {},
},
"data_view": Dehydrated {
"preview_short": DataView(3),
Expand All @@ -727,8 +727,8 @@ describe('InspectedElement', () => {
"preview_long": Tue Dec 31 2019 23:42:42 GMT+0000 (Coordinated Universal Time),
},
"fn": Dehydrated {
"preview_short": ƒ exampleFunction() {},
"preview_long": ƒ exampleFunction() {},
"preview_short": exampleFunction() {},
"preview_long": exampleFunction() {},
},
"html_element": Dehydrated {
"preview_short": <div />,
Expand Down Expand Up @@ -778,8 +778,8 @@ describe('InspectedElement', () => {
"Symbol(name)": "hello",
},
"proxy": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
"react_element": Dehydrated {
"preview_short": <span />,
Expand Down Expand Up @@ -2018,16 +2018,16 @@ describe('InspectedElement', () => {
{
"proxy": {
"$$typeof": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
"Symbol(Symbol.iterator)": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
"constructor": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ describe('InspectedElementContext', () => {
expect(inspectedElement.props).toMatchInlineSnapshot(`
{
"anonymous_fn": Dehydrated {
"preview_short": ƒ () {},
"preview_long": ƒ () {},
"preview_short": () => {},
"preview_long": () => {},
},
"array_buffer": Dehydrated {
"preview_short": ArrayBuffer(3),
Expand All @@ -230,8 +230,8 @@ describe('InspectedElementContext', () => {
"preview_long": 123n,
},
"bound_fn": Dehydrated {
"preview_short": ƒ bound exampleFunction() {},
"preview_long": ƒ bound exampleFunction() {},
"preview_short": bound exampleFunction() {},
"preview_long": bound exampleFunction() {},
},
"data_view": Dehydrated {
"preview_short": DataView(3),
Expand All @@ -242,8 +242,8 @@ describe('InspectedElementContext', () => {
"preview_long": Thu Jan 01 1970 00:00:00 GMT+0000 (Coordinated Universal Time),
},
"fn": Dehydrated {
"preview_short": ƒ exampleFunction() {},
"preview_long": ƒ exampleFunction() {},
"preview_short": exampleFunction() {},
"preview_long": exampleFunction() {},
},
"html_element": Dehydrated {
"preview_short": <div />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
cursor: pointer;
border-radius: 0.125rem;
padding: 0px 2px;
}

.Link:hover {
background-color: var(--color-background-hover);
}


.ExpandCollapseToggleSpacer {
flex: 0 0 1rem;
width: 1rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import isArray from 'react-devtools-shared/src/isArray';
import {InspectedElementContext} from './InspectedElementContext';
import {PROTOCOLS_SUPPORTED_AS_LINKS_IN_KEY_VALUE} from './constants';
import KeyValueContextMenuContainer from './KeyValueContextMenuContainer';
import {ContextMenuContext} from '../context';

import type {ContextMenuContextType} from '../context';
import type {InspectedElement} from 'react-devtools-shared/src/frontend/types';
import type {Element} from 'react-devtools-shared/src/frontend/types';
import type {Element as ReactElement} from 'react';
Expand Down Expand Up @@ -91,6 +93,8 @@ export default function KeyValue({
const contextMenuTriggerRef = useRef(null);

const {inspectPaths} = useContext(InspectedElementContext);
const {viewAttributeSourceFunction} =
useContext<ContextMenuContextType>(ContextMenuContext);

let isInspectable = false;
let isReadOnlyBasedOnMetadata = false;
Expand Down Expand Up @@ -268,8 +272,8 @@ export default function KeyValue({
<KeyValueContextMenuContainer
key="root"
anchorElementRef={contextMenuTriggerRef}
attributeSourceCanBeInspected={pathIsFunction}
canBeCopiedToClipboard={!pathIsFunction}
attributeSourceCanBeInspected={false}
canBeCopiedToClipboard={true}
store={store}
bridge={bridge}
id={id}
Expand Down Expand Up @@ -305,6 +309,36 @@ export default function KeyValue({
</div>
</KeyValueContextMenuContainer>
);
} else if (pathIsFunction && viewAttributeSourceFunction != null) {
children = (
<KeyValueContextMenuContainer
key="root"
anchorElementRef={contextMenuTriggerRef}
attributeSourceCanBeInspected={true}
canBeCopiedToClipboard={false}
store={store}
bridge={bridge}
id={id}
path={fullPath}>
<div
data-testname="KeyValue"
className={styles.Item}
hidden={hidden}
ref={contextMenuTriggerRef}
style={style}>
<div className={styles.ExpandCollapseToggleSpacer} />
{renderedName}
<div className={styles.AfterName}>:</div>
<span
className={styles.Link}
onClick={() => {
viewAttributeSourceFunction(id, fullPath);
}}>
{getMetaValueLabel(value)}
</span>
</div>
</KeyValueContextMenuContainer>
);
} else if (
hasOwnProperty.call(value, meta.type) &&
!hasOwnProperty.call(value, meta.unserializable)
Expand All @@ -313,8 +347,8 @@ export default function KeyValue({
<KeyValueContextMenuContainer
key="root"
anchorElementRef={contextMenuTriggerRef}
attributeSourceCanBeInspected={pathIsFunction}
canBeCopiedToClipboard={!pathIsFunction}
attributeSourceCanBeInspected={false}
canBeCopiedToClipboard={true}
store={store}
bridge={bridge}
id={id}
Expand Down
7 changes: 4 additions & 3 deletions packages/react-devtools-shared/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -843,9 +843,10 @@ export function formatDataForPreview(
case 'html_element':
return `<${truncateForDisplay(data.tagName.toLowerCase())} />`;
case 'function':
return truncateForDisplay(
${typeof data.name === 'function' ? '' : data.name}() {}`,
);
if (typeof data.name === 'function' || data.name === '') {
return '() => {}';
}
return `${truncateForDisplay(data.name)}() {}`;
case 'string':
return `"${data}"`;
case 'bigint':
Expand Down

0 comments on commit 36c0434

Please sign in to comment.