| /** |
| * @fileoverview Report missing `key` props in iterators/collection literals. |
| * @author Ben Mosher |
| */ |
| |
| 'use strict'; |
| |
| const hasProp = require('jsx-ast-utils/hasProp'); |
| const docsUrl = require('../util/docsUrl'); |
| |
| |
| // ------------------------------------------------------------------------------ |
| // Rule Definition |
| // ------------------------------------------------------------------------------ |
| |
| const defaultOptions = { |
| checkFragmentShorthand: false |
| }; |
| |
| module.exports = { |
| meta: { |
| docs: { |
| description: 'Report missing `key` props in iterators/collection literals', |
| category: 'Possible Errors', |
| recommended: true, |
| url: docsUrl('jsx-key') |
| }, |
| schema: [{ |
| type: 'object', |
| properties: { |
| checkFragmentShorthand: { |
| type: 'boolean', |
| default: defaultOptions.checkFragmentShorthand |
| } |
| }, |
| additionalProperties: false |
| }] |
| }, |
| |
| create(context) { |
| const options = Object.assign({}, defaultOptions, context.options[0]); |
| const checkFragmentShorthand = options.checkFragmentShorthand; |
| |
| function checkIteratorElement(node) { |
| if (node.type === 'JSXElement' && !hasProp(node.openingElement.attributes, 'key')) { |
| context.report({ |
| node, |
| message: 'Missing "key" prop for element in iterator' |
| }); |
| } else if (checkFragmentShorthand && node.type === 'JSXFragment') { |
| context.report({ |
| node, |
| message: 'Missing "key" prop for element in iterator. Shorthand fragment syntax does support providing keys' |
| }); |
| } |
| } |
| |
| function getReturnStatement(body) { |
| return body.filter(item => item.type === 'ReturnStatement')[0]; |
| } |
| |
| return { |
| JSXElement(node) { |
| if (hasProp(node.openingElement.attributes, 'key')) { |
| return; |
| } |
| |
| if (node.parent.type === 'ArrayExpression') { |
| context.report({ |
| node, |
| message: 'Missing "key" prop for element in array' |
| }); |
| } |
| }, |
| |
| JSXFragment(node) { |
| if (!checkFragmentShorthand) { |
| return; |
| } |
| |
| if (node.parent.type === 'ArrayExpression') { |
| context.report({ |
| node, |
| message: 'Missing "key" prop for element in array. Shorthand fragment syntax does support providing keys' |
| }); |
| } |
| }, |
| |
| // Array.prototype.map |
| CallExpression(node) { |
| if (node.callee && node.callee.type !== 'MemberExpression') { |
| return; |
| } |
| |
| if (node.callee && node.callee.property && node.callee.property.name !== 'map') { |
| return; |
| } |
| |
| const fn = node.arguments[0]; |
| const isFn = fn && fn.type === 'FunctionExpression'; |
| const isArrFn = fn && fn.type === 'ArrowFunctionExpression'; |
| |
| if (isArrFn && (fn.body.type === 'JSXElement' || fn.body.type === 'JSXFragment')) { |
| checkIteratorElement(fn.body); |
| } |
| |
| if (isFn || isArrFn) { |
| if (fn.body.type === 'BlockStatement') { |
| const returnStatement = getReturnStatement(fn.body.body); |
| if (returnStatement && returnStatement.argument) { |
| checkIteratorElement(returnStatement.argument); |
| } |
| } |
| } |
| } |
| }; |
| } |
| }; |