Module: Rubocop::Cop::VariableInspector

Included in:
Lint::ShadowingOuterLocalVariable, Lint::UnusedLocalVariable
Defined in:
lib/rubocop/cop/variable_inspector.rb

Overview

This module provides a way to track local variables and scopes of Ruby. This is intended to be used as mix-in, and the user class may override some of hook methods.

Defined Under Namespace

Classes: NodeScanner, Scope, VariableEntry, VariableTable

Constant Summary collapse

VARIABLE_ASSIGNMENT_TYPES =
[:lvasgn].freeze
ARGUMENT_DECLARATION_TYPES =
[
  :arg, :optarg, :restarg, :blockarg,
  :kwarg, :kwoptarg, :kwrestarg,
  :shadowarg
].freeze
VARIABLE_DECLARATION_TYPES =
(VARIABLE_ASSIGNMENT_TYPES + ARGUMENT_DECLARATION_TYPES).freeze
VARIABLE_USE_TYPES =
[:lvar].freeze
SCOPE_TYPES =
[:module, :class, :sclass, :def, :defs, :block].freeze

Instance Method Summary collapse

Instance Method Details

#after_declaring_variable(variable_entry) ⇒ Object



276
277
# File 'lib/rubocop/cop/variable_inspector.rb', line 276

def after_declaring_variable(variable_entry)
end

#after_entering_scope(scope) ⇒ Object



264
265
# File 'lib/rubocop/cop/variable_inspector.rb', line 264

def after_entering_scope(scope)
end

#after_leaving_scope(scope) ⇒ Object



270
271
# File 'lib/rubocop/cop/variable_inspector.rb', line 270

def after_leaving_scope(scope)
end

#before_declaring_variable(variable_entry) ⇒ Object



273
274
# File 'lib/rubocop/cop/variable_inspector.rb', line 273

def before_declaring_variable(variable_entry)
end

#before_entering_scope(scope) ⇒ Object

Hooks



261
262
# File 'lib/rubocop/cop/variable_inspector.rb', line 261

def before_entering_scope(scope)
end

#before_leaving_scope(scope) ⇒ Object



267
268
# File 'lib/rubocop/cop/variable_inspector.rb', line 267

def before_leaving_scope(scope)
end

#inspect_variables(root_node) ⇒ Object

Starting point.



156
157
158
159
160
161
162
163
164
165
# File 'lib/rubocop/cop/variable_inspector.rb', line 156

def inspect_variables(root_node)
  return unless root_node

  # Wrap with begin node if it's standalone node.
  unless root_node.type == :begin
    root_node = Parser::AST::Node.new(:begin, [root_node])
  end

  inspect_variables_in_scope(root_node)
end

#inspect_variables_in_scope(scope_node) ⇒ Object

This is called for each scope recursively.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/rubocop/cop/variable_inspector.rb', line 168

def inspect_variables_in_scope(scope_node)
  variable_table.push_scope(scope_node)

  NodeScanner.scan_nodes_in_scope(scope_node) do |node, index|
    if scope_node.type == :block && index == 0 && node.type == :send
      # Avoid processing method argument nodes of outer scope
      # in current block scope.
      # See #process_node.
      throw :skip_children
    elsif [:sclass, :defs].include?(scope_node.type) && index == 0
      throw :skip_children
    end

    process_node(node)
  end

  variable_table.pop_scope
end

#process_node(node) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/rubocop/cop/variable_inspector.rb', line 187

def process_node(node)
  case node.type
  when *ARGUMENT_DECLARATION_TYPES
    variable_table.add_variable_entry(node)
  when *VARIABLE_ASSIGNMENT_TYPES
    variable_name = node.children.first
    variable_entry = variable_table.find_variable_entry(variable_name)
    if variable_entry
      variable_entry.used = true
    else
      variable_table.add_variable_entry(node)
    end
  when *VARIABLE_USE_TYPES
    variable_name = node.children.first
    variable_entry = variable_table.find_variable_entry(variable_name)
    unless variable_entry
      fail "Using undeclared local variable \"#{variable_name}\" " +
           "at #{node.loc.expression}, #{node.inspect}"
    end
    variable_entry.used = true
  when :block
    # The variable foo belongs to the top level scope,
    # but in AST, it's under the block node.
    #
    # Ruby:
    #   some_method(foo = 1) do
    #   end
    #   puts foo
    #
    # AST:
    #   (begin
    #     (block
    #       (send nil :some_method
    #         (lvasgn :foo
    #           (int 1)))
    #       (args) nil)
    #     (send nil :puts
    #       (lvar :foo)))
    #
    # So the nodes of the method argument need to be processed
    # in current scope before dive into the block scope.
    NodeScanner.scan_nodes_in_scope(node.children.first) do |n|
      process_node(n)
    end
    # Now go into the block scope.
    inspect_variables_in_scope(node)
  when :sclass, :defs
    # Same thing.
    #
    # Ruby:
    #   instance = Object.new
    #   class << instance
    #     foo = 1
    #   end
    #
    # AST:
    #   (begin
    #     (lvasgn :instance
    #       (send
    #         (const nil :Object) :new))
    #     (sclass
    #       (lvar :instance)
    #       (begin
    #         (lvasgn :foo
    #           (int 1))
    process_node(node.children.first)
    inspect_variables_in_scope(node)
  when *SCOPE_TYPES
    inspect_variables_in_scope(node)
  end
end

#variable_tableObject



151
152
153
# File 'lib/rubocop/cop/variable_inspector.rb', line 151

def variable_table
  @variable_table ||= VariableTable.new(self)
end