solargraph 0.18.3 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph/api_map/probe.rb +222 -0
  3. data/lib/solargraph/api_map/source_to_yard.rb +3 -3
  4. data/lib/solargraph/api_map/store.rb +135 -0
  5. data/lib/solargraph/api_map.rb +169 -609
  6. data/lib/solargraph/diagnostics/rubocop.rb +4 -4
  7. data/lib/solargraph/language_server/host.rb +53 -19
  8. data/lib/solargraph/language_server/message/extended/document.rb +1 -1
  9. data/lib/solargraph/language_server/message/extended/search.rb +1 -1
  10. data/lib/solargraph/language_server/message/method_not_found.rb +1 -1
  11. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -15
  12. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -15
  13. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +3 -15
  14. data/lib/solargraph/language_server/message_types.rb +10 -0
  15. data/lib/solargraph/language_server.rb +1 -0
  16. data/lib/solargraph/library.rb +8 -0
  17. data/lib/solargraph/node_methods.rb +6 -1
  18. data/lib/solargraph/page.rb +2 -1
  19. data/lib/solargraph/pin/attribute.rb +8 -12
  20. data/lib/solargraph/pin/base.rb +20 -95
  21. data/lib/solargraph/pin/base_variable.rb +15 -74
  22. data/lib/solargraph/pin/block.rb +21 -0
  23. data/lib/solargraph/pin/block_parameter.rb +30 -44
  24. data/lib/solargraph/pin/class_variable.rb +3 -0
  25. data/lib/solargraph/pin/constant.rb +4 -8
  26. data/lib/solargraph/pin/conversions.rb +4 -3
  27. data/lib/solargraph/pin/documenting.rb +27 -0
  28. data/lib/solargraph/pin/global_variable.rb +3 -0
  29. data/lib/solargraph/pin/instance_variable.rb +5 -4
  30. data/lib/solargraph/pin/local_variable.rb +8 -15
  31. data/lib/solargraph/pin/localized.rb +12 -0
  32. data/lib/solargraph/pin/method.rb +6 -67
  33. data/lib/solargraph/pin/method_parameter.rb +24 -11
  34. data/lib/solargraph/pin/namespace.rb +26 -35
  35. data/lib/solargraph/pin/reference.rb +15 -8
  36. data/lib/solargraph/pin/symbol.rb +34 -3
  37. data/lib/solargraph/pin/yard_object.rb +11 -4
  38. data/lib/solargraph/pin.rb +16 -2
  39. data/lib/solargraph/server.rb +2 -2
  40. data/lib/solargraph/source/change.rb +10 -13
  41. data/lib/solargraph/source/fragment.rb +42 -94
  42. data/lib/solargraph/source/location.rb +13 -0
  43. data/lib/solargraph/source/mapper.rb +426 -0
  44. data/lib/solargraph/source/position.rb +1 -0
  45. data/lib/solargraph/source/range.rb +11 -3
  46. data/lib/solargraph/source.rb +93 -284
  47. data/lib/solargraph/version.rb +1 -1
  48. data/lib/solargraph/views/_method.erb +59 -60
  49. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  50. data/lib/solargraph/views/_namespace.erb +26 -26
  51. data/lib/solargraph/views/document.erb +23 -16
  52. data/lib/solargraph/views/layout.erb +38 -10
  53. data/lib/solargraph/views/search.erb +12 -11
  54. data/lib/solargraph/workspace/config.rb +27 -6
  55. data/lib/solargraph/workspace.rb +10 -2
  56. data/lib/solargraph.rb +10 -2
  57. data/lib/yard-solargraph.rb +3 -0
  58. metadata +25 -20
  59. data/lib/solargraph/pin/directed/attribute.rb +0 -20
  60. data/lib/solargraph/pin/directed/method.rb +0 -22
  61. data/lib/solargraph/pin/directed.rb +0 -9
  62. data/lib/solargraph/pin/parameter.rb +0 -23
@@ -7,6 +7,8 @@ module Solargraph
7
7
  autoload :Cache, 'solargraph/api_map/cache'
8
8
  autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
9
9
  autoload :Completion, 'solargraph/api_map/completion'
10
+ autoload :Probe, 'solargraph/api_map/probe'
11
+ autoload :Store, 'solargraph/api_map/store'
10
12
 
11
13
  include Solargraph::ApiMap::SourceToYard
12
14
  include CoreFills
@@ -17,27 +19,41 @@ module Solargraph
17
19
  attr_reader :workspace
18
20
 
19
21
  # @param workspace [Solargraph::Workspace]
20
- def initialize workspace = nil
21
- # @todo Deprecate strings for the workspace parameter
22
- workspace = Solargraph::Workspace.new(workspace) if workspace.kind_of?(String)
23
- workspace = Solargraph::Workspace.new(nil) if workspace.nil?
22
+ def initialize workspace = Solargraph::Workspace.new(nil)
24
23
  @workspace = workspace
25
24
  require_extensions
26
25
  @virtual_source = nil
27
26
  @yard_stale = true
28
- process_maps
27
+ # process_maps
28
+ @sources = workspace.sources
29
29
  yard_map
30
30
  end
31
31
 
32
+ # Create an ApiMap with a workspace in the specified directory.
33
+ #
34
+ # @return [ApiMap]
32
35
  def self.load directory
33
36
  self.new(Solargraph::Workspace.new(directory))
34
37
  end
35
38
 
39
+ # @return [ApiMap::Store]
40
+ def store
41
+ @store ||= ApiMap::Store.new(@sources)
42
+ end
43
+
44
+ def pins
45
+ store.pins
46
+ end
47
+
36
48
  # An array of required paths in the workspace.
