jsx-no-bind.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /**
  2. * @fileoverview Prevents usage of Function.prototype.bind and arrow functions
  3. * in React component props.
  4. * @author Daniel Lo Nigro <dan.cx>
  5. * @author Jacky Ho
  6. */
  7. 'use strict';
  8. const propName = require('jsx-ast-utils/propName');
  9. const docsUrl = require('../util/docsUrl');
  10. const jsxUtil = require('../util/jsx');
  11. const report = require('../util/report');
  12. // -----------------------------------------------------------------------------
  13. // Rule Definition
  14. // -----------------------------------------------------------------------------
  15. const messages = {
  16. bindCall: 'JSX props should not use .bind()',
  17. arrowFunc: 'JSX props should not use arrow functions',
  18. bindExpression: 'JSX props should not use ::',
  19. func: 'JSX props should not use functions',
  20. };
  21. module.exports = {
  22. meta: {
  23. docs: {
  24. description: 'Disallow `.bind()` or arrow functions in JSX props',
  25. category: 'Best Practices',
  26. recommended: false,
  27. url: docsUrl('jsx-no-bind'),
  28. },
  29. messages,
  30. schema: [{
  31. type: 'object',
  32. properties: {
  33. allowArrowFunctions: {
  34. default: false,
  35. type: 'boolean',
  36. },
  37. allowBind: {
  38. default: false,
  39. type: 'boolean',
  40. },
  41. allowFunctions: {
  42. default: false,
  43. type: 'boolean',
  44. },
  45. ignoreRefs: {
  46. default: false,
  47. type: 'boolean',
  48. },
  49. ignoreDOMComponents: {
  50. default: false,
  51. type: 'boolean',
  52. },
  53. },
  54. additionalProperties: false,
  55. }],
  56. },
  57. create(context) {
  58. const configuration = context.options[0] || {};
  59. // Keep track of all the variable names pointing to a bind call,
  60. // bind expression or an arrow function in different block statements
  61. const blockVariableNameSets = {};
  62. /**
  63. * @param {string | number} blockStart
  64. */
  65. function setBlockVariableNameSet(blockStart) {
  66. blockVariableNameSets[blockStart] = {
  67. arrowFunc: new Set(),
  68. bindCall: new Set(),
  69. bindExpression: new Set(),
  70. func: new Set(),
  71. };
  72. }
  73. function getNodeViolationType(node) {
  74. const nodeType = node.type;
  75. if (
  76. !configuration.allowBind
  77. && nodeType === 'CallExpression'
  78. && node.callee.type === 'MemberExpression'
  79. && node.callee.property.type === 'Identifier'
  80. && node.callee.property.name === 'bind'
  81. ) {
  82. return 'bindCall';
  83. }
  84. if (nodeType === 'ConditionalExpression') {
  85. return getNodeViolationType(node.test)
  86. || getNodeViolationType(node.consequent)
  87. || getNodeViolationType(node.alternate);
  88. }
  89. if (!configuration.allowArrowFunctions && nodeType === 'ArrowFunctionExpression') {
  90. return 'arrowFunc';
  91. }
  92. if (
  93. !configuration.allowFunctions
  94. && (nodeType === 'FunctionExpression' || nodeType === 'FunctionDeclaration')
  95. ) {
  96. return 'func';
  97. }
  98. if (!configuration.allowBind && nodeType === 'BindExpression') {
  99. return 'bindExpression';
  100. }
  101. return null;
  102. }
  103. /**
  104. * @param {string | number} violationType
  105. * @param {any} variableName
  106. * @param {string | number} blockStart
  107. */
  108. function addVariableNameToSet(violationType, variableName, blockStart) {
  109. blockVariableNameSets[blockStart][violationType].add(variableName);
  110. }
  111. function getBlockStatementAncestors(node) {
  112. return context.getAncestors(node).reverse().filter(
  113. (ancestor) => ancestor.type === 'BlockStatement'
  114. );
  115. }
  116. function reportVariableViolation(node, name, blockStart) {
  117. const blockSets = blockVariableNameSets[blockStart];
  118. const violationTypes = Object.keys(blockSets);
  119. return violationTypes.find((type) => {
  120. if (blockSets[type].has(name)) {
  121. report(context, messages[type], type, {
  122. node,
  123. });
  124. return true;
  125. }
  126. return false;
  127. });
  128. }
  129. function findVariableViolation(node, name) {
  130. getBlockStatementAncestors(node).find(
  131. (block) => reportVariableViolation(node, name, block.range[0])
  132. );
  133. }
  134. return {
  135. BlockStatement(node) {
  136. setBlockVariableNameSet(node.range[0]);
  137. },
  138. FunctionDeclaration(node) {
  139. const blockAncestors = getBlockStatementAncestors(node);
  140. const variableViolationType = getNodeViolationType(node);
  141. if (blockAncestors.length > 0 && variableViolationType) {
  142. addVariableNameToSet(variableViolationType, node.id.name, blockAncestors[0].range[0]);
  143. }
  144. },
  145. VariableDeclarator(node) {
  146. if (!node.init) {
  147. return;
  148. }
  149. const blockAncestors = getBlockStatementAncestors(node);
  150. const variableViolationType = getNodeViolationType(node.init);
  151. if (
  152. blockAncestors.length > 0
  153. && variableViolationType
  154. && node.parent.kind === 'const' // only support const right now
  155. ) {
  156. addVariableNameToSet(
  157. variableViolationType, node.id.name, blockAncestors[0].range[0]
  158. );
  159. }
  160. },
  161. JSXAttribute(node) {
  162. const isRef = configuration.ignoreRefs && propName(node) === 'ref';
  163. if (isRef || !node.value || !node.value.expression) {
  164. return;
  165. }
  166. const isDOMComponent = jsxUtil.isDOMComponent(node.parent);
  167. if (configuration.ignoreDOMComponents && isDOMComponent) {
  168. return;
  169. }
  170. const valueNode = node.value.expression;
  171. const valueNodeType = valueNode.type;
  172. const nodeViolationType = getNodeViolationType(valueNode);
  173. if (valueNodeType === 'Identifier') {
  174. findVariableViolation(node, valueNode.name);
  175. } else if (nodeViolationType) {
  176. report(context, messages[nodeViolationType], nodeViolationType, {
  177. node,
  178. });
  179. }
  180. },
  181. };
  182. },
  183. };