Skip to content

Commit b0ff6be

Browse files
committed
Merge pull request #332 from silvenon/jsx-key
Fix jsx-key false-positives (fixes #320)
2 parents 53f32af + 1642a76 commit b0ff6be

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

lib/rules/jsx-key.js

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,69 @@
44
*/
55
'use strict';
66

7+
// var Components = require('../util/Components');
8+
79
// ------------------------------------------------------------------------------
810
// Rule Definition
911
// ------------------------------------------------------------------------------
1012

1113
module.exports = function(context) {
1214

13-
function isKeyProp(decl) {
14-
if (decl.type === 'JSXSpreadAttribute') {
15-
return false;
15+
function hasKeyProp(node) {
16+
return node.openingElement.attributes.some(function(decl) {
17+
if (decl.type === 'JSXSpreadAttribute') {
18+
return false;
19+
}
20+
return (decl.name.name === 'key');
21+
});
22+
}
23+
24+
function checkIteratorElement(node) {
25+
if (node.type === 'JSXElement' && !hasKeyProp(node)) {
26+
context.report(node, 'Missing "key" prop for element in iterator');
1627
}
17-
return (decl.name.name === 'key');
28+
}
29+
30+
function getReturnStatement(body) {
31+
return body.filter(function(item) {
32+
return item.type === 'ReturnStatement';
33+
})[0];
1834
}
1935

2036
return {
2137
JSXElement: function(node) {
22-
if (node.openingElement.attributes.some(isKeyProp)) {
23-
return; // has key prop
38+
if (hasKeyProp(node)) {
39+
return;
2440
}
2541

2642
if (node.parent.type === 'ArrayExpression') {
2743
context.report(node, 'Missing "key" prop for element in array');
2844
}
45+
},
2946

30-
if (node.parent.type === 'ArrowFunctionExpression') {
31-
context.report(node, 'Missing "key" prop for element in iterator');
47+
// Array.prototype.map
48+
CallExpression: function (node) {
49+
if (node.callee.property.name !== 'map') {
50+
return;
51+
}
52+
53+
var fn = node.arguments[0];
54+
var isFn = fn.type === 'FunctionExpression';
55+
var isArrFn = fn.type === 'ArrowFunctionExpression';
56+
57+
if (isArrFn && fn.body.type === 'JSXElement') {
58+
checkIteratorElement(fn.body);
59+
}
60+
61+
if (isFn || isArrFn) {
62+
if (fn.body.type === 'BlockStatement') {
63+
checkIteratorElement(
64+
getReturnStatement(fn.body.body).argument
65+
);
66+
}
3267
}
3368
}
3469
};
3570
};
71+
72+
module.exports.schema = [];

tests/lib/rules/jsx-key.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ ruleTester.run('jsx-key', rule, {
2727
valid: [
2828
{code: '<App />;', parserOptions: parserOptions},
2929
{code: '[<App key={0} />, <App key={1} />];', parserOptions: parserOptions},
30-
{code: '[1, 2, 3].map(x => <App key={x} />);', parserOptions: parserOptions}
30+
{code: '[1, 2, 3].map(function(x) { return <App key={x} /> });', parserOptions: parserOptions},
31+
{code: '[1, 2, 3].map(x => <App key={x} />);', parserOptions: parserOptions},
32+
{code: '[1, 2, 3].map(x => { return <App key={x} /> });', parserOptions: parserOptions},
33+
{code: '[1, 2, 3].foo(x => <App />);', parserOptions: parserOptions},
34+
{code: 'var App = () => <div />;', parserOptions: parserOptions}
3135
],
3236
invalid: [
3337
{code: '[<App />];',
@@ -42,7 +46,15 @@ ruleTester.run('jsx-key', rule, {
4246
errors: [{message: 'Missing "key" prop for element in array'}],
4347
parserOptions: parserOptions},
4448

49+
{code: '[1, 2 ,3].map(function(x) { return <App /> });',
50+
errors: [{message: 'Missing "key" prop for element in iterator'}],
51+
parserOptions: parserOptions},
52+
4553
{code: '[1, 2 ,3].map(x => <App />);',
54+
errors: [{message: 'Missing "key" prop for element in iterator'}],
55+
parserOptions: parserOptions},
56+
57+
{code: '[1, 2 ,3].map(x => { return <App /> });',
4658
errors: [{message: 'Missing "key" prop for element in iterator'}],
4759
parserOptions: parserOptions}
4860
]

0 commit comments

Comments
 (0)