37
49
  #
38
50
  # @return [Array<String>]
39
51
  def required
40
- @required ||= []
52
+ result = []
53
+ @sources.each do |s|
54
+ result.concat s.required
55
+ end
56
+ result.uniq
41
57
  end
42
58
 
43
59
  # Get a YardMap associated with the current workspace.
@@ -65,8 +81,9 @@ module Solargraph
65
81
  # one source can be virtualized at a time.
66
82
  #
67
83
  # @param source [Solargraph::Source]
84
+ # @return [Solargraph::Source]
68
85
  def virtualize source
69
- eliminate @virtual_source unless @virtual_source.nil?
86
+ store.remove @virtual_source unless @virtual_source.nil?
70
87
  if workspace.has_source?(source)
71
88
  @sources = workspace.sources
72
89
  @virtual_source = nil
@@ -78,10 +95,18 @@ module Solargraph
78
95
  process_virtual
79
96
  end
80
97
  end
98
+ source
81
99
  end
82
100
 
83
- # @todo Candidate for deprecation
84
- def append_source code, filename = nil
101
+ # Create a Source from the code and filename, and virtualize the result.
102
+ # This method can be useful for directly testing the ApiMap. In practice,
103
+ # applications should use a Library to synchronize the ApiMap to a
104
+ # workspace.
105
+ #
106
+ # @param code [String]
107
+ # @param filename [String]
108
+ # @return [Solargraph::Source]
109
+ def virtualize_string code, filename = nil
85
110
  source = Source.load_string(code, filename)
86
111
  virtualize source
87
112
  end
@@ -92,27 +117,12 @@ module Solargraph
92
117
  def refresh force = false
93
118
  return unless @force or changed?
94
119
  if force
95
- process_maps
120
+ @api_map = ApiMap::Store.new(@sources)
96
121
  else
97
- current_workspace_sources.reject{|s| workspace.sources.include?(s)}.each do |source|
98
- eliminate source
99
- end
122
+ store.remove *(current_workspace_sources.reject{ |s| workspace.sources.include?(s) })
100
123
  @sources = workspace.sources
101
124
  @sources.push @virtual_source unless @virtual_source.nil?
102
- cache.clear
103
- namespace_map.clear
104
- @sources.each do |s|
105
- s.namespaces.each do |n|
106
- namespace_map[n] ||= []
107
- namespace_map[n].concat s.namespace_pins(n)
108
- end
109
- end
110
- @sources.each do |source|
111
- if @stime.nil? or source.stime > @stime
112
- eliminate source
113
- map_source source
114
- end
115
- end
125
+ store.update *(@sources.select{ |s| @stime.nil? or s.stime > @stime })
116
126
  end
117
127
  @stime = Time.new
118
128
  end
@@ -142,8 +152,7 @@ module Solargraph
142
152
  #
143
153
  # @return [Array<String>]
144
154
  def namespaces
145
- # refresh
146
- namespace_map.keys
155
+ store.namespaces
147
156
  end
148
157
 
149
158
  # True if the namespace exists.
@@ -152,14 +161,14 @@ module Solargraph
152
161
  # @param root [String] The context to search
153
162
  # @return [Boolean]
154
163
  def namespace_exists? name, root = ''
155
- !find_fully_qualified_namespace(name, root).nil?
164
+ !qualify(name, root).nil?
156
165
  end
157
166
 
158
167
  # Get suggestions for constants in the specified namespace. The result
159
168
  # may contain both constant and namespace pins.
160
169
  #
161
- # @param fqns [String] The fully qualified namespace
162
- # @param visibility [Array<Symbol>] :public and/or :private
170
+ # @param namespace [String] The namespace
171
+ # @param context [String] The context
163
172
  # @return [Array<Solargraph::Pin::Base>]
164
173
  def get_constants namespace, context = ''
165
174
  namespace ||= ''
@@ -168,13 +177,13 @@ module Solargraph
168
177
  bases = context.split('::')
169
178
  while bases.length > 0
170
179
  built = bases.join('::')
171
- fqns = find_fully_qualified_namespace(namespace, built)
180
+ fqns = qualify(namespace, built)
172
181
  visibility = [:public]
173
182
  visibility.push :private if fqns == context
174
183
  result.concat inner_get_constants(fqns, visibility, skip)
175
184
  bases.pop
176
185
  end
177
- fqns = find_fully_qualified_namespace(namespace, '')
186
+ fqns = qualify(namespace, '')
178
187
  visibility = [:public]
179
188
  visibility.push :private if fqns == context
180
189
  result.concat inner_get_constants(fqns, visibility, skip)
@@ -182,54 +191,18 @@ module Solargraph
182
191
  end
183
192
 
184
193
  # Get a fully qualified namespace name. This method will start the search
185
- # in the specified root until it finds a match for the name.
194
+ # in the specified context until it finds a match for the name.
186
195
  #
187
- # @param name [String] The namespace to match
188
- # @param root [String] The context to search
196
+ # @param namespace [String] The namespace to match
197
+ # @param context [String] The context to search
189
198
  # @return [String]
