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.



22
23
24
25
26
# File 'lib/ruby_language_server/scope_parser.rb', line 22

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.



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

def current_scope
  @current_scope
end

#linesObject (readonly)

Returns the value of attribute lines.



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

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



106
107
108
109
110
111
# File 'lib/ruby_language_server/scope_parser.rb', line 106

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



145
146
147
148
# File 'lib/ruby_language_server/scope_parser.rb', line 145

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

#on_block_var(args, rest) ⇒ Object



133
134
135
136
# File 'lib/ruby_language_server/scope_parser.rb', line 133

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

#on_bodystmt(args, _rest) ⇒ Object



92
93
94
# File 'lib/ruby_language_server/scope_parser.rb', line 92

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

#on_class(args, rest) ⇒ Object



101
102
103
104
# File 'lib/ruby_language_server/scope_parser.rb', line 101

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



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/ruby_language_server/scope_parser.rb', line 178

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



150
151
152
# File 'lib/ruby_language_server/scope_parser.rb', line 150

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]


156
157
158
# File 'lib/ruby_language_server/scope_parser.rb', line 156

def on_defs(args, rest)
  on_def(rest[1], rest[2..-1]) if args[1][1] == 'self' && rest[0][1] == '.'
end

#on_do_block(args, rest) ⇒ Object



125
126
127
128
129
130
131
# File 'lib/ruby_language_server/scope_parser.rb', line 125

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



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

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



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
264
265
266
267
# File 'lib/ruby_language_server/scope_parser.rb', line 203

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



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ruby_language_server/scope_parser.rb', line 113

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…



162
163
164
165
# File 'lib/ruby_language_server/scope_parser.rb', line 162

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

#on_module(args, rest) ⇒ Object



96
97
98
99
# File 'lib/ruby_language_server/scope_parser.rb', line 96

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



172
173
174
175
# File 'lib/ruby_language_server/scope_parser.rb', line 172

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

#on_program(args, _rest) ⇒ Object



76
77
78
# File 'lib/ruby_language_server/scope_parser.rb', line 76

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

#on_sclass(_args, rest) ⇒ Object



66
67
68
# File 'lib/ruby_language_server/scope_parser.rb', line 66

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

#on_var_field(args, rest) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruby_language_server/scope_parser.rb', line 80

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



139
140
141
142
143
# File 'lib/ruby_language_server/scope_parser.rb', line 139

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.



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

def on_vcall(_args, rest)
  # Seriously - discard args.  Maybe process rest?
  process(rest)
end

#process(sexp) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ruby_language_server/scope_parser.rb', line 37

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
    process(args)
  when FalseClass
    process(args)
  else
    RubyLanguageServer.logger.warn("We don't respond to the likes of #{root} of class #{root.class}")
  end
end

#root_scopeObject



28
29
30
31
32
33
34
35
# File 'lib/ruby_language_server/scope_parser.rb', line 28

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