solargraph 0.18.2 → 0.18.3

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +33 -28
  3. data/lib/solargraph/api_map.rb +997 -1044
  4. data/lib/solargraph/api_map/source_to_yard.rb +4 -3
  5. data/lib/solargraph/diagnostics/rubocop.rb +4 -3
  6. data/lib/solargraph/language_server/host.rb +140 -70
  7. data/lib/solargraph/language_server/message/base.rb +1 -0
  8. data/lib/solargraph/language_server/message/client.rb +6 -2
  9. data/lib/solargraph/language_server/message/text_document/completion.rb +34 -39
  10. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  11. data/lib/solargraph/language_server/message/text_document/did_close.rb +1 -0
  12. data/lib/solargraph/language_server/message/text_document/did_save.rb +1 -3
  13. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  14. data/lib/solargraph/language_server/message/text_document/hover.rb +25 -30
  15. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +1 -1
  16. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +8 -7
  17. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +1 -1
  18. data/lib/solargraph/language_server/transport/socket.rb +15 -17
  19. data/lib/solargraph/library.rb +34 -16
  20. data/lib/solargraph/node_methods.rb +96 -96
  21. data/lib/solargraph/pin.rb +1 -0
  22. data/lib/solargraph/pin/base.rb +2 -1
  23. data/lib/solargraph/pin/base_variable.rb +45 -5
  24. data/lib/solargraph/pin/block_parameter.rb +5 -2
  25. data/lib/solargraph/pin/method.rb +22 -0
  26. data/lib/solargraph/pin/namespace.rb +32 -2
  27. data/lib/solargraph/pin/reference.rb +21 -0
  28. data/lib/solargraph/pin/yard_object.rb +9 -0
  29. data/lib/solargraph/shell.rb +136 -136
  30. data/lib/solargraph/source.rb +134 -188
  31. data/lib/solargraph/source/change.rb +70 -0
  32. data/lib/solargraph/source/fragment.rb +120 -66
  33. data/lib/solargraph/source/position.rb +41 -0
  34. data/lib/solargraph/source/updater.rb +48 -0
  35. data/lib/solargraph/version.rb +3 -3
  36. data/lib/solargraph/workspace/config.rb +4 -9
  37. data/lib/solargraph/yard_map/core_docs.rb +0 -1
  38. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 642ac02a775d6f81c76137c676057b4fefe524c2
4
- data.tar.gz: 79357368d35da0297313c3085aa9127f333d14b3
3
+ metadata.gz: 249d94c36b0d3d32bd9c7d06933aa5b886b3c73e
4
+ data.tar.gz: 7229efdede3aee37cca5da7ed160f378c5cee95e
5
5
  SHA512:
