GoScrobble/web/node_modules/eslint-plugin-react/lib/rules/jsx-key.js

165 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @fileoverview Report missing `key` props in iterators/collection literals.
* @author Ben Mosher
*/
'use strict';
const hasProp = require('jsx-ast-utils/hasProp');
const propName = require('jsx-ast-utils/propName');
const docsUrl = require('../util/docsUrl');
const pragmaUtil = require('../util/pragma');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const defaultOptions = {
checkFragmentShorthand: false,
checkKeyMustBeforeSpread: false
};
module.exports = {
meta: {
docs: {
description: 'Report missing `key` props in iterators/collection literals',
category: 'Possible Errors',
recommended: true,
url: docsUrl('jsx-key')
},
messages: {
missingIterKey: 'Missing "key" prop for element in iterator',
missingIterKeyUsePrag: 'Missing "key" prop for element in iterator. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead',
missingArrayKey: 'Missing "key" prop for element in array',
missingArrayKeyUsePrag: 'Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use {{reactPrag}}.{{fragPrag}} instead',
keyBeforeSpread: '`key` prop must be placed before any `{...spread}, to avoid conflicting with Reacts new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`'
},
schema: [{
type: 'object',
properties: {
checkFragmentShorthand: {
type: 'boolean',
default: defaultOptions.checkFragmentShorthand
},
checkKeyMustBeforeSpread: {
type: 'boolean',
default: defaultOptions.checkKeyMustBeforeSpread
}
},
additionalProperties: false
}]
},
create(context) {
const options = Object.assign({}, defaultOptions, context.options[0]);
const checkFragmentShorthand = options.checkFragmentShorthand;
const checkKeyMustBeforeSpread = options.checkKeyMustBeforeSpread;
const reactPragma = pragmaUtil.getFromContext(context);
const fragmentPragma = pragmaUtil.getFragmentFromContext(context);
function checkIteratorElement(node) {
if (node.type === 'JSXElement' && !hasProp(node.openingElement.attributes, 'key')) {
context.report({
node,
messageId: 'missingIterKey'
});
} else if (checkFragmentShorthand && node.type === 'JSXFragment') {
context.report({
node,
messageId: 'missingIterKeyUsePrag',
data: {
reactPrag: reactPragma,
fragPrag: fragmentPragma
}
});
}
}
function getReturnStatement(body) {
return body.filter((item) => item.type === 'ReturnStatement')[0];
}
function isKeyAfterSpread(attributes) {
let hasFoundSpread = false;
return attributes.some((attribute) => {
if (attribute.type === 'JSXSpreadAttribute') {
hasFoundSpread = true;
return false;
}
if (attribute.type !== 'JSXAttribute') {
return false;
}
return hasFoundSpread && propName(attribute) === 'key';
});
}
return {
JSXElement(node) {
if (hasProp(node.openingElement.attributes, 'key')) {
if (checkKeyMustBeforeSpread && isKeyAfterSpread(node.openingElement.attributes)) {
context.report({
node,
messageId: 'keyBeforeSpread'
});
}
return;
}
if (node.parent.type === 'ArrayExpression') {
context.report({
node,
messageId: 'missingArrayKey'
});
}
},
JSXFragment(node) {
if (!checkFragmentShorthand) {
return;
}
if (node.parent.type === 'ArrayExpression') {
context.report({
node,
messageId: 'missingArrayKeyUsePrag',
data: {
reactPrag: reactPragma,
fragPrag: fragmentPragma
}
});
}
},
// Array.prototype.map
'CallExpression, OptionalCallExpression'(node) {
if (node.callee && node.callee.type !== 'MemberExpression' && node.callee.type !== 'OptionalMemberExpression') {
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);
}
}
}
}
};
}
};