Skip to content

Commit e4aefd0

Browse files
chore: linter rules to detect forwardRef usage (callstack#3632)
1 parent 0556ba6 commit e4aefd0

File tree

11 files changed

+89
-8
lines changed

11 files changed

+89
-8
lines changed

Diff for: .eslintrc

+14
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
"extends": "@callstack",
77

8+
"plugins": ["eslint-plugin-local-rules"],
9+
810
"rules": {
11+
"local-rules/no-import-react-forwardref": "error",
12+
"local-rules/no-react-forwardref-usage": "error",
13+
914
"one-var": "off",
1015
"no-multi-assign": "off",
1116
"no-nested-ternary": "off",
@@ -49,6 +54,15 @@
4954
"react-native-a11y/has-valid-accessibility-descriptors": "off"
5055
},
5156

57+
"overrides": [
58+
{
59+
"files": ["*.test.js", "*.test.tsx"],
60+
"rules": {
61+
"local-rules/no-react-forwardref-usage": "off"
62+
}
63+
}
64+
],
65+
5266
"settings": {
5367
"import/extensions": [".js", ".ts", ".tsx"],
5468
"import/parsers": {

Diff for: eslint-local-rules/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Object.assign(exports, require('./no-import-react-forwardref'));
2+
Object.assign(exports, require('./no-react-forwardref-usage'));

Diff for: eslint-local-rules/no-import-react-forwardref.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
exports['no-import-react-forwardref'] = {
2+
meta: {
3+
docs: {
4+
description: 'Disallow importing of React.forwardRef',
5+
category: 'Possible Errors',
6+
recommended: false,
7+
},
8+
schema: [],
9+
},
10+
create(context) {
11+
return {
12+
ImportDeclaration(node) {
13+
if (node.source.value !== 'react') return;
14+
15+
for (const specifier of node.specifiers) {
16+
if (specifier.type !== 'ImportSpecifier') continue;
17+
if (specifier.imported.name !== 'forwardRef') continue;
18+
19+
context.report({
20+
loc: specifier.loc,
21+
message: 'Import forwardRef from src/utils/forwardRef instead',
22+
});
23+
}
24+
},
25+
};
26+
},
27+
};

Diff for: eslint-local-rules/no-react-forwardref-usage.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
exports['no-react-forwardref-usage'] = {
2+
meta: {
3+
docs: {
4+
description: 'Disallow usage of React.forwardRef',
5+
category: 'Possible Errors',
6+
recommended: false,
7+
},
8+
schema: [],
9+
},
10+
create(context) {
11+
return {
12+
CallExpression(node) {
13+
if (node.callee?.type !== 'MemberExpression') return;
14+
15+
const { callee } = node;
16+
if (callee.object.type !== 'Identifier') return;
17+
if (callee.object.name !== 'React') return;
18+
if (callee.property.type !== 'Identifier') return;
19+
if (callee.property.name !== 'forwardRef') return;
20+
21+
context.report({
22+
loc: callee.loc,
23+
message: 'Use forwardRef from src/utils/forwardRef instead',
24+
});
25+
},
26+
};
27+
},
28+
};

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"dedent": "^0.7.0",
8080
"eslint": "8.31.0",
8181
"eslint-plugin-flowtype": "^8.0.3",
82+
"eslint-plugin-local-rules": "^1.3.2",
8283
"expo-constants": "^9.3.5",
8384
"flow-bin": "0.92.0",
8485
"glob": "^7.1.3",

Diff for: src/components/Surface.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useInternalTheme } from '../core/theming';
1212
import overlay, { isAnimatedValue } from '../styles/overlay';
1313
import shadow from '../styles/shadow';
1414
import type { ThemeProp, MD3Elevation } from '../types';
15+
import { forwardRef } from '../utils/forwardRef';
1516

1617
export type Props = React.ComponentPropsWithRef<typeof View> & {
1718
/**
@@ -39,7 +40,7 @@ export type Props = React.ComponentPropsWithRef<typeof View> & {
3940
ref?: React.RefObject<View>;
4041
};
4142

42-
const MD2Surface = React.forwardRef<View, Props>(
43+
const MD2Surface = forwardRef<View, Props>(
4344
({ style, theme: overrideTheme, ...rest }: Omit<Props, 'elevation'>, ref) => {
4445
const { elevation = 4 } = (StyleSheet.flatten(style) || {}) as ViewStyle;
4546
const { dark: isDarkTheme, mode, colors } = useInternalTheme(overrideTheme);
@@ -105,7 +106,7 @@ const MD2Surface = React.forwardRef<View, Props>(
105106
* });
106107
* ```
107108
*/
108-
const Surface = React.forwardRef<View, Props>(
109+
const Surface = forwardRef<View, Props>(
109110
(
110111
{
111112
elevation = 1,

Diff for: src/components/ToggleButton/ToggleButton.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import color from 'color';
1313
import { useInternalTheme } from '../../core/theming';
1414
import { black, white } from '../../styles/themes/v2/colors';
1515
import type { ThemeProp } from '../../types';
16+
import { forwardRef } from '../../utils/forwardRef';
1617
import type { IconSource } from '../Icon';
1718
import IconButton from '../IconButton/IconButton';
1819
import { ToggleButtonGroupContext } from './ToggleButtonGroup';
@@ -97,7 +98,7 @@ export type Props = {
9798
*
9899
* ```
99100
*/
100-
const ToggleButton = React.forwardRef<View, Props>(
101+
const ToggleButton = forwardRef<View, Props>(
101102
(
102103
{
103104
icon,

Diff for: src/components/Typography/Text.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99

1010
import { useInternalTheme } from '../../core/theming';
1111
import { Font, MD3TypescaleKey, ThemeProp } from '../../types';
12+
import { forwardRef } from '../../utils/forwardRef';
1213

1314
export type Props = React.ComponentProps<typeof NativeText> & {
1415
/**
@@ -149,4 +150,4 @@ const styles = StyleSheet.create({
149150
},
150151
});
151152

152-
export default React.forwardRef(Text);
153+
export default forwardRef(Text);

Diff for: src/components/Typography/v2/Text.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import type { MD2Theme } from 'src/types';
1010

1111
import { useInternalTheme } from '../../../core/theming';
12+
import { forwardRef } from '../../../utils/forwardRef';
1213

1314
type Props = React.ComponentProps<typeof NativeText> & {
1415
style?: StyleProp<TextStyle>;
@@ -58,4 +59,4 @@ const styles = StyleSheet.create({
5859
},
5960
});
6061

61-
export default React.forwardRef(Text);
62+
export default forwardRef(Text);

Diff for: src/utils/forwardRef.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {
2-
forwardRef as reactForwardRef,
1+
import * as React from 'react';
2+
import type {
33
ForwardRefRenderFunction,
44
PropsWithoutRef,
55
RefAttributes,
@@ -20,4 +20,4 @@ export type ForwarRefComponent<T, P = {}> = ForwardRefExoticComponent<
2020
*/
2121
export const forwardRef: <T, P = {}>(
2222
render: ForwardRefRenderFunction<T, P>
23-
) => ForwarRefComponent<T, P> = reactForwardRef;
23+
) => ForwarRefComponent<T, P> = React.forwardRef;

Diff for: yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -5206,6 +5206,11 @@ eslint-plugin-jest@^27.0.1:
52065206
dependencies:
52075207
"@typescript-eslint/utils" "^5.10.0"
52085208

5209+
eslint-plugin-local-rules@^1.3.2:
5210+
version "1.3.2"
5211+
resolved "https://registry.yarnpkg.com/eslint-plugin-local-rules/-/eslint-plugin-local-rules-1.3.2.tgz#b9c9522915faeb9e430309fb909fc1dbcd7aedb3"
5212+
integrity sha512-X4ziX+cjlCYnZa+GB1ly3mmj44v2PeIld3tQVAxelY6AMrhHSjz6zsgsT6nt0+X5b7eZnvL/O7Q3pSSK2kF/+Q==
5213+
52095214
eslint-plugin-prettier@^4.0.0:
52105215
version "4.2.1"
52115216
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"

0 commit comments

Comments
 (0)