Class: RubyLanguageServer::SEXPProcessor

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RubyLanguageServer::ScopeParserCommands::RubyCommands

#on_attr_accessor_command, #on_attr_command, #on_attr_reader_command, #on_attr_writer_command

Methods included from RubyLanguageServer::ScopeParserCommands::RailsCommands

#rails_add_reference

Methods included from RubyLanguageServer::ScopeParserCommands::RspecCommands

#on_describe_command, #on_it_command

Methods included from RubyLanguageServer::ScopeParserCommands::RakeCommands

#on_namespace_command, #on_task_command

Constructor Details

#initialize(sexp, lines = 1) ⇒ SEXPProcessor

Returns a new instance of SEXPProcessor.



20
21
22
23
24
# File 'lib/ruby_language_server/scope_parser.rb', line 20

def initialize(sexp, lines = 1)
  @sexp = sexp
  @lines = lines
  @root_scope = nil
end

Instance Attribute Details

#current_scopeObject (readonly)

Returns the value of attribute current_scope.



18
19
20
# File 'lib/ruby_language_server/scope_parser.rb', line 18

def current_scope
  @current_scope
end

#linesObject (readonly)

Returns the value of attribute lines.



18
19
20
# File 'lib/ruby_language_server/scope_parser.rb', line 18

def lines
  @lines
end

#sexpObject (readonly)

Returns the value of attribute sexp.



18
19
20
# File 'lib/ruby_language_server/scope_parser.rb', line 18

def sexp
  @sexp
end

Instance Method Details

#assign_subclass(scope, sexp) ⇒ Object



102
103
104
105
106
107
# 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) ⇒ Object



141
142
143
144
# 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) ⇒ Object



129
130
131
132
# 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) ⇒ Object



88
89
90
# File 'lib/ruby_language_server/scope_parser.rb', line 88

def on_bodystmt(args, _rest)
  process(args)
end

#on_class(args, rest) ⇒ Object



97
98
99
100
# 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) ⇒ Object

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



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# 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) ⇒ Object



146
147
148
# 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) ⇒ Object

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]


152
153
154
# 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) ⇒ Object



121
122
123
124
125
126
127
# 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) ⇒ Object

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



164
165
166
# 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) ⇒ Object

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



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
258
259
260
261
262
263
# 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) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# 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) ⇒ Object

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



158
159
160
161
# File 'lib/ruby_language_server/scope_parser.rb', line 158

def on_mlhs(args, rest)
  process(args)
  process(rest)
end

#on_module(args, rest) ⇒ Object



92
93
94
95
# 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) ⇒ Object



168
169
170
171
# File 'lib/ruby_language_server/scope_parser.rb', line 168

def on_params(args, rest)
  process(args)
  process(rest)
end

#on_program(args, _rest) ⇒ Object



72
73
74
# File 'lib/ruby_language_server/scope_parser.rb', line 72

def on_program(args, _rest)
  process(args)
end

#on_sclass(_args, rest) ⇒ Object



62
63
64
# File 'lib/ruby_language_server/scope_parser.rb', line 62

def on_sclass(_args, rest)
  process(rest)
end

#on_var_field(args, rest) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
# 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) ⇒ Object

Used only to describe subclasses? – nope



135
136
137
138
139
# 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) ⇒ Object

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



67
68
69
70
# 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) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 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_scopeObject



26
27
28
29
30
31
32
33
# 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