|
|
/** * @fileoverview Enforce event handler naming conventions in JSX * @author Jake Marsh */
'use strict';
const docsUrl = require('../util/docsUrl');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = { meta: { docs: { description: 'Enforce event handler naming conventions in JSX', category: 'Stylistic Issues', recommended: false, url: docsUrl('jsx-handler-names') },
messages: { badHandlerName: 'Handler function for {{propKey}} prop key must be a camelCase name beginning with \'{{handlerPrefix}}\' only', badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}\'' },
schema: [{ anyOf: [ { type: 'object', properties: { eventHandlerPrefix: {type: 'string'}, eventHandlerPropPrefix: {type: 'string'}, checkLocalVariables: {type: 'boolean'}, checkInlineFunction: {type: 'boolean'} }, additionalProperties: false }, { type: 'object', properties: { eventHandlerPrefix: {type: 'string'}, eventHandlerPropPrefix: { type: 'boolean', enum: [false] }, checkLocalVariables: {type: 'boolean'}, checkInlineFunction: {type: 'boolean'} }, additionalProperties: false }, { type: 'object', properties: { eventHandlerPrefix: { type: 'boolean', enum: [false] }, eventHandlerPropPrefix: {type: 'string'}, checkLocalVariables: {type: 'boolean'}, checkInlineFunction: {type: 'boolean'} }, additionalProperties: false }, { type: 'object', properties: { checkLocalVariables: {type: 'boolean'} }, additionalProperties: false }, { type: 'object', properties: { checkInlineFunction: {type: 'boolean'} }, additionalProperties: false } ] }] },
create(context) { function isPrefixDisabled(prefix) { return prefix === false; }
function isInlineHandler(node) { return node.value.expression.type === 'ArrowFunctionExpression'; }
const configuration = context.options[0] || {};
const eventHandlerPrefix = isPrefixDisabled(configuration.eventHandlerPrefix) ? null : configuration.eventHandlerPrefix || 'handle'; const eventHandlerPropPrefix = isPrefixDisabled(configuration.eventHandlerPropPrefix) ? null : configuration.eventHandlerPropPrefix || 'on';
const EVENT_HANDLER_REGEX = !eventHandlerPrefix ? null : new RegExp(`^((props\\.${eventHandlerPropPrefix || ''})|((.*\\.)?${eventHandlerPrefix}))[0-9]*[A-Z].*$`); const PROP_EVENT_HANDLER_REGEX = !eventHandlerPropPrefix ? null : new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`);
const checkLocal = !!configuration.checkLocalVariables;
const checkInlineFunction = !!configuration.checkInlineFunction;
return { JSXAttribute(node) { if ( !node.value || !node.value.expression || (!checkInlineFunction && isInlineHandler(node)) || ( !checkLocal && (isInlineHandler(node) ? !node.value.expression.body.callee || !node.value.expression.body.callee.object : !node.value.expression.object ) ) ) { return; }
const propKey = typeof node.name === 'object' ? node.name.name : node.name; const expression = node.value.expression; const propValue = context.getSourceCode() .getText(checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression) .replace(/\s*/g, '') .replace(/^this\.|.*::/, '');
if (propKey === 'ref') { return; }
const propIsEventHandler = PROP_EVENT_HANDLER_REGEX && PROP_EVENT_HANDLER_REGEX.test(propKey); const propFnIsNamedCorrectly = EVENT_HANDLER_REGEX && EVENT_HANDLER_REGEX.test(propValue);
if ( propIsEventHandler && propFnIsNamedCorrectly !== null && !propFnIsNamedCorrectly ) { context.report({ node, messageId: 'badHandlerName', data: { propKey, handlerPrefix: eventHandlerPrefix } }); } else if ( propFnIsNamedCorrectly && propIsEventHandler !== null && !propIsEventHandler ) { context.report({ node, messageId: 'badPropKey', data: { propValue, handlerPropPrefix: eventHandlerPropPrefix } }); } } }; } };
|