| /** |
| * @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 |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../types').Rule.RuleModule} */ |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| defaultOptions: [{ builtinGlobals: true }], |
| |
| docs: { |
| description: "Disallow variable redeclaration", |
| recommended: true, |
| url: "https://eslint.org/docs/latest/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" }, |
| }, |
| additionalProperties: false, |
| }, |
| ], |
| }, |
| |
| create(context) { |
| const [{ builtinGlobals }] = context.options; |
| const sourceCode = context.sourceCode; |
| |
| /** |
| * 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 ( |
| 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 = sourceCode.getScope(node); |
| |
| /* |
| * 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(node) { |
| const scope = sourceCode.getScope(node); |
| |
| 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, |
| |
| StaticBlock: checkForBlock, |
| |
| BlockStatement: checkForBlock, |
| ForStatement: checkForBlock, |
| ForInStatement: checkForBlock, |
| ForOfStatement: checkForBlock, |
| SwitchStatement: checkForBlock, |
| }; |
| }, |
| }; |