190
- def find_fully_qualified_namespace name, root = '', skip = []
191
- # refresh
192
- return nil if name.nil?
193
- return nil if skip.include?(root)
194
- skip.push root
195
- if name == ''
196
- if root == ''
197
- return ''
198
- else
199
- return find_fully_qualified_namespace(root, '', skip)
200
- end
201
- else
202
- if (root == '')
203
- return name unless namespace_map[name].nil?
204
- im = @namespace_includes['']
205
- unless im.nil?
206
- im.each do |i|
207
- i.resolve self
208
- return i.name unless i.name.nil?
209
- end
210
- end
211
- else
212
- roots = root.to_s.split('::')
213
- while roots.length > 0
214
- fqns = roots.join('::') + '::' + name
215
- return fqns unless namespace_map[fqns].nil?
216
- roots.pop
217
- end
218
- return name unless namespace_map[name].nil?
219
- im = @namespace_includes['']
220
- unless im.nil?
221
- im.each do |i|
222
- i.resolve self
223
- return i.name unless i.name.nil?
224
- end
225
- end
226
- end
227
- end
228
- result = yard_map.find_fully_qualified_namespace(name, root)
229
- if result.nil?
230
- result = live_map.get_fqns(name, root)
231
- end
232
- result
199
+ def qualify namespace, context = ''
200
+ inner_qualify namespace, context, []
201
+ end
202
+
203
+ # @deprecated Use #qualify instead
204
+ def find_fully_qualified_namespace namespace, context = ''
205
+ qualify namespace, context
233
206
  end
234
207
 
235
208
  # Get an array of instance variable pins defined in specified namespace
@@ -239,13 +212,7 @@ module Solargraph
239
212
  # @param scope [Symbol] :instance or :class
240
213
  # @return [Array<Solargraph::Pin::InstanceVariable>]
241
214
  def get_instance_variable_pins(namespace, scope = :instance)
242
- raw = @ivar_pins[namespace]
243
- return [] if raw.nil?
244
- # @todo This is a crazy workaround because instance variables in the
245
- # global namespace might be in either scope
246
- pins = prefer_non_nil_variables(raw)
247
- return pins if namespace.empty?
248
- pins.select{ |pin| pin.scope == scope }
215
+ store.get_instance_variables(namespace, scope)
249
216
  end
250
217
 
251
218
  # Get an array of class variable pins for a namespace.
@@ -258,221 +225,7 @@ module Solargraph
258
225
 
259
226
  # @return [Array<Solargraph::Pin::Base>]
260
227
  def get_symbols
