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



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
282
283
284
# File 'lib/syntax_tree/with_scope.rb', line 239

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



192
193
194
195
196
197
198
# File 'lib/syntax_tree/with_scope.rb', line 192

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

  super
end

#visit_blockarg(node) ⇒ Object



185
186
187
188
189
190
# File 'lib/syntax_tree/with_scope.rb', line 185

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



178
179
180
181
182
183
# File 'lib/syntax_tree/with_scope.rb', line 178

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
167
168
169
# File 'lib/syntax_tree/with_scope.rb', line 153

def visit_params(node)
  add_argument_definitions(node.requireds)

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

  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



210
211
212
213
214
215
# File 'lib/syntax_tree/with_scope.rb', line 210

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



171
172
173
174
175
176
# File 'lib/syntax_tree/with_scope.rb', line 171

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



202
203
204
205
206
207
# File 'lib/syntax_tree/with_scope.rb', line 202

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



218
219
220
221
222
223
224
225
226
227
# File 'lib/syntax_tree/with_scope.rb', line 218

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



230
231
232
233
234
235
236
# File 'lib/syntax_tree/with_scope.rb', line 230

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