no-redeclare.js 5.6 KB

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