no-redeclare.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /**
  2. * @fileoverview Rule to flag when the same variable is declared more then once.
  3. * @author Ilya Volodin
  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: [{ builtinGlobals: true }],
  18. docs: {
  19. description: "Disallow variable redeclaration",
  20. recommended: true,
  21. url: "https://eslint.org/docs/latest/rules/no-redeclare",
  22. },
  23. messages: {
  24. redeclared: "'{{id}}' is already defined.",
  25. redeclaredAsBuiltin:
  26. "'{{id}}' is already defined as a built-in global variable.",
  27. redeclaredBySyntax:
  28. "'{{id}}' is already defined by a variable declaration.",
  29. },
  30. schema: [
  31. {
  32. type: "object",
  33. properties: {
  34. builtinGlobals: { type: "boolean" },
  35. },
  36. additionalProperties: false,
  37. },
  38. ],
  39. },
  40. create(context) {
  41. const [{ builtinGlobals }] = context.options;
  42. const sourceCode = context.sourceCode;
  43. /**
  44. * Iterate declarations of a given variable.
  45. * @param {escope.variable} variable The variable object to iterate declarations.
  46. * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations.
  47. */
  48. function* iterateDeclarations(variable) {
  49. if (
  50. builtinGlobals &&
  51. (variable.eslintImplicitGlobalSetting === "readonly" ||
  52. variable.eslintImplicitGlobalSetting === "writable")
  53. ) {
  54. yield { type: "builtin" };
  55. }
  56. for (const id of variable.identifiers) {
  57. yield { type: "syntax", node: id, loc: id.loc };
  58. }
  59. if (variable.eslintExplicitGlobalComments) {
  60. for (const comment of variable.eslintExplicitGlobalComments) {
  61. yield {
  62. type: "comment",
  63. node: comment,
  64. loc: astUtils.getNameLocationInGlobalDirectiveComment(
  65. sourceCode,
  66. comment,
  67. variable.name,
  68. ),
  69. };
  70. }
  71. }
  72. }
  73. /**
  74. * Find variables in a given scope and flag redeclared ones.
  75. * @param {Scope} scope An eslint-scope scope object.
  76. * @returns {void}
  77. * @private
  78. */
  79. function findVariablesInScope(scope) {
  80. for (const variable of scope.variables) {
  81. const [declaration, ...extraDeclarations] =
  82. iterateDeclarations(variable);
  83. if (extraDeclarations.length === 0) {
  84. continue;
  85. }
  86. /*
  87. * If the type of a declaration is different from the type of
  88. * the first declaration, it shows the location of the first
  89. * declaration.
  90. */
  91. const detailMessageId =
  92. declaration.type === "builtin"
  93. ? "redeclaredAsBuiltin"
  94. : "redeclaredBySyntax";
  95. const data = { id: variable.name };
  96. // Report extra declarations.
  97. for (const { type, node, loc } of extraDeclarations) {
  98. const messageId =
  99. type === declaration.type
  100. ? "redeclared"
  101. : detailMessageId;
  102. context.report({ node, loc, messageId, data });
  103. }
  104. }
  105. }
  106. /**
  107. * Find variables in the current scope.
  108. * @param {ASTNode} node The node of the current scope.
  109. * @returns {void}
  110. * @private
  111. */
  112. function checkForBlock(node) {
  113. const scope = sourceCode.getScope(node);
  114. /*
  115. * In ES5, some node type such as `BlockStatement` doesn't have that scope.
  116. * `scope.block` is a different node in such a case.
  117. */
  118. if (scope.block === node) {
  119. findVariablesInScope(scope);
  120. }
  121. }
  122. return {
  123. Program(node) {
  124. const scope = sourceCode.getScope(node);
  125. findVariablesInScope(scope);
  126. // Node.js or ES modules has a special scope.
  127. if (
  128. scope.type === "global" &&
  129. scope.childScopes[0] &&
  130. // The special scope's block is the Program node.
  131. scope.block === scope.childScopes[0].block
  132. ) {
  133. findVariablesInScope(scope.childScopes[0]);
  134. }
  135. },
  136. FunctionDeclaration: checkForBlock,
  137. FunctionExpression: checkForBlock,
  138. ArrowFunctionExpression: checkForBlock,
  139. StaticBlock: checkForBlock,
  140. BlockStatement: checkForBlock,
  141. ForStatement: checkForBlock,
  142. ForInStatement: checkForBlock,
  143. ForOfStatement: checkForBlock,
  144. SwitchStatement: checkForBlock,
  145. };
  146. },
  147. };