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
@@ -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