261
- # refresh
262
- @symbol_pins
263
- end
264
-
265
- # Find a class, instance, or global variable by name in the provided
266
- # namespace and scope.
267
- #
268
- # @param name [String] The variable name, e.g., `@foo`, `@@bar`, or `$baz`
269
- # @param fqns [String] The fully qualified namespace
270
- # @param scope [Symbol] :class or :instance
271
- # @return [Solargraph::Pin::BaseVariable]
272
- def find_variable_pin name, fqns, scope
273
- var = nil
274
- return nil if name.nil?
275
- if name.start_with?('@@')
276
- # class variable
277
- var = get_class_variable_pins(fqns).select{|pin| pin.name == name}.first
278
- return nil if var.nil?
279
- elsif name.start_with?('@')
280
- # instance variable
281
- var = get_instance_variable_pins(fqns, scope).select{|pin| pin.name == name}.first
282
- return nil if var.nil?
283
- elsif name.start_with?('$')
284
- # global variable
285
- var = get_global_variable_pins.select{|pin| pin.name == name}.first
286
- return nil if var.nil?
287
- end
288
- var
289
- end
290
-
291
- def find_namespace_pin fqns
292
- crawl_constants fqns, '', [:public, :private]
293
- end
294
-
295
- def crawl_constants name, fqns, visibility
296
- return nil if name.nil?
297
- chain = name.split('::')
298
- cursor = chain.shift
299
- return nil if cursor.nil?
300
- unless fqns.empty?
301
- bases = fqns.split('::')
302
- result = nil
303
- until bases.empty?
304
- built = bases.join('::')
305
- result = get_constants(built, '').select{|pin| pin.name == cursor and visibility.include?(pin.visibility)}.first
306
- break unless result.nil?
307
- bases.pop
308
- visibility -= [:private]
309
- end
310
- return nil if result.nil?
311
- end
312
- result = get_constants(fqns, '').select{|pin| pin.name == cursor and visibility.include?(pin.visibility)}.first
313
- visibility -= [:private]
314
- until chain.empty? or result.nil?
315
- fqns = result.path
316
- cursor = chain.shift
317
- result = get_constants(fqns, '').select{|pin| pin.name == cursor and visibility.include?(pin.visibility)}.first
318
- end
319
- result
320
- end
321
-
322
- # This method checks the signature from the namespace's internal context,
323
- # i.e., the first word in the signature can be a private or protected
324
- # method, a private constant, an instance variable, or a class variable.
325
- # Local variables are not accessible.
326
- #
327
- # @return [String]
328
- def infer_type signature, namespace = '', scope: :instance
329
- context = combine_type(namespace, scope)
330
- parts = signature.split('.')
331
- base = parts.shift
332
- return nil if base.nil?
333
- type = infer_word_type(base, context, true)
334
- return nil if type.nil?
335
- until parts.empty?
336
- word = parts.shift
337
- type = infer_method_type(word, type)
338
- return nil if type.nil?
339
- end
340
- type
341
- end
342
-
343
- # @return [Solargraph::Pin::Base]
344
- def tail_pins signature, fqns, scope, visibility
345
- return [] if signature.nil?
346
- type = combine_type(fqns, scope)
347
- return infer_word_pins(signature, type, true) unless signature.include?('.')
348
- parts = signature.split('.')
349
- last = parts.pop
350
- base = parts.join('.')
351
- type = infer_type(base, fqns, scope: scope)
352
- return [] if type.nil?
353
- infer_word_pins(last, type, true)
354
- end
355
-
356
- # @return [Solargraph::Pin::Base]
357
- def tail_pin signature, fqns, scope, visibility
358
- tail_pins(signature, fqns, scope, visibility).first
359
- end
360
-
361
- # Get an array of pins for a word in the provided context. A word can be
362
- # a constant, a global variable, or a method name. Private and protected
363
- # words are excluded by default. Set the `internal` parameter to `true` to
364
- # to include private and protected methods, private constants, instance
365
- # variables, and class variables.
366
- #
367
- # @param word [String]
368
- # @param base_type [String]
369
- # @param internal [Boolean]
370
- # @return [Array<Solargraph::Pin::Base>]
371
- def infer_word_pins word, base_type, internal = false
372
- pins = []
373
- namespace, scope = extract_namespace_and_scope(base_type)
374
- if word == 'self' and internal
375
- context = (internal ? namespace.split('::')[0..-2].join(';;') : '')
376
- fqns = find_fully_qualified_namespace(namespace, context)
377
- pins.concat get_path_suggestions(fqns) unless fqns.nil?
378
- return pins
379
- end
380
- fqns = find_fully_qualified_namespace(word, namespace)
381
- unless fqns.nil?
382
- pins.concat get_path_suggestions(fqns) unless fqns.nil?
383
- return pins
384
- end
385
- if internal
386
- if word.start_with?('@@')
387
- pins.concat get_class_variable_pins(namespace).select{|pin| pin.name == word}
388
- return pins
389
- elsif word.start_with?('@')
390
- pins.concat get_instance_variable_pins(namespace, scope).select{|pin| pin.name == word}
391
- return pins
392
- end
393
- end
394
- if word.start_with?('$')
395
- pins.concat get_global_variable_pins.select{|pin| pin.name == word}
396
- return pins
397
- end
398
- pins.concat get_constants(namespace, (internal ? namespace : '')).select{|pin| pin.name == word}
399
- if pins.empty?
400
- pins.concat get_type_methods(base_type, (internal ? base_type : '')).select{|pin| pin.name == word}
401
- pins.concat get_type_methods('Kernel').select{|pin| pin.name == word}
402
- end
403
- pins
404
- end
405
-
406
- # @return [Solargraph::Pin::Base]
407
- def infer_word_pin word, base_type, internal = false
408
- infer_word_pins(word, base_type, internal).first
409
- end
410
-
411
- # @return [String]
412
- def infer_word_type word, base_type, internal = false
413
- return base_type if word == 'self' and internal
414
- if word == 'new'
415
- namespace, scope = extract_namespace_and_scope(base_type)
416
- return namespace if scope == :class
417
- end
418
- pin = infer_word_pin(word, base_type, internal)
419
- return nil if pin.nil?
420
- pin.resolve self
421
- pin.return_type
422
- end
423
-
424
- # Get an array of pins for a method name in the provided context. Private
425
- # and protected methods are excluded by default. Set the `internal`
426
- # parameter to `true` to include all methods.
427
- #
428
- # @param method_name [String] The name of the method
429
- # @param base_type [String] The context type (e.g., `String` or `Class<String>`)
430
- # @param internal [Boolean] True if the call came from inside the base type
431
- # @return [Array<Solargraph::Pin::Base>]
432
- def infer_method_pins method_name, base_type, internal = false
433
- get_type_methods(base_type, (internal ? base_type : '')).select{|pin| pin.name == method_name}
434
- end
435
-
436
- # Get the first pin that matches a method name in the provided context.
437
- # Private and protected methods are excluded by default. Set the `internal`
438
- # parameter to `true` to include all methods.
439
- #
440
- # @param method_name [String] The name of the method
441
- # @param base_type [String] The context type (e.g., `String` or `Class<String>`)
442
- # @param internal [Boolean] True if the call came from inside the base type
443
- # @return [Solargraph::Pin::Base]
444
- def infer_method_pin method_name, base_type, internal = false
445
- infer_method_pins(method_name, base_type, internal).first
446
- end
447
-
448
- # Infer the type returned by a method in the provided context. Private and
449
- # protected methods are excluded by default. Set the `internal` parameter
450
- # to `true` to include all methods.
451
- #
452
- # @param method_name [String] The name of the method
453
- # @param base_type [String] The context type (e.g., `String` or `Class<String>`)
454
- # @param internal [Boolean] True if the call came from inside the base type
455
- # @return [String]
456
- def infer_method_type method_name, base_type, internal = false
457
- namespace, scope = extract_namespace_and_scope(base_type)
458
- method = infer_method_pin(method_name, base_type, internal)
459
- return nil if method.nil?
460
- method.resolve self
461
- return namespace if method.name == 'new' and scope == :class
462
- return base_type if method.return_type == 'self'
463
- method.return_type
464
- end
465
-
466
- def infer_deep_signature_type chain, base_type
467
- return nil if base_type.nil?
468
- internal = true
469
- until chain.empty?
470
- base = chain.shift
471
- base_type = infer_method_type(base, base_type, internal)
472
- return nil if base_type.nil?
473
- internal = false
474
- end
475
- base_type
228
+ store.get_symbols
476
229
  end
477
230
 
478
231
  # @return [Array<Solargraph::Pin::GlobalVariable>]
@@ -484,17 +237,6 @@ module Solargraph
484
237
  globals
485
238
  end
486
239
 
487
- def get_type_methods type, context = ''
488
- return [] if type.nil?
489
- namespace, scope = extract_namespace_and_scope(type)
490
- base = extract_namespace(context)
491
- fqns = find_fully_qualified_namespace(namespace, base)
492
- return [] if fqns.nil?
493
- visibility = [:public]
494
- visibility.push :private, :protected if fqns == base
495
- get_methods fqns, scope: scope, visibility: visibility
496
- end
497
-
498
240
  def get_methods fqns, scope: :instance, visibility: [:public], deep: true
