no-useless-rename.js 5.9 KB

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