Skip to content

Commit

Permalink
feat: support get special component displayName
Browse files Browse the repository at this point in the history
support format Suspense、Profiler、StrictMode and Context.
  • Loading branch information
xyy94813 committed Jul 19, 2021
1 parent dbbd9e5 commit 76399c2
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 15 deletions.
12 changes: 4 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@
"smoke": "node tests/smoke/run"
},
"lint-staged": {
"*.js": [
"prettier --write \"**/*.{js,json}\"",
"git add"
]
"*.js": ["prettier --write \"**/*.{js,json}\"", "git add"]
},
"author": {
"name": "Algolia, Inc.",
Expand Down Expand Up @@ -80,15 +77,14 @@
},
"peerDependencies": {
"react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1",
"react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1"
"react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1",
"react-is": "^16.0.0"
},
"dependencies": {
"@base2/pretty-print-object": "1.0.0",
"is-plain-object": "3.0.1"
},
"jest": {
"setupFilesAfterEnv": [
"<rootDir>tests/setupTests.js"
]
"setupFilesAfterEnv": ["<rootDir>tests/setupTests.js"]
}
}
56 changes: 55 additions & 1 deletion src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

/* eslint-disable react/no-string-refs */

import React, { Fragment, Component } from 'react';
import React, {
Fragment,
Component,
Suspense,
createContext,
Profiler,
StrictMode,
} from 'react';
import { createRenderer } from 'react-test-renderer/shallow';
import { mount } from 'enzyme';
import reactElementToJSXString, { preserveFunctionLineBreak } from './index';
Expand Down Expand Up @@ -1113,6 +1120,53 @@ describe('reactElementToJSXString(ReactElement)', () => {
).toEqual(`<div render={<><div /><div /></>} />`);
});

it('reactElementToJSXString(<Suspense fallback="loading" />)', () => {
expect(reactElementToJSXString(<Suspense fallback="loading" />)).toEqual(
`<Suspense fallback="loading" />`
);
});

it('reactElementToJSXString(<Profiler id="Main" />)', () => {
expect(reactElementToJSXString(<Profiler id="Main" />)).toEqual(
`<Profiler id="Main" />`
);
});

it('reactElementToJSXString(<StrictMode />)', () => {
expect(reactElementToJSXString(<StrictMode />)).toEqual(`<StrictMode />`);
});

it('reactElementToJSXString(<Context.Provider><Context.Consumer/></Context.Provider>)', () => {
const Context = createContext('Custom Context');
expect(
reactElementToJSXString(
<Context.Provider>
<Context.Consumer />
</Context.Provider>
)
).toEqual(
`<Context.Provider>
<Context.Consumer />
</Context.Provider>`
);
});

it('reactElementToJSXString: context with displayName', () => {
const Context = createContext('Custom Context');
Context.displayName = 'CustomContext';
expect(
reactElementToJSXString(
<Context.Provider>
<Context.Consumer />
</Context.Provider>
)
).toEqual(
`<CustomContext.Provider>
<CustomContext.Consumer />
</CustomContext.Provider>`
);
});

it('should not cause recursive loop when prop object contains an element', () => {
const Test = () => <div>Test</div>;

Expand Down
121 changes: 121 additions & 0 deletions src/libs/getComponentNameFromType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Reference from https://github.com/facebook/react/blob/28625c6f45423e6edc5ca0e2932281769c0d431e/packages/shared/getComponentNameFromType.js
*
* @flow
*/

import type { Context } from 'react';
import { Fragment } from 'react';
import {
ContextConsumer,
ContextProvider,
ForwardRef,
Portal,
Memo,
Profiler,
StrictMode,
Suspense,
SuspenseList,
Lazy,
} from 'react-is';

/**
* didn't export the type in React
* same as https://github.com/facebook/react/blob/310187264d01a31bc3079358f13662d31a079d9e/packages/react/index.js
*/
type LazyComponent<T, P> = {
$$typeof: Symbol | number,
_payload: P,
_init: (payload: P) => T,
};

// Keep in sync with react-reconciler/getComponentNameFromFiber
function getWrappedName(
outerType: mixed,
innerType: any,
wrapperName: string
): string {
const displayName = (outerType: any).displayName;
if (displayName) {
return displayName;
}
const functionName = innerType.displayName || innerType.name || '';
return functionName !== '' ? `${wrapperName}(${functionName})` : wrapperName;
}

// Keep in sync with react-reconciler/getComponentNameFromFiber
function getContextName(type: Context<any>) {
return type.displayName || 'Context';
}

// Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead.
// eslint-disable-next-line complexity
function getComponentNameFromType(type: mixed): string | null {
if (type === null || type === undefined) {
// Host root, text node or just invalid type.
return null;
}
if (typeof type === 'function') {
return (type: any).displayName || type.name || null;
}
if (typeof type === 'string') {
return type;
}
// eslint-disable-next-line default-case
switch (type) {
case Fragment:
return 'Fragment';
case Portal:
return 'Portal';
case Profiler:
return 'Profiler';
case StrictMode:
return 'StrictMode';
case Suspense:
return 'Suspense';
case SuspenseList:
return 'SuspenseList';
// case REACT_CACHE_TYPE:
// return 'Cache';
}
if (typeof type === 'object') {
// eslint-disable-next-line default-case
switch (type.$$typeof) {
case ContextConsumer: {
/**
* in DEV, should get context from `_context`.
* https://github.com/facebook/react/blob/e16d61c3000e2de6217d06b9afad162e883f73c4/packages/react/src/ReactContext.js#L44-L125
*/
const context: any = type._context ?? type;
return `${getContextName(context)}.Consumer`;
}
case ContextProvider: {
const context: any = type._context;
return `${getContextName(context)}.Provider`;
}
case ForwardRef:
// eslint-disable-next-line no-case-declarations
return getWrappedName(type, type.render, 'ForwardRef');
case Memo: {
const outerName = (type: any).displayName || null;
if (outerName !== null) {
return outerName;
}
return getComponentNameFromType(type.type) || 'Memo';
}
case Lazy: {
const lazyComponent: LazyComponent<any, any> = (type: any);
const payload = lazyComponent._payload;
const init = lazyComponent._init;
try {
return getComponentNameFromType(init(payload));
} catch (x) {
return null;
}
}
}
}
return null;
}

export default getComponentNameFromType;
19 changes: 13 additions & 6 deletions src/parser/parseReactElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,22 @@ import {
createReactFragmentTreeNode,
} from './../tree';
import type { TreeNode } from './../tree';
import getComponentNameFromType from '../libs/getComponentNameFromType';

const supportFragment = Boolean(Fragment);

const getReactElementDisplayName = (element: ReactElement<*>): string =>
element.type.displayName ||
(element.type.name !== '_default' ? element.type.name : null) || // function name
(typeof element.type === 'function' // function without a name, you should provide one
? 'No Display Name'
: element.type);
const getReactElementDisplayName = (element: ReactElement<*>): string => {
const displayName = getComponentNameFromType(element.type);
if (
displayName === '_default' ||
displayName === null ||
displayName === undefined
) {
return 'No Display Name';
}

return displayName;
};

const noChildren = (propsValue, propName) => propName !== 'children';

Expand Down

0 comments on commit 76399c2

Please sign in to comment.