Skip to content

Commit 5a931dd

Browse files
authored
no-array-for-each: Ignore React.Children.forEach (#1088)
1 parent b74f56b commit 5a931dd

File tree

6 files changed

+115
-16
lines changed

6 files changed

+115
-16
lines changed

rules/no-array-callback-reference.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const {isParenthesized} = require('eslint-utils');
33
const getDocumentationUrl = require('./utils/get-documentation-url');
44
const methodSelector = require('./utils/method-selector');
55
const {notFunctionSelector} = require('./utils/not-function');
6+
const isNodeMatches = require('./utils/is-node-matches');
67

78
const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name';
89
const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name';
@@ -71,7 +72,7 @@ const iteratorMethods = [
7172
const ignoredCallee = [
7273
// http://bluebirdjs.com/docs/api/promise.map.html
7374
'Promise',
74-
'React.children',
75+
'React.Children',
7576
'Children',
7677
'lodash',
7778
'underscore',
@@ -80,18 +81,6 @@ const ignoredCallee = [
8081
'async'
8182
];
8283

83-
const toSelector = name => {
84-
const splitted = name.split('.');
85-
return `[callee.${'object.'.repeat(splitted.length)}name!="${splitted.shift()}"]`;
86-
};
87-
88-
// Select all the call expressions except the ones present in the ignore list.
89-
const ignoredCalleeSelector = [
90-
// `this.{map, filter, …}()`
91-
'[callee.object.type!="ThisExpression"]',
92-
...ignoredCallee.map(name => toSelector(name))
93-
].join('');
94-
9584
function check(context, node, method, options) {
9685
const {type} = node;
9786

@@ -162,11 +151,17 @@ const create = context => {
162151
max: 2
163152
}),
164153
options.extraSelector,
165-
ignoredCalleeSelector,
166154
ignoredFirstArgumentSelector
167155
].join('');
168156

169157
rules[selector] = node => {
158+
if (
159+
isNodeMatches(node.callee.object, ignoredCallee) ||
160+
node.callee.object.type === 'ThisExpression'
161+
) {
162+
return;
163+
}
164+
170165
const [iterator] = node.arguments;
171166
check(context, iterator, method, options, sourceCode);
172167
};

rules/no-array-for-each.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const shouldAddParenthesesToExpressionStatementExpression = require('./utils/sho
1414
const getParenthesizedTimes = require('./utils/get-parenthesized-times');
1515
const extendFixRange = require('./utils/extend-fix-range');
1616
const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside');
17+
const isNodeMatches = require('./utils/is-node-matches');
1718

1819
const MESSAGE_ID = 'no-array-for-each';
1920
const messages = {
@@ -315,6 +316,11 @@ function isFixable(callExpression, sourceCode, {scope, functionInfo, allIdentifi
315316
return true;
316317
}
317318

319+
const ignoredObjects = [
320+
'React.Children',
321+
'Children'
322+
];
323+
318324
const create = context => {
319325
const functionStack = [];
320326
const callExpressions = [];
@@ -349,6 +355,10 @@ const create = context => {
349355
returnStatements.push(node);
350356
},
351357
[arrayForEachCallSelector](node) {
358+
if (isNodeMatches(node.callee.object, ignoredObjects)) {
359+
return;
360+
}
361+
352362
callExpressions.push({
353363
node,
354364
scope: context.getScope()

rules/utils/is-node-matches.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
function isNodeMatchesNameOrPath(node, nameOrPath) {
4+
const names = nameOrPath.split('.');
5+
for (let index = names.length - 1; index >= 0; index--) {
6+
const name = names[index];
7+
8+
if (index === 0) {
9+
return node.type === 'Identifier' && node.name === name;
10+
}
11+
12+
if (
13+
node.type !== 'MemberExpression' ||
14+
node.optional ||
15+
node.property.type !== 'Identifier' ||
16+
node.property.name !== name
17+
) {
18+
return false;
19+
}
20+
21+
node = node.object;
22+
}
23+
}
24+
25+
/**
26+
Check if node matches any object name or key path.
27+
28+
@param {Node} node - The AST node to check.
29+
@param {string[]} nameOrPaths - The object name or key paths.
30+
@returns {boolean}
31+
*/
32+
function isNodeMatches(node, nameOrPaths) {
33+
return nameOrPaths.some(nameOrPath => isNodeMatchesNameOrPath(node, nameOrPath));
34+
}
35+
36+
module.exports = isNodeMatches;

test/no-array-for-each.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ test.snapshot({
55
valid: [
66
'new foo.forEach(element => bar())',
77
'forEach(element => bar())',
8-
'foo.notForEach(element => bar())'
8+
'foo.notForEach(element => bar())',
9+
// #1087
10+
'React.Children.forEach(children, (child) => {});',
11+
'Children.forEach(children, (child) => {});'
912
],
1013
invalid: [
1114
// Not fixable
@@ -334,7 +337,12 @@ test.snapshot({
334337
bar() {}
335338
}
336339
].forEach((Foo, bar) => process(Foo, bar))
337-
`
340+
`,
341+
'foo.React.Children.forEach(bar)',
342+
'NotReact.Children.forEach(bar)',
343+
'React.NotChildren.forEach(bar)',
344+
'React?.Children.forEach(bar)',
345+
'NotChildren.forEach(bar)'
338346
]
339347
});
340348

test/snapshots/no-array-for-each.js.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,3 +1404,53 @@ Generated by [AVA](https://avajs.dev).
14041404
> 5 | ].forEach((Foo, bar) => process(Foo, bar))␊
14051405
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
14061406
`
1407+
1408+
## Invalid #77
1409+
1 | foo.React.Children.forEach(bar)
1410+
1411+
> Error 1/1
1412+
1413+
`␊
1414+
> 1 | foo.React.Children.forEach(bar)␊
1415+
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
1416+
`
1417+
1418+
## Invalid #78
1419+
1 | NotReact.Children.forEach(bar)
1420+
1421+
> Error 1/1
1422+
1423+
`␊
1424+
> 1 | NotReact.Children.forEach(bar)␊
1425+
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
1426+
`
1427+
1428+
## Invalid #79
1429+
1 | React.NotChildren.forEach(bar)
1430+
1431+
> Error 1/1
1432+
1433+
`␊
1434+
> 1 | React.NotChildren.forEach(bar)␊
1435+
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
1436+
`
1437+
1438+
## Invalid #80
1439+
1 | React?.Children.forEach(bar)
1440+
1441+
> Error 1/1
1442+
1443+
`␊
1444+
> 1 | React?.Children.forEach(bar)␊
1445+
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
1446+
`
1447+
1448+
## Invalid #81
1449+
1 | NotChildren.forEach(bar)
1450+
1451+
> Error 1/1
1452+
1453+
`␊
1454+
> 1 | NotChildren.forEach(bar)␊
1455+
| ^^^^^^^ Do not use `Array#forEach(…)`.␊
1456+
`
166 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)