You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 lines
9.8 KiB

4 years ago
  1. /**
  2. * @fileoverview Prefer destructuring from arrays and objects
  3. * @author Alex LaFroscia
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. type: "suggestion",
  12. docs: {
  13. description: "require destructuring from arrays and/or objects",
  14. category: "ECMAScript 6",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/prefer-destructuring"
  17. },
  18. fixable: "code",
  19. schema: [
  20. {
  21. /*
  22. * old support {array: Boolean, object: Boolean}
  23. * new support {VariableDeclarator: {}, AssignmentExpression: {}}
  24. */
  25. oneOf: [
  26. {
  27. type: "object",
  28. properties: {
  29. VariableDeclarator: {
  30. type: "object",
  31. properties: {
  32. array: {
  33. type: "boolean"
  34. },
  35. object: {
  36. type: "boolean"
  37. }
  38. },
  39. additionalProperties: false
  40. },
  41. AssignmentExpression: {
  42. type: "object",
  43. properties: {
  44. array: {
  45. type: "boolean"
  46. },
  47. object: {
  48. type: "boolean"
  49. }
  50. },
  51. additionalProperties: false
  52. }
  53. },
  54. additionalProperties: false
  55. },
  56. {
  57. type: "object",
  58. properties: {
  59. array: {
  60. type: "boolean"
  61. },
  62. object: {
  63. type: "boolean"
  64. }
  65. },
  66. additionalProperties: false
  67. }
  68. ]
  69. },
  70. {
  71. type: "object",
  72. properties: {
  73. enforceForRenamedProperties: {
  74. type: "boolean"
  75. }
  76. },
  77. additionalProperties: false
  78. }
  79. ],
  80. messages: {
  81. preferDestructuring: "Use {{type}} destructuring."
  82. }
  83. },
  84. create(context) {
  85. const enabledTypes = context.options[0];
  86. const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
  87. let normalizedOptions = {
  88. VariableDeclarator: { array: true, object: true },
  89. AssignmentExpression: { array: true, object: true }
  90. };
  91. if (enabledTypes) {
  92. normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
  93. ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
  94. : enabledTypes;
  95. }
  96. //--------------------------------------------------------------------------
  97. // Helpers
  98. //--------------------------------------------------------------------------
  99. // eslint-disable-next-line jsdoc/require-description
  100. /**
  101. * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
  102. * @param {string} destructuringType "array" or "object"
  103. * @returns {boolean} `true` if the destructuring type should be checked for the given node
  104. */
  105. function shouldCheck(nodeType, destructuringType) {
  106. return normalizedOptions &&
  107. normalizedOptions[nodeType] &&
  108. normalizedOptions[nodeType][destructuringType];
  109. }
  110. /**
  111. * Determines if the given node is accessing an array index
  112. *
  113. * This is used to differentiate array index access from object property
  114. * access.
  115. * @param {ASTNode} node the node to evaluate
  116. * @returns {boolean} whether or not the node is an integer
  117. */
  118. function isArrayIndexAccess(node) {
  119. return Number.isInteger(node.property.value);
  120. }
  121. /**
  122. * Report that the given node should use destructuring
  123. * @param {ASTNode} reportNode the node to report
  124. * @param {string} type the type of destructuring that should have been done
  125. * @param {Function|null} fix the fix function or null to pass to context.report
  126. * @returns {void}
  127. */
  128. function report(reportNode, type, fix) {
  129. context.report({
  130. node: reportNode,
  131. messageId: "preferDestructuring",
  132. data: { type },
  133. fix
  134. });
  135. }
  136. /**
  137. * Determines if a node should be fixed into object destructuring
  138. *
  139. * The fixer only fixes the simplest case of object destructuring,
  140. * like: `let x = a.x`;
  141. *
  142. * Assignment expression is not fixed.
  143. * Array destructuring is not fixed.
  144. * Renamed property is not fixed.
  145. * @param {ASTNode} node the the node to evaluate
  146. * @returns {boolean} whether or not the node should be fixed
  147. */
  148. function shouldFix(node) {
  149. return node.type === "VariableDeclarator" &&
  150. node.id.type === "Identifier" &&
  151. node.init.type === "MemberExpression" &&
  152. node.id.name === node.init.property.name;
  153. }
  154. /**
  155. * Fix a node into object destructuring.
  156. * This function only handles the simplest case of object destructuring,
  157. * see {@link shouldFix}.
  158. * @param {SourceCodeFixer} fixer the fixer object
  159. * @param {ASTNode} node the node to be fixed.
  160. * @returns {Object} a fix for the node
  161. */
  162. function fixIntoObjectDestructuring(fixer, node) {
  163. const rightNode = node.init;
  164. const sourceCode = context.getSourceCode();
  165. return fixer.replaceText(
  166. node,
  167. `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
  168. );
  169. }
  170. /**
  171. * Check that the `prefer-destructuring` rules are followed based on the
  172. * given left- and right-hand side of the assignment.
  173. *
  174. * Pulled out into a separate method so that VariableDeclarators and
  175. * AssignmentExpressions can share the same verification logic.
  176. * @param {ASTNode} leftNode the left-hand side of the assignment
  177. * @param {ASTNode} rightNode the right-hand side of the assignment
  178. * @param {ASTNode} reportNode the node to report the error on
  179. * @returns {void}
  180. */
  181. function performCheck(leftNode, rightNode, reportNode) {
  182. if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
  183. return;
  184. }
  185. if (isArrayIndexAccess(rightNode)) {
  186. if (shouldCheck(reportNode.type, "array")) {
  187. report(reportNode, "array", null);
  188. }
  189. return;
  190. }
  191. const fix = shouldFix(reportNode)
  192. ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
  193. : null;
  194. if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
  195. report(reportNode, "object", fix);
  196. return;
  197. }
  198. if (shouldCheck(reportNode.type, "object")) {
  199. const property = rightNode.property;
  200. if (
  201. (property.type === "Literal" && leftNode.name === property.value) ||
  202. (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
  203. ) {
  204. report(reportNode, "object", fix);
  205. }
  206. }
  207. }
  208. /**
  209. * Check if a given variable declarator is coming from an property access
  210. * that should be using destructuring instead
  211. * @param {ASTNode} node the variable declarator to check
  212. * @returns {void}
  213. */
  214. function checkVariableDeclarator(node) {
  215. // Skip if variable is declared without assignment
  216. if (!node.init) {
  217. return;
  218. }
  219. // We only care about member expressions past this point
  220. if (node.init.type !== "MemberExpression") {
  221. return;
  222. }
  223. performCheck(node.id, node.init, node);
  224. }
  225. /**
  226. * Run the `prefer-destructuring` check on an AssignmentExpression
  227. * @param {ASTNode} node the AssignmentExpression node
  228. * @returns {void}
  229. */
  230. function checkAssigmentExpression(node) {
  231. if (node.operator === "=") {
  232. performCheck(node.left, node.right, node);
  233. }
  234. }
  235. //--------------------------------------------------------------------------
  236. // Public
  237. //--------------------------------------------------------------------------
  238. return {
  239. VariableDeclarator: checkVariableDeclarator,
  240. AssignmentExpression: checkAssigmentExpression
  241. };
  242. }
  243. };