Blame view

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