25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

no-array-index-key.js 4.8 KiB

3 yıl önce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * @fileoverview Prevent usage of Array index in keys
  3. * @author Joe Lencioni
  4. */
  5. 'use strict';
  6. const has = require('has');
  7. const astUtil = require('../util/ast');
  8. const docsUrl = require('../util/docsUrl');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. module.exports = {
  13. meta: {
  14. docs: {
  15. description: 'Prevent usage of Array index in keys',
  16. category: 'Best Practices',
  17. recommended: false,
  18. url: docsUrl('no-array-index-key')
  19. },
  20. schema: []
  21. },
  22. create: function(context) {
  23. // --------------------------------------------------------------------------
  24. // Public
  25. // --------------------------------------------------------------------------
  26. const indexParamNames = [];
  27. const iteratorFunctionsToIndexParamPosition = {
  28. every: 1,
  29. filter: 1,
  30. find: 1,
  31. findIndex: 1,
  32. forEach: 1,
  33. map: 1,
  34. reduce: 2,
  35. reduceRight: 2,
  36. some: 1
  37. };
  38. const ERROR_MESSAGE = 'Do not use Array index in keys';
  39. function isArrayIndex(node) {
  40. return node.type === 'Identifier'
  41. && indexParamNames.indexOf(node.name) !== -1;
  42. }
  43. function getMapIndexParamName(node) {
  44. const callee = node.callee;
  45. if (callee.type !== 'MemberExpression') {
  46. return null;
  47. }
  48. if (callee.property.type !== 'Identifier') {
  49. return null;
  50. }
  51. if (!has(iteratorFunctionsToIndexParamPosition, callee.property.name)) {
  52. return null;
  53. }
  54. const firstArg = node.arguments[0];
  55. if (!firstArg) {
  56. return null;
  57. }
  58. if (!astUtil.isFunctionLikeExpression(firstArg)) {
  59. return null;
  60. }
  61. const params = firstArg.params;
  62. const indexParamPosition = iteratorFunctionsToIndexParamPosition[callee.property.name];
  63. if (params.length < indexParamPosition + 1) {
  64. return null;
  65. }
  66. return params[indexParamPosition].name;
  67. }
  68. function getIdentifiersFromBinaryExpression(side) {
  69. if (side.type === 'Identifier') {
  70. return side;
  71. }
  72. if (side.type === 'BinaryExpression') {
  73. // recurse
  74. const left = getIdentifiersFromBinaryExpression(side.left);
  75. const right = getIdentifiersFromBinaryExpression(side.right);
  76. return [].concat(left, right).filter(Boolean);
  77. }
  78. return null;
  79. }
  80. function checkPropValue(node) {
  81. if (isArrayIndex(node)) {
  82. // key={bar}
  83. context.report({
  84. node: node,
  85. message: ERROR_MESSAGE
  86. });
  87. return;
  88. }
  89. if (node.type === 'TemplateLiteral') {
  90. // key={`foo-${bar}`}
  91. node.expressions.filter(isArrayIndex).forEach(() => {
  92. context.report({node: node, message: ERROR_MESSAGE});
  93. });
  94. return;
  95. }
  96. if (node.type === 'BinaryExpression') {
  97. // key={'foo' + bar}
  98. const identifiers = getIdentifiersFromBinaryExpression(node);
  99. identifiers.filter(isArrayIndex).forEach(() => {
  100. context.report({node: node, message: ERROR_MESSAGE});
  101. });
  102. return;
  103. }
  104. }
  105. return {
  106. CallExpression: function(node) {
  107. if (
  108. node.callee
  109. && node.callee.type === 'MemberExpression'
  110. && ['createElement', 'cloneElement'].indexOf(node.callee.property.name) !== -1
  111. && node.arguments.length > 1
  112. ) {
  113. // React.createElement
  114. if (!indexParamNames.length) {
  115. return;
  116. }
  117. const props = node.arguments[1];
  118. if (props.type !== 'ObjectExpression') {
  119. return;
  120. }
  121. props.properties.forEach(prop => {
  122. if (!prop.key || prop.key.name !== 'key') {
  123. // { ...foo }
  124. // { foo: bar }
  125. return;
  126. }
  127. checkPropValue(prop.value);
  128. });
  129. return;
  130. }
  131. const mapIndexParamName = getMapIndexParamName(node);
  132. if (!mapIndexParamName) {
  133. return;
  134. }
  135. indexParamNames.push(mapIndexParamName);
  136. },
  137. JSXAttribute: function(node) {
  138. if (node.name.name !== 'key') {
  139. // foo={bar}
  140. return;
  141. }
  142. if (!indexParamNames.length) {
  143. // Not inside a call expression that we think has an index param.
  144. return;
  145. }
  146. const value = node.value;
  147. if (!value || value.type !== 'JSXExpressionContainer') {
  148. // key='foo' or just simply 'key'
  149. return;
  150. }
  151. checkPropValue(value.expression);
  152. },
  153. 'CallExpression:exit': function(node) {
  154. const mapIndexParamName = getMapIndexParamName(node);
  155. if (!mapIndexParamName) {
  156. return;
  157. }
  158. indexParamNames.pop();
  159. }
  160. };
  161. }
  162. };