• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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