Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

192 строки
5.4 KiB

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