499
241
  result = []
500
242
  skip = []
@@ -511,9 +253,9 @@ module Solargraph
511
253
  # @param fragment [Solargraph::Source::Fragment]
512
254
  # @return [ApiMap::Completion]
513
255
  def complete fragment
514
- return Completion.new([], fragment.whole_word_range) if fragment.string? or fragment.comment? or fragment.calculated_signature.start_with?('.')
256
+ return Completion.new([], fragment.whole_word_range) if fragment.string? or fragment.comment?
515
257
  result = []
516
- if !fragment.signature.include?('.')
258
+ if !fragment.signature.include?('.') and !fragment.base_literal?
517
259
  if fragment.signature.start_with?('@@')
518
260
  result.concat get_class_variable_pins(fragment.namespace)
519
261
  elsif fragment.signature.start_with?('@')
@@ -524,34 +266,38 @@ module Solargraph
524
266
  result.concat get_symbols
525
267
  else
526
268
  unless fragment.signature.include?('::')
527
- result.concat prefer_non_nil_variables(fragment.local_variable_pins)
528
- result.concat get_type_methods(combine_type(fragment.namespace, fragment.scope), fragment.namespace)
529
- result.concat get_type_methods('Kernel')
269
+ result.concat prefer_non_nil_variables(fragment.locals)
270
+ result.concat get_methods(fragment.namespace, scope: fragment.scope, visibility: [:public, :private, :protected])
271
+ result.concat get_methods('Kernel')
530
272
  result.concat ApiMap.keywords
531
273
  end
532
274
  result.concat get_constants(fragment.base, fragment.namespace)
533
275
  end
534
276
  else
535
- if fragment.signature.include?('::') and !fragment.signature.include?('.')
536
- result.concat get_constants(fragment.calculated_base, fragment.namespace)
537
- else
538
- if fragment.calculated_signature.end_with?('.')
539
- rest = fragment.calculated_signature.split('.')
540
- else
541
- rest = fragment.calculated_base.split('.')
277
+ if fragment.base_literal?
278
+ pin = get_path_suggestions(fragment.base_literal).select{|pin| pin.kind == Pin::NAMESPACE}.first
279
+ unless pin.nil?
280
+ if fragment.base.empty?
281
+ result.concat get_methods(pin.path)
282
+ else
283
+ type = probe.infer_signature_type(fragment.base, pin, fragment.locals)
284
+ unless type.nil?
285
+ namespace, scope = extract_namespace_and_scope(type)
286
+ result.concat get_methods(namespace, scope: scope)
287
+ end
288
+ end
542
289
  end
543
- base = rest.shift
544
- type = infer_word_type(base, fragment.namespace, scope: fragment.scope)
290
+ elsif fragment.signature.include?('::') and !fragment.signature.include?('.')
291
+ result.concat get_constants(fragment.base, fragment.namespace)
292
+ else
293
+ type = probe.infer_signature_type(fragment.base, fragment.named_path, fragment.locals)
545
294
  unless type.nil?
546
- rest.each do |m|
547
- type = infer_method_type(m, type)
548
- next if type.nil?
549
- end
550
- result.concat get_type_methods(type) unless type.nil?
295
+ namespace, scope = extract_namespace_and_scope(type)
296
+ result.concat get_methods(namespace, scope: scope)
551
297
  end
552
298
  end
553
299
  end
554
- filtered = result.uniq(&:identifier).select{|s| s.kind != Solargraph::LanguageServer::CompletionItemKinds::METHOD or s.name.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}.sort_by.with_index{ |x, idx| [x.name, idx] }
300
+ filtered = result.uniq(&:identifier).select{|s| s.kind != Pin::METHOD or s.name.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}.sort_by.with_index{ |x, idx| [x.name, idx] }
555
301
  Completion.new(filtered, fragment.whole_word_range)
556
302
  end
557
303
 
@@ -559,74 +305,25 @@ module Solargraph
559
305
  # @return [Array<Solargraph::Pin::Base>]
560
306
  def define fragment
561
307
  return [] if fragment.string? or fragment.comment?
562
- tail_pins fragment.whole_signature, fragment.namespace, fragment.scope, [:public, :private, :protected]
563
- end
564
-
565
- def infer_fragment_type fragment
566
- parts = fragment.whole_signature.split('.')
567
- base = parts.shift
568
- type = nil
569
- lvar = prefer_non_nil_variables(fragment.local_variable_pins(base)).first
570
- unless lvar.nil?
571
- lvar.resolve self
572
- type = lvar.return_type
573
- return nil if type.nil?
574
- end
575
- type = infer_word_type(base, fragment.namespace, fragment.scope) if type.nil?
576
- return nil if type.nil?
577
- until parts.empty?
578
- meth = parts.shift
579
- type = infer_word_type(meth, type)
580
- return nil if type.nil?
581
- end
582
- type
308
+ probe.infer_signature_pins fragment.whole_signature, fragment.named_path, fragment.locals
583
309
  end
584
310
 
311
+ # Infer a return type from a fragment. This method will attempt to resolve
312
+ # signatures.
313
+ #
585
314
  # @param fragment [Solargraph::Source::Fragment]
315
+ # @return [String]
316
+ def infer_type fragment
317
+ return nil if fragment.string? or fragment.comment?
318
+ probe.infer_signature_type fragment.whole_signature, fragment.named_path, fragment.locals
319
+ end
320
+
321
+ # @param fragment [Solargraph::Source::Fragment]
322
+ # @return [Array<Solargraph::Pin::Base>]
586
323
  def signify fragment
