Permalink
Cannot retrieve contributors at this time
| /** | |
| * @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 | |
| }; | |
| } | |
| }; |