jsx-handler-names.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /**
  2. * @fileoverview Enforce event handler naming conventions in JSX
  3. * @author Jake Marsh
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. const messages = {
  12. badHandlerName: 'Handler function for {{propKey}} prop key must be a camelCase name beginning with \'{{handlerPrefix}}\' only',
  13. badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}}\'',
  14. };
  15. module.exports = {
  16. meta: {
  17. docs: {
  18. description: 'Enforce event handler naming conventions in JSX',
  19. category: 'Stylistic Issues',
  20. recommended: false,
  21. url: docsUrl('jsx-handler-names'),
  22. },
  23. messages,
  24. schema: [{
  25. anyOf: [
  26. {
  27. type: 'object',
  28. properties: {
  29. eventHandlerPrefix: { type: 'string' },
  30. eventHandlerPropPrefix: { type: 'string' },
  31. checkLocalVariables: { type: 'boolean' },
  32. checkInlineFunction: { type: 'boolean' },
  33. },
  34. additionalProperties: false,
  35. }, {
  36. type: 'object',
  37. properties: {
  38. eventHandlerPrefix: { type: 'string' },
  39. eventHandlerPropPrefix: {
  40. type: 'boolean',
  41. enum: [false],
  42. },
  43. checkLocalVariables: { type: 'boolean' },
  44. checkInlineFunction: { type: 'boolean' },
  45. },
  46. additionalProperties: false,
  47. }, {
  48. type: 'object',
  49. properties: {
  50. eventHandlerPrefix: {
  51. type: 'boolean',
  52. enum: [false],
  53. },
  54. eventHandlerPropPrefix: { type: 'string' },
  55. checkLocalVariables: { type: 'boolean' },
  56. checkInlineFunction: { type: 'boolean' },
  57. },
  58. additionalProperties: false,
  59. }, {
  60. type: 'object',
  61. properties: {
  62. checkLocalVariables: { type: 'boolean' },
  63. },
  64. additionalProperties: false,
  65. }, {
  66. type: 'object',
  67. properties: {
  68. checkInlineFunction: { type: 'boolean' },
  69. },
  70. additionalProperties: false,
  71. },
  72. ],
  73. }],
  74. },
  75. create(context) {
  76. function isPrefixDisabled(prefix) {
  77. return prefix === false;
  78. }
  79. function isInlineHandler(node) {
  80. return node.value.expression.type === 'ArrowFunctionExpression';
  81. }
  82. const configuration = context.options[0] || {};
  83. const eventHandlerPrefix = isPrefixDisabled(configuration.eventHandlerPrefix)
  84. ? null
  85. : configuration.eventHandlerPrefix || 'handle';
  86. const eventHandlerPropPrefix = isPrefixDisabled(configuration.eventHandlerPropPrefix)
  87. ? null
  88. : configuration.eventHandlerPropPrefix || 'on';
  89. const EVENT_HANDLER_REGEX = !eventHandlerPrefix
  90. ? null
  91. : new RegExp(`^((props\\.${eventHandlerPropPrefix || ''})|((.*\\.)?${eventHandlerPrefix}))[0-9]*[A-Z].*$`);
  92. const PROP_EVENT_HANDLER_REGEX = !eventHandlerPropPrefix
  93. ? null
  94. : new RegExp(`^(${eventHandlerPropPrefix}[A-Z].*|ref)$`);
  95. const checkLocal = !!configuration.checkLocalVariables;
  96. const checkInlineFunction = !!configuration.checkInlineFunction;
  97. return {
  98. JSXAttribute(node) {
  99. if (
  100. !node.value
  101. || !node.value.expression
  102. || (!checkInlineFunction && isInlineHandler(node))
  103. || (
  104. !checkLocal
  105. && (isInlineHandler(node)
  106. ? !node.value.expression.body.callee || !node.value.expression.body.callee.object
  107. : !node.value.expression.object
  108. )
  109. )
  110. ) {
  111. return;
  112. }
  113. const propKey = typeof node.name === 'object' ? node.name.name : node.name;
  114. const expression = node.value.expression;
  115. const propValue = context.getSourceCode()
  116. .getText(checkInlineFunction && isInlineHandler(node) ? expression.body.callee : expression)
  117. .replace(/\s*/g, '')
  118. .replace(/^this\.|.*::/, '');
  119. if (propKey === 'ref') {
  120. return;
  121. }
  122. const propIsEventHandler = PROP_EVENT_HANDLER_REGEX && PROP_EVENT_HANDLER_REGEX.test(propKey);
  123. const propFnIsNamedCorrectly = EVENT_HANDLER_REGEX && EVENT_HANDLER_REGEX.test(propValue);
  124. if (
  125. propIsEventHandler
  126. && propFnIsNamedCorrectly !== null
  127. && !propFnIsNamedCorrectly
  128. ) {
  129. report(context, messages.badHandlerName, 'badHandlerName', {
  130. node,
  131. data: {
  132. propKey,
  133. handlerPrefix: eventHandlerPrefix,
  134. },
  135. });
  136. } else if (
  137. propFnIsNamedCorrectly
  138. && propIsEventHandler !== null
  139. && !propIsEventHandler
  140. ) {
  141. report(context, messages.badPropKey, 'badPropKey', {
  142. node,
  143. data: {
  144. propValue,
  145. handlerPropPrefix: eventHandlerPropPrefix,
  146. },
  147. });
  148. }
  149. },
  150. };
  151. },
  152. };