jsx-handler-names.js 5.0 KB

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