| /** |
| * @fileoverview Rule to flag when the same variable is declared more then once. |
| * @author Ilya Volodin |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "disallow variable redeclaration", |
| category: "Best Practices", |
| recommended: true, |
| url: "https://eslint.org/docs/rules/no-redeclare" |
| }, |
| |
| messages: { |
| redeclared: "'{{id}}' is already defined.", |
| redeclaredAsBuiltin: "'{{id}}' is already defined as a built-in global variable.", |
| redeclaredBySyntax: "'{{id}}' is already defined by a variable declaration." |
| }, |
| |
| schema: [ |
| { |
| type: "object", |
| properties: { |
| builtinGlobals: { type: "boolean", default: true } |
| }, |
| additionalProperties: false |
| } |
| ] |
| }, |
| |
| create(context) { |
| const options = { |
| builtinGlobals: Boolean( |
| context.options.length === 0 || |
| context.options[0].builtinGlobals |
| ) |
| }; |
| const sourceCode = context.getSourceCode(); |
| |
| /** |
| * Iterate declarations of a given variable. |
| * @param {escope.variable} variable The variable object to iterate declarations. |
| * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations. |
| */ |
| function *iterateDeclarations(variable) { |
| if (options.builtinGlobals && ( |
| variable.eslintImplicitGlobalSetting === "readonly" || |
| variable.eslintImplicitGlobalSetting === "writable" |
| )) { |
| yield { type: "builtin" }; |
| } |
| |
| for (const id of variable.identifiers) { |
| yield { type: "syntax", node: id, loc: id.loc }; |
| } |
| |
| if (variable.eslintExplicitGlobalComments) { |
| for (const comment of variable.eslintExplicitGlobalComments) { |
| yield { |
| type: "comment", |
| node: comment, |
| loc: astUtils.getNameLocationInGlobalDirectiveComment( |
| sourceCode, |
| comment, |
| variable.name |
| ) |
| }; |
| } |
| } |
| } |
| |
| /** |
| * Find variables in a given scope and flag redeclared ones. |
| * @param {Scope} scope - An eslint-scope scope object. |
| * @returns {void} |
| * @private |
| */ |
| function findVariablesInScope(scope) { |
| for (const variable of scope.variables) { |
| const [ |
| declaration, |
| ...extraDeclarations |
| ] = iterateDeclarations(variable); |
| |
| if (extraDeclarations.length === 0) { |
| continue; |
| } |
| |
| /* |
| * If the type of a declaration is different from the type of |
| * the first declaration, it shows the location of the first |
| * declaration. |
| */ |
| const detailMessageId = declaration.type === "builtin" |
| ? "redeclaredAsBuiltin" |
| : "redeclaredBySyntax"; |
| const data = { id: variable.name }; |
| |
| // Report extra declarations. |
| for (const { type, node, loc } of extraDeclarations) { |
| const messageId = type === declaration.type |
| ? "redeclared" |
| : detailMessageId; |
| |
| context.report({ node, loc, messageId, data }); |
| } |
| } |
| } |
| |
| /** |
| * Find variables in the current scope. |
| * @param {ASTNode} node The node of the current scope. |
| * @returns {void} |
| * @private |
| */ |
| function checkForBlock(node) { |
| const scope = context.getScope(); |
| |
| /* |
| * In ES5, some node type such as `BlockStatement` doesn't have that scope. |
| * `scope.block` is a different node in such a case. |
| */ |
| if (scope.block === node) { |
| findVariablesInScope(scope); |
| } |
| } |
| |
| return { |
| Program() { |
| const scope = context.getScope(); |
| |
| findVariablesInScope(scope); |
| |
| // Node.js or ES modules has a special scope. |
| if ( |
| scope.type === "global" && |
| scope.childScopes[0] && |
| |
| // The special scope's block is the Program node. |
| scope.block === scope.childScopes[0].block |
| ) { |
| findVariablesInScope(scope.childScopes[0]); |
| } |
| }, |
| |
| FunctionDeclaration: checkForBlock, |
| FunctionExpression: checkForBlock, |
| ArrowFunctionExpression: checkForBlock, |
| |
| BlockStatement: checkForBlock, |
| ForStatement: checkForBlock, |
| ForInStatement: checkForBlock, |
| ForOfStatement: checkForBlock, |
| SwitchStatement: checkForBlock |
| }; |
| } |
| }; |