no-useless-rename.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /**
  2. * @fileoverview Disallow renaming import, export, and destructured assignments to the same name.
  3. * @author Kai Cataldo
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Rule Definition
  12. //------------------------------------------------------------------------------
  13. /** @type {import('../types').Rule.RuleModule} */
  14. module.exports = {
  15. meta: {
  16. type: "suggestion",
  17. defaultOptions: [
  18. {
  19. ignoreDestructuring: false,
  20. ignoreImport: false,
  21. ignoreExport: false,
  22. },
  23. ],
  24. docs: {
  25. description:
  26. "Disallow renaming import, export, and destructured assignments to the same name",
  27. recommended: false,
  28. url: "https://eslint.org/docs/latest/rules/no-useless-rename",
  29. },
  30. fixable: "code",
  31. schema: [
  32. {
  33. type: "object",
  34. properties: {
  35. ignoreDestructuring: { type: "boolean" },
  36. ignoreImport: { type: "boolean" },
  37. ignoreExport: { type: "boolean" },
  38. },
  39. additionalProperties: false,
  40. },
  41. ],
  42. messages: {
  43. unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed.",
  44. },
  45. },
  46. create(context) {
  47. const sourceCode = context.sourceCode;
  48. const [{ ignoreDestructuring, ignoreImport, ignoreExport }] =
  49. context.options;
  50. //--------------------------------------------------------------------------
  51. // Helpers
  52. //--------------------------------------------------------------------------
  53. /**
  54. * Reports error for unnecessarily renamed assignments
  55. * @param {ASTNode} node node to report
  56. * @param {ASTNode} initial node with initial name value
  57. * @param {string} type the type of the offending node
  58. * @returns {void}
  59. */
  60. function reportError(node, initial, type) {
  61. const name =
  62. initial.type === "Identifier" ? initial.name : initial.value;
  63. return context.report({
  64. node,
  65. messageId: "unnecessarilyRenamed",
  66. data: {
  67. name,
  68. type,
  69. },
  70. fix(fixer) {
  71. const replacementNode =
  72. node.type === "Property" ? node.value : node.local;
  73. if (
  74. sourceCode.getCommentsInside(node).length >
  75. sourceCode.getCommentsInside(replacementNode).length
  76. ) {
  77. return null;
  78. }
  79. // Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties.
  80. if (
  81. replacementNode.type === "AssignmentPattern" &&
  82. astUtils.isParenthesised(
  83. sourceCode,
  84. replacementNode.left,
  85. )
  86. ) {
  87. return null;
  88. }
  89. return fixer.replaceText(
  90. node,
  91. sourceCode.getText(replacementNode),
  92. );
  93. },
  94. });
  95. }
  96. /**
  97. * Checks whether a destructured assignment is unnecessarily renamed
  98. * @param {ASTNode} node node to check
  99. * @returns {void}
  100. */
  101. function checkDestructured(node) {
  102. if (ignoreDestructuring) {
  103. return;
  104. }
  105. for (const property of node.properties) {
  106. /**
  107. * Properties using shorthand syntax and rest elements can not be renamed.
  108. * If the property is computed, we have no idea if a rename is useless or not.
  109. */
  110. if (
  111. property.type !== "Property" ||
  112. property.shorthand ||
  113. property.computed
  114. ) {
  115. continue;
  116. }
  117. const key =
  118. (property.key.type === "Identifier" && property.key.name) ||
  119. (property.key.type === "Literal" && property.key.value);
  120. const renamedKey =
  121. property.value.type === "AssignmentPattern"
  122. ? property.value.left.name
  123. : property.value.name;
  124. if (key === renamedKey) {
  125. reportError(
  126. property,
  127. property.key,
  128. "Destructuring assignment",
  129. );
  130. }
  131. }
  132. }
  133. /**
  134. * Checks whether an import is unnecessarily renamed
  135. * @param {ASTNode} node node to check
  136. * @returns {void}
  137. */
  138. function checkImport(node) {
  139. if (ignoreImport) {
  140. return;
  141. }
  142. if (
  143. node.imported.range[0] !== node.local.range[0] &&
  144. astUtils.getModuleExportName(node.imported) === node.local.name
  145. ) {
  146. reportError(node, node.imported, "Import");
  147. }
  148. }
  149. /**
  150. * Checks whether an export is unnecessarily renamed
  151. * @param {ASTNode} node node to check
  152. * @returns {void}
  153. */
  154. function checkExport(node) {
  155. if (ignoreExport) {
  156. return;
  157. }
  158. if (
  159. node.local.range[0] !== node.exported.range[0] &&
  160. astUtils.getModuleExportName(node.local) ===
  161. astUtils.getModuleExportName(node.exported)
  162. ) {
  163. reportError(node, node.local, "Export");
  164. }
  165. }
  166. //--------------------------------------------------------------------------
  167. // Public
  168. //--------------------------------------------------------------------------
  169. return {
  170. ObjectPattern: checkDestructured,
  171. ImportSpecifier: checkImport,
  172. ExportSpecifier: checkExport,
  173. };
  174. },
  175. };