Module: SyntaxTree::WithScope

Defined in:
lib/syntax_tree/with_scope.rb

Overview

WithScope is a module intended to be included in classes inheriting from Visitor. The module overrides a few visit methods to automatically keep track of local variables and arguments defined in the current scope. Example usage:

class MyVisitor < Visitor
  include WithScope

  def visit_ident(node)
    # Check if we're visiting an identifier for an argument, a local
    # variable or something else
    local = current_scope.find_local(node)

    if local.type == :argument
      # handle identifiers for arguments
    elsif local.type == :variable
      # handle identifiers for variables
    else
      # handle other identifiers, such as method names
    end
  end
end

Defined Under Namespace

Classes: Scope

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#current_scopeObject (readonly)

Returns the value of attribute current_scope.



120
121
122
# File 'lib/syntax_tree/with_scope.rb', line 120

def current_scope
  @current_scope
end

Instance Method Details

#initialize(*args, **kwargs, &block) ⇒ Object



122
123
124
125
126
127
# File 'lib/syntax_tree/with_scope.rb', line 122

def initialize(*args, **kwargs, &block)
  super

  @current_scope = Scope.new(0)
  @next_scope_id = 0
end

#visit_binary(node) ⇒ Object

Visit for capturing local variables defined in regex named capture groups



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/syntax_tree/with_scope.rb', line 236

def visit_binary(node)
  if node.operator == :=~
    left = node.left

    if left.is_a?(RegexpLiteral) && left.parts.length == 1 &&
         left.parts.first.is_a?(TStringContent)
      content = left.parts.first

      value = content.value
      location = content.location
      start_line = location.start_line

      Regexp
        .new(value, Regexp::FIXEDENCODING)
        .names
        .each do |name|
          offset = value.index(/\(\?<#{Regexp.escape(name)}>/)
          line = start_line + value[0...offset].count("\n")

          # We need to add 3 to account for these three characters
          # prefixing a named capture (?<
          column = location.start_column + offset + 3
          if value[0...offset].include?("\n")
            column =
              value[0...offset].length - value[0...offset].rindex("\n") +
                3 - 1
          end

          ident_location =
            Location.new(
              start_line: line,
              start_char: location.start_char + offset,
              start_column: column,
              end_line: line,
              end_char: location.start_char + offset + name.length,
              end_column: column + name.length
            )

          identifier = Ident.new(value: name, location: ident_location)
          current_scope.add_local_definition(identifier, :variable)
        end
    end
  end

  super
end

#visit_block_var(node) ⇒ Object Also known as: visit_lambda_var



189
190
191
192
193
194
195
# File 'lib/syntax_tree/with_scope.rb', line 189

def visit_block_var(node)
  node.locals.each do |local|
    current_scope.add_local_definition(local, :variable)
  end

  super
end

#visit_blockarg(node) ⇒ Object



182
183
184
185
186
187
# File 'lib/syntax_tree/with_scope.rb', line 182

def visit_blockarg(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end

#visit_class(node) ⇒ Object

Visits for nodes that create new scopes, such as classes, modules and method definitions.



131
132
133
# File 'lib/syntax_tree/with_scope.rb', line 131

def visit_class(node)
  with_scope { super }
end

#visit_def(node) ⇒ Object



147
148
149
# File 'lib/syntax_tree/with_scope.rb', line 147

def visit_def(node)
  with_scope { super }
end

#visit_kwrest_param(node) ⇒ Object



175
176
177
178
179
180
# File 'lib/syntax_tree/with_scope.rb', line 175

def visit_kwrest_param(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end

#visit_method_add_block(node) ⇒ Object

When we find a method invocation with a block, only the code that happens inside of the block needs a fresh scope. The method invocation itself happens in the same scope.



142
143
144
145
# File 'lib/syntax_tree/with_scope.rb', line 142

def visit_method_add_block(node)
  visit(node.call)
  with_scope(current_scope) { visit(node.block) }
end

#visit_module(node) ⇒ Object



135
136
137
# File 'lib/syntax_tree/with_scope.rb', line 135

def visit_module(node)
  with_scope { super }
end

#visit_params(node) ⇒ Object

Visit for keeping track of local arguments, such as method and block arguments.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/syntax_tree/with_scope.rb', line 153

def visit_params(node)
  add_argument_definitions(node.requireds)
  add_argument_definitions(node.posts)

  node.keywords.each do |param|
    current_scope.add_local_definition(param.first, :argument)
  end

  node.optionals.each do |param|
    current_scope.add_local_definition(param.first, :argument)
  end

  super
end

#visit_pinned_var_ref(node) ⇒ Object

Visit for keeping track of local variable definitions



207
208
209
210
211
212
# File 'lib/syntax_tree/with_scope.rb', line 207

def visit_pinned_var_ref(node)
  value = node.value
  current_scope.add_local_usage(value, :variable) if value.is_a?(Ident)

  super
end

#visit_rest_param(node) ⇒ Object



168
169
170
171
172
173
# File 'lib/syntax_tree/with_scope.rb', line 168

def visit_rest_param(node)
  name = node.name
  current_scope.add_local_definition(name, :argument) if name

  super
end

#visit_var_field(node) ⇒ Object

Visit for keeping track of local variable definitions



199
200
201
202
203
204
# File 'lib/syntax_tree/with_scope.rb', line 199

def visit_var_field(node)
  value = node.value
  current_scope.add_local_definition(value, :variable) if value.is_a?(Ident)

  super
end

#visit_var_ref(node) ⇒ Object

Visits for keeping track of variable and argument usages



215
216
217
218
219
220
221
222
223
224
# File 'lib/syntax_tree/with_scope.rb', line 215

def visit_var_ref(node)
  value = node.value

  if value.is_a?(Ident)
    definition = current_scope.find_local(value.value)
    current_scope.add_local_usage(value, definition.type) if definition
  end

  super
end

#visit_vcall(node) ⇒ Object

When using regex named capture groups, vcalls might actually be a variable



227
228
229
230
231
232
233
# File 'lib/syntax_tree/with_scope.rb', line 227

def visit_vcall(node)
  value = node.value
  definition = current_scope.find_local(value.value)
  current_scope.add_local_usage(value, definition.type) if definition

  super
end