1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | 'use strict';
|
7 |
|
8 | const hasProp = require('jsx-ast-utils/hasProp');
|
9 | const docsUrl = require('../util/docsUrl');
|
10 | const pragmaUtil = require('../util/pragma');
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const defaultOptions = {
|
17 | checkFragmentShorthand: false
|
18 | };
|
19 |
|
20 | module.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 |
|
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 | };
|