6
- metadata.gz: f4a69583c1cf3962a447cfb2e7e512de1046ca013aab9ebfffed46d87f4551dec1b9aacbcc9d04dc41c9fd61dcdad0383ffeecf8cb6d0818f781d04630b29893
7
- data.tar.gz: f47ff7d37744cfba9572aee11539710d886c4e1e7a6299f82a977d1e2d036a7398ad1d0b622fe0f456c355efe1fde94d4774e0493c91ba46202437c50f961351
6
+ metadata.gz: d46fb7bbec935080b31fe33d9219c5189c0a5d475e4be2329bed3ac502f0a195b6c320438470289aaacc643086f213a472cd38408e168ce20fc29b33225593d2
7
+ data.tar.gz: 5023b4965a27c1e60f52537ee8a2cc72eafbde90eab78f7bbbd3d71dff412034d86f4f53b638d74a7542357024e8d12943b71b1797e02de10573922a03178e05
data/lib/solargraph.rb CHANGED
@@ -1,28 +1,33 @@
1
- require 'solargraph/version'
2
- require 'rubygems/package'
3
- require 'yard-solargraph'
4
-
5
- module Solargraph
6
- autoload :Shell, 'solargraph/shell'
7
- autoload :Source, 'solargraph/source'
8
- autoload :ApiMap, 'solargraph/api_map'
9
- autoload :NodeMethods, 'solargraph/node_methods'
10
- autoload :Suggestion, 'solargraph/suggestion'
11
- autoload :Server, 'solargraph/server'
12
- autoload :YardMap, 'solargraph/yard_map'
13
- autoload :Pin, 'solargraph/pin'
14
- autoload :LiveMap, 'solargraph/live_map'
15
- autoload :ServerMethods, 'solargraph/server_methods'
16
- autoload :Plugin, 'solargraph/plugin'
17
- autoload :CoreFills, 'solargraph/core_fills'
18
- autoload :LanguageServer, 'solargraph/language_server'
19
- autoload :Workspace, 'solargraph/workspace'
20
- autoload :Page, 'solargraph/page'
21
- autoload :Library, 'solargraph/library'
22
- autoload :Diagnostics, 'solargraph/diagnostics'
23
-
24
- YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
25
- YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
26
- end
27
-
28
- Solargraph::YardMap::CoreDocs.require_minimum
1
+ require 'solargraph/version'
2
+ require 'rubygems/package'
3
+ require 'yard-solargraph'
4
+
5
+ module Solargraph
6
+ class InvalidOffsetError < RangeError; end
7
+ class DiagnosticsError < RuntimeError; end
8
+ class FileNotFoundError < Exception; end
9
+ class SourceNotAvailableError < StandardError; end
10
+ ''
11
+ autoload :Shell, 'solargraph/shell'
12
+ autoload :Source, 'solargraph/source'
13
+ autoload :ApiMap, 'solargraph/api_map'
14
+ autoload :NodeMethods, 'solargraph/node_methods'
15
+ autoload :Suggestion, 'solargraph/suggestion'
16
+ autoload :Server, 'solargraph/server'
17
+ autoload :YardMap, 'solargraph/yard_map'
18
+ autoload :Pin, 'solargraph/pin'
19
+ autoload :LiveMap, 'solargraph/live_map'
20
+ autoload :ServerMethods, 'solargraph/server_methods'
21
+ autoload :Plugin, 'solargraph/plugin'
22
+ autoload :CoreFills, 'solargraph/core_fills'
23
+ autoload :LanguageServer, 'solargraph/language_server'
24
+ autoload :Workspace, 'solargraph/workspace'
25
+ autoload :Page, 'solargraph/page'
26
+ autoload :Library, 'solargraph/library'
27
+ autoload :Diagnostics, 'solargraph/diagnostics'
28
+
29
+ YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
30
+ YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
31
+ end
32
+
33
+ Solargraph::YardMap::CoreDocs.require_minimum
@@ -1,1044 +1,997 @@
1
- require 'rubygems'
2
- require 'set'
3
- require 'time'
4
-
5
- module Solargraph
6
- class ApiMap
7
- autoload :Cache, 'solargraph/api_map/cache'
8
- autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
9
- autoload :Completion, 'solargraph/api_map/completion'
10
-
11
- include NodeMethods
12
- include Solargraph::ApiMap::SourceToYard
13
- include CoreFills
14
-
15
- # The workspace to analyze and process.
16
- #
17
- # @return [Solargraph::Workspace]
18
- attr_reader :workspace
19
-
20
- # @param workspace [Solargraph::Workspace]
21
- def initialize workspace = nil
22
- # @todo Deprecate strings for the workspace parameter
23
- workspace = Solargraph::Workspace.new(workspace) if workspace.kind_of?(String)
24
- workspace = Solargraph::Workspace.new(nil) if workspace.nil?
25
- @workspace = workspace
26
- require_extensions
27
- @virtual_source = nil
28
- @yard_stale = true
29
- process_maps
30
- yard_map
31
- end
32
-
33
- def self.load directory
34
- self.new(Solargraph::Workspace.new(directory))
35
- end
36
-
37
- # An array of required paths in the workspace.
38
- #
39
- # @return [Array<String>]
40
- def required
41
- @required ||= []
42
- end
43
-
44
- # Get a YardMap associated with the current workspace.
45
- #
46
- # @return [Solargraph::YardMap]
47
- def yard_map
48
- # refresh
49
- if @yard_map.nil? || @yard_map.required.to_set != required.to_set
50
- @yard_map = Solargraph::YardMap.new(required: required, workspace: workspace)
51
- end
52
- @yard_map
53
- end
54
-
55
- # Get a LiveMap associated with the current workspace.
56
- #
57
- # @return [Solargraph::LiveMap]
58
- def live_map
59
- @live_map ||= Solargraph::LiveMap.new(self)
60
- end
61
-
62
- # Declare a virtual source that will be included in the map regardless of
63
- # whether it's in the workspace.
64
- #
65
- # If the source is in the workspace, virtualizing it has no effect. Only
66
- # one source can be virtualized at a time.
67
- #
68
- # @param [Solargraph::Source]
69
- def virtualize source
70
- eliminate @virtual_source unless @virtual_source.nil?
71
- if workspace.has_source?(source)
72
- @sources = workspace.sources
73
- @virtual_source = nil
74
- else
75
- @virtual_source = source
76
- @sources = workspace.sources
77
- unless @virtual_source.nil?
78
- @sources.push @virtual_source
79
- process_virtual
80
- end
81
- end
82
- end
83
-
84
- # @todo Candidate for deprecation
85
- def append_source code, filename = nil
86
- source = Source.load_string(code, filename)
87
- virtualize source
88
- end
89
-
90
- # Refresh the ApiMap.
91
- #
92
- # @param force [Boolean] Perform a refresh even if the map is not "stale."
93
- def refresh force = false
94
- return unless @force or changed?
95
- if force
96
- process_maps
97
- else
98
- current_workspace_sources.reject{|s| workspace.sources.include?(s)}.each do |source|
99
- eliminate source
100
- end
101
- @sources = workspace.sources
102
- @sources.push @virtual_source unless @virtual_source.nil?
103
- cache.clear
104
- namespace_map.clear
105
- @sources.each do |s|
106
- s.namespace_nodes.each_pair do |k, v|
107
- namespace_map[k] ||= []
108
- namespace_map[k].concat v
109
- end
110
- end
111
- @sources.each do |source|
112
- if @stime.nil? or source.stime > @stime
113
- eliminate source
114
- map_source source
115
- end
116
- end
117
- end
118
- @stime = Time.new
119
- end
120
-
121
- # True if a workspace file has been created, modified, or deleted since
122
- # the last time the map was processed.
123
- #
124
- # @return [Boolean]
125
- def changed?
126
- return true if current_workspace_sources.length != workspace.sources.length
127
- return true if @stime.nil?
128
- return true if workspace.stime > @stime
129
- return true if !@virtual_source.nil? and @virtual_source.stime > @stime
130
- false
131
- end
132
-
133
- # Get the docstring associated with a node.
134
- #
135
- # @param node [AST::Node]
136
- # @return [YARD::Docstring]
137
- def get_docstring_for node
138
- source = get_source_for(node)
139
- return nil if source.nil?
140
- source.docstring_for(node)
141
- end
142
-
143
- # An array of suggestions based on Ruby keywords (`if`, `end`, etc.).
144
- #
145
- # @return [Array<Solargraph::Pin::Keyword>]
146
- def self.keywords
147
- @keyword_suggestions ||= KEYWORDS.map{ |s|
148
- Pin::Keyword.new(s)
149
- }.freeze
150
- end
151
-
152
- # An array of namespace names defined in the ApiMap.
153
- #
154
- # @return [Array<String>]
155
- def namespaces
156
- # refresh
157
- namespace_map.keys
158
- end
159
-
160
- # True if the namespace exists.
161
- #
162
- # @param name [String] The namespace to match
163
- # @param root [String] The context to search
164
- # @return [Boolean]
165
- def namespace_exists? name, root = ''
166
- !find_fully_qualified_namespace(name, root).nil?
167
- end
168
-
169
- # Get an array of constant pins defined in the ApiMap. (This method does
170
- # not include constants from external gems or the Ruby core.)
171
- #
172
- # @param namespace [String] The namespace to match
173
- # @param root [String] The context to search
174
- # @return [Array<Solargraph::Pin::Constant>]
175
- def get_constant_pins namespace, root
176
- fqns = find_fully_qualified_namespace(namespace, root)
177
- @const_pins[fqns] || []
178
- end
179
-
180
- # Get suggestions for constants in the specified namespace. The result
181
- # will include constant variables, classes, and modules.
182
- #
183
- # @param namespace [String] The namespace to match
184
- # @param context [String] The context to search
185
- # @return [Array<Solargraph::Pin::Base>]
186
- def get_constants namespace, context = ''
187
- namespace ||= ''
188
- skip = []
189
- result = []
190
- if context.empty?
191
- visi = [:public]
192
- visi.push :private if namespace.empty?
193
- result.concat inner_get_constants(namespace, visi, skip)
194
- else
195
- parts = context.split('::')
196
- until parts.empty?
197
- subcontext = parts.join('::')
198
- fqns = find_fully_qualified_namespace(namespace, subcontext)
199
- visi = [:public]
200
- visi.push :private if namespace.empty? and subcontext == context
201
- result.concat inner_get_constants(fqns, visi, skip)
202
- parts.pop
203
- end
204
- end
205
- # result.map{|pin| Suggestion.pull(pin)}
206
- result
207
- end
208
-
209
- def find_fully_qualified_type namespace_type, context_type = ''
210
- namespace, scope = extract_namespace_and_scope(namespace_type)
211
- context = extract_namespace(context_type)
212
- fqns = find_fully_qualified_namespace(namespace, context)
213
- subtypes = get_subtypes(namespace_type)
214
- fqns = "#{fqns}<#{subtypes.join(', ')}>" unless subtypes.empty?
215
- return fqns if scope == :instance
216
- type = get_namespace_type(fqns)
217
- "#{type == :class ? 'Class<' : 'Module<'}#{fqns}>"
218
- end
219
-
220
- # Get a fully qualified namespace name. This method will start the search
221
- # in the specified root until it finds a match for the name.
222
- #
223
- # @param name [String] The namespace to match
224
- # @param root [String] The context to search
225
- # @return [String]
226
- def find_fully_qualified_namespace name, root = '', skip = []
227
- # refresh
228
- return nil if name.nil?
229
- return nil if skip.include?(root)
230
- skip.push root
231
- if name == ''
232
- if root == ''
233
- return ''
234
- else
235
- return find_fully_qualified_namespace(root, '', skip)
236
- end
237
- else
238
- if (root == '')
239
- return name unless namespace_map[name].nil?
240
- im = @namespace_includes['']
241
- unless im.nil?
242
- im.each do |i|
243
- reroot = "#{root == '' ? '' : root + '::'}#{i}"
244
- recname = find_fully_qualified_namespace name.to_s, reroot, skip
245
- return recname unless recname.nil?
246
- end
247
- end
248
- else
249
- roots = root.to_s.split('::')
250
- while roots.length > 0
251
- fqns = roots.join('::') + '::' + name
252
- return fqns unless namespace_map[fqns].nil?
253
- roots.pop
254
- end
255
- return name unless namespace_map[name].nil?
256
- im = @namespace_includes['']
257
- unless im.nil?
258
- im.each do |i|
259
- recname = find_fully_qualified_namespace name, i, skip
260
- return recname unless recname.nil?
261
- end
262
- end
263
- end
264
- end
265
- result = yard_map.find_fully_qualified_namespace(name, root)
266
- if result.nil?
267
- result = live_map.get_fqns(name, root)
268
- end
269
- result
270
- end
271
-
272
- # Get an array of instance variable pins defined in specified namespace
273
- # and scope.
274
- #
275
- # @param namespace [String] A fully qualified namespace
276
- # @param scope [Symbol] :instance or :class
277
- # @return [Array<Solargraph::Pin::InstanceVariable>]
278
- def get_instance_variable_pins(namespace, scope = :instance)
279
- suggest_unique_variables((@ivar_pins[namespace] || []).select{ |pin| pin.scope == scope })
280
- end
281
-
282
- # Get an array of class variable pins for a namespace.
283
- #
284
- # @param namespace [String] A fully qualified namespace
285
- # @return [Array<Solargraph::Pin::ClassVariable>]
286
- def get_class_variable_pins(namespace)
287
- suggest_unique_variables(@cvar_pins[namespace] || [])
288
- end
289
-
290
- # @return [Array<Solargraph::Pin::Base>]
291
- def get_symbols
292
- # refresh
293
- @symbol_pins
294
- end
295
-
296
- # @return [String]
297
- def get_filename_for(node)
298
- @sources.each do |source|
299
- return source.filename if source.include?(node)
300
- end
301
- nil
302
- end
303
-
304
- # @return [Solargraph::Source]
305
- def get_source_for(node)
306
- return @virtual_source if !@virtual_source.nil? and @virtual_source.include?(node)
307
- @sources.each do |source|
308
- return source if source.include?(node)
309
- end
310
- nil
311
- end
312
-
313
- # @return [Array<Solargraph::Pin::GlobalVariable>]
314
- def get_global_variable_pins
315
- globals = []
316
- @sources.each do |s|
317
- globals.concat s.global_variable_pins
318
- end
319
- globals
320
- end
321
-
322
- def get_type_methods type, context = ''
323
- return [] if type.nil?
324
- namespace, scope = extract_namespace_and_scope(type)
325
- base = extract_namespace(context)
326
- fqns = find_fully_qualified_namespace(namespace, base)
327
- return [] if fqns.nil?
328
- visibility = [:public]
329
- visibility.push :private, :protected if fqns == base
330
- get_methods fqns, scope: scope, visibility: visibility
331
- end
332
-
333
- def get_methods fqns, scope: :instance, visibility: [:public], deep: true
334
- result = []
335
- if fqns == ''
336
- skip = []
337
- result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
338
- result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
339
- result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
340
- else
341
- result.concat inner_get_methods(fqns, scope, visibility, deep, [])
342
- end
343
- # result.each{|pin| pin.resolve(self)}
344
- result
345
- end
346
-
347
- # Get the return type for a signature within the specified namespace and
348
- # scope.
349
- #
350
- # @example
351
- # api_map.infer_signature_type('String.new', '') #=> 'String'
352
- #
353
- # @param signature [String]
354
- # @param namespace [String] A fully qualified namespace
355
- # @param scope [Symbol] :class or :instance
356
- # @return [String]
357
- def infer_signature_type signature, namespace, scope: :class, call_node: nil
358
- return nil if signature.start_with?('.')
359
- # inner_infer_signature_type signature, namespace, scope, call_node, true
360
- base, rest = signature.split('.', 2)
361
- if base == 'self'
362
- if rest.nil?
363
- combine_type(namespace, scope)
364
- else
365
- inner_infer_signature_type(rest, namespace, scope, call_node, false)
366
- end
367
- else
368
- pins = infer_signature_pins(base, namespace, scope, call_node)
369
- return nil if pins.empty?
370
- pin = pins.first
371
- if rest.nil?
372
- pin.resolve self
373
- pin.return_type
374
- elsif pin.signature.nil? or pin.signature.empty?
375
- if pin.path.nil?
376
- pin.resolve self
377
- fqtype = find_fully_qualified_type(pin.return_type, namespace)
378
- return nil if fqtype.nil?
379
- subns, subsc = extract_namespace_and_scope(fqtype)
380
- inner_infer_signature_type(rest, subns, subsc, call_node, true)
381
- else
382
- inner_infer_signature_type(rest, pin.path, scope, call_node, true)
383
- end
384
- else
385
- subtype = inner_infer_signature_type(pin.signature, namespace, scope, call_node, true)
386
- subns, subsc = extract_namespace_and_scope(subtype)
387
- inner_infer_signature_type(rest, subns, subsc, call_node, false)
388
- end
389
- end
390
- end
391
-
392
- # @return [ApiMap::Completion]
393
- def complete fragment
394
- return Completion.new([], fragment.whole_word_range) if fragment.string? or fragment.comment?
395
- result = []
396
- if fragment.base.empty?
397
- if fragment.signature.start_with?('@@')
398
- result.concat get_class_variable_pins(fragment.namespace)
399
- elsif fragment.signature.start_with?('@')
400
- result.concat get_instance_variable_pins(fragment.namespace, fragment.scope)
401
- elsif fragment.signature.start_with?('$')
402
- result.concat suggest_unique_variables(get_global_variable_pins)
403
- elsif fragment.signature.start_with?(':') and !fragment.signature.start_with?('::')
404
- result.concat get_symbols
405
- else
406
- unless fragment.signature.include?('::')
407
- result.concat suggest_unique_variables(fragment.local_variable_pins)
408
- result.concat get_type_methods(combine_type(fragment.namespace, fragment.scope), fragment.namespace)
409
- result.concat ApiMap.keywords
410
- end
411
- result.concat get_constants(fragment.base, fragment.namespace)
412
- end
413
- else
414
- if fragment.signature.include?('::') and !fragment.signature.include?('.')
415
- result.concat get_constants(fragment.base, fragment.namespace)
416
- else
417
- type = infer_signature_type(fragment.base, fragment.namespace, scope: fragment.scope, call_node: fragment.node)
418
- result.concat get_type_methods(type)
419
- end
420
- end
421
- 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] }
422
- Completion.new(filtered, fragment.whole_word_range)
423
- end
424
-
425
- def define fragment
426
- return [] if fragment.string? or fragment.comment?
427
- pins = infer_signature_pins fragment.whole_signature, fragment.namespace, fragment.scope, fragment.node
428
- return pins if pins.empty?
429
- if pins.first.variable?
430
- result = []
431
- pins.select{|pin| pin.variable?}.each do |pin|
432
- pin.resolve self
433
- result.concat infer_signature_pins(pin.return_type, fragment.namespace, fragment.scope, fragment.node)
434
- end
435
- result
436
- else
437
- pins.reject{|pin| pin.path.nil?}
438
- end
439
- end
440
-
441
- def signify fragment
442
- return [] unless fragment.argument?
443
- pins = infer_signature_pins(fragment.recipient.whole_signature, fragment.recipient.namespace, fragment.recipient.scope, fragment.recipient.node)
444
- # pins.each{|pin| pin.resolve self}
445
- pins
446
- end
447
-
448
- def infer_signature_pins signature, namespace, scope, call_node
449
- return [] if signature.nil? or signature.empty?
450
- base, rest = signature.split('.', 2)
451
- if base.start_with?('@@')
452
- pin = get_class_variable_pins(namespace).select{|pin| pin.name == base}.first
453
- return [] if pin.nil?
454
- return [pin] if rest.nil?
455
- fqns = find_fully_qualified_namespace(pin.return_type, namespace)
456
- return [] if fqns.nil?
457
- return inner_infer_signature_pins rest, namespace, scope, call_node, false
458
- elsif base.start_with?('@')
459
- pin = get_instance_variable_pins(namespace, scope).select{|pin| pin.name == base}.first
460
- return [] if pin.nil?
461
- pin.resolve self
462
- return [pin] if rest.nil?
463
- fqtype = find_fully_qualified_type(pin.return_type, namespace)
464
- return [] if fqtype.nil?
465
- subns, subsc = extract_namespace_and_scope(fqtype)
466
- return inner_infer_signature_pins rest, subns, subsc, call_node, false
467
- elsif base.start_with?('$')
468
- # @todo globals
469
- else
470
- type = find_fully_qualified_namespace(base, namespace)
471
- unless type.nil?
472
- if rest.nil?
473
- return get_path_suggestions(type)
474
- else
475
- return inner_infer_signature_pins rest, type, :class, call_node, false
476
- end
477
- end
478
- source = get_source_for(call_node)
479
- unless source.nil?
480
- lvpins = suggest_unique_variables(source.local_variable_pins.select{|pin| pin.name == base and pin.visible_from?(call_node)})
481
- unless lvpins.empty?
482
- if rest.nil?
483
- return lvpins
484
- else
485
- lvp = lvpins.first
486
- lvp.resolve self
487
- type = lvp.return_type
488
- unless type.nil?
489
- fqtype = find_fully_qualified_type(type, namespace)
490
- return [] if fqtype.nil?
491
- subns, subsc = extract_namespace_and_scope(fqtype)
492
- return inner_infer_signature_pins(rest, subns, subsc, call_node, false)
493
- end
494
- end
495
- end
496
- end
497
- return inner_infer_signature_pins signature, namespace, scope, call_node, true
498
- end
499
- end
500
-
501
- # Get the namespace's type (Class or Module).
502
- #
503
- # @param [String] A fully qualified namespace
504
- # @return [Symbol] :class, :module, or nil
505
- def get_namespace_type fqns
506
- pin = @namespace_path_pins[fqns]
507
- return yard_map.get_namespace_type(fqns) if pin.nil?
508
- pin.first.type
509
- end
510
-
511
- def combine_type namespace, scope
512
- if scope == :instance
513
- namespace
514
- else
515
- type = get_namespace_type(namespace)
516
- "#{type == :class ? 'Class' : 'Module'}<#{namespace}>"
517
- end
518
- end
519
-
520
- # Get an array of all suggestions that match the specified path.
521
- #
522
- # @param path [String] The path to find
523
- # @return [Array<Solargraph::Pin::Base>]
524
- def get_path_suggestions path
525
- return [] if path.nil?
526
- # refresh
527
- result = []
528
- if path.include?('#')
529
- # It's an instance method
530
- parts = path.split('#')
531
- result = get_methods(parts[0], visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
532
- elsif path.include?('.')
533
- # It's a class method
534
- parts = path.split('.')
535
- result = get_methods(parts[0], scope: :class, visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
536
- else
537
- # It's a class or module
538
- parts = path.split('::')
539
- np = @namespace_pins[parts[0..-2].join('::')]
540
- unless np.nil?
541
- result.concat np.select{|p| p.name == parts.last}
542
- end
543
- result.concat yard_map.objects(path)
544
- end
545
- # @todo Resolve the pins?
546
- result.map{|pin| pin.resolve(self); pin}
547
- # result
548
- end
549
-
550
- # Get a list of documented paths that match the query.
551
- #
552
- # @example
553
- # api_map.query('str') # Results will include `String` and `Struct`
554
- #
555
- # @param query [String] The text to match
556
- # @return [Array<String>]
557
- def search query
558
- # refresh
559
- rake_yard(@sources) if @yard_stale
560
- @yard_stale = false
561
- found = []
562
- code_object_paths.each do |k|
563
- if found.empty? or (query.include?('.') or query.include?('#')) or !(k.include?('.') or k.include?('#'))
564
- found.push k if k.downcase.include?(query.downcase)
565
- end
566
- end
567
- found.concat(yard_map.search(query)).uniq.sort
568
- end
569
-
570
- # Get YARD documentation for the specified path.
571
- #
572
- # @example
573
- # api_map.document('String#split')
574
- #
575
- # @param path [String] The path to find
576
- # @return [Array<YARD::CodeObject::Base>]
577
- def document path
578
- # refresh
579
- rake_yard(@sources) if @yard_stale
580
- @yard_stale = false
581
- docs = []
582
- docs.push code_object_at(path) unless code_object_at(path).nil?
583
- docs.concat yard_map.document(path)
584
- docs
585
- end
586
-
587
- def query_symbols query
588
- result = []
589
- @sources.each do |s|
590
- result.concat s.query_symbols(query)
591
- end
592
- result
593
- end
594
-
595
- def superclass_of fqns
596
- found = @superclasses[fqns]
597
- return nil if found.nil?
598
- find_fully_qualified_namespace(found, fqns)
599
- end
600
-
601
- def locate_pin location
602
- @sources.each do |source|
603
- pin = source.locate_pin(location)
604
- unless pin.nil?
605
- pin.resolve self
606
- return pin
607
- end
608
- end
609
- nil
610
- end
611
-
612
- private
613
-
614
- # @return [Hash]
615
- def namespace_map
616
- @namespace_map ||= {}
617
- end
618
-
619
- def process_maps
620
- @sources = workspace.sources
621
- @sources.push @virtual_source unless @virtual_source.nil?
622
- cache.clear
623
- @ivar_pins = {}
624
- @cvar_pins = {}
625
- @const_pins = {}
626
- @method_pins = {}
627
- @symbol_pins = []
628
- @attr_pins = {}
629
- @namespace_includes = {}
630
- @namespace_extends = {}
631
- @superclasses = {}
632
- @namespace_pins = {}
633
- @namespace_path_pins = {}
634
- namespace_map.clear
635
- @required = workspace.config.required.clone
636
- @sources.each do |s|
637
- s.namespace_nodes.each_pair do |k, v|
638
- namespace_map[k] ||= []
639
- namespace_map[k].concat v
640
- end
641
- end
642
- @sources.each do |s|
643
- map_source s
644
- end
645
- @required.uniq!
646
- live_map.refresh
647
- @yard_stale = true
648
- @stime = Time.now
649
- end
650
-
651
- def rebuild_local_yardoc
652
- return if workspace.nil? or !File.exist?(File.join(workspace, '.yardoc'))
653
- STDERR.puts "Rebuilding local yardoc for #{workspace}"
654
- Dir.chdir(workspace) { Process.spawn('yardoc') }
655
- end
656
-
657
- def process_virtual
658
- unless @virtual_source.nil?
659
- cache.clear
660
- namespace_map.clear
661
- @sources.each do |s|
662
- s.namespace_nodes.each_pair do |k, v|
663
- namespace_map[k] ||= []
664
- namespace_map[k].concat v
665
- end
666
- end
667
- map_source @virtual_source
668
- end
669
- end
670
-
671
- def eliminate source
672
- [@ivar_pins.values, @cvar_pins.values, @const_pins.values, @method_pins.values, @attr_pins.values, @namespace_pins.values].each do |pinsets|
673
- pinsets.each do |pins|
674
- pins.delete_if{|pin| pin.filename == source.filename}
675
- end
676
- end
677
- @symbol_pins.delete_if{|pin| pin.filename == source.filename}
678
- end
679
-
680
- # @param [Solargraph::Source]
681
- def map_source source
682
- source.method_pins.each do |pin|
683
- @method_pins[pin.namespace] ||= []
684
- @method_pins[pin.namespace].push pin
685
- end
686
- source.attribute_pins.each do |pin|
687
- @attr_pins[pin.namespace] ||= []
688
- @attr_pins[pin.namespace].push pin
689
- end
690
- source.instance_variable_pins.each do |pin|
691
- @ivar_pins[pin.namespace] ||= []
692
- @ivar_pins[pin.namespace].push pin
693
- end
694
- source.class_variable_pins.each do |pin|
695
- @cvar_pins[pin.namespace] ||= []
696
- @cvar_pins[pin.namespace].push pin
697
- end
698
- source.constant_pins.each do |pin|
699
- @const_pins[pin.namespace] ||= []
700
- @const_pins[pin.namespace].push pin
701
- end
702
- source.symbol_pins.each do |pin|
703
- @symbol_pins.push pin
704
- end
705
- source.namespace_includes.each_pair do |ns, i|
706
- @namespace_includes[ns || ''] ||= []
707
- @namespace_includes[ns || ''].concat(i).uniq!
708
- end
709
- source.namespace_extends.each_pair do |ns, e|
710
- @namespace_extends[ns || ''] ||= []
711
- @namespace_extends[ns || ''].concat(e).uniq!
712
- end
713
- source.superclasses.each_pair do |cls, sup|
714
- @superclasses[cls] = sup
715
- end
716
- source.namespace_pins.each do |pin|
717
- @namespace_path_pins[pin.path] ||= []
718
- @namespace_path_pins[pin.path].push pin
719
- @namespace_pins[pin.namespace] ||= []
720
- @namespace_pins[pin.namespace].push pin
721
- end
722
- path_macros.merge! source.path_macros
723
- source.required.each do |r|
724
- required.push r
725
- end
726
- end
727
-
728
- # @return [Solargraph::ApiMap::Cache]
729
- def cache
730
- @cache ||= Cache.new
731
- end
732
-
733
- def inner_get_methods fqns, scope, visibility, deep, skip
734
- reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
735
- return [] if skip.include?(reqstr)
736
- skip.push reqstr
737
- result = []
738
- if scope == :instance
739
- aps = @attr_pins[fqns]
740
- result.concat aps unless aps.nil?
741
- end
742
- mps = @method_pins[fqns]
743
- result.concat mps.select{|pin| (pin.scope == scope or fqns == '') and visibility.include?(pin.visibility)} unless mps.nil?
744
- if fqns != '' and scope == :class and !result.map(&:path).include?("#{fqns}.new")
745
- # Create a [Class].new method pin from [Class]#initialize
746
- init = inner_get_methods(fqns, :instance, [:private], deep, skip - [fqns]).select{|pin| pin.name == 'initialize'}.first
747
- unless init.nil?
748
- result.unshift Solargraph::Pin::Directed::Method.new(init.source, init.node, init.namespace, :class, :public, init.docstring, 'new', init.namespace)
749
- end
750
- end
751
- if deep
752
- sc = @superclasses[fqns]
753
- unless sc.nil?
754
- sc_visi = [:public]
755
- sc_visi.push :protected if visibility.include?(:protected)
756
- sc_fqns = find_fully_qualified_namespace(sc, fqns)
757
- result.concat inner_get_methods(sc_fqns, scope, sc_visi, true, skip)
758
- end
759
- if scope == :instance
760
- im = @namespace_includes[fqns]
761
- unless im.nil?
762
- im.each do |i|
763
- ifqns = find_fully_qualified_namespace(i, fqns)
764
- result.concat inner_get_methods(ifqns, scope, visibility, deep, skip)
765
- end
766
- end
767
- result.concat yard_map.get_instance_methods(fqns, visibility: visibility)
768
- result.concat inner_get_methods('Object', :instance, [:public], deep, skip) unless fqns == 'Object'
769
- else
770
- em = @namespace_extends[fqns]
771
- unless em.nil?
772
- em.each do |e|
773
- efqns = find_fully_qualified_namespace(e, fqns)
774
- result.concat inner_get_methods(efqns, :instance, visibility, deep, skip)
775
- end
776
- end
777
- result.concat yard_map.get_methods(fqns, '', visibility: visibility)
778
- type = get_namespace_type(fqns)
779
- if type == :class
780
- result.concat inner_get_methods('Class', :instance, [:public], deep, skip)
781
- else
782
- result.concat inner_get_methods('Module', :instance, [:public], deep, skip)
783
- end
784
- end
785
- end
786
- result
787
- end
788
-
789
- def inner_get_constants fqns, visibility, skip
790
- return [] if skip.include?(fqns)
791
- skip.push fqns
792
- result = []
793
- result.concat @const_pins[fqns] if @const_pins.has_key?(fqns)
794
- result.concat @namespace_pins[fqns] if @namespace_pins.has_key?(fqns)
795
- result.keep_if{|pin| visibility.include?(pin.visibility)}
796
- result.concat yard_map.get_constants(fqns)
797
- is = @namespace_includes[fqns]
798
- unless is.nil?
799
- is.each do |i|
800
- here = find_fully_qualified_namespace(i, fqns)
801
- result.concat inner_get_constants(here, [:public], skip)
802
- end
803
- end
804
- result
805
- end
806
-
807
- # Extract a namespace from a type.
808
- #
809
- # @example
810
- # extract_namespace('String') => 'String'
811
- # extract_namespace('Class<String>') => 'String'
812
- #
813
- # @return [String]
814
- def extract_namespace type
815
- extract_namespace_and_scope(type)[0]
816
- end
817
-
818
- # Extract a namespace and a scope (:instance or :class) from a type.
819
- #
820
- # @example
821
- # extract_namespace('String') #=> ['String', :instance]
822
- # extract_namespace('Class<String>') #=> ['String', :class]
823
- # extract_namespace('Module<Enumerable') #=> ['Enumberable', :class]
824
- #
825
- # @return [Array] The namespace (String) and scope (Symbol).
826
- def extract_namespace_and_scope type
827
- scope = :instance
828
- result = type.to_s.gsub(/<.*$/, '')
829
- if (result == 'Class' or result == 'Module') and type.include?('<')
830
- result = type.match(/<([a-z0-9:_]*)/i)[1]
831
- scope = :class
832
- end
833
- [result, scope]
834
- end
835
-
836
- def require_extensions
837
- Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
838
- STDERR.puts "Loading extension #{n}"
839
- require n.match(/^(solargraph\-[a-z0-9_\-]*?\-ext)\-[0-9\.]*$/)[1]
840
- end
841
- end
842
-
843
- # @return [Array<Solargraph::Pin::Base>]
844
- def suggest_unique_variables pins
845
- result = []
846
- nil_pins = []
847
- val_names = []
848
- pins.each do |pin|
849
- if pin.nil_assignment? and pin.return_type.nil?
850
- nil_pins.push pin
851
- else
852
- unless val_names.include?(pin.name)
853
- result.push pin
854
- val_names.push pin.name
855
- end
856
- end
857
- end
858
- nil_pins.reject{|p| val_names.include?(p.name)}.each do |pin|
859
- result.push pin
860
- end
861
- result
862
- end
863
-
864
- def source_file_mtime(filename)
865
- # @todo This is naively inefficient.
866
- @sources.each do |s|
867
- return s.mtime if s.filename == filename
868
- end
869
- nil
870
- end
871
-
872
- # @return [Array<Solargraph::Pin::Namespace>]
873
- def find_namespace_pins fqns
874
- set = nil
875
- if fqns.include?('::')
876
- set = @namespace_pins[fqns.split('::')[0..-2].join('::')]
877
- else
878
- set = @namespace_pins['']
879
- end
880
- return [] if set.nil?
881
- set.select{|p| p.path == fqns}
882
- end
883
-
884
- # @todo DRY this method. It's duplicated in CodeMap
885
- def get_subtypes type
886
- return [] if type.nil?
887
- match = type.match(/<([a-z0-9_:, ]*)>/i)
888
- return [] if match.nil?
889
- match[1].split(',').map(&:strip)
890
- end
891
-
892
- # @return [Hash]
893
- def path_macros
894
- @path_macros ||= {}
895
- end
896
-
897
- def get_call_arguments node
898
- return get_call_arguments(node.children[1]) if [:ivasgn, :cvasgn, :lvasgn].include?(node.type)
899
- return [] unless node.type == :send
900
- result = []
901
- node.children[2..-1].each do |c|
902
- result.push unpack_name(c)
903
- end
904
- result
905
- end
906
-
907
- # @todo This method shouldn't need to calculate the path. In fact, it should work directly off a pin.
908
- def get_return_type_from_macro namespace, signature, call_node, scope, visibility
909
- return nil if signature.empty? or signature.include?('.') or call_node.nil?
910
- path = "#{namespace}#{scope == :class ? '.' : '#'}#{signature}"
911
- macmeth = get_path_suggestions(path).first
912
- type = nil
913
- unless macmeth.nil?
914
- macmeths = Suggestion.pull(macmeth)
915
- macro = path_macros[macmeth.path]
916
- macro = macro.first unless macro.nil?
917
- # @todo Smelly respond_to? call
918
- if macro.nil? and macmeth.respond_to?(:code_object) and !macmeth.code_object.nil? and !macmeth.code_object.base_docstring.nil? and macmeth.code_object.base_docstring.all.include?('@!macro')
919
- all = YARD::Docstring.parser.parse(macmeth.code_object.base_docstring.all).directives
920
- macro = all.select{|m| m.tag.tag_name == 'macro'}.first
921
- end
922
- unless macro.nil?
923
- docstring = YARD::Docstring.parser.parse(macro.tag.text).to_docstring
924
- rt = docstring.tag(:return)
925
- unless rt.nil? or rt.types.nil? or call_node.nil?
926
- args = get_call_arguments(call_node)
927
- type = "#{args[rt.types[0][1..-1].to_i-1]}"
928
- end
929
- end
930
- end
931
- type
932
- end
933
-
934
- def inner_infer_signature_type signature, namespace, scope, call_node, top
935
- namespace ||= ''
936
- if cache.has_signature_type?(signature, namespace, scope)
937
- return cache.get_signature_type(signature, namespace, scope)
938
- end
939
- return nil if signature.nil?
940
- return namespace if signature.empty? and scope == :instance
941
- return nil if signature.empty? # @todo This might need to return Class<namespace>
942
- if !signature.include?('.')
943
- fqns = find_fully_qualified_namespace(signature, namespace)
944
- unless fqns.nil? or fqns.empty?
945
- type = (get_namespace_type(fqns) == :class ? 'Class' : 'Module')
946
- return "#{type}<#{fqns}>"
947
- end
948
- end
949
- result = nil
950
- parts = signature.split('.', 2)
951
- type = find_fully_qualified_namespace(parts[0], namespace)
952
- if type.nil?
953
- # It's a variable or method call
954
- if top and parts[0] == 'self'
955
- if parts[1].nil?
956
- result = namespace
957
- else
958
- return inner_infer_signature_type(parts[1], namespace, scope, call_node, false)
959
- end
960
- elsif parts[0] == 'new' and scope == :class
961
- scope = :instance
962
- if parts[1].nil?
963
- result = namespace
964
- else
965
- result = inner_infer_signature_type(parts[1], namespace, :instance, call_node, false)
966
- end
967
- else
968
- visibility = [:public]
969
- visibility.concat [:private, :protected] if top
970
- if scope == :instance || namespace == ''
971
- tmp = get_methods(extract_namespace(namespace), visibility: visibility)
972
- else
973
- tmp = get_methods(namespace, visibility: visibility, scope: :class)
974
- # tmp = get_type_methods(namespace, (top ? namespace : ''))
975
- end
976
- tmp.concat get_methods('Kernel', visibility: [:public]) if top
977
- matches = tmp.select{|s| s.name == parts[0]}
978
- return nil if matches.empty?
979
- matches.each do |m|
980
- type = get_return_type_from_macro(namespace, signature, call_node, scope, visibility)
981
- if type.nil?
982
- if METHODS_RETURNING_SELF.include?(m.path)
983
- type = curtype
984
- elsif METHODS_RETURNING_SUBTYPES.include?(m.path)
985
- subtypes = get_subtypes(namespace)
986
- type = subtypes[0]
987
- elsif !m.return_type.nil?
988
- if m.return_type == 'self'
989
- type = combine_type(namespace, scope)
990
- else
991
- type = m.return_type
992
- end
993
- end
994
- end
995
- break unless type.nil?
996
- end
997
- unless type.nil?
998
- scope = :instance
999
- if parts[1].nil?
1000
- result = type
1001
- else
1002
- subns, subsc = extract_namespace_and_scope(type)
1003
- result = inner_infer_signature_type(parts[1], subns, subsc, call_node, false)
1004
- end
1005
- end
1006
- end
1007
- else
1008
- return inner_infer_signature_type(parts[1], type, :class, call_node, false)
1009
- end
1010
- # @todo Assuming `self` only works at the top level
1011
- # result = type if result == 'self'
1012
- unless result.nil?
1013
- if scope == :class
1014
- nstype = get_namespace_type(result)
1015
- result = "#{nstype == :class ? 'Class<' : 'Module<'}#{result}>"
1016
- end
1017
- end
1018
- cache.set_signature_type signature, namespace, scope, result
1019
- result
1020
- end
1021
-
1022
- # @todo call_node might be superfluous here. We're already past looking for local variables.
1023
- def inner_infer_signature_pins signature, namespace, scope, call_node, top
1024
- base, rest = signature.split('.', 2)
1025
- type = nil
1026
- if rest.nil?
1027
- visibility = [:public]
1028
- visibility.push :private, :protected if top
1029
- methods = []
1030
- methods.concat get_methods(namespace, visibility: visibility, scope: scope).select{|pin| pin.name == base}
1031
- methods.concat get_methods('Kernel', scope: :instance).select{|pin| pin.name == base} if top
1032
- return methods
1033
- else
1034
- type = inner_infer_signature_type base, namespace, scope, call_node, top
1035
- nxt_ns, nxt_scope = extract_namespace_and_scope(type)
1036
- return inner_infer_signature_pins rest, nxt_ns, nxt_scope, call_node, false
1037
- end
1038
- end
1039
-
1040
- def current_workspace_sources
1041
- @sources - [@virtual_source]
1042
- end
1043
- end
1044
- end
1
+ require 'rubygems'
2
+ require 'set'
3
+ require 'time'
4
+
5
+ module Solargraph
6
+ class ApiMap
7
+ autoload :Cache, 'solargraph/api_map/cache'
8
+ autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
9
+ autoload :Completion, 'solargraph/api_map/completion'
10
+
11
+ include Solargraph::ApiMap::SourceToYard
12
+ include CoreFills
13
+
14
+ # The workspace to analyze and process.
15
+ #
16
+ # @return [Solargraph::Workspace]
17
+ attr_reader :workspace
18
+
19
+ # @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?
24
+ @workspace = workspace
25
+ require_extensions
26
+ @virtual_source = nil
27
+ @yard_stale = true
28
+ process_maps
29
+ yard_map
30
+ end
31
+
32
+ def self.load directory
33
+ self.new(Solargraph::Workspace.new(directory))
34
+ end
35
+
36
+ # An array of required paths in the workspace.
37
+ #
38
+ # @return [Array<String>]
39
+ def required
40
+ @required ||= []
41
+ end
42
+
43
+ # Get a YardMap associated with the current workspace.
44
+ #
45
+ # @return [Solargraph::YardMap]
46
+ def yard_map
47
+ # refresh
48
+ if @yard_map.nil? || @yard_map.required.to_set != required.to_set
49
+ @yard_map = Solargraph::YardMap.new(required: required, workspace: workspace)
50
+ end
51
+ @yard_map
52
+ end
53
+
54
+ # Get a LiveMap associated with the current workspace.
55
+ #
56
+ # @return [Solargraph::LiveMap]
57
+ def live_map
58
+ @live_map ||= Solargraph::LiveMap.new(self)
59
+ end
60
+
61
+ # Declare a virtual source that will be included in the map regardless of
62
+ # whether it's in the workspace.
63
+ #
64
+ # If the source is in the workspace, virtualizing it has no effect. Only
65
+ # one source can be virtualized at a time.
66
+ #
67
+ # @param source [Solargraph::Source]
68
+ def virtualize source
69
+ eliminate @virtual_source unless @virtual_source.nil?
70
+ if workspace.has_source?(source)
71
+ @sources = workspace.sources
72
+ @virtual_source = nil
73
+ else
74
+ @virtual_source = source
75
+ @sources = workspace.sources
76
+ unless @virtual_source.nil?
77
+ @sources.push @virtual_source
78
+ process_virtual
79
+ end
80
+ end
81
+ end
82
+
83
+ # @todo Candidate for deprecation
84
+ def append_source code, filename = nil
85
+ source = Source.load_string(code, filename)
86
+ virtualize source
87
+ end
88
+
89
+ # Refresh the ApiMap.
90
+ #
91
+ # @param force [Boolean] Perform a refresh even if the map is not "stale."
92
+ def refresh force = false
93
+ return unless @force or changed?
94
+ if force
95
+ process_maps
96
+ else
97
+ current_workspace_sources.reject{|s| workspace.sources.include?(s)}.each do |source|
98
+ eliminate source
99
+ end
100
+ @sources = workspace.sources
101
+ @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
116
+ end
117
+ @stime = Time.new
118
+ end
119
+
120
+ # True if a workspace file has been created, modified, or deleted since
121
+ # the last time the map was processed.
122
+ #
123
+ # @return [Boolean]
124
+ def changed?
125
+ return true if current_workspace_sources.length != workspace.sources.length
126
+ return true if @stime.nil?
127
+ return true if workspace.stime > @stime
128
+ return true if !@virtual_source.nil? and @virtual_source.stime > @stime
129
+ false
130
+ end
131
+
132
+ # An array of suggestions based on Ruby keywords (`if`, `end`, etc.).
133
+ #
134
+ # @return [Array<Solargraph::Pin::Keyword>]
135
+ def self.keywords
136
+ @keyword_suggestions ||= KEYWORDS.map{ |s|
137
+ Pin::Keyword.new(s)
138
+ }.freeze
139
+ end
140
+
141
+ # An array of namespace names defined in the ApiMap.
142
+ #
143
+ # @return [Array<String>]
144
+ def namespaces
145
+ # refresh
146
+ namespace_map.keys
147
+ end
148
+
149
+ # True if the namespace exists.
150
+ #
151
+ # @param name [String] The namespace to match
152
+ # @param root [String] The context to search
153
+ # @return [Boolean]
154
+ def namespace_exists? name, root = ''
155
+ !find_fully_qualified_namespace(name, root).nil?
156
+ end
157
+
158
+ # Get suggestions for constants in the specified namespace. The result
159
+ # may contain both constant and namespace pins.
160
+ #
161
+ # @param fqns [String] The fully qualified namespace
162
+ # @param visibility [Array<Symbol>] :public and/or :private
163
+ # @return [Array<Solargraph::Pin::Base>]
164
+ def get_constants namespace, context = ''
165
+ namespace ||= ''
166
+ skip = []
167
+ result = []
168
+ bases = context.split('::')
169
+ while bases.length > 0
170
+ built = bases.join('::')
171
+ fqns = find_fully_qualified_namespace(namespace, built)
172
+ visibility = [:public]
173
+ visibility.push :private if fqns == context
174
+ result.concat inner_get_constants(fqns, visibility, skip)
175
+ bases.pop
176
+ end
177
+ fqns = find_fully_qualified_namespace(namespace, '')
178
+ visibility = [:public]
179
+ visibility.push :private if fqns == context
180
+ result.concat inner_get_constants(fqns, visibility, skip)
181
+ result
182
+ end
183
+
184
+ # 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.
186
+ #
187
+ # @param name [String] The namespace to match
188
+ # @param root [String] The context to search
189
+ # @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
233
+ end
234
+
235
+ # Get an array of instance variable pins defined in specified namespace
236
+ # and scope.
237
+ #
238
+ # @param namespace [String] A fully qualified namespace
239
+ # @param scope [Symbol] :instance or :class
240
+ # @return [Array<Solargraph::Pin::InstanceVariable>]
241
+ 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 }
249
+ end
250
+
251
+ # Get an array of class variable pins for a namespace.
252
+ #
253
+ # @param namespace [String] A fully qualified namespace
254
+ # @return [Array<Solargraph::Pin::ClassVariable>]
255
+ def get_class_variable_pins(namespace)
256
+ prefer_non_nil_variables(@cvar_pins[namespace] || [])
257
+ end
258
+
259
+ # @return [Array<Solargraph::Pin::Base>]
260
+ 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
476
+ end
477
+
478
+ # @return [Array<Solargraph::Pin::GlobalVariable>]
479
+ def get_global_variable_pins
480
+ globals = []
481
+ @sources.each do |s|
482
+ globals.concat s.global_variable_pins
483
+ end
484
+ globals
485
+ end
486
+
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
+ def get_methods fqns, scope: :instance, visibility: [:public], deep: true
499
+ result = []
500
+ skip = []
501
+ if fqns == ''
502
+ result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
503
+ result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
504
+ result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
505
+ else
506
+ result.concat inner_get_methods(fqns, scope, visibility, deep, skip)
507
+ end
508
+ result
509
+ end
510
+
511
+ # @param fragment [Solargraph::Source::Fragment]
512
+ # @return [ApiMap::Completion]
513
+ def complete fragment
514
+ return Completion.new([], fragment.whole_word_range) if fragment.string? or fragment.comment? or fragment.calculated_signature.start_with?('.')
515
+ result = []
516
+ if !fragment.signature.include?('.')
517
+ if fragment.signature.start_with?('@@')
518
+ result.concat get_class_variable_pins(fragment.namespace)
519
+ elsif fragment.signature.start_with?('@')
520
+ result.concat get_instance_variable_pins(fragment.namespace, fragment.scope)
521
+ elsif fragment.signature.start_with?('$')
522
+ result.concat get_global_variable_pins
523
+ elsif fragment.signature.start_with?(':') and !fragment.signature.start_with?('::')
524
+ result.concat get_symbols
525
+ else
526
+ 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')
530
+ result.concat ApiMap.keywords
531
+ end
532
+ result.concat get_constants(fragment.base, fragment.namespace)
533
+ end
534
+ 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('.')
542
+ end
543
+ base = rest.shift
544
+ type = infer_word_type(base, fragment.namespace, scope: fragment.scope)
545
+ 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?
551
+ end
552
+ end
553
+ 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] }
555
+ Completion.new(filtered, fragment.whole_word_range)
556
+ end
557
+
558
+ # @param fragment [Solargraph::Source::Fragment]
559
+ # @return [Array<Solargraph::Pin::Base>]
560
+ def define fragment
561
+ 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
583
+ end
584
+
585
+ # @param fragment [Solargraph::Source::Fragment]
586
+ def signify fragment
587
+ return [] unless fragment.argument?
588
+ 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
630
+ end
631
+
632
+ # Get an array of all suggestions that match the specified path.
633
+ #
634
+ # @param path [String] The path to find
635
+ # @return [Array<Solargraph::Pin::Base>]
636
+ def get_path_suggestions path
637
+ return [] if path.nil?
638
+ # refresh
639
+ 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
660
+ end
661
+
662
+ # Get a list of documented paths that match the query.
663
+ #
664
+ # @example
665
+ # api_map.query('str') # Results will include `String` and `Struct`
666
+ #
667
+ # @param query [String] The text to match
668
+ # @return [Array<String>]
669
+ def search query
670
+ rake_yard(@sources) if @yard_stale
671
+ @yard_stale = false
672
+ found = []
673
+ code_object_paths.each do |k|
674
+ if found.empty? or (query.include?('.') or query.include?('#')) or !(k.include?('.') or k.include?('#'))
675
+ found.push k if k.downcase.include?(query.downcase)
676
+ end
677
+ end
678
+ found.concat(yard_map.search(query)).uniq.sort
679
+ end
680
+
681
+ # Get YARD documentation for the specified path.
682
+ #
683
+ # @example
684
+ # api_map.document('String#split')
685
+ #
686
+ # @param path [String] The path to find
687
+ # @return [Array<YARD::CodeObject::Base>]
688
+ def document path
689
+ rake_yard(@sources) if @yard_stale
690
+ @yard_stale = false
691
+ docs = []
692
+ docs.push code_object_at(path) unless code_object_at(path).nil?
693
+ docs.concat yard_map.document(path)
694
+ docs
695
+ end
696
+
697
+ def query_symbols query
698
+ result = []
699
+ @sources.each do |s|
700
+ result.concat s.query_symbols(query)
701
+ end
702
+ result
703
+ end
704
+
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
+ def locate_pin location
713
+ @sources.each do |source|
714
+ pin = source.locate_pin(location)
715
+ unless pin.nil?
716
+ pin.resolve self
717
+ return pin
718
+ end
719
+ end
720
+ nil
721
+ end
722
+
723
+ private
724
+
725
+ # @return [Hash]
726
+ def namespace_map
727
+ @namespace_map ||= {}
728
+ end
729
+
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
767
+
768
+ def process_virtual
769
+ 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
+ map_source @virtual_source
779
+ end
780
+ end
781
+
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
+ # @param [Solargraph::Source]
798
+ 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
844
+ path_macros.merge! source.path_macros
845
+ source.required.each do |r|
846
+ required.push r
847
+ end
848
+ end
849
+
850
+ # @return [Solargraph::ApiMap::Cache]
851
+ def cache
852
+ @cache ||= Cache.new
853
+ end
854
+
855
+ def inner_get_methods fqns, scope, visibility, deep, skip
856
+ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
857
+ return [] if skip.include?(reqstr)
858
+ skip.push reqstr
859
+ result = []
860
+ 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
872
+ end
873
+ if deep
874
+ scref = @superclasses[fqns]
875
+ unless scref.nil?
876
+ sc_visi = [:public]
877
+ 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?
881
+ end
882
+ 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
889
+ end
890
+ result.concat yard_map.get_instance_methods(fqns, visibility: visibility)
891
+ result.concat inner_get_methods('Object', :instance, [:public], deep, skip) unless fqns == 'Object'
892
+ 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
899
+ end
900
+ result.concat yard_map.get_methods(fqns, '', visibility: visibility)
901
+ type = get_namespace_type(fqns)
902
+ if type == :class
903
+ result.concat inner_get_methods('Class', :instance, [:public], deep, skip)
904
+ else
905
+ result.concat inner_get_methods('Module', :instance, [:public], deep, skip)
906
+ end
907
+ end
908
+ end
909
+ result
910
+ end
911
+
912
+ def inner_get_constants fqns, visibility, skip
913
+ return [] if skip.include?(fqns)
914
+ skip.push fqns
915
+ 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)}
919
+ 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
926
+ end
927
+ result
928
+ end
929
+
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
+ def require_extensions
960
+ Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
961
+ STDERR.puts "Loading extension #{n}"
962
+ require n.match(/^(solargraph\-[a-z0-9_\-]*?\-ext)\-[0-9\.]*$/)[1]
963
+ end
964
+ end
965
+
966
+ # @return [Array<Solargraph::Pin::Base>]
967
+ def prefer_non_nil_variables pins
968
+ result = []
969
+ nil_pins = []
970
+ pins.each do |pin|
971
+ if pin.nil_assignment? and pin.return_type.nil?
972
+ nil_pins.push pin
973
+ else
974
+ result.push pin
975
+ end
976
+ end
977
+ result + nil_pins
978
+ end
979
+
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
+ # @return [Hash]
989
+ def path_macros
990
+ @path_macros ||= {}
991
+ end
992
+
993
+ def current_workspace_sources
994
+ @sources - [@virtual_source]
995
+ end
996
+ end
997
+ end