class RubyLanguageServer::SEXPProcessor

This class is responsible for processing the generated sexp from the ScopeParser below. It builds scopes that amount to heirarchical arrays with information about what classes, methods, variables, etc - are in each scope.

Attributes

current_scope[R]
lines[R]
sexp[R]

Public Class Methods

new(sexp, lines = 1) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 20
def initialize(sexp, lines = 1)
  @sexp = sexp
  @lines = lines
  @root_scope = nil
end

Public Instance Methods

assign_subclass(scope, sexp) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 102
def assign_subclass(scope, sexp)
  return unless !sexp[0].nil? && sexp[0][0] == :var_ref

  (_, (_, name)) = sexp[0]
  scope.set_superclass_name(name)
end
on_assign(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 141
def on_assign(args, rest)
  process(args)
  process(rest)
end
on_block_var(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 129
def on_block_var(args, rest)
  process(args)
  process(rest)
end
on_bodystmt(args, _rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 88
def on_bodystmt(args, _rest)
  process(args)
end
on_class(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 97
def on_class(args, rest)
  scope = add_scope(args.last, rest, ScopeData::Scope::TYPE_CLASS)
  assign_subclass(scope, rest)
end
on_command(args, rest) click to toggle source

The on_command function idea is stolen from RipperTags github.com/tmm1/ripper-tags/blob/master/lib/ripper-tags/parser.rb

# File lib/ruby_language_server/scope_parser.rb, line 174
def on_command(args, rest)
  # [:@ident, "public", [6, 8]]
  (_, name, (line, _column)) = args

  method_name = "on_#{name}_command"
  if respond_to? method_name
    return send(method_name, line, args, rest)
  else
    RubyLanguageServer.logger.debug("We don't have a #{method_name} with #{args}")
  end

  case name
  when 'public', 'private', 'protected'
    # FIXME: access control...
    process(rest)
  when 'delegate'
    # on_delegate(*args[0][1..-1])
  when 'def_delegator', 'def_instance_delegator'
    # on_def_delegator(*args[0][1..-1])
  when 'def_delegators', 'def_instance_delegators'
    # on_def_delegators(*args[0][1..-1])
  end
end
on_def(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 146
def on_def(args, rest)
  add_scope(args, rest, ScopeData::Scope::TYPE_METHOD)
end
on_defs(args, rest) click to toggle source

def self.something(par)…

:var_ref, [:@kw, “self”, [28, 14]]], [[:@period, “.”, [28, 18]], [:@ident, “something”, [28, 19]], [:paren, [:params, [[:@ident, “par”, [28, 23]]], nil, nil, nil, nil, nil, nil]], [:bodystmt, [[:assign, [:var_field, [:@ident, “pax”, [29, 12]]], [:var_ref, [:@ident, “par”, [29, 18]]]]], nil, nil, nil]
# File lib/ruby_language_server/scope_parser.rb, line 152
def on_defs(args, rest)
  on_def(rest[1], rest[2..]) if args[1][1] == 'self' && rest[0][1] == '.'
end
on_do_block(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 121
def on_do_block(args, rest)
  ((_, ((_, (_, (_, _name, (line, column))))))) = args
  push_scope(ScopeData::Scope::TYPE_BLOCK, 'block', line, column, false)
  process(args)
  process(rest)
  pop_scope
end
on_ident(name, ((line, column))) click to toggle source

ident is something that gets processed at parameters to a function or block

# File lib/ruby_language_server/scope_parser.rb, line 164
def on_ident(name, ((line, column)))
  add_variable(name, line, column)
end
on_method_add_arg(call, args) click to toggle source

The on_method_add_arg function is downright stolen from RipperTags github.com/tmm1/ripper-tags/blob/master/lib/ripper-tags/parser.rb

# File lib/ruby_language_server/scope_parser.rb, line 199
def on_method_add_arg(call, args)
  call_name = call && call[0]
  first_arg = args && args[0] == :args && args[1]

  if call_name == :call && first_arg
    if args.length == 2
      # augment call if a single argument was used
      call = call.dup
      call[3] = args[1]
    end
    call
  elsif call_name == :fcall && first_arg
    name, line = call[1]
    case name
    when 'alias_method' # this is an fcall
      [:alias, args[1][0], args[2][0], line] if args[1] && args[2]
    when 'define_method' # this is an fcall
      [:def, args[1][0], line]
    when 'public_class_method', 'private_class_method', 'private', 'public', 'protected'
      access = name.sub('_class_method', '')

      if args[1][1] == 'self'
        klass = 'self'
        method_name = args[1][2]
      else
        klass = nil
        method_name = args[1][1]
      end

      [:def_with_access, klass, method_name, access, line]
      # when 'scope', 'named_scope'
      #   [:rails_def, :scope, args[1][0], line]
      # when /^attr_(accessor|reader|writer)$/
      #   gen_reader = Regexp.last_match(1) != 'writer'
      #   gen_writer = Regexp.last_match(1) != 'reader'
      #   args[1..-1].each_with_object([]) do |arg, gen|
      #     gen << [:def, arg[0], line] if gen_reader
      #     gen << [:def, "#{arg[0]}=", line] if gen_writer
      #   end
      # when 'has_many', 'has_and_belongs_to_many'
      #   a = args[1][0]
      #   kind = name.to_sym
      #   gen = []
      #   unless a.is_a?(Enumerable) && !a.is_a?(String)
      #     a = a.to_s
      #     gen << [:rails_def, kind, a, line]
      #     gen << [:rails_def, kind, "#{a}=", line]
      #     if (sing = a.chomp('s')) != a
      #       # poor man's singularize
      #       gen << [:rails_def, kind, "#{sing}_ids", line]
      #       gen << [:rails_def, kind, "#{sing}_ids=", line]
      #     end
      #   end
      #   gen
      # when 'belongs_to', 'has_one'
      #   a = args[1][0]
      #   unless a.is_a?(Enumerable) && !a.is_a?(String)
      #     kind = name.to_sym
      #     %W[#{a} #{a}= build_#{a} create_#{a} create_#{a}!].inject([]) do |all, ident|
      #       all << [:rails_def, kind, ident, line]
      #     end
      #   end
    end
  end
end
on_method_add_block(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 109
def on_method_add_block(args, rest)
  scope = @current_scope
  process(args)
  process(rest)
  # add_scope(args, rest, ScopeData::Scope::TYPE_BLOCK)
  unless @current_scope == scope
    scope.bottom_line = [scope&.bottom_line, @current_scope.bottom_line].compact.max
    scope.save!
    pop_scope
  end
end
on_mlhs(args, rest) click to toggle source

Multiple left hand side (foo, bar) = somethingg…

# File lib/ruby_language_server/scope_parser.rb, line 158
def on_mlhs(args, rest)
  process(args)
  process(rest)
end
on_module(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 92
def on_module(args, rest)
  scope = add_scope(args.last, rest, ScopeData::Scope::TYPE_MODULE)
  assign_subclass(scope, rest)
end
on_params(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 168
def on_params(args, rest)
  process(args)
  process(rest)
end
on_program(args, _rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 72
def on_program(args, _rest)
  process(args)
end
on_sclass(_args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 62
def on_sclass(_args, rest)
  process(rest)
end
on_var_field(args, rest) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 76
def on_var_field(args, rest)
  (_, name, (line, column)) = args
  return if name.nil?

  if name.start_with?('@')
    add_ivar(name, line, column)
  else
    add_variable(name, line, column)
  end
  process(rest)
end
on_var_ref(_args, _rest) click to toggle source

Used only to describe subclasses? – nope

# File lib/ruby_language_server/scope_parser.rb, line 135
def on_var_ref(_args, _rest)
  # [:@const, "Bar", [13, 20]]
  # (_, name) = args
  # @current_scope.set_superclass_name(name)
end
on_vcall(_args, rest) click to toggle source

foo = bar – bar is in the vcall. Pretty sure we don't want to remember this.

# File lib/ruby_language_server/scope_parser.rb, line 67
def on_vcall(_args, rest)
  # Seriously - discard args.  Maybe process rest?
  process(rest)
end
process(sexp) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 35
def process(sexp)
  return if sexp.nil?

  root, args, *rest = sexp
  # RubyLanguageServer.logger.error("Doing #{[root, args, rest]}")
  case root
  when Array
    sexp.each { |child| process(child) }
  when Symbol
    root = root.to_s.gsub(/^@+/, '')
    method_name = "on_#{root}"
    if respond_to? method_name
      send(method_name, args, rest)
    else
      RubyLanguageServer.logger.debug("We don't have a #{method_name} with #{args}")
      process(args)
    end
  when String
    # We really don't do anything with it!
    RubyLanguageServer.logger.debug("We don't do Strings like #{root} with #{args}")
  when NilClass, FalseClass
    process(args)
  else
    RubyLanguageServer.logger.warn("We don't respond to the likes of #{root} of class #{root.class}")
  end
end
root_scope() click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 26
def root_scope
  return @root_scope unless @root_scope.nil?

  @root_scope = ScopeData::Scope.where(path: nil, class_type: ScopeData::Scope::TYPE_ROOT).first_or_create!
  @current_scope = @root_scope
  process(@sexp)
  @root_scope
end

Private Instance Methods

add_ivar(name, line, column) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 284
def add_ivar(name, line, column)
  scope = @current_scope
  unless scope == root_scope
    ivar_scope_types = [ScopeData::Base::TYPE_CLASS, ScopeData::Base::TYPE_MODULE]
    while !ivar_scope_types.include?(scope.class_type) && !scope.parent.nil?
      scope = scope.parent
    end
  end
  add_variable(name, line, column, scope)
end
add_scope(args, rest, type) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 295
def add_scope(args, rest, type)
  (_, name, (line, column)) = args
  scope = push_scope(type, name, line, column)
  process(rest)
  pop_scope
  scope
end
add_variable(name, line, column, scope = @current_scope) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 267
def add_variable(name, line, column, scope = @current_scope)
  newvar = scope.variables.where(name: name).first_or_create!(
    line: line,
    column: column,
    code_file: scope.code_file
  )
  if scope.top_line.blank?
    scope.top_line = line
    scope.save!
  end
  # new_variable = ScopeData::Variable.build(scope, name, line, column)
  # # blocks don't declare their first line in the parser
  # scope.top_line ||= line
  # scope.variables << new_variable unless scope.has_variable_or_constant?(new_variable)
  newvar
end
close_sibling_scopes(line) click to toggle source

This is a very poor man's “end” handler because there is no end handler. The notion is that when you start the next scope, all the previous peers and unclosed descendents of the previous peer should be closed.

# File lib/ruby_language_server/scope_parser.rb, line 317
def close_sibling_scopes(line)
  parent_scope = @current_scope
  parent_scope&.descendants&.each do |scope|
    scope.bottom_line = [scope.bottom_line, line - 1].compact.min
    scope.save!
  end
end
pop_scope() click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 325
def pop_scope
  @current_scope = @current_scope.parent || root_scope # in case we are leaving a root class/module
end
push_scope(type, name, top_line, column, close_siblings = true) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 307
def push_scope(type, name, top_line, column, close_siblings = true)
  close_sibling_scopes(top_line) if close_siblings
  new_scope = ScopeData::Scope.build(@current_scope, type, name, top_line, column)
  new_scope.bottom_line = @lines
  new_scope.save!
  @current_scope = new_scope
end
type_is_class_or_module(type) click to toggle source
# File lib/ruby_language_server/scope_parser.rb, line 303
def type_is_class_or_module(type)
  [RubyLanguageServer::ScopeData::Base::TYPE_CLASS, RubyLanguageServer::ScopeData::Base::TYPE_MODULE].include?(type)
end