1 | /**
|
2 | * @fileoverview Rule to flag on declaring variables already declared in the outer scope
|
3 | * @author Ilya Volodin
|
4 | * @copyright 2013 Ilya Volodin. All rights reserved.
|
5 | */
|
6 |
|
7 | ;
|
8 |
|
9 | //------------------------------------------------------------------------------
|
10 | // Requirements
|
11 | //------------------------------------------------------------------------------
|
12 |
|
13 | var astUtils = require("../ast-utils");
|
14 |
|
15 | //------------------------------------------------------------------------------
|
16 | // Rule Definition
|
17 | //------------------------------------------------------------------------------
|
18 |
|
19 | module.exports = function(context) {
|
20 |
|
21 | var options = {
|
22 | builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals),
|
23 | hoist: (context.options[0] && context.options[0].hoist) || "functions",
|
24 | allow: (context.options[0] && context.options[0].allow) || []
|
25 | };
|
26 |
|
27 | /**
|
28 | * Check if variable name is allowed.
|
29 | *
|
30 | * @param {ASTNode} variable The variable to check.
|
31 | * @returns {boolean} Whether or not the variable name is allowed.
|
32 | */
|
33 | function isAllowed(variable) {
|
34 | return options.allow.indexOf(variable.name) !== -1;
|
35 | }
|
36 |
|
37 | /**
|
38 | * Checks if a variable of the class name in the class scope of ClassDeclaration.
|
39 | *
|
40 | * ClassDeclaration creates two variables of its name into its outer scope and its class scope.
|
41 | * So we should ignore the variable in the class scope.
|
42 | *
|
43 | * @param {Object} variable The variable to check.
|
44 | * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration.
|
45 | */
|
46 | function isDuplicatedClassNameVariable(variable) {
|
47 | var block = variable.scope.block;
|
48 | return block.type === "ClassDeclaration" && block.id === variable.identifiers[0];
|
49 | }
|
50 |
|
51 | /**
|
52 | * Checks if a variable is inside the initializer of scopeVar.
|
53 | *
|
54 | * To avoid reporting at declarations such as `var a = function a() {};`.
|
55 | * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`.
|
56 | *
|
57 | * @param {Object} variable The variable to check.
|
58 | * @param {Object} scopeVar The scope variable to look for.
|
59 | * @returns {boolean} Whether or not the variable is inside initializer of scopeVar.
|
60 | */
|
61 | function isOnInitializer(variable, scopeVar) {
|
62 | var outerScope = scopeVar.scope;
|
63 | var outerDef = scopeVar.defs[0];
|
64 | var outer = outerDef && outerDef.parent && outerDef.parent.range;
|
65 | var innerScope = variable.scope;
|
66 | var innerDef = variable.defs[0];
|
67 | var inner = innerDef && innerDef.name.range;
|
68 |
|
69 | return (
|
70 | outer &&
|
71 | inner &&
|
72 | outer[0] < inner[0] &&
|
73 | inner[1] < outer[1] &&
|
74 | ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") &&
|
75 | outerScope === innerScope.upper
|
76 | );
|
77 | }
|
78 |
|
79 | /**
|
80 | * Get a range of a variable's identifier node.
|
81 | * @param {Object} variable The variable to get.
|
82 | * @returns {Array|undefined} The range of the variable's identifier node.
|
83 | */
|
84 | function getNameRange(variable) {
|
85 | var def = variable.defs[0];
|
86 | return def && def.name.range;
|
87 | }
|
88 |
|
89 | /**
|
90 | * Checks if a variable is in TDZ of scopeVar.
|
91 | * @param {Object} variable The variable to check.
|
92 | * @param {Object} scopeVar The variable of TDZ.
|
93 | * @returns {boolean} Whether or not the variable is in TDZ of scopeVar.
|
94 | */
|
95 | function isInTdz(variable, scopeVar) {
|
96 | var outerDef = scopeVar.defs[0];
|
97 | var inner = getNameRange(variable);
|
98 | var outer = getNameRange(scopeVar);
|
99 | return (
|
100 | inner &&
|
101 | outer &&
|
102 | inner[1] < outer[0] &&
|
103 | // Excepts FunctionDeclaration if is {"hoist":"function"}.
|
104 | (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration")
|
105 | );
|
106 | }
|
107 |
|
108 | /**
|
109 | * Checks the current context for shadowed variables.
|
110 | * @param {Scope} scope - Fixme
|
111 | * @returns {void}
|
112 | */
|
113 | function checkForShadows(scope) {
|
114 | var variables = scope.variables;
|
115 | for (var i = 0; i < variables.length; ++i) {
|
116 | var variable = variables[i];
|
117 |
|
118 | // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration.
|
119 | if (variable.identifiers.length === 0 ||
|
120 | isDuplicatedClassNameVariable(variable) ||
|
121 | isAllowed(variable)
|
122 | ) {
|
123 | continue;
|
124 | }
|
125 |
|
126 | // Gets shadowed variable.
|
127 | var shadowed = astUtils.getVariableByName(scope.upper, variable.name);
|
128 | if (shadowed &&
|
129 | (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) &&
|
130 | !isOnInitializer(variable, shadowed) &&
|
131 | !(options.hoist !== "all" && isInTdz(variable, shadowed))
|
132 | ) {
|
133 | context.report({
|
134 | node: variable.identifiers[0],
|
135 | message: "'{{name}}' is already declared in the upper scope.",
|
136 | data: variable
|
137 | });
|
138 | }
|
139 | }
|
140 | }
|
141 |
|
142 | return {
|
143 | "Program:exit": function() {
|
144 | var globalScope = context.getScope();
|
145 | var stack = globalScope.childScopes.slice();
|
146 | var scope;
|
147 |
|
148 | while (stack.length) {
|
149 | scope = stack.pop();
|
150 | stack.push.apply(stack, scope.childScopes);
|
151 | checkForShadows(scope);
|
152 | }
|
153 | }
|
154 | };
|
155 |
|
156 | };
|
157 |
|
158 | module.exports.schema = [
|
159 | {
|
160 | "type": "object",
|
161 | "properties": {
|
162 | "builtinGlobals": {"type": "boolean"},
|
163 | "hoist": {"enum": ["all", "functions", "never"]},
|
164 | "allow": {
|
165 | "type": "array",
|
166 | "items": {
|
167 | "type": "string"
|
168 | }
|
169 | }
|
170 | },
|
171 | "additionalProperties": false
|
172 | }
|
173 | ];
|