UNPKG

3.67 kBJavaScriptView Raw
1/**
2 * @fileoverview Report missing `key` props in iterators/collection literals.
3 * @author Ben Mosher
4 */
5
6'use strict';
7
8const hasProp = require('jsx-ast-utils/hasProp');
9const docsUrl = require('../util/docsUrl');
10const pragmaUtil = require('../util/pragma');
11
12// ------------------------------------------------------------------------------
13// Rule Definition
14// ------------------------------------------------------------------------------
15
16const defaultOptions = {
17 checkFragmentShorthand: false
18};
19
20module.exports = {
21 meta: {
22 docs: {
23 description: 'Report missing `key` props in iterators/collection literals',
24 category: 'Possible Errors',
25 recommended: true,
26 url: docsUrl('jsx-key')
27 },
28 schema: [{
29 type: 'object',
30 properties: {
31 checkFragmentShorthand: {
32 type: 'boolean',
33 default: defaultOptions.checkFragmentShorthand
34 }
35 },
36 additionalProperties: false
37 }]
38 },
39
40 create(context) {
41 const options = Object.assign({}, defaultOptions, context.options[0]);
42 const checkFragmentShorthand = options.checkFragmentShorthand;
43 const reactPragma = pragmaUtil.getFromContext(context);
44 const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
45
46 function checkIteratorElement(node) {
47 if (node.type === 'JSXElement' && !hasProp(node.openingElement.attributes, 'key')) {
48 context.report({
49 node,
50 message: 'Missing "key" prop for element in iterator'
51 });
52 } else if (checkFragmentShorthand && node.type === 'JSXFragment') {
53 context.report({
54 node,
55 message: `Missing "key" prop for element in iterator. Shorthand fragment syntax does not support providing keys. Use ${reactPragma}.${fragmentPragma} instead`
56 });
57 }
58 }
59
60 function getReturnStatement(body) {
61 return body.filter((item) => item.type === 'ReturnStatement')[0];
62 }
63
64 return {
65 JSXElement(node) {
66 if (hasProp(node.openingElement.attributes, 'key')) {
67 return;
68 }
69
70 if (node.parent.type === 'ArrayExpression') {
71 context.report({
72 node,
73 message: 'Missing "key" prop for element in array'
74 });
75 }
76 },
77
78 JSXFragment(node) {
79 if (!checkFragmentShorthand) {
80 return;
81 }
82
83 if (node.parent.type === 'ArrayExpression') {
84 context.report({
85 node,
86 message: `Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use ${reactPragma}.${fragmentPragma} instead`
87 });
88 }
89 },
90
91 // Array.prototype.map
92 'CallExpression, OptionalCallExpression'(node) {
93 if (node.callee && node.callee.type !== 'MemberExpression' && node.callee.type !== 'OptionalMemberExpression') {
94 return;
95 }
96
97 if (node.callee && node.callee.property && node.callee.property.name !== 'map') {
98 return;
99 }
100
101 const fn = node.arguments[0];
102 const isFn = fn && fn.type === 'FunctionExpression';
103 const isArrFn = fn && fn.type === 'ArrowFunctionExpression';
104
105 if (isArrFn && (fn.body.type === 'JSXElement' || fn.body.type === 'JSXFragment')) {
106 checkIteratorElement(fn.body);
107 }
108
109 if (isFn || isArrFn) {
110 if (fn.body.type === 'BlockStatement') {
111 const returnStatement = getReturnStatement(fn.body.body);
112 if (returnStatement && returnStatement.argument) {
113 checkIteratorElement(returnStatement.argument);
114 }
115 }
116 }
117 }
118 };
119 }
120};