solargraph 0.18.2 → 0.18.3

Sign up to get free protection for your applications and to get access to all the features.
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