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
@@ -1,868 +0,0 @@
1
- require 'parser/current'
2
-
3
- module Solargraph
4
- class CodeMap
5
- # The root node of the parsed code.
6
- #
7
- # @return [Parser::AST::Node]
8
- attr_reader :node
9
-
10
- # The source code being analyzed.
11
- #
12
- # @return [String]
13
- attr_reader :code
14
-
15
- # The source object generated from the code.
16
- #
17
- # @return [Solargraph::ApiMap::Source]
18
- attr_reader :source
19
-
20
- # The filename for the source code.
21
- #
22
- # @return [String]
23
- attr_reader :filename
24
-
25
- include NodeMethods
26
- include CoreFills
27
-
28
- def initialize code: '', filename: nil, api_map: nil, cursor: nil
29
- # HACK: Adjust incoming filename's path separator for yardoc file comparisons
30
- filename = filename.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless filename.nil? or File::ALT_SEPARATOR.nil?
31
- @filename = filename
32
- @api_map = api_map
33
- if !filename.nil? and filename.end_with?('.erb')
34
- @source = self.api_map.virtualize(convert_erb(code), filename, cursor)
35
- else
36
- @source = self.api_map.virtualize(code, filename, cursor)
37
- end
38
- @node = @source.node
39
- @code = @source.code
40
- @comments = @source.comments
41
- self.api_map.refresh
42
- end
43
-
44
- # Get the associated ApiMap.
45
- #
46
- # @return [Solargraph::ApiMap]
47
- def api_map
48
- @api_map ||= ApiMap.new(nil)
49
- end
50
-
51
- # Get the offset of the specified line and column.
52
- # The offset (also called the "index") is typically used to identify the
53
- # cursor's location in the code when generating suggestions.
54
- # The line and column numbers should start at zero.
55
- #
56
- # @param line [Integer]
57
- # @param col [Integer]
58
- # @return [Integer]
59
- def get_offset line, col
60
- CodeMap.get_offset @code, line, col
61
- end
62
-
63
- def self.get_offset text, line, col
64
- offset = 0
65
- if line > 0
66
- text.lines[0..line - 1].each { |l|
67
- offset += l.length
68
- }
69
- end
70
- offset + col
71
- end
72
-
73
- # Get an array of nodes containing the specified index, starting with the
74
- # topmost node and ending with the nearest.
75
- #
76
- # @param index [Integer]
77
- # @return [Array<AST::Node>]
78
- def tree_at(index)
79
- arr = []
80
- arr.push @node
81
- inner_node_at(index, @node, arr)
82
- arr
83
- end
84
-
85
- # Get the nearest node that contains the specified index.
86
- #
87
- # @param index [Integer]
88
- # @return [AST::Node]
89
- def node_at(index)
90
- tree_at(index).first
91
- end
92
-
93
- # Determine if the specified index is inside a string.
94
- #
95
- # @return [Boolean]
96
- def string_at?(index)
97
- n = node_at(index)
98
- n.kind_of?(AST::Node) and n.type == :str
99
- end
100
-
101
- # Determine if the specified index is inside a comment.
102
- #
103
- # @return [Boolean]
104
- def comment_at?(index)
105
- return false if string_at?(index)
106
- line, col = Solargraph::ApiMap::Source.get_position_at(source.code, index)
107
- return false if source.stubbed_lines.include?(line)
108
- @comments.each do |c|
109
- return true if index > c.location.expression.begin_pos and index <= c.location.expression.end_pos
110
- end
111
- # Extra test due to some comments not getting tracked
112
- while (index > 0 and @code[index] != "\n")
113
- return true if @code[index] == '#'
114
- index -= 1
115
- end
116
- false
117
- end
118
-
119
- # Find the nearest parent node from the specified index. If one or more
120
- # types are provided, find the nearest node whose type is in the list.
121
- #
122
- # @param index [Integer]
123
- # @param types [Array<Symbol>]
124
- # @return [AST::Node]
125
- def parent_node_from(index, *types)
126
- arr = tree_at(index)
127
- arr.each { |a|
128
- if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
129
- return a
130
- end
131
- }
132
- @node
133
- end
134
-
135
- # Get the namespace at the specified location. For example, given the code
136
- # `class Foo; def bar; end; end`, index 14 (the center) is in the
137
- # "Foo" namespace.
138
- #
139
- # @return [String]
140
- def namespace_at(index)
141
- tree = tree_at(index)
142
- return nil if tree.length == 0
143
- slice = tree
144
- parts = []
145
- slice.reverse.each { |n|
146
- if n.type == :class or n.type == :module
147
- c = const_from(n.children[0])
148
- parts.push c
149
- end
150
- }
151
- parts.join("::")
152
- end
153
-
154
- # Get the namespace for the specified node. For example, given the code
155
- # `class Foo; def bar; end; end`, the node for `def bar` is in the "Foo"
156
- # namespace.
157
- #
158
- # @return [String]
159
- def namespace_from(node)
160
- if node.respond_to?(:loc)
161
- namespace_at(node.loc.expression.begin_pos)
162
- else
163
- ''
164
- end
165
- end
166
-
167
- # Select the word that directly precedes the specified index.
168
- # A word can only consist of letters, numbers, and underscores.
169
- #
170
- # @param index [Integer]
171
- # @return [String]
172
- def word_at index
173
- word = ''
174
- cursor = index - 1
175
- while cursor > -1
176
- char = @code[cursor, 1]
177
- break if char.nil? or char == ''
178
- word = char + word if char == '$'
179
- break unless char.match(/[a-z0-9_]/i)
180
- word = char + word
181
- cursor -= 1
182
- end
183
- word
184
- end
185
-
186
- # @return [Array<Solargraph::Suggestion>]
187
- def get_class_variables_at(index)
188
- ns = namespace_at(index) || ''
189
- api_map.get_class_variables(ns)
190
- end
191
-
192
- def get_instance_variables_at(index)
193
- # @todo There are a lot of other cases that need to be handled here
194
- node = parent_node_from(index, :def, :defs, :class, :module, :sclass)
195
- ns = namespace_at(index) || ''
196
- scope = (node.type == :def ? :instance : :class)
197
- api_map.get_instance_variables(ns, scope)
198
- end
199
-
200
- # Get suggestions for code completion at the specified location in the
201
- # source.
202
- #
203
- # @return [Array<Solargraph::Suggestion>] The completion suggestions
204
- def suggest_at index, filtered: true
205
- return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
206
- signature = get_signature_at(index)
207
- unless signature.include?('.')
208
- if signature.start_with?(':')
209
- return api_map.get_symbols
210
- elsif signature.start_with?('@@')
211
- return get_class_variables_at(index)
212
- elsif signature.start_with?('@')
213
- return get_instance_variables_at(index)
214
- elsif signature.start_with?('$')
215
- return api_map.get_global_variables
216
- end
217
- end
218
- result = []
219
- type = nil
220
- if signature.include?('.')
221
- type = infer_signature_at(index)
222
- if type.nil? and signature.include?('.')
223
- last_period = @code[0..index].rindex('.')
224
- type = infer_signature_at(last_period)
225
- end
226
- end
227
- if type.nil?
228
- unless signature.include?('.')
229
- namespace = namespace_at(index)
230
- if signature.include?('::')
231
- parts = signature.split('::', -1)
232
- ns = parts[0..-2].join('::')
233
- result = api_map.get_constants(ns, namespace)
234
- else
235
- type = infer_literal_node_type(node_at(index - 2))
236
- return [] if type.nil? and signature.empty? and !@code[0..index].rindex('.').nil? and @code[@code[0..index].rindex('.')..-1].strip == '.'
237
- if type.nil?
238
- result.concat get_local_variables_and_methods_at(index)
239
- result.concat ApiMap.keywords
240
- result.concat api_map.get_constants('', namespace)
241
- result.concat api_map.get_constants('')
242
- result.concat api_map.get_instance_methods('Kernel', namespace)
243
- result.concat api_map.get_methods('', namespace)
244
- result.concat api_map.get_instance_methods('', namespace)
245
- else
246
- result.concat api_map.get_instance_methods(type) unless @code[index - 1] != '.'
247
- end
248
- end
249
- end
250
- else
251
- result.concat api_map.get_instance_methods(type) unless (type == '' and signature.include?('.'))
252
- end
253
- result.keep_if{|s| s.kind != Solargraph::Suggestion::METHOD or s.label.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}
254
- result = reduce_starting_with(result, word_at(index)) if filtered
255
- # Use a stable sort to keep the class order (e.g., local methods before superclass methods)
256
- result.uniq(&:path).sort_by.with_index{ |x, idx| [x.label, idx] }
257
- end
258
-
259
- def signatures_at index
260
- sig = signature_index_before(index)
261
- return [] if sig.nil?
262
- word = word_at(sig)
263
- sugg = suggest_at(sig - word.length)
264
- sugg.select{|s| s.label == word}
265
- end
266
-
267
- # @return [Array<Solargraph::Suggestion>]
268
- def define_symbol_at index
269
- return [] if string_at?(index)
270
- signature = get_signature_at(index, final: true)
271
- return [] if signature.to_s.empty?
272
- node = parent_node_from(index, :class, :module, :def, :defs) || @node
273
- ns_here = namespace_from(node)
274
- unless signature.include?('.')
275
- if local_variable_in_node?(signature, node)
276
- return get_local_variables_from(node).select{|s| s.label == signature}
277
- elsif signature.start_with?('@@')
278
- return api_map.get_class_variables(ns_here).select{|s| s.label == signature}
279
- elsif signature.start_with?('@')
280
- return api_map.get_instance_variables(ns_here, (node.type == :def ? :instance : :class)).select{|s| s.label == signature}
281
- end
282
- end
283
- path = infer_path_from_signature_and_node(signature, node)
284
- ps = []
285
- ps = api_map.get_path_suggestions(path) unless path.nil?
286
- return ps unless ps.empty?
287
- ps = api_map.get_path_suggestions(signature)
288
- return ps unless ps.empty?
289
- scope = (node.type == :def ? :instance : :class)
290
- final = []
291
- if scope == :instance
292
- final.concat api_map.get_instance_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
293
- else
294
- final.concat api_map.get_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
295
- end
296
- if final.empty? and !signature.include?('.')
297
- fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
298
- final.concat api_map.get_path_suggestions(fqns) unless fqns.nil? or fqns.empty?
299
- end
300
- final
301
- end
302
-
303
- def resolve_object_at index
304
- define_symbol_at index
305
- end
306
-
307
- # Infer the type of the signature located at the specified index.
308
- #
309
- # @example
310
- # # Given the following code:
311
- # nums = [1, 2, 3]
312
- # nums.join
313
- # # ...and given an index that points at the end of "nums.join",
314
- # # infer_signature_at will identify nums as an Array and the return
315
- # # type of Array#join as a String, so the signature's type will be
316
- # # String.
317
- #
318
- # @return [String]
319
- def infer_signature_at index
320
- beg_sig, signature = get_signature_data_at(index)
321
- # Check for literals first
322
- return 'Integer' if signature.match(/^[0-9]+?\.?$/)
323
- literal = nil
324
- if (signature.empty? and @code[index - 1] == '.') or signature == '[].'
325
- literal = node_at(index - 2)
326
- else
327
- literal = node_at(1 + beg_sig)
328
- end
329
- type = infer_literal_node_type(literal)
330
- if type.nil?
331
- node = parent_node_from(index, :class, :module, :def, :defs, :block) || @node
332
- result = infer_signature_from_node signature, node
333
- if result.nil? or result.empty?
334
- # The rest of this routine is dedicated to method and block parameters
335
- arg = nil
336
- if node.type == :def or node.type == :defs or node.type == :block
337
- # Check for method arguments
338
- parts = signature.split('.', 2)
339
- # @type [Solargraph::Suggestion]
340
- arg = get_method_arguments_from(node).keep_if{|s| s.to_s == parts[0] }.first
341
- unless arg.nil?
342
- if parts[1].nil?
343
- result = arg.return_type
344
- else
345
- result = api_map.infer_signature_type(parts[1], arg.return_type, scope: :instance)
346
- end
347
- end
348
- end
349
- if arg.nil?
350
- # Check for yieldparams
351
- parts = signature.split('.', 2)
352
- yp = get_yieldparams_at(index).keep_if{|s| s.to_s == parts[0]}.first
353
- unless yp.nil?
354
- if parts[1].nil? or parts[1].empty?
355
- result = yp.return_type
356
- else
357
- newsig = parts[1..-1].join('.')
358
- result = api_map.infer_signature_type(newsig, yp.return_type, scope: :instance)
359
- end
360
- end
361
- end
362
- #elsif match = result.match(/^\$(\-?[0-9]*)$/)
363
- # STDERR.puts "TODO: handle expression variable #{match[1]}"
364
- end
365
- else
366
- if signature.empty? or signature == '[].'
367
- result = type
368
- else
369
- cursed = get_signature_index_at(index)
370
- if signature.start_with?('[].')
371
- rest = signature[3..-1]
372
- else
373
- if signature.start_with?('.')
374
- rest = signature[literal.loc.expression.end_pos+(cursed-literal.loc.expression.end_pos)..-1]
375
- else
376
- rest = signature
377
- end
378
- end
379
- return type if rest.nil?
380
- lit_code = @code[literal.loc.expression.begin_pos..literal.loc.expression.end_pos]
381
- rest = rest[lit_code.length..-1] if rest.start_with?(lit_code)
382
- rest = rest[1..-1] if rest.start_with?('.')
383
- rest = rest[0..-2] if rest.end_with?('.')
384
- if rest.empty?
385
- result = type
386
- else
387
- result = api_map.infer_signature_type(rest, type, scope: :instance)
388
- end
389
- end
390
- end
391
- result
392
- end
393
-
394
- def local_variable_in_node?(name, node)
395
- return true unless find_local_variable_node(name, node).nil?
396
- if node.type == :def or node.type == :defs
397
- args = get_method_arguments_from(node).keep_if{|a| a.label == name}
398
- return true unless args.empty?
399
- end
400
- false
401
- end
402
-
403
- def infer_signature_from_node signature, node, call_node: nil
404
- inferred = nil
405
- parts = signature.split('.')
406
- ns_here = namespace_from(node)
407
- if parts[0] and parts[0].include?('::')
408
- sub = get_namespace_or_constant(parts[0], ns_here)
409
- unless sub.nil?
410
- return sub if signature.match(/^#{parts[0]}\.$/)
411
- parts[0] = sub
412
- end
413
- end
414
- unless signature.include?('.')
415
- fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
416
- return "Class<#{fqns}>" unless fqns.nil? or fqns.empty?
417
- end
418
- start = parts[0]
419
- return nil if start.nil?
420
- remainder = parts[1..-1]
421
- if start.start_with?('@@')
422
- cv = api_map.get_class_variable_pins(ns_here).select{|s| s.name == start}.first
423
- unless cv.nil?
424
- vartype = (cv.return_type || api_map.infer_assignment_node_type(cv.node, cv.namespace))
425
- return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
426
- end
427
- elsif start.start_with?('@')
428
- scope = (node.type == :def ? :instance : :class)
429
- iv = api_map.get_instance_variable_pins(ns_here, scope).select{|s| s.name == start}.first
430
- unless iv.nil?
431
- vartype = (iv.return_type || api_map.infer_assignment_node_type(iv.node, iv.namespace))
432
- return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
433
- end
434
- elsif start.start_with?('$')
435
- gv = api_map.get_global_variable_pins.select{|s| s.name == start}.first
436
- unless gv.nil?
437
- vartype = (gv.return_type || api_map.infer_assignment_node_type(gv.node, gv.namespace))
438
- return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
439
- end
440
- end
441
- # @todo There might be some redundancy between find_local_variable_node and call_node
442
- var = find_local_variable_node(start, node)
443
- if var.nil?
444
- arg = get_method_arguments_from(node).select{|s| s.label == start}.first
445
- if arg.nil?
446
- scope = (node.type == :def ? :instance : :class)
447
- type = api_map.infer_signature_type(signature, ns_here, scope: scope, call_node: call_node)
448
- return type unless type.nil?
449
- else
450
- type = arg.return_type
451
- end
452
- else
453
- # Signature starts with a local variable
454
- type = nil
455
- lvp = source.local_variable_pins.select{|p| p.name == var.children[0].to_s and p.visible_from?(node) and (!p.nil_assignment? or p.return_type)}.first
456
- unless lvp.nil?
457
- type = lvp.return_type
458
- if type.nil?
459
- vsig = resolve_node_signature(var.children[1])
460
- type = infer_signature_from_node vsig, node, call_node: lvp.assignment_node
461
- end
462
- end
463
- end
464
- unless type.nil?
465
- if remainder.empty?
466
- inferred = type
467
- else
468
- inferred = api_map.infer_signature_type(remainder.join('.'), type, scope: :instance, call_node: call_node)
469
- end
470
- end
471
- if inferred.nil? and node.respond_to?(:loc)
472
- index = node.loc.expression.begin_pos
473
- block_node = parent_node_from(index, :block, :class, :module, :sclass, :def, :defs)
474
- unless block_node.nil? or block_node.type != :block or block_node.children[0].nil?
475
- scope_node = parent_node_from(index, :class, :module, :def, :defs) || @node
476
- meth = get_yielding_method_with_yieldself(block_node, scope_node)
477
- unless meth.nil?
478
- match = meth.docstring.all.match(/@yieldself \[([a-z0-9:_]*)/i)
479
- self_yield = match[1]
480
- inferred = api_map.infer_signature_type(signature, self_yield, scope: :instance)
481
- end
482
- end
483
- end
484
- inferred
485
- end
486
-
487
- # Get the signature at the specified index.
488
- # A signature is a method call that can start with a constant, method, or
489
- # variable and does not include any method arguments. Examples:
490
- #
491
- # * String.new -> String.new
492
- # * @x.bar -> @x.bar
493
- # * y.split(', ').length -> y.split.length
494
- #
495
- # @param index [Integer]
496
- # @return [String]
497
- def get_signature_at index, final: false
498
- sig = get_signature_data_at(index)[1]
499
- if final
500
- cursor = index
501
- while @code[cursor] =~ /[a-z0-9_\?]/i
502
- sig += @code[cursor]
503
- cursor += 1
504
- break if cursor >= @code.length
505
- end
506
- end
507
- sig
508
- end
509
-
510
- def get_signature_index_at index
511
- get_signature_data_at(index)[0]
512
- end
513
-
514
- # Get an array of local variables and methods that can be accessed from
515
- # the specified location in the code.
516
- #
517
- # @param index [Integer]
518
- # @return [Array<Solargraph::Suggestion>]
519
- def get_local_variables_and_methods_at(index)
520
- result = []
521
- local = parent_node_from(index, :class, :module, :def, :defs) || @node
522
- result += get_local_variables_from(node_at(index))
523
- scope = namespace_at(index) || @node
524
- if local.type == :def
525
- result += api_map.get_instance_methods(scope, visibility: [:public, :private, :protected])
526
- else
527
- result += api_map.get_methods(scope, scope, visibility: [:public, :private, :protected])
528
- end
529
- if local.type == :def or local.type == :defs
530
- result += get_method_arguments_from local
531
- end
532
- result.concat get_yieldparams_at(index)
533
- result
534
- end
535
-
536
- #def get_call_arguments_at index
537
- # called = parent_node_from(index, :send)
538
- #end
539
-
540
- private
541
-
542
- def get_signature_data_at index
543
- brackets = 0
544
- squares = 0
545
- parens = 0
546
- signature = ''
547
- index -=1
548
- in_whitespace = false
549
- while index >= 0
550
- break if index > 0 and comment_at?(index - 1)
551
- unless !in_whitespace and string_at?(index)
552
- break if brackets > 0 or parens > 0 or squares > 0
553
- char = @code[index, 1]
554
- if brackets.zero? and parens.zero? and squares.zero? and [' ', "\n", "\t"].include?(char)
555
- in_whitespace = true
556
- else
557
- if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
558
- unless char == '.' or @code[index+1..-1].strip.start_with?('.')
559
- old = @code[index+1..-1]
560
- nxt = @code[index+1..-1].lstrip
561
- index += (@code[index+1..-1].length - @code[index+1..-1].lstrip.length)
562
- break
563
- end
564
- end
565
- if char == ')'
566
- parens -=1
567
- elsif char == ']'
568
- squares -=1
569
- elsif char == '}'
570
- brackets -= 1
571
- elsif char == '('
572
- parens += 1
573
- elsif char == '{'
574
- brackets += 1
575
- elsif char == '['
576
- squares += 1
577
- signature = ".[]#{signature}" if squares == 0 and @code[index-2] != '%'
578
- end
579
- if brackets.zero? and parens.zero? and squares.zero?
580
- break if ['"', "'", ',', ';', '%'].include?(char)
581
- signature = char + signature if char.match(/[a-z0-9:\._@\$]/i) and @code[index - 1] != '%'
582
- break if char == '$'
583
- if char == '@'
584
- signature = "@#{signature}" if @code[index-1, 1] == '@'
585
- break
586
- end
587
- end
588
- in_whitespace = false
589
- end
590
- end
591
- index -= 1
592
- end
593
- signature = signature[1..-1] if signature.start_with?('.')
594
- [index + 1, signature]
595
- end
596
-
597
- # Get a node's arguments as an array of suggestions. The node's type must
598
- # be a method (:def or :defs).
599
- #
600
- # @param node [AST::Node]
601
- # @return [Array<Suggestion>]
602
- def get_method_arguments_from node
603
- return [] unless node.type == :def or node.type == :defs
604
- param_hash = {}
605
- cmnt = api_map.get_docstring_for(node)
606
- unless cmnt.nil?
607
- tags = cmnt.tags(:param)
608
- tags.each do |tag|
609
- param_hash[tag.name] = tag.types[0]
610
- end
611
- end
612
- result = []
613
- args = node.children[(node.type == :def ? 1 : 2)]
614
- return result unless args.kind_of?(AST::Node) and args.type == :args
615
- args.children.each do |arg|
616
- name = arg.children[0].to_s
617
- result.push Suggestion.new(name, kind: Suggestion::PROPERTY, insert: name, return_type: param_hash[name])
618
- end
619
- result
620
- end
621
-
622
- def get_yieldparams_at index
623
- block_node = parent_node_from(index, :block, :class, :module, :def, :defs)
624
- return [] if block_node.nil? or block_node.type != :block
625
- scope_node = parent_node_from(index, :class, :module, :def, :defs) || @node
626
- return [] if block_node.nil?
627
- get_yieldparams_from block_node, scope_node
628
- end
629
-
630
- def get_yieldparams_from block_node, scope_node
631
- return [] unless block_node.kind_of?(AST::Node) and block_node.type == :block
632
- result = []
633
- unless block_node.nil? or block_node.children[1].nil?
634
- ymeth = get_yielding_method(block_node, scope_node)
635
- yps = []
636
- unless ymeth.nil? or ymeth.docstring.nil?
637
- yps = ymeth.docstring.tags(:yieldparam) || []
638
- end
639
- self_yield = nil
640
- meth = get_yielding_method_with_yieldself(block_node, scope_node)
641
- unless meth.nil?
642
- match = meth.docstring.all.match(/@yieldself \[([a-z0-9:_]*)/i)
643
- self_yield = match[1]
644
- if self_yield == 'self'
645
- blocksig = resolve_node_signature(block_node.children[0]).split('.')[0..-2].join('.')
646
- self_yield = infer_signature_from_node(blocksig, scope_node)
647
- end
648
- end
649
- block_node.children[1].children.each_with_index do |a, i|
650
- rt = nil
651
- if yps[i].nil? or yps[i].types.nil? or yps[i].types.empty?
652
- zsig = api_map.resolve_node_signature(block_node.children[0])
653
- vartype = infer_signature_from_node(zsig.split('.')[0..-2].join('.'), scope_node)
654
- subtypes = get_subtypes(vartype)
655
- zpath = infer_path_from_signature_and_node(zsig, scope_node)
656
- rt = subtypes[i] if METHODS_WITH_YIELDPARAM_SUBTYPES.include?(zpath)
657
- else
658
- rt = yps[i].types[0]
659
- end
660
- result.push Suggestion.new(a.children[0], kind: Suggestion::PROPERTY, return_type: rt)
661
- end
662
- result.concat api_map.get_instance_methods(self_yield, namespace_from(scope_node)) unless self_yield.nil?
663
- end
664
- result
665
- end
666
-
667
- def get_yielding_method block_node, scope_node
668
- recv = resolve_node_signature(block_node.children[0].children[0])
669
- fqns = namespace_from(block_node)
670
- lvarnode = find_local_variable_node(recv, scope_node)
671
- if lvarnode.nil?
672
- #sig = api_map.infer_signature_type(recv, fqns)
673
- sig = infer_signature_from_node(recv, scope_node)
674
- else
675
- tmp = resolve_node_signature(lvarnode.children[1])
676
- sig = infer_signature_from_node tmp, scope_node
677
- end
678
- if sig.nil?
679
- meths = api_map.get_methods(fqns, fqns)
680
- else
681
- meths = api_map.get_instance_methods(sig, fqns)
682
- end
683
- meths += api_map.get_methods('')
684
- meth = meths.keep_if{ |s| s.to_s == block_node.children[0].children[1].to_s }.first
685
- meth
686
- end
687
-
688
- def get_yielding_method_with_yieldself block_node, scope_node
689
- meth = get_yielding_method block_node, scope_node
690
- if meth.nil? or meth.docstring.nil? or !meth.docstring.all.include?('@yieldself')
691
- meth = nil
692
- tree = @source.tree_for(block_node)
693
- unless tree.nil?
694
- tree.each do |p|
695
- break if [:def, :defs, :class, :module, :sclass].include?(p.type)
696
- return get_yielding_method_with_yieldself(p, scope_node) if p.type == :block
697
- end
698
- end
699
- end
700
- meth
701
- end
702
-
703
- # @param suggestions [Array<Solargraph::Suggestion>]
704
- # @param word [String]
705
- def reduce_starting_with(suggestions, word)
706
- suggestions.reject { |s|
707
- !s.label.start_with?(word)
708
- }
709
- end
710
-
711
- # Find all the local variables in the node's scope.
712
- #
713
- # @return [Array<Solargraph::Suggestion>]
714
- def get_local_variables_from(node)
715
- node ||= @node
716
- namespace = namespace_from(node)
717
- arr = []
718
- nil_pins = []
719
- val_names = []
720
- @source.local_variable_pins.select{|p| p.visible_from?(node) }.each do |pin|
721
- if pin.nil_assignment? and pin.return_type.nil?
722
- nil_pins.push pin
723
- else
724
- unless val_names.include?(pin.name)
725
- arr.push Suggestion.pull(pin)
726
- val_names.push pin.name
727
- end
728
- end
729
- end
730
- nil_pins.reject{|p| val_names.include?(p.name)}.each do |pin|
731
- arr.push Suggestion.pull(pin)
732
- end
733
- arr
734
- end
735
-
736
- def inner_node_at(index, node, arr)
737
- node.children.each do |c|
738
- if c.kind_of?(AST::Node) and c.respond_to?(:loc)
739
- unless c.loc.expression.nil?
740
- if index >= c.loc.expression.begin_pos
741
- if c.respond_to?(:end)
742
- if index < c.end.end_pos
743
- arr.unshift c
744
- end
745
- elsif index < c.loc.expression.end_pos
746
- arr.unshift c
747
- end
748
- end
749
- end
750
- inner_node_at(index, c, arr)
751
- end
752
- end
753
- end
754
-
755
- def find_local_variable_node name, scope
756
- scope.children.each { |c|
757
- if c.kind_of?(AST::Node)
758
- if c.type == :lvasgn and c.children[0].to_s == name
759
- return c
760
- else
761
- unless [:class, :module, :def, :defs].include?(c.type)
762
- sub = find_local_variable_node(name, c)
763
- return sub unless sub.nil?
764
- end
765
- end
766
- end
767
- }
768
- nil
769
- end
770
-
771
- def get_namespace_or_constant con, namespace
772
- parts = con.split('::')
773
- conc = parts.shift
774
- result = nil
775
- is_constant = false
776
- while parts.length > 0
777
- result = api_map.find_fully_qualified_namespace("#{conc}::#{parts[0]}", namespace)
778
- if result.nil? or result.empty?
779
- pin = api_map.get_constant_pins(conc, namespace).select{|s| s.name == parts[0]}.first
780
- return nil if pin.nil?
781
- result = pin.return_type || api_map.infer_assignment_node_type(pin.node, namespace)
782
- break if result.nil?
783
- is_constant = true
784
- conc = result
785
- parts.shift
786
- else
787
- is_constant = false
788
- conc += "::#{parts.shift}"
789
- end
790
- end
791
- return result if is_constant
792
- end
793
-
794
- def signature_index_before index
795
- open_parens = 0
796
- cursor = index - 1
797
- while cursor >= 0
798
- break if cursor < 0
799
- if @code[cursor] == ')'
800
- open_parens -= 1
801
- elsif @code[cursor] == '('
802
- open_parens += 1
803
- end
804
- break if open_parens == 1
805
- cursor -= 1
806
- end
807
- cursor = nil if cursor < 0
808
- cursor
809
- end
810
-
811
- def infer_path_from_signature_and_node signature, node
812
- # @todo Improve this method
813
- parts = signature.split('.')
814
- last = parts.pop
815
- type = infer_signature_from_node(parts.join('.'), node)
816
- return nil if type.nil?
817
- "#{type.gsub(/<[a-z0-9:, ]*>/i, '')}##{last}"
818
- end
819
-
820
- def get_subtypes type
821
- return nil if type.nil?
822
- match = type.match(/<([a-z0-9_:, ]*)>/i)
823
- return [] if match.nil?
824
- match[1].split(',').map(&:strip)
825
- end
826
-
827
- # @param template [String]
828
- def convert_erb template
829
- result = ''
830
- i = 0
831
- in_code = false
832
- if template.start_with?('<%=')
833
- i += 3
834
- result += ';;;'
835
- in_code = true
836
- elsif template.start_with? '<%'
837
- i += 2
838
- result += ';;'
839
- in_code = true
840
- end
841
- while i < template.length
842
- if in_code
843
- if template[i, 2] == '%>'
844
- i += 2
845
- result += ';;'
846
- in_code = false
847
- else
848
- result += template[i]
849
- end
850
- else
851
- if template[i, 3] == '<%='
852
- i += 2
853
- result += ';;;'
854
- in_code = true
855
- elsif template[i, 2] == '<%'
856
- i += 1
857
- result += ';;'
858
- in_code = true
859
- else
860
- result += template[i].sub(/[^\s]/, ' ')
861
- end
862
- end
863
- i += 1
864
- end
865
- result
866
- end
867
- end
868
- end