587
324
  return [] unless fragment.argument?
588
325
  return [] if fragment.recipient.whole_signature.nil? or fragment.recipient.whole_signature.empty?
589
- base, rest = fragment.recipient.whole_signature.split('.', 2)
590
- return infer_word_pins(base, fragment.recipient.namespace, true) if rest.nil?
591
- type = nil
592
- lvar = prefer_non_nil_variables(fragment.local_variable_pins(base)).first
593
- unless lvar.nil?
594
- lvar.resolve self
595
- type = lvar.return_type
596
- return [] if type.nil?
597
- end
598
- type = infer_word_type(base, fragment.namespace, fragment.scope) if type.nil?
599
- return [] if type.nil?
600
- ns, sc = extract_namespace_and_scope(type)
601
- tail_pins(rest, ns, sc, [:public, :private, :protected])
602
- end
603
-
604
- # Get the namespace's type (Class or Module).
605
- #
606
- # @param [String] A fully qualified namespace
607
- # @return [Symbol] :class, :module, or nil
608
- def get_namespace_type fqns
609
- pin = @namespace_path_pins[fqns]
610
- return yard_map.get_namespace_type(fqns) if pin.nil?
611
- pin.first.type
612
- end
613
-
614
- # Convert a namespace and scope into a type.
615
- #
616
- # @example
617
- # combine_type('String', :instance) => 'String'
618
- # combine_type('String', :class) => 'Class<String>'
619
- #
620
- # @param namespace [String]
621
- # @param scope [Symbol] :class or :instance
622
- def combine_type namespace, scope
623
- return '' if namespace.empty?
624
- if scope == :instance
625
- namespace
626
- else
627
- type = get_namespace_type(namespace)
628
- "#{type == :class ? 'Class' : 'Module'}<#{namespace}>"
629
- end
326
+ probe.infer_signature_pins fragment.recipient.whole_signature, fragment.named_path, fragment.locals
630
327
  end
631
328
 
632
329
  # Get an array of all suggestions that match the specified path.
@@ -635,28 +332,10 @@ module Solargraph
635
332
  # @return [Array<Solargraph::Pin::Base>]
636
333
  def get_path_suggestions path
637
334
  return [] if path.nil?
638
- # refresh
639
335
  result = []
