solargraph 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +16 -12
  3. data/lib/solargraph/api_map.rb +516 -588
  4. data/lib/solargraph/api_map/completion.rb +16 -0
  5. data/lib/solargraph/api_map/source_to_yard.rb +2 -2
  6. data/lib/solargraph/language_server.rb +12 -0
  7. data/lib/solargraph/language_server/completion_item_kinds.rb +31 -0
  8. data/lib/solargraph/language_server/error_codes.rb +16 -0
  9. data/lib/solargraph/language_server/host.rb +305 -0
  10. data/lib/solargraph/language_server/message.rb +70 -0
  11. data/lib/solargraph/language_server/message/base.rb +64 -0
  12. data/lib/solargraph/language_server/message/cancel_request.rb +11 -0
  13. data/lib/solargraph/language_server/message/client.rb +5 -0
  14. data/lib/solargraph/language_server/message/client/register_capability.rb +13 -0
  15. data/lib/solargraph/language_server/message/completion_item.rb +9 -0
  16. data/lib/solargraph/language_server/message/completion_item/resolve.rb +23 -0
  17. data/lib/solargraph/language_server/message/exit_notification.rb +12 -0
  18. data/lib/solargraph/language_server/message/extended.rb +15 -0
  19. data/lib/solargraph/language_server/message/extended/document.rb +18 -0
  20. data/lib/solargraph/language_server/message/extended/search.rb +18 -0
  21. data/lib/solargraph/language_server/message/initialize.rb +39 -0
  22. data/lib/solargraph/language_server/message/initialized.rb +10 -0
  23. data/lib/solargraph/language_server/message/method_not_found.rb +14 -0
  24. data/lib/solargraph/language_server/message/method_not_implemented.rb +12 -0
  25. data/lib/solargraph/language_server/message/shutdown.rb +11 -0
  26. data/lib/solargraph/language_server/message/text_document.rb +21 -0
  27. data/lib/solargraph/language_server/message/text_document/base.rb +17 -0
  28. data/lib/solargraph/language_server/message/text_document/completion.rb +69 -0
  29. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
  30. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
  31. data/lib/solargraph/language_server/message/text_document/did_close.rb +12 -0
  32. data/lib/solargraph/language_server/message/text_document/did_open.rb +13 -0
  33. data/lib/solargraph/language_server/message/text_document/did_save.rb +15 -0
  34. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +31 -0
  35. data/lib/solargraph/language_server/message/text_document/formatting.rb +36 -0
  36. data/lib/solargraph/language_server/message/text_document/hover.rb +19 -0
  37. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +29 -0
  38. data/lib/solargraph/language_server/message/text_document/signature_help.rb +23 -0
  39. data/lib/solargraph/language_server/message/workspace.rb +11 -0
  40. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +9 -0
  41. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +30 -0
  42. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +31 -0
  43. data/lib/solargraph/language_server/symbol_kinds.rb +32 -0
  44. data/lib/solargraph/language_server/transport.rb +7 -0
  45. data/lib/solargraph/language_server/transport/socket.rb +66 -0
  46. data/lib/solargraph/language_server/uri_helpers.rb +21 -0
  47. data/lib/solargraph/library.rb +225 -0
  48. data/lib/solargraph/live_map.rb +1 -1
  49. data/lib/solargraph/page.rb +61 -0
  50. data/lib/solargraph/pin.rb +7 -0
  51. data/lib/solargraph/pin/attribute.rb +9 -0
  52. data/lib/solargraph/pin/base.rb +76 -6
  53. data/lib/solargraph/pin/base_variable.rb +29 -7
  54. data/lib/solargraph/pin/block_parameter.rb +53 -0
  55. data/lib/solargraph/pin/constant.rb +6 -2
  56. data/lib/solargraph/pin/conversions.rb +65 -0
  57. data/lib/solargraph/pin/directed/attribute.rb +4 -0
  58. data/lib/solargraph/pin/directed/method.rb +6 -1
  59. data/lib/solargraph/pin/helper.rb +35 -0
  60. data/lib/solargraph/pin/keyword.rb +22 -0
  61. data/lib/solargraph/pin/local_variable.rb +0 -1
  62. data/lib/solargraph/pin/method.rb +55 -2
  63. data/lib/solargraph/pin/method_parameter.rb +19 -0
  64. data/lib/solargraph/pin/namespace.rb +7 -2
  65. data/lib/solargraph/pin/parameter.rb +23 -0
  66. data/lib/solargraph/pin/plugin/method.rb +3 -2
  67. data/lib/solargraph/pin/yard_object.rb +101 -0
  68. data/lib/solargraph/server.rb +82 -135
  69. data/lib/solargraph/shell.rb +20 -1
  70. data/lib/solargraph/source.rb +709 -0
  71. data/lib/solargraph/source/flawed_builder.rb +10 -0
  72. data/lib/solargraph/source/fragment.rb +319 -0
  73. data/lib/solargraph/source/position.rb +26 -0
  74. data/lib/solargraph/source/range.rb +39 -0
  75. data/lib/solargraph/suggestion.rb +29 -4
  76. data/lib/solargraph/version.rb +1 -1
  77. data/lib/solargraph/workspace.rb +105 -0
  78. data/lib/solargraph/{api_map → workspace}/config.rb +1 -1
  79. data/lib/solargraph/yard_map.rb +59 -37
  80. metadata +168 -5
  81. data/lib/solargraph/api_map/source.rb +0 -470
  82. data/lib/solargraph/code_map.rb +0 -868
