--- /dev/null
+/**
+ * @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
+ };
+ }
+};