640
- if path.include?('#')
641
- # It's an instance method
642
- parts = path.split('#')
643
- result = get_methods(parts[0], visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
644
- elsif path.include?('.')
645
- # It's a class method
646
- parts = path.split('.')
647
- result = get_methods(parts[0], scope: :class, visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
648
- else
649
- # It's a class or module
650
- parts = path.split('::')
651
- np = @namespace_pins[parts[0..-2].join('::')]
652
- unless np.nil?
653
- result.concat np.select{|p| p.name == parts.last}
654
- end
655
- result.concat yard_map.objects(path)
656
- end
657
- # @todo Resolve the pins?
658
- result.map{|pin| pin.resolve(self); pin}
659
- # result
336
+ result.concat store.get_path_pins(path)
337
+ result.concat yard_map.objects(path)
338
+ result
660
339
  end
661
340
 
662
341
  # Get a list of documented paths that match the query.
@@ -702,145 +381,33 @@ module Solargraph
702
381
  result
703
382
  end
704
383
 
705
- def superclass_of fqns
706
- found = @superclasses[fqns]
707
- return nil if found.nil?
708
- found.resolve self
709
- found.name
710
- end
711
-
712
384
  def locate_pin location
713
385
  @sources.each do |source|
714
386
  pin = source.locate_pin(location)
715
387
  unless pin.nil?
716
- pin.resolve self
388
+ # pin.resolve self
717
389
  return pin
718
390
  end
719
391
  end
720
392
  nil
721
393
  end
722
394
 
723
- private
724
-
725
- # @return [Hash]
726
- def namespace_map
727
- @namespace_map ||= {}
395
+ # @return [Probe]
396
+ def probe
397
+ @probe ||= Probe.new(self)
728
398
  end
729
399
 
730
- def process_maps
731
- @sources = workspace.sources
732
- @sources.push @virtual_source unless @virtual_source.nil?
733
- cache.clear
734
- @ivar_pins = {}
735
- @cvar_pins = {}
736
- @const_pins = {}
737
- @method_pins = {}
738
- @symbol_pins = []
739
- @attr_pins = {}
740
- @namespace_includes = {}
741
- @namespace_extends = {}
742
- @superclasses = {}
743
- @namespace_pins = {}
744
- @namespace_path_pins = {}
745
- namespace_map.clear
746
- @required = workspace.config.required.clone
747
- @sources.each do |s|
748
- s.namespaces.each do |n|
749
- namespace_map[n] ||= []
750
- namespace_map[n].concat s.namespace_pins(n)
751
- end
752
- end
753
- @sources.each do |s|
754
- map_source s
755
- end
756
- @required.uniq!
757
- live_map.refresh
758
- @yard_stale = true
759
- @stime = Time.now
760
- end
761
-
762
- def rebuild_local_yardoc
763
- return if workspace.nil? or !File.exist?(File.join(workspace, '.yardoc'))
764
- STDERR.puts "Rebuilding local yardoc for #{workspace}"
765
- Dir.chdir(workspace) { Process.spawn('yardoc') }
766
- end
400
+ private
767
401
 
768
402
  def process_virtual
769
403
  unless @virtual_source.nil?
770
- cache.clear
771
- namespace_map.clear
772
- @sources.each do |s|
773
- s.namespace_pins.each do |pin|
774
- namespace_map[pin.path] ||= []
775
- namespace_map[pin.path].push pin
776
- end
777
- end
778
404
  map_source @virtual_source
779
405
  end
780
406
  end
781
407
 
782
- def eliminate source
783
- [@ivar_pins.values, @cvar_pins.values, @const_pins.values, @method_pins.values, @attr_pins.values, @namespace_pins.values].each do |pinsets|
784
- pinsets.each do |pins|
785
- pins.delete_if{|pin| pin.filename == source.filename}
786
- end
787
- end
788
- [@namespace_includes.values, @namespace_extends.values].each do |refsets|
789
- refsets.each do |refs|
790
- refs.delete_if{|ref| ref.pin.filename == source.filename}
791
- end
792
- end
793
- @superclasses.delete_if{|key, ref| ref.pin.filename == source.filename}
794
- @symbol_pins.delete_if{|pin| pin.filename == source.filename}
795
- end
796
-
797
408
  # @param [Solargraph::Source]
798
409
  def map_source source
799
- source.method_pins.each do |pin|
800
- @method_pins[pin.namespace] ||= []
801
- @method_pins[pin.namespace].push pin
802
- end
803
- source.attribute_pins.each do |pin|
804
- @attr_pins[pin.namespace] ||= []
805
- @attr_pins[pin.namespace].push pin
806
- end
807
- source.instance_variable_pins.each do |pin|
808
- @ivar_pins[pin.namespace] ||= []
809
- @ivar_pins[pin.namespace].push pin
810
- end
811
- source.class_variable_pins.each do |pin|
812
- @cvar_pins[pin.namespace] ||= []
813
- @cvar_pins[pin.namespace].push pin
814
- end
815
- source.constant_pins.each do |pin|
816
- @const_pins[pin.namespace] ||= []
817
- @const_pins[pin.namespace].push pin
818
- end
819
- source.symbol_pins.each do |pin|
820
- @symbol_pins.push pin
821
- end
822
- source.namespace_pins.each do |pin|
823
- @namespace_path_pins[pin.path] ||= []
824
- @namespace_path_pins[pin.path].push pin
825
- @namespace_pins[pin.namespace] ||= []
826
- @namespace_pins[pin.namespace].push pin
827
- # @todo Determine whether references should be resolve here or
828
- # dynamically during queries
829
- unless pin.superclass_reference.nil?
830
- @superclasses[pin.path] = pin.superclass_reference
831
- # pin.superclass_reference.resolve self
832
- end
833
- pin.include_references.each do |ref|
834
- @namespace_includes[pin.path] ||= []
835
- @namespace_includes[pin.path].push ref
836
- # ref.resolve self
837
- end
838
- pin.extend_references.each do |ref|
839
- @namespace_extends[pin.path] ||= []
840
- @namespace_extends[pin.path].push ref
841
- # ref.resolve self
842
- end
843
- end
410
+ store.update source
844
411
  path_macros.merge! source.path_macros
845
412
  source.required.each do |r|
846
413
  required.push r
@@ -858,44 +425,28 @@ module Solargraph
858
425
  skip.push reqstr
859
426
  result = []
860
427
  if scope == :instance
861
- aps = @attr_pins[fqns]
862
- result.concat aps unless aps.nil?
863
- end
864
- mps = @method_pins[fqns]
865
- result.concat mps.select{|pin| (pin.scope == scope or fqns == '') and visibility.include?(pin.visibility)} unless mps.nil?
866
- if fqns != '' and scope == :class and !result.map(&:path).include?("#{fqns}.new")
867
- # Create a [Class].new method pin from [Class]#initialize
868
- init = inner_get_methods(fqns, :instance, [:private], deep, skip - [fqns]).select{|pin| pin.name == 'initialize'}.first
869
- unless init.nil?
870
- result.unshift Solargraph::Pin::Directed::Method.new(init.source, init.node, init.namespace, :class, :public, init.docstring, 'new', init.namespace)
871
- end
428
+ result.concat store.get_attrs(fqns)
872
429
  end
430
+ result.concat store.get_methods(fqns, scope: scope, visibility: visibility)
873
431
  if deep
874
- scref = @superclasses[fqns]
875
- unless scref.nil?
432
+ sc = store.get_superclass(fqns)
433
+ unless sc.nil?
434
+ fqsc = qualify(sc, fqns)
876
435
  sc_visi = [:public]
877
436
  sc_visi.push :protected if visibility.include?(:protected)
878
- # sc_fqns = find_fully_qualified_namespace(sc, fqns)
879
- scref.resolve self
880
- result.concat inner_get_methods(scref.name, scope, sc_visi, true, skip) unless scref.name.nil?
437
+ result.concat inner_get_methods(fqsc, scope, sc_visi, true, skip) unless fqsc.nil?
881
438
  end
882
439
  if scope == :instance
883
- im = @namespace_includes[fqns]
884
- unless im.nil?
885
- im.each do |i|
886
- i.resolve self
887
- result.concat inner_get_methods(i.name, scope, visibility, deep, skip) unless i.name.nil?
888
- end
440
+ store.get_includes(fqns).each do |im|
441
+ fqim = qualify(im, fqns)
442
+ result.concat inner_get_methods(fqim, scope, visibility, deep, skip) unless fqim.nil?
889
443
  end
890
444
  result.concat yard_map.get_instance_methods(fqns, visibility: visibility)
891
445
  result.concat inner_get_methods('Object', :instance, [:public], deep, skip) unless fqns == 'Object'
892
446
  else
893
- em = @namespace_extends[fqns]
894
- unless em.nil?
895
- em.each do |e|
896
- e.resolve self
897
- result.concat inner_get_methods(e.name, :instance, visibility, deep, skip) unless e.name.nil?
898
- end
447
+ store.get_extends(fqns).each do |em|
448
+ fqem = qualify(em, fqns)
449
+ result.concat inner_get_methods(fqem, :instance, visibility, deep, skip) unless fqem.nil?
899
450
  end
900
451
  result.concat yard_map.get_methods(fqns, '', visibility: visibility)
901
452
  type = get_namespace_type(fqns)
@@ -913,49 +464,15 @@ module Solargraph
913
464
  return [] if skip.include?(fqns)
914
465
  skip.push fqns
915
466
  result = []
916
- result.concat @const_pins[fqns] if @const_pins.has_key?(fqns)
917
- result.concat @namespace_pins[fqns] if @namespace_pins.has_key?(fqns)
918
- result.keep_if{|pin| !pin.name.empty? and visibility.include?(pin.visibility)}
467
+ result.concat store.get_constants(fqns, visibility)
919
468
  result.concat yard_map.get_constants(fqns)
920
- is = @namespace_includes[fqns]
921
- unless is.nil?
922
- is.each do |i|
923
- i.resolve self
924
- result.concat inner_get_constants(i.name, [:public], skip) unless i.name.nil?
925
- end
469
+ store.get_includes(fqns).each do |is|
470
+ fqis = qualify(is, fqns)
471
+ result.concat inner_get_constants(fqis, [:public], skip) unless fqis.nil?
926
472
  end
927
473
  result
928
474
  end
929
475
 
930
- # Extract a namespace from a type.
931
- #
932
- # @example
933
- # extract_namespace('String') => 'String'
934
- # extract_namespace('Class<String>') => 'String'
935
- #
936
- # @return [String]
937
- def extract_namespace type
938
- extract_namespace_and_scope(type)[0]
939
- end
940
-
941
- # Extract a namespace and a scope (:instance or :class) from a type.
942
- #
943
- # @example
944
- # extract_namespace('String') #=> ['String', :instance]
945
- # extract_namespace('Class<String>') #=> ['String', :class]
946
- # extract_namespace('Module<Enumerable') #=> ['Enumberable', :class]
947
- #
948
- # @return [Array] The namespace (String) and scope (Symbol).
949
- def extract_namespace_and_scope type
950
- scope = :instance
951
- result = type.to_s.gsub(/<.*$/, '')
952
- if (result == 'Class' or result == 'Module') and type.include?('<')
953
- result = type.match(/<([a-z0-9:_]*)/i)[1]
954
- scope = :class
955
- end
956
- [result, scope]
957
- end
958
-
959
476
  def require_extensions
960
477
  Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
961
478
  STDERR.puts "Loading extension #{n}"
@@ -968,7 +485,7 @@ module Solargraph
968
485
  result = []
969
486
  nil_pins = []
970
487
  pins.each do |pin|
971
- if pin.nil_assignment? and pin.return_type.nil?
488
+ if pin.variable? and pin.nil_assignment?
972
489
  nil_pins.push pin
973
490
  else
974
491
  result.push pin
@@ -977,14 +494,6 @@ module Solargraph
977
494
  result + nil_pins
978
495
  end
979
496
 
980
- # @todo DRY this method. It's duplicated in CodeMap
981
- def get_subtypes type
982
- return [] if type.nil?
983
- match = type.match(/<([a-z0-9_:, ]*)>/i)
984
- return [] if match.nil?
985
- match[1].split(',').map(&:strip)
986
- end
987
-
988
497
  # @return [Hash]
989
498
  def path_macros
990
499
  @path_macros ||= {}
@@ -993,5 +502,56 @@ module Solargraph
993
502
  def current_workspace_sources
994
503
  @sources - [@virtual_source]
995
504
  end
505
+
506
+ def inner_qualify name, root, skip
507
+ return nil if name.nil?
508
+ return nil if skip.include?(root)
509
+ skip.push root
510
+ if name == ''
511
+ if root == ''
512
+ return ''
513
+ else
514
+ return inner_qualify(root, '', skip)
515
+ end
516
+ else
517
+ if (root == '')
518
+ return name if store.namespace_exists?(name)
519
+ # @todo What to do about the @namespace_includes stuff above?
520
+ else
521
+ roots = root.to_s.split('::')
522
+ while roots.length > 0
523
+ fqns = roots.join('::') + '::' + name
524
+ return fqns if store.namespace_exists?(fqns)
525
+ roots.pop
526
+ end
527
+ return name if store.namespace_exists?(name)
528
+ end
529
+ end
530
+ result = yard_map.find_fully_qualified_namespace(name, root)
531
+ if result.nil?
532
+ result = live_map.get_fqns(name, root)
533
+ end
534
+ result
535
+ end
536
+
537
+ # Get the namespace's type (Class or Module).
538
+ #
539
+ # @param [String] A fully qualified namespace
540
+ # @return [Symbol] :class, :module, or nil
541
+ def get_namespace_type fqns
542
+ pin = store.get_path_pins(fqns).first
543
+ return yard_map.get_namespace_type(fqns) if pin.nil?
544
+ pin.type
545
+ end
546
+
547
+ def extract_namespace_and_scope type
548
+ scope = :instance
549
+ result = type.to_s.gsub(/<.*$/, '')
550
+ if (result == 'Class' or result == 'Module') and type.include?('<')
551
+ result = type.match(/<([a-z0-9:_]*)/i)[1]
552
+ scope = :class
553
+ end
554
+ [result, scope]
555
+ end
996
556
  end
997
557
  end