solargraph 0.18.3 → 0.19.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 (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