eslint-config-typed 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/react-coding-style/rules/display-name.d.mts +2 -2
- package/dist/plugins/react-coding-style/rules/display-name.d.mts.map +1 -1
- package/dist/plugins/react-coding-style/rules/display-name.mjs +110 -30
- package/dist/plugins/react-coding-style/rules/display-name.mjs.map +1 -1
- package/dist/plugins/react-coding-style/rules/rules.d.mts +2 -2
- package/dist/rules/eslint-react-coding-style-rules.d.mts +1 -3
- package/dist/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
- package/dist/rules/eslint-react-coding-style-rules.mjs +1 -1
- package/dist/rules/eslint-react-coding-style-rules.mjs.map +1 -1
- package/dist/rules/eslint-react-rules.d.mts +1 -1
- package/dist/rules/eslint-react-rules.mjs +1 -1
- package/dist/rules/eslint-react-rules.mjs.map +1 -1
- package/dist/types/rules/eslint-cypress-rules.d.mts +2 -2
- package/dist/types/rules/eslint-import-rules.d.mts +214 -168
- package/dist/types/rules/eslint-import-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-jest-rules.d.mts +47 -45
- package/dist/types/rules/eslint-jest-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-playwright-rules.d.mts +41 -34
- package/dist/types/rules/eslint-playwright-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-prefer-arrow-functions-rules.d.mts +6 -2
- package/dist/types/rules/eslint-prefer-arrow-functions-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-react-coding-style-rules.d.mts +21 -6
- package/dist/types/rules/eslint-react-coding-style-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-react-rules.d.mts +208 -185
- package/dist/types/rules/eslint-react-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-rules.d.mts +1244 -1073
- package/dist/types/rules/eslint-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-stylistic-rules.d.mts +707 -619
- package/dist/types/rules/eslint-stylistic-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-ts-restrictions-rules.d.mts +94 -76
- package/dist/types/rules/eslint-ts-restrictions-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-unicorn-rules.d.mts +145 -121
- package/dist/types/rules/eslint-unicorn-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-vitest-rules.d.mts +43 -42
- package/dist/types/rules/eslint-vitest-rules.d.mts.map +1 -1
- package/dist/types/rules/typescript-eslint-rules.d.mts +1973 -1763
- package/dist/types/rules/typescript-eslint-rules.d.mts.map +1 -1
- package/package.json +58 -57
- package/src/plugins/react-coding-style/README.md +4 -3
- package/src/plugins/react-coding-style/rules/display-name.mts +160 -38
- package/src/plugins/react-coding-style/rules/display-name.test.mts +70 -6
- package/src/plugins/react-coding-style/rules/shared.test.mts +148 -0
- package/src/plugins/vitest-coding-style/rules/original-assert-api-type.test.mts +147 -143
- package/src/rules/eslint-react-coding-style-rules.mts +1 -1
- package/src/rules/eslint-react-rules.mts +1 -1
- package/src/types/rules/eslint-cypress-rules.mts +2 -2
- package/src/types/rules/eslint-import-rules.mts +214 -168
- package/src/types/rules/eslint-jest-rules.mts +47 -45
- package/src/types/rules/eslint-playwright-rules.mts +41 -34
- package/src/types/rules/eslint-prefer-arrow-functions-rules.mts +6 -2
- package/src/types/rules/eslint-react-coding-style-rules.mts +21 -6
- package/src/types/rules/eslint-react-rules.mts +208 -185
- package/src/types/rules/eslint-rules.mts +1229 -1058
- package/src/types/rules/eslint-stylistic-rules.mts +707 -619
- package/src/types/rules/eslint-ts-restrictions-rules.mts +94 -76
- package/src/types/rules/eslint-unicorn-rules.mts +145 -121
- package/src/types/rules/eslint-vitest-rules.mts +43 -42
- package/src/types/rules/typescript-eslint-rules.mts +1973 -1763
|
@@ -3,16 +3,16 @@ import {
|
|
|
3
3
|
type TSESLint,
|
|
4
4
|
type TSESTree,
|
|
5
5
|
} from '@typescript-eslint/utils';
|
|
6
|
-
import {
|
|
6
|
+
import { castDeepMutable } from 'ts-data-forge';
|
|
7
7
|
import { isReactApiCall } from './shared.mjs';
|
|
8
8
|
|
|
9
9
|
type Options = readonly [
|
|
10
10
|
Readonly<{
|
|
11
|
-
|
|
11
|
+
ignoreName?: string | readonly string[];
|
|
12
12
|
}>?,
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
type MessageIds = 'missingDisplayName';
|
|
15
|
+
type MessageIds = 'missingDisplayName' | 'mismatchedDisplayName';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Rule to require displayName property for React components
|
|
@@ -23,16 +23,23 @@ export const displayNameRule: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
23
23
|
type: 'suggestion',
|
|
24
24
|
docs: {
|
|
25
25
|
description:
|
|
26
|
-
'Require
|
|
26
|
+
'Require React.memo components to define displayName matching the component name',
|
|
27
27
|
},
|
|
28
28
|
schema: [
|
|
29
29
|
{
|
|
30
30
|
type: 'object',
|
|
31
31
|
properties: {
|
|
32
|
-
|
|
33
|
-
type: 'boolean',
|
|
32
|
+
ignoreName: {
|
|
34
33
|
description:
|
|
35
|
-
'
|
|
34
|
+
'Component names allowed to have displayName different from the variable name.',
|
|
35
|
+
oneOf: [
|
|
36
|
+
{ type: 'string' },
|
|
37
|
+
{
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: { type: 'string' },
|
|
40
|
+
minItems: 0,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
36
43
|
},
|
|
37
44
|
},
|
|
38
45
|
additionalProperties: false,
|
|
@@ -41,12 +48,17 @@ export const displayNameRule: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
41
48
|
messages: {
|
|
42
49
|
missingDisplayName:
|
|
43
50
|
'Component should have a displayName property for better debugging',
|
|
51
|
+
mismatchedDisplayName:
|
|
52
|
+
'displayName should match the component name "{{componentName}}"',
|
|
44
53
|
},
|
|
45
54
|
},
|
|
46
55
|
create: (context) => {
|
|
47
56
|
const options = context.options[0] ?? {};
|
|
48
57
|
|
|
49
|
-
const
|
|
58
|
+
const ignoreNameSet = normalizeNames(options.ignoreName);
|
|
59
|
+
|
|
60
|
+
const shouldIgnoreMismatch = (componentName: string): boolean =>
|
|
61
|
+
ignoreNameSet.has(componentName);
|
|
50
62
|
|
|
51
63
|
const checkComponent = (
|
|
52
64
|
node: DeepReadonly<TSESTree.VariableDeclarator>,
|
|
@@ -65,48 +77,47 @@ export const displayNameRule: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
65
77
|
|
|
66
78
|
const componentName = node.id.name;
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
80
|
+
const assignment = getDisplayNameAssignment(node);
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
if (assignment === undefined) {
|
|
83
|
+
context.report({
|
|
84
|
+
node: castDeepMutable(node),
|
|
85
|
+
messageId: 'missingDisplayName',
|
|
86
|
+
});
|
|
73
87
|
|
|
74
|
-
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
if (!isComponentDisplayNameAssignment(assignment, componentName)) {
|
|
92
|
+
context.report({
|
|
93
|
+
node: castDeepMutable(node),
|
|
94
|
+
messageId: 'missingDisplayName',
|
|
95
|
+
});
|
|
77
96
|
|
|
78
|
-
if (grandParent.type !== AST_NODE_TYPES.Program) {
|
|
79
97
|
return;
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
const
|
|
100
|
+
const displayName = extractDisplayName(assignment.right);
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
if (displayName === undefined) {
|
|
103
|
+
context.report({
|
|
104
|
+
node: assignment.right,
|
|
105
|
+
messageId: 'mismatchedDisplayName',
|
|
106
|
+
data: { componentName },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
85
111
|
|
|
86
|
-
if (
|
|
112
|
+
if (shouldIgnoreMismatch(componentName)) {
|
|
87
113
|
return;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const hasDisplayName =
|
|
93
|
-
nextStatement !== undefined &&
|
|
94
|
-
nextStatement.type === AST_NODE_TYPES.ExpressionStatement &&
|
|
95
|
-
nextStatement.expression.type === AST_NODE_TYPES.AssignmentExpression &&
|
|
96
|
-
nextStatement.expression.left.type ===
|
|
97
|
-
AST_NODE_TYPES.MemberExpression &&
|
|
98
|
-
nextStatement.expression.left.object.type ===
|
|
99
|
-
AST_NODE_TYPES.Identifier &&
|
|
100
|
-
nextStatement.expression.left.object.name === componentName &&
|
|
101
|
-
nextStatement.expression.left.property.type ===
|
|
102
|
-
AST_NODE_TYPES.Identifier &&
|
|
103
|
-
nextStatement.expression.left.property.name === 'displayName';
|
|
104
|
-
|
|
105
|
-
if (!hasDisplayName) {
|
|
116
|
+
if (displayName !== componentName) {
|
|
106
117
|
context.report({
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
node: assignment.right,
|
|
119
|
+
messageId: 'mismatchedDisplayName',
|
|
120
|
+
data: { componentName },
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
123
|
};
|
|
@@ -115,5 +126,116 @@ export const displayNameRule: TSESLint.RuleModule<MessageIds, Options> = {
|
|
|
115
126
|
VariableDeclarator: checkComponent,
|
|
116
127
|
};
|
|
117
128
|
},
|
|
118
|
-
defaultOptions: [{
|
|
129
|
+
defaultOptions: [{ ignoreName: [] }],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const normalizeNames = (
|
|
133
|
+
names: string | readonly string[] | undefined,
|
|
134
|
+
): ReadonlySet<string> => {
|
|
135
|
+
if (names === undefined) {
|
|
136
|
+
return new Set();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (typeof names === 'string') {
|
|
140
|
+
return new Set([names]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return new Set(names);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const getDisplayNameAssignment = (
|
|
147
|
+
node: DeepReadonly<TSESTree.VariableDeclarator>,
|
|
148
|
+
): DeepReadonly<TSESTree.AssignmentExpression> | undefined => {
|
|
149
|
+
let mut_current = node.parent as DeepReadonly<TSESTree.Node> | undefined;
|
|
150
|
+
|
|
151
|
+
let mut_statement: DeepReadonly<TSESTree.Statement> | undefined = undefined;
|
|
152
|
+
|
|
153
|
+
while (mut_current !== undefined) {
|
|
154
|
+
if (
|
|
155
|
+
mut_current.type === AST_NODE_TYPES.VariableDeclaration ||
|
|
156
|
+
mut_current.type === AST_NODE_TYPES.ExportNamedDeclaration
|
|
157
|
+
) {
|
|
158
|
+
mut_statement = mut_current as DeepReadonly<TSESTree.Statement>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (mut_current.type === AST_NODE_TYPES.Program) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
mut_current = mut_current.parent;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (mut_current === undefined || mut_statement === undefined) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const program = mut_current;
|
|
173
|
+
|
|
174
|
+
const componentIndex = program.body.indexOf(
|
|
175
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
176
|
+
mut_statement as TSESTree.Statement,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (componentIndex === -1) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const nextStatement = program.body[componentIndex + 1];
|
|
184
|
+
|
|
185
|
+
if (nextStatement === undefined) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (nextStatement.type !== AST_NODE_TYPES.ExpressionStatement) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (nextStatement.expression.type !== AST_NODE_TYPES.AssignmentExpression) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return nextStatement.expression;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const isComponentDisplayNameAssignment = (
|
|
201
|
+
assignment: DeepReadonly<TSESTree.AssignmentExpression>,
|
|
202
|
+
componentName: string,
|
|
203
|
+
): assignment is TSESTree.AssignmentExpression => {
|
|
204
|
+
if (assignment.left.type !== AST_NODE_TYPES.MemberExpression) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (assignment.left.object.type !== AST_NODE_TYPES.Identifier) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (assignment.left.object.name !== componentName) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
assignment.left.property.type === AST_NODE_TYPES.Identifier &&
|
|
218
|
+
assignment.left.property.name === 'displayName'
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const extractDisplayName = (
|
|
223
|
+
expression: DeepReadonly<TSESTree.Expression>,
|
|
224
|
+
): string | undefined => {
|
|
225
|
+
if (
|
|
226
|
+
expression.type === AST_NODE_TYPES.Literal &&
|
|
227
|
+
typeof expression.value === 'string'
|
|
228
|
+
) {
|
|
229
|
+
return expression.value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
expression.type === AST_NODE_TYPES.TemplateLiteral &&
|
|
234
|
+
expression.expressions.length === 0 &&
|
|
235
|
+
expression.quasis.length === 1
|
|
236
|
+
) {
|
|
237
|
+
return expression.quasis[0]?.value.cooked ?? undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return undefined;
|
|
119
241
|
};
|
|
@@ -41,6 +41,13 @@ describe('display-name', () => {
|
|
|
41
41
|
const notAComponent = someFunction();
|
|
42
42
|
`,
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Exported component with displayName',
|
|
46
|
+
code: dedent`
|
|
47
|
+
export const MyComponent = React.memo(() => <div>Hello</div>);
|
|
48
|
+
MyComponent.displayName = 'MyComponent';
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
44
51
|
],
|
|
45
52
|
invalid: [
|
|
46
53
|
{
|
|
@@ -50,6 +57,26 @@ describe('display-name', () => {
|
|
|
50
57
|
`,
|
|
51
58
|
errors: [{ messageId: 'missingDisplayName' }],
|
|
52
59
|
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Exported component without displayName',
|
|
62
|
+
code: dedent`
|
|
63
|
+
export const MyComponent = React.memo(() => <div>Hello</div>);
|
|
64
|
+
`,
|
|
65
|
+
errors: [{ messageId: 'missingDisplayName' }],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'Component with mismatched displayName',
|
|
69
|
+
code: dedent`
|
|
70
|
+
const MyComponent = React.memo(() => <div>Hello</div>);
|
|
71
|
+
MyComponent.displayName = 'Other';
|
|
72
|
+
`,
|
|
73
|
+
errors: [
|
|
74
|
+
{
|
|
75
|
+
messageId: 'mismatchedDisplayName',
|
|
76
|
+
data: { componentName: 'MyComponent' },
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
53
80
|
{
|
|
54
81
|
name: 'Named import without displayName',
|
|
55
82
|
code: dedent`
|
|
@@ -58,19 +85,47 @@ describe('display-name', () => {
|
|
|
58
85
|
`,
|
|
59
86
|
errors: [{ messageId: 'missingDisplayName' }],
|
|
60
87
|
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Named import with mismatched displayName',
|
|
90
|
+
code: dedent`
|
|
91
|
+
import { memo } from 'react';
|
|
92
|
+
const MyComponent = memo(() => <div>Hello</div>);
|
|
93
|
+
MyComponent.displayName = 'Component';
|
|
94
|
+
`,
|
|
95
|
+
errors: [
|
|
96
|
+
{
|
|
97
|
+
messageId: 'mismatchedDisplayName',
|
|
98
|
+
data: { componentName: 'MyComponent' },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Exported component with mismatched displayName',
|
|
104
|
+
code: dedent`
|
|
105
|
+
export const MyComponent = React.memo(() => <div>Hello</div>);
|
|
106
|
+
MyComponent.displayName = 'Component';
|
|
107
|
+
`,
|
|
108
|
+
errors: [
|
|
109
|
+
{
|
|
110
|
+
messageId: 'mismatchedDisplayName',
|
|
111
|
+
data: { componentName: 'MyComponent' },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
61
115
|
],
|
|
62
116
|
});
|
|
63
117
|
});
|
|
64
118
|
|
|
65
|
-
describe('
|
|
66
|
-
tester.run('display-name with
|
|
119
|
+
describe('ignoreName option', () => {
|
|
120
|
+
tester.run('display-name with ignoreName', displayNameRule, {
|
|
67
121
|
valid: [
|
|
68
122
|
{
|
|
69
|
-
name: 'Component
|
|
123
|
+
name: 'Component with mismatched displayName (ignored)',
|
|
70
124
|
code: dedent`
|
|
71
125
|
const MyComponent = React.memo(() => <div>Hello</div>);
|
|
126
|
+
MyComponent.displayName = 'Other';
|
|
72
127
|
`,
|
|
73
|
-
options: [{
|
|
128
|
+
options: [{ ignoreName: 'MyComponent' }],
|
|
74
129
|
},
|
|
75
130
|
{
|
|
76
131
|
name: 'Component with displayName',
|
|
@@ -78,10 +133,19 @@ describe('display-name', () => {
|
|
|
78
133
|
const MyComponent = React.memo(() => <div>Hello</div>);
|
|
79
134
|
MyComponent.displayName = 'MyComponent';
|
|
80
135
|
`,
|
|
81
|
-
options: [{
|
|
136
|
+
options: [{ ignoreName: ['MyComponent'] }],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
invalid: [
|
|
140
|
+
{
|
|
141
|
+
name: 'Component without displayName is still reported',
|
|
142
|
+
code: dedent`
|
|
143
|
+
const MyComponent = React.memo(() => <div>Hello</div>);
|
|
144
|
+
`,
|
|
145
|
+
options: [{ ignoreName: ['MyComponent'] }],
|
|
146
|
+
errors: [{ messageId: 'missingDisplayName' }],
|
|
82
147
|
},
|
|
83
148
|
],
|
|
84
|
-
invalid: [],
|
|
85
149
|
});
|
|
86
150
|
});
|
|
87
151
|
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import parser from '@typescript-eslint/parser';
|
|
2
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
3
|
+
import { type TSESLint } from '@typescript-eslint/utils';
|
|
4
|
+
import { getReactMemoArrowFunction, isReactApiCall } from './shared.mjs';
|
|
5
|
+
|
|
6
|
+
const tester = new RuleTester({
|
|
7
|
+
languageOptions: {
|
|
8
|
+
parser,
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2020,
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
ecmaFeatures: { jsx: true },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const reactApiRule: TSESLint.RuleModule<'reactApiDetected', readonly []> = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: 'problem',
|
|
20
|
+
docs: { description: 'test helper isReactApiCall' },
|
|
21
|
+
schema: [],
|
|
22
|
+
messages: {
|
|
23
|
+
reactApiDetected: 'React API call detected',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create: (context) => ({
|
|
28
|
+
CallExpression: (node) => {
|
|
29
|
+
if (isReactApiCall(context, node, 'memo')) {
|
|
30
|
+
context.report({ node, messageId: 'reactApiDetected' });
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const reactMemoArrowRule: TSESLint.RuleModule<'arrowDetected', readonly []> = {
|
|
37
|
+
meta: {
|
|
38
|
+
type: 'problem',
|
|
39
|
+
docs: { description: 'test helper getReactMemoArrowFunction' },
|
|
40
|
+
schema: [],
|
|
41
|
+
messages: {
|
|
42
|
+
arrowDetected: 'React.memo received arrow function',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultOptions: [],
|
|
46
|
+
create: (context) => ({
|
|
47
|
+
CallExpression: (node) => {
|
|
48
|
+
if (!isReactApiCall(context, node, 'memo')) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const arrow = getReactMemoArrowFunction(node);
|
|
53
|
+
|
|
54
|
+
if (arrow !== undefined) {
|
|
55
|
+
assert.strictEqual(arrow.type, 'ArrowFunctionExpression');
|
|
56
|
+
|
|
57
|
+
context.report({ node, messageId: 'arrowDetected' });
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe('shared helpers', () => {
|
|
64
|
+
tester.run('isReactApiCall', reactApiRule, {
|
|
65
|
+
valid: [
|
|
66
|
+
{
|
|
67
|
+
name: 'non React call',
|
|
68
|
+
code: 'const x = fn();',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'memo imported from non-react',
|
|
72
|
+
code: `
|
|
73
|
+
import { memo } from 'not-react';
|
|
74
|
+
const Component = memo(() => null);
|
|
75
|
+
`,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'React member but different method',
|
|
79
|
+
code: `
|
|
80
|
+
import * as React from 'react';
|
|
81
|
+
const Component = React.useMemo(() => null, []);
|
|
82
|
+
`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
invalid: [
|
|
86
|
+
{
|
|
87
|
+
name: 'named memo import from react',
|
|
88
|
+
code: `
|
|
89
|
+
import { memo } from 'react';
|
|
90
|
+
const Component = memo(() => null);
|
|
91
|
+
`,
|
|
92
|
+
errors: [{ messageId: 'reactApiDetected' }],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'namespace React memo call',
|
|
96
|
+
code: `
|
|
97
|
+
import * as React from 'react';
|
|
98
|
+
const Component = React.memo(() => null);
|
|
99
|
+
`,
|
|
100
|
+
errors: [{ messageId: 'reactApiDetected' }],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'global React memo call without import',
|
|
104
|
+
code: `
|
|
105
|
+
const Component = React.memo(() => null);
|
|
106
|
+
`,
|
|
107
|
+
errors: [{ messageId: 'reactApiDetected' }],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
tester.run('getReactMemoArrowFunction', reactMemoArrowRule, {
|
|
113
|
+
valid: [
|
|
114
|
+
{
|
|
115
|
+
name: 'memo with non-arrow first argument',
|
|
116
|
+
code: `
|
|
117
|
+
import { memo } from 'react';
|
|
118
|
+
function Component() { return null; }
|
|
119
|
+
const Wrapped = memo(Component);
|
|
120
|
+
`,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'non React call with arrow argument',
|
|
124
|
+
code: `
|
|
125
|
+
const Wrapped = wrap(() => null);
|
|
126
|
+
`,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
invalid: [
|
|
130
|
+
{
|
|
131
|
+
name: 'memo with arrow function argument',
|
|
132
|
+
code: `
|
|
133
|
+
import { memo } from 'react';
|
|
134
|
+
const Wrapped = memo(() => null);
|
|
135
|
+
`,
|
|
136
|
+
errors: [{ messageId: 'arrowDetected' }],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'React namespace memo with arrow function argument',
|
|
140
|
+
code: `
|
|
141
|
+
import * as React from 'react';
|
|
142
|
+
const Wrapped = React.memo(() => null);
|
|
143
|
+
`,
|
|
144
|
+
errors: [{ messageId: 'arrowDetected' }],
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
});
|