@@ -3,6 +3,7 @@ require 'json'
3
3
  require 'fileutils'
4
4
  require 'rubygems/package'
5
5
  require 'zlib'
6
+ require 'eventmachine'
6
7
 
7
8
  module Solargraph
8
9
  class Shell < Thor
@@ -39,6 +40,24 @@ module Solargraph
39
40
  end
40
41
  end
41
42
 
43
+ desc 'socket', 'Run a Solargraph socket server'
44
+ option :host, type: :string, aliases: :h, desc: 'The server host', default: '127.0.0.1'
45
+ option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7658
46
+ def socket
47
+ port = options[:port]
48
+ port = available_port if port.zero?
49
+ EventMachine.run do
50
+ Signal.trap("INT") do
51
+ EventMachine.stop
52
+ end
53
+ Signal.trap("TERM") do
54
+ EventMachine.stop
55
+ end
56
+ EventMachine.start_server options[:host], port, Solargraph::LanguageServer::Transport::Socket
57
+ STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}"
58
+ end
59
+ end
60
+
42
61
  desc 'suggest', 'Get code suggestions for the provided input'
43
62
  long_desc <<-LONGDESC
44
63
  Analyze a Ruby file and output a list of code suggestions in JSON format.
@@ -80,7 +99,7 @@ module Solargraph
80
99
  end
81
100
  end
82
101
  end
83
- conf = Solargraph::ApiMap::Config.new.raw_data
102
+ conf = Solargraph::Workspace::Config.new.raw_data
84
103
  unless matches.empty?
85
104
  matches.each do |m|
86
105
  conf['extensions'].push m
