Class: RubyLint::Iterator

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-lint/iterator.rb

Overview

The Iterator class provides the means to iterate over an AST generated by Parser using callback methods for the various node types generated by this parser.

For each node type two events are called: one before and one after processing the node and all of its children. The names of these events are the following:

  • on_X
  • after_X

Here “X” is the name of the event. For example, when iterator an integer this would result in the event names on_integer and after_integer.

These event names are used to call the corresponding callback methods if they exist. Each callback method takes a single argument: the node (an instance of AST::Node) that belongs to the event.

Creating iterator classes is done by extending this particular class and adding the needed methods to it:

class MyIterator < RubyLint::Iterator
  def on_int(node)
    puts node.children[0]
  end

  def after_int(node)
    puts '---'
  end
end

When used this particular iterator class would display the values of all integers it processes. After processing an integer it will display three dashes.

Skipping Child Nodes

The on_* callbacks can tell the Iterator class to not process any following child nodes by calling skip_child_nodes!:

def on_const(node)
  # ...

  skip_child_nodes!(node)
end

Internally this uses throw and makes sure to only skip the child nodes of the specified node (throw calls bubble up regardless of catch calls, unlike when using begin/rescue).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Iterator

Returns a new instance of Iterator

Parameters:

  • options (Hash) (defaults to: {})

    Hash containing custom options to set for the iterator.



64
65
66
67
68
69
70
71
72
# File 'lib/ruby-lint/iterator.rb', line 64

def initialize(options = {})
  options.each do |key, value|
    instance_variable_set("@#{key}", value)
  end

  after_initialize if respond_to?(:after_initialize)

  @arity_cache = {}
end

Instance Attribute Details

#arity_cacheObject (readonly)

Returns the value of attribute arity_cache



58
59
60
# File 'lib/ruby-lint/iterator.rb', line 58

def arity_cache
  @arity_cache
end

#arity_cache Hash containing the amount of arguments for(Hashcontainingtheamountofarguments) ⇒ Hash (readonly)

each method.

Returns:

  • (Hash)


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
# File 'lib/ruby-lint/iterator.rb', line 57

class Iterator
  attr_reader :arity_cache

  ##
  # @param [Hash] options Hash containing custom options to set for the
  #  iterator.
  #
  def initialize(options = {})
    options.each do |key, value|
      instance_variable_set("@#{key}", value)
    end

    after_initialize if respond_to?(:after_initialize)

    @arity_cache = {}
  end

  ##
  # Recursively processes the specified list of nodes.
  #
  # @param [RubyLint::Node] node A node and optionally a set of sub nodes to
  #  iterate over.
  #
  def iterate(node)
    return unless node.is_a?(AST::Node)

    before    = :on_#{node.type}"
    after     = :after_#{node.type}"
    skip_node = catch :skip_child_nodes do
      execute_callback(before, node)
    end

    if skip_node != node
      node.children.each do |child|
        iterate(child) if child.is_a?(AST::Node)
      end
    end

    execute_callback(after, node)
  end

  protected

  ##
  # Instructs {#iterate} to not process any child nodes.
  #
  # @param [RubyLint::AST::Node] node
  #
  def skip_child_nodes!(node)
    throw :skip_child_nodes, node
  end

  ##
  # Executes the specified callback method if it exists.
  #
  # @param [String|Symbol] name The name of the callback method to execute.
  # @param [Array] args Arguments to pass to the callback method.
  #
  def execute_callback(name, *args)
    return unless respond_to?(name)

    unless arity_cache.key?(name)
      arity_cache[name] = method(name).arity
    end

    if arity_cache[name] == 0
      send(name)
    else
      send(name, *args)
    end
  end
end

Instance Method Details

#execute_callback(name, *args) ⇒ Object (protected)

Executes the specified callback method if it exists.

Parameters:

  • name (String|Symbol)

    The name of the callback method to execute.

  • args (Array)

    Arguments to pass to the callback method.



115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ruby-lint/iterator.rb', line 115

def execute_callback(name, *args)
  return unless respond_to?(name)

  unless arity_cache.key?(name)
    arity_cache[name] = method(name).arity
  end

  if arity_cache[name] == 0
    send(name)
  else
    send(name, *args)
  end
end

#iterate(node) ⇒ Object

Recursively processes the specified list of nodes.

Parameters:

  • node (RubyLint::Node)

    A node and optionally a set of sub nodes to iterate over.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ruby-lint/iterator.rb', line 80

def iterate(node)
  return unless node.is_a?(AST::Node)

  before    = :on_#{node.type}"
  after     = :after_#{node.type}"
  skip_node = catch :skip_child_nodes do
    execute_callback(before, node)
  end

  if skip_node != node
    node.children.each do |child|
      iterate(child) if child.is_a?(AST::Node)
    end
  end

  execute_callback(after, node)
end

#skip_child_nodes!(node) ⇒ Object (protected)

Instructs #iterate to not process any child nodes.

Parameters:



105
106
107
# File 'lib/ruby-lint/iterator.rb', line 105

def skip_child_nodes!(node)
  throw :skip_child_nodes, node
end