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