@@ -0,0 +1,709 @@
1
+ require 'parser/current'
2
+ require 'time'
3
+
4
+ module Solargraph
5
+ class Source
6
+ autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
7
+ autoload :Fragment, 'solargraph/source/fragment'
8
+ autoload :Position, 'solargraph/source/position'
9
+ autoload :Range, 'solargraph/source/range'
10
+
11
+ # @return [String]
12
+ attr_reader :code
13
+
14
+ # @return [Parser::AST::Node]
15
+ attr_reader :node
16
+
17
+ # @return [Array]
18
+ attr_reader :comments
19
+
20
+ # @return [String]
21
+ attr_reader :filename
22
+
23
+ # Get the file's modification time.
24
+ #
25
+ # @return [Time]
26
+ attr_reader :mtime
27
+
28
+ # @return [Array<Integer>]
29
+ attr_reader :stubbed_lines
30
+
31
+ attr_reader :directives
32
+
33
+ attr_reader :path_macros
34
+
35
+ attr_accessor :version
36
+
37
+ # Get the time of the last synchronization.
38
+ #
39
+ # @return [Time]
40
+ attr_reader :stime
41
+
42
+ include NodeMethods
43
+
44
+ def initialize code, node, comments, filename, stubbed_lines = []
45
+ @code = code
46
+ @fixed = code
47
+ @filename = filename
48
+ @stubbed_lines = stubbed_lines
49
+ @version = 0
50
+ process_parsed node, comments
51
+ end
52
+
53
+ def macro path
54
+ @path_macros[path]
55
+ end
56
+
57
+ def namespaces
58
+ @namespace_nodes.keys
59
+ end
60
+
61
+ def namespace_nodes
62
+ @namespace_nodes
63
+ end
64
+
65
+ def namespace_includes
66
+ @namespace_includes ||= {}
67
+ end
68
+
69
+ def namespace_extends
70
+ @namespaces_extends ||= {}
71
+ end
72
+
73
+ def superclasses
74
+ @superclasses ||= {}
75
+ end
76
+
77
+ # @return [Array<Solargraph::Pin::Method>]
78
+ def method_pins
79
+ @method_pins ||= []
80
+ end
81
+
82
+ # @return [Array<Solargraph::Pin::Attribute>]
83
+ def attribute_pins
84
+ @attribute_pins ||= []
85
+ end
86
+
87
+ # @return [Array<Solargraph::Pin::InstanceVariable>]
88
+ def instance_variable_pins
89
+ @instance_variable_pins ||= []
90
+ end
91
+
92
+ # @return [Array<Solargraph::Pin::ClassVariable>]
93
+ def class_variable_pins
94
+ @class_variable_pins ||= []
95
+ end
96
+
97
+ # @return [Array<Solargraph::Pin::LocalVariable>]
98
+ def local_variable_pins
99
+ @local_variable_pins ||= []
100
+ end
101
+
102
+ # @return [Array<Solargraph::Pin::GlobalVariable>]
103
+ def global_variable_pins
104
+ @global_variable_pins ||= []
105
+ end
106
+
107
+ # @return [Array<Solargraph::Pin::Constant>]
108
+ def constant_pins
109
+ @constant_pins ||= []
110
+ end
111
+
112
+ # @return [Array<Solargraph::Pin::Symbol>]
113
+ def symbol_pins
114
+ @symbol_pins ||= []
115
+ end
116
+
117
+ # @return [Array<Solargraph::Pin::Namespace>]
118
+ def namespace_pins
119
+ @namespace_pins ||= []
120
+ end
121
+
122
+ # @return [Array<String>]
123
+ def required
124
+ @required ||= []
125
+ end
126
+
127
+ # @return [YARD::Docstring]
128
+ def docstring_for node
129
+ return @docstring_hash[node.loc] if node.respond_to?(:loc)
130
+ nil
131
+ end
132
+
133
+ # @return [String]
134
+ def code_for node
135
+ b = node.location.expression.begin.begin_pos
136
+ e = node.location.expression.end.end_pos
137
+ frag = code[b..e-1].to_s
138
+ frag.strip.gsub(/,$/, '')
139
+ end
140
+
141
+ def tree_for node
142
+ @node_tree[node] || []
143
+ end
144
+
145
+ # Determine if the specified index is inside a string.
146
+ #
147
+ # @return [Boolean]
148
+ def string_at?(index)
149
+ n = node_at(index)
150
+ n.kind_of?(AST::Node) and (n.type == :str or n.type == :dstr)
151
+ end
152
+
153
+ # Get the nearest node that contains the specified index.
154
+ #
155
+ # @param index [Integer]
156
+ # @return [AST::Node]
157
+ def node_at(index)
158
+ tree_at(index).first
159
+ end
160
+
161
+ # Get an array of nodes containing the specified index, starting with the
162
+ # nearest node and ending with the root.
163
+ #
164
+ # @param index [Integer]
165
+ # @return [Array<AST::Node>]
166
+ def tree_at(index)
167
+ arr = []
168
+ arr.push @node
169
+ inner_node_at(index, @node, arr)
170
+ arr
171
+ end
172
+
173
+ # Find the nearest parent node from the specified index. If one or more
174
+ # types are provided, find the nearest node whose type is in the list.
175
+ #
176
+ # @param index [Integer]
177
+ # @param types [Array<Symbol>]
178
+ # @return [AST::Node]
179
+ def parent_node_from(index, *types)
180
+ arr = tree_at(index)
181
+ arr.each { |a|
182
+ if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
183
+ return a
184
+ end
185
+ }
186
+ nil
187
+ end
188
+
189
+ # @return [String]
190
+ def namespace_for node
191
+ parts = []
192
+ ([node] + (@node_tree[node] || [])).each do |n|
193
+ next unless n.kind_of?(AST::Node)
194
+ if n.type == :class or n.type == :module
195
+ parts.unshift unpack_name(n.children[0])
196
+ end
197
+ end
198
+ parts.join('::')
199
+ end
200
+
201
+ def path_for node
202
+ path = namespace_for(node) || ''
203
+ mp = (method_pins + attribute_pins).select{|p| p.node == node}.first
204
+ unless mp.nil?
205
+ path += (mp.scope == :instance ? '#' : '.') + mp.name
206
+ end
207
+ path
208
+ end
209
+
210
+ def include? node
211
+ node_object_ids.include? node.object_id
212
+ end
213
+
214
+ def synchronize changes, version
215
+ changes.each do |change|
216
+ reparse change
217
+ end
218
+ @version = version
219
+ self
220
+ end
221
+
222
+ def get_offset line, col
223
+ Source.get_offset(code, line, col)
224
+ end
225
+
226
+ def self.get_offset text, line, col
227
+ offset = 0
228
+ if line > 0
229
+ text.lines[0..line - 1].each { |l|
230
+ offset += l.length
231
+ }
232
+ end
233
+ offset + col
234
+ end
235
+
236
+ def overwrite text
237
+ reparse({'text' => text})
238
+ end
239
+
240
+ def query_symbols query
241
+ return [] if query.empty?
242
+ down = query.downcase
243
+ all_symbols.select{|p| p.path.downcase.include?(down)}
244
+ end
245
+
246
+ def all_symbols
247
+ result = []
248
+ result.concat namespace_pins
249
+ result.concat method_pins
250
+ result.concat constant_pins
251
+ result
252
+ end
253
+
254
+ def locate_pin location
255
+ return nil unless location.start_with?("#{filename}:")
256
+ @all_pins.select{|pin| pin.location == location}.first
257
+ end
258
+
259
+ # @return [Solargraph::Source::Fragment]
260
+ def fragment_at line, column
261
+ Fragment.new(self, get_offset(line, column))
262
+ end
263
+
264
+ private
265
+
266
+ def inner_node_at(index, node, arr)
267
+ node.children.each do |c|
268
+ if c.kind_of?(AST::Node) and c.respond_to?(:loc)
269
+ unless c.loc.expression.nil?
270
+ if index >= c.loc.expression.begin_pos
271
+ if c.respond_to?(:end)
272
+ if index < c.end.end_pos
273
+ arr.unshift c
274
+ end
275
+ elsif index < c.loc.expression.end_pos
276
+ arr.unshift c
277
+ end
278
+ end
279
+ end
280
+ inner_node_at(index, c, arr)
281
+ end
282
+ end
283
+ end
284
+
285
+ def reparse change
286
+ if change['range']
287
+ start_offset = Source.get_offset(@code, change['range']['start']['line'], change['range']['start']['character'])
288
+ end_offset = Source.get_offset(@code, change['range']['end']['line'], change['range']['end']['character'])
289
+ rewrite = (start_offset == 0 ? '' : @code[0..start_offset-1].to_s) + change['text'].force_encoding('utf-8') + @code[end_offset..-1].to_s
290
+ # return if @code == rewrite
291
+ again = true
292
+ if change['text'].match(/^[^a-z0-9\s]+?$/i)
293
+ tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
294
+ again = false
295
+ else
296
+ tmp = rewrite
297
+ end
298
+ @code = rewrite
299
+ begin
300
+ node, comments = Source.parse(tmp, filename)
301
+ process_parsed node, comments
302
+ @fixed = tmp
303
+ rescue Parser::SyntaxError => e
304
+ if again
305
+ again = false
306
+ tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
307
+ retry
308
+ else
309
+ @code = rewrite
310
+ hard_fix_node
311
+ end
312
+ end
313
+ else
314
+ tmp = change['text']
315
+ return if @code == tmp
316
+ @code = tmp
317
+ begin
318
+ node, comments = Source.parse(@code, filename)
319
+ process_parsed node, comments
320
+ @fixed = @code
321
+ rescue Parser::SyntaxError => e
322
+ hard_fix_node
323
+ end
324
+ end
325
+ end
326
+
327
+ def hard_fix_node
328
+ tmp = @code.gsub(/[^\s]/, ' ')
329
+ @fixed = tmp
330
+ node, comments = Source.parse(tmp, filename)
331
+ process_parsed node, comments
332
+ end
333
+
334
+ def process_parsed node, comments
335
+ root = AST::Node.new(:source, [filename])
336
+ root = root.append node
337
+ @node = root
338
+ @comments = comments
339
+ @directives = {}
340
+ @path_macros = {}
341
+ @docstring_hash = associate_comments(node, comments)
342
+ @mtime = (!filename.nil? and File.exist?(filename) ? File.mtime(filename) : nil)
343
+ @namespace_nodes = {}
344
+ @all_nodes = []
345
+ @node_stack = []
346
+ @node_tree = {}
347
+ namespace_pins.clear
348
+ instance_variable_pins.clear
349
+ class_variable_pins.clear
350
+ local_variable_pins.clear
351
+ symbol_pins.clear
352
+ constant_pins.clear
353
+ method_pins.clear
354
+ namespace_includes.clear
355
+ namespace_extends.clear
356
+ superclasses.clear
357
+ attribute_pins.clear
358
+ @node_object_ids = nil
359
+ inner_map_node @node
360
+ @directives.each_pair do |k, v|
361
+ v.each do |d|
362
+ ns = namespace_for(k.node)
363
+ docstring = YARD::Docstring.parser.parse(d.tag.text).to_docstring
364
+ if d.tag.tag_name == 'attribute'
365
+ t = (d.tag.types.nil? || d.tag.types.empty?) ? nil : d.tag.types.flatten.join('')
366
+ if t.nil? or t.include?('r')
367
+ attribute_pins.push Solargraph::Pin::Directed::Attribute.new(self, k.node, ns, :reader, docstring, d.tag.name)
368
+ end
369
+ if t.nil? or t.include?('w')
370
+ attribute_pins.push Solargraph::Pin::Directed::Attribute.new(self, k.node, ns, :writer, docstring, "#{d.tag.name}=")
371
+ end
372
+ elsif d.tag.tag_name == 'method'
373
+ gen_src = Source.virtual("def #{d.tag.name};end", filename)
374
+ gen_pin = gen_src.method_pins.first
375
+ method_pins.push Solargraph::Pin::Directed::Method.new(gen_src, gen_pin.node, ns, :instance, :public, docstring, gen_pin.name)
376
+ elsif d.tag.tag_name == 'macro'
377
+ # @todo Handle various types of macros (attach, new, whatever)
378
+ path = path_for(k.node)
379
+ @path_macros[path] = v
380
+ else
381
+ STDERR.puts "Nothing to do for directive: #{d}"
382
+ end
383
+ end
384
+ end
385
+ @all_pins = namespace_pins + instance_variable_pins + class_variable_pins + local_variable_pins + symbol_pins + constant_pins + method_pins + attribute_pins
386
+ @stime = Time.now
387
+ end
388
+
389
+ def associate_comments node, comments
390
+ return nil if comments.nil?
391
+ comment_hash = Parser::Source::Comment.associate_locations(node, comments)
392
+ yard_hash = {}
393
+ comment_hash.each_pair { |k, v|
394
+ ctxt = ''
395
+ num = nil
396
+ started = false
397
+ v.each { |l|
398
+ # Trim the comment and minimum leading whitespace
399
+ p = l.text.gsub(/^#/, '')
400
+ if num.nil? and !p.strip.empty?
401
+ num = p.index(/[^ ]/)
402
+ started = true
403
+ elsif started and !p.strip.empty?
404
+ cur = p.index(/[^ ]/)
405
+ num = cur if cur < num
406
+ end
407
+ if started
408
+ ctxt += "#{p[num..-1]}\n"
409
+ end
410
+ }
411
+ parse = YARD::Docstring.parser.parse(ctxt)
412
+ unless parse.directives.empty?
413
+ @directives[k] ||= []
414
+ @directives[k].concat parse.directives
415
+ end
416
+ yard_hash[k] = parse.to_docstring
417
+ }
418
+ yard_hash
419
+ end
420
+
421
+ def inner_map_node node, tree = [], visibility = :public, scope = :instance, fqn = nil, stack = []
422
+ stack.push node
423
+ source = self
424
+ if node.kind_of?(AST::Node)
425
+ # @node_tree[node] = @node_stack.clone
426
+ @all_nodes.push node
427
+ # if node.type == :str or node.type == :dstr
428
+ # stack.pop
429
+ # return
430
+ # end
431
+ @node_stack.unshift node
432
+ if node.type == :class or node.type == :module
433
+ visibility = :public
434
+ if node.children[0].kind_of?(AST::Node) and node.children[0].children[0].kind_of?(AST::Node) and node.children[0].children[0].type == :cbase
435
+ tree = pack_name(node.children[0])
436
+ else
437
+ tree = tree + pack_name(node.children[0])
438
+ end
439
+ fqn = tree.join('::')
440
+ @namespace_nodes[fqn] ||= []
441
+ @namespace_nodes[fqn].push node
442
+ namespace_pins.push Solargraph::Pin::Namespace.new(self, node, tree[0..-2].join('::') || '', :public)
443
+ if node.type == :class and !node.children[1].nil?
444
+ sc = unpack_name(node.children[1])
445
+ superclasses[fqn] = sc
446
+ end
447
+ end
448
+ file = source.filename
449
+ node.children.each do |c|
450
+ if c.kind_of?(AST::Node)
451
+ @node_tree[c] = @node_stack.clone
452
+ if c.type == :ivasgn
453
+ par = find_parent(stack, :class, :module, :def, :defs)
454
+ local_scope = ( (par.kind_of?(AST::Node) and par.type == :def) ? :instance : :class )
455
+ if c.children[1].nil?
456
+ ora = find_parent(stack, :or_asgn)
457
+ unless ora.nil?
458
+ u = c.updated(:ivasgn, c.children + ora.children[1..-1], nil)
459
+ @all_nodes.push u
460
+ @node_tree[u] = @node_stack.clone
461
+ @docstring_hash[u.loc] = docstring_for(ora)
462
+ instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, u, fqn || '', local_scope)
463
+ end
464
+ else
465
+ instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, c, fqn || '', local_scope)
466
+ end
467
+ elsif c.type == :cvasgn
468
+ if c.children[1].nil?
469
+ ora = find_parent(stack, :or_asgn)
470
+ unless ora.nil?
471
+ u = c.updated(:cvasgn, c.children + ora.children[1..-1], nil)
472
+ @node_tree[u] = @node_stack.clone
473
+ @all_nodes.push u
474
+ @docstring_hash[u.loc] = docstring_for(ora)
475
+ class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, u, fqn || '')
476
+ end
477
+ else
478
+ class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, c, fqn || '')
479
+ end
480
+ elsif c.type == :lvasgn
481
+ if c.children[1].nil?
482
+ ora = find_parent(stack, :or_asgn)
483
+ unless ora.nil?
484
+ u = c.updated(:lvasgn, c.children + ora.children[1..-1], nil)
485
+ @all_nodes.push u
486
+ @node_tree[u] = @node_stack.clone
487
+ @docstring_hash[u.loc] = docstring_for(ora)
488
+ local_variable_pins.push Solargraph::Pin::LocalVariable.new(self, u, fqn || '', @node_stack.clone)
489
+ end
490
+ else
491
+ @node_tree[c] = @node_stack.clone
492
+ local_variable_pins.push Solargraph::Pin::LocalVariable.new(self, c, fqn || '', @node_stack.clone)
493
+ end
494
+ elsif c.type == :gvasgn
495
+ global_variable_pins.push Solargraph::Pin::GlobalVariable.new(self, c, fqn || '')
496
+ elsif c.type == :sym
497
+ symbol_pins.push Solargraph::Pin::Symbol.new(self, c, fqn)
498
+ elsif c.type == :casgn
499
+ constant_pins.push Solargraph::Pin::Constant.new(self, c, fqn, :public)
500
+ elsif c.type == :def and c.children[0].to_s[0].match(/[a-z]/i)
501
+ method_pins.push Solargraph::Pin::Method.new(source, c, fqn || '', scope, visibility)
502
+ elsif c.type == :defs
503
+ s_visi = visibility
504
+ s_visi = :public if scope != :class
505
+ if c.children[0].is_a?(AST::Node) and c.children[0].type == :self
506
+ dfqn = fqn || ''
507
+ else
508
+ dfqn = unpack_name(c.children[0])
509
+ end
510
+ unless dfqn.nil?
511
+ method_pins.push Solargraph::Pin::Method.new(source, c, dfqn, :class, s_visi)
512
+ inner_map_node c, tree, scope, :class, dfqn, stack
513
+ end
514
+ next
515
+ elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
516
+ visibility = c.children[1]
517
+ elsif c.type == :send and [:private_class_method].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
518
+ if c.children[2].type == :sym or c.children[2].type == :str
519
+ ref = method_pins.select{|p| p.name == c.children[2].children[0].to_s}.first
520
+ unless ref.nil?
521
+ source.method_pins.delete ref
522
+ source.method_pins.push Solargraph::Pin::Method.new(ref.source, ref.node, ref.namespace, ref.scope, :private)
523
+ end
524
+ else
525
+ inner_map_node c, tree, :private, :class, fqn, stack
526
+ next
527
+ end
528
+ elsif c.type == :send and [:private_constant].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
529
+ if c.children[2].type == :sym or c.children[2].type == :str
530
+ cn = c.children[2].children[0].to_s
531
+ ref = constant_pins.select{|p| p.name == cn}.first
532
+ if ref.nil?
533
+ ref = namespace_pins.select{|p| p.name == cn}.first
534
+ unless ref.nil?
535
+ source.namespace_pins.delete ref
536
+ source.namespace_pins.push Solargraph::Pin::Namespace.new(ref.source, ref.node, ref.namespace, :private)
537
+ end
538
+ else
539
+ source.constant_pins.delete ref
540
+ source.constant_pins.push Solargraph::Pin::Constant.new(ref.source, ref.node, ref.namespace, :private)
541
+ end
542
+ end
543
+ next
544
+ elsif c.type == :send and c.children[1] == :include
545
+ namespace_includes[fqn] ||= []
546
+ c.children[2..-1].each do |i|
547
+ namespace_includes[fqn].push unpack_name(i)
548
+ end
549
+ elsif c.type == :send and c.children[1] == :extend
550
+ namespace_extends[fqn] ||= []
551
+ c.children[2..-1].each do |i|
552
+ namespace_extends[fqn].push unpack_name(i)
553
+ end
554
+ elsif c.type == :send and [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
555
+ c.children[2..-1].each do |a|
556
+ if c.children[1] == :attr_reader or c.children[1] == :attr_accessor
557
+ attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn || '', :reader, docstring_for(c)) #AttrPin.new(c)
558
+ end
559
+ if c.children[1] == :attr_writer or c.children[1] == :attr_accessor
560
+ attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn || '', :writer, docstring_for(c)) #AttrPin.new(c)
561
+ end
562
+ end
563
+ elsif c.type == :sclass and c.children[0].type == :self
564
+ inner_map_node c, tree, :public, :class, fqn || '', stack
565
+ next
566
+ elsif c.type == :send and c.children[1] == :require
567
+ if c.children[2].kind_of?(AST::Node) and c.children[2].type == :str
568
+ required.push c.children[2].children[0].to_s
569
+ end
570
+ elsif c.type == :args
571
+ if @node_stack.first.type == :block
572
+ pi = 0
573
+ c.children.each do |u|
574
+ local_variable_pins.push Solargraph::Pin::BlockParameter.new(self, u, fqn || '', @node_stack.clone, pi)
575
+ pi += 1
576
+ end
577
+ else
578
+ c.children.each do |u|
579
+ local_variable_pins.push Solargraph::Pin::MethodParameter.new(self, u, fqn || '', @node_stack.clone)
580
+ end
581
+ end
582
+ end
583
+ inner_map_node c, tree, visibility, scope, fqn, stack
584
+ end
585
+ end
586
+ @node_stack.shift
587
+ end
588
+ stack.pop
589
+ end
590
+
591
+ def find_parent(stack, *types)
592
+ stack.reverse.each { |p|
593
+ return p if types.include?(p.type)
594
+ }
595
+ nil
596
+ end
597
+
598
+ def node_object_ids
599
+ @node_object_ids ||= @all_nodes.map(&:object_id)
600
+ end
601
+
602
+ class << self
603
+ # @return [Solargraph::Source]
604
+ def load filename
605
+ code = File.read(filename)
606
+ Source.load_string(code, filename)
607
+ end
608
+
609
+ # @deprecated Use load_string instead
610
+ def virtual code, filename = nil
611
+ load_string code, filename
612
+ end
613
+
614
+ # @return [Solargraph::Source]
615
+ def load_string code, filename = nil
616
+ begin
617
+ node, comments = parse(code, filename)
618
+ Source.new(code, node, comments, filename)
619
+ rescue Parser::SyntaxError => e
620
+ tmp = code.gsub(/[^ \t\r\n]/, ' ')
621
+ node, comments = parse(tmp, filename)
622
+ Source.new(code, node, comments, filename)
623
+ end
624
+ end
625
+
626
+ def get_position_at(code, offset)
627
+ cursor = 0
628
+ line = 0
629
+ col = nil
630
+ code.each_line do |l|
631
+ if cursor + l.length > offset
632
+ col = offset - cursor
633
+ break
634
+ end
635
+ if cursor + l.length == offset
636
+ if l.end_with?("\n")
637
+ col = 0
638
+ line += 1
639
+ break
640
+ else
641
+ col = l.length
642
+ break
643
+ end
644
+ end
645
+ # if cursor + l.length - 1 == offset and !l.end_with?("\n")
646
+ # col = l.length - 1
647
+ # break
648
+ # end
649
+ cursor += l.length
650
+ line += 1
651
+ end
652
+ raise "Invalid offset" if col.nil?
653
+ [line, col]
654
+ end
655
+
656
+ def parse code, filename = nil
657
+ parser = Parser::CurrentRuby.new(FlawedBuilder.new)
658
+ parser.diagnostics.all_errors_are_fatal = true
659
+ parser.diagnostics.ignore_warnings = true
660
+ buffer = Parser::Source::Buffer.new(filename, 1)
661
+ buffer.source = code
662
+ parser.parse_with_comments(buffer)
663
+ end
664
+
665
+ def fix code, filename = nil, offset = nil
666
+ tries = 0
667
+ # code.gsub!(/\r/, '')
668
+ offset = Source.get_offset(code, offset[0], offset[1]) if offset.kind_of?(Array)
669
+ pos = nil
670
+ pos = get_position_at(code, offset) unless offset.nil?
671
+ stubs = []
672
+ fixed_position = false
673
+ tmp = code.sub(/\.(\s*\z)$/, ' \1')
674
+ begin
675
+ node, comments = Source.parse(tmp, filename)
676
+ Source.new(code, node, comments, filename, stubs)
677
+ rescue Parser::SyntaxError => e
678
+ if tries < 10
679
+ tries += 1
680
+ # Stub periods before the offset to retain the expected node tree
681
+ if !offset.nil? and ['.', '{', '('].include?(tmp[offset-1])
682
+ tmp = tmp[0, offset-1] + ';' + tmp[offset..-1]
683
+ elsif !fixed_position and !offset.nil?
684
+ fixed_position = true
685
+ beg = beginning_of_line_from(tmp, offset)
686
+ tmp = "#{tmp[0, beg]}##{tmp[beg+1..-1]}"
687
+ stubs.push(pos[0])
688
+ elsif e.message.include?('token $end')
689
+ tmp += "\nend"
690
+ elsif e.message.include?("unexpected `@'")
691
+ tmp = tmp[0, e.diagnostic.location.begin_pos] + '_' + tmp[e.diagnostic.location.begin_pos+1..-1]
692
+ end
693
+ retry
694
+ end
695
+ STDERR.puts "Unable to parse file #{filename.nil? ? 'undefined' : filename}: #{e.message}"
696
+ node, comments = parse(code.gsub(/[^\s]/, ' '), filename)
697
+ Source.new(code, node, comments, filename)
698
+ end
699
+ end
700
+
701
+ def beginning_of_line_from str, i
702
+ while i > 0 and str[i-1] != "\n"
703
+ i -= 1
704
+ end
705
+ i
706
+ end
707
+ end
708
+ end
709
+ end