solargraph 0.17.4 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
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