solargraph 0.53.4 → 0.54.1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +14 -5
  5. data/lib/solargraph/api_map.rb +67 -24
  6. data/lib/solargraph/complex_type/type_methods.rb +70 -39
  7. data/lib/solargraph/complex_type/unique_type.rb +187 -73
  8. data/lib/solargraph/complex_type.rb +105 -40
  9. data/lib/solargraph/doc_map.rb +19 -3
  10. data/lib/solargraph/gem_pins.rb +9 -1
  11. data/lib/solargraph/language_server/host/dispatch.rb +8 -1
  12. data/lib/solargraph/language_server/host/message_worker.rb +29 -3
  13. data/lib/solargraph/language_server/host/sources.rb +1 -61
  14. data/lib/solargraph/language_server/host.rb +21 -68
  15. data/lib/solargraph/language_server/message/base.rb +1 -1
  16. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  17. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  18. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  19. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  20. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  21. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  22. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  23. data/lib/solargraph/language_server/progress.rb +135 -0
  24. data/lib/solargraph/language_server.rb +1 -0
  25. data/lib/solargraph/library.rb +144 -113
  26. data/lib/solargraph/location.rb +14 -1
  27. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  28. data/lib/solargraph/parser/node_processor.rb +1 -0
  29. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  30. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -7
  31. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -5
  32. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  34. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +53 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +6 -4
  36. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  37. data/lib/solargraph/parser.rb +2 -5
  38. data/lib/solargraph/pin/base.rb +15 -1
  39. data/lib/solargraph/pin/base_variable.rb +35 -6
  40. data/lib/solargraph/pin/block.rb +48 -11
  41. data/lib/solargraph/pin/callable.rb +147 -0
  42. data/lib/solargraph/pin/closure.rb +8 -3
  43. data/lib/solargraph/pin/common.rb +2 -6
  44. data/lib/solargraph/pin/conversions.rb +3 -2
  45. data/lib/solargraph/pin/delegated_method.rb +5 -1
  46. data/lib/solargraph/pin/documenting.rb +2 -0
  47. data/lib/solargraph/pin/instance_variable.rb +2 -2
  48. data/lib/solargraph/pin/method.rb +54 -32
  49. data/lib/solargraph/pin/namespace.rb +4 -4
  50. data/lib/solargraph/pin/parameter.rb +14 -39
  51. data/lib/solargraph/pin/proxy_type.rb +1 -1
  52. data/lib/solargraph/pin/signature.rb +3 -129
  53. data/lib/solargraph/pin.rb +4 -1
  54. data/lib/solargraph/range.rb +2 -4
  55. data/lib/solargraph/rbs_map/conversions.rb +79 -42
  56. data/lib/solargraph/rbs_map/core_fills.rb +6 -6
  57. data/lib/solargraph/rbs_map.rb +11 -3
  58. data/lib/solargraph/shell.rb +35 -15
  59. data/lib/solargraph/source/chain/array.rb +6 -5
  60. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  61. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  62. data/lib/solargraph/source/chain/call.rb +78 -50
  63. data/lib/solargraph/source/chain/link.rb +9 -0
  64. data/lib/solargraph/source/chain/or.rb +1 -1
  65. data/lib/solargraph/source/chain.rb +60 -16
  66. data/lib/solargraph/source/cursor.rb +13 -2
  67. data/lib/solargraph/source/updater.rb +1 -0
  68. data/lib/solargraph/source.rb +102 -129
  69. data/lib/solargraph/source_map/clip.rb +4 -4
  70. data/lib/solargraph/source_map/data.rb +30 -0
  71. data/lib/solargraph/source_map/mapper.rb +3 -2
  72. data/lib/solargraph/source_map.rb +37 -15
  73. data/lib/solargraph/type_checker/rules.rb +6 -1
  74. data/lib/solargraph/type_checker.rb +50 -25
  75. data/lib/solargraph/version.rb +1 -1
  76. data/lib/solargraph/views/environment.erb +3 -5
  77. data/lib/solargraph/workspace/config.rb +2 -1
  78. data/lib/solargraph/workspace.rb +13 -0
  79. metadata +6 -3
  80. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require 'observer'
4
5
 
5
6
  module Solargraph
6
7
  # A Library handles coordination between a Workspace and an ApiMap.
7
8
  #
8
9
  class Library
9
10
  include Logging
11
+ include Observable
10
12
 
11
13
  # @return [Solargraph::Workspace]
12
14
  attr_reader :workspace
@@ -17,12 +19,19 @@ module Solargraph
17
19
  # @return [Source, nil]
18
20
  attr_reader :current
19
21
 
22
+ # @return [LanguageServer::Progress, nil]
23
+ attr_reader :cache_progress
24
+
20
25
  # @param workspace [Solargraph::Workspace]
21
26
  # @param name [String, nil]
22
27
  def initialize workspace = Solargraph::Workspace.new, name = nil
23
28
  @workspace = workspace
24
29
  @name = name
25
- @synchronized = false
30
+ # @type [Integer, nil]
31
+ @total = nil
32
+ # @type [Source, nil]
33
+ @current = nil
34
+ @sync_count = 0
26
35
  end
27
36
 
28
37
  def inspect
@@ -35,7 +44,7 @@ module Solargraph
35
44
  #
36
45
  # @return [Boolean]
37
46
  def synchronized?
38
- @synchronized
47
+ @sync_count < 2
39
48
  end
40
49
 
41
50
  # Attach a source to the library.
@@ -47,17 +56,15 @@ module Solargraph
47
56
  # @param source [Source, nil]
48
57
  # @return [void]
49
58
  def attach source
50
- mutex.synchronize do
51
- if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
52
- source_map_hash.delete @current.filename
53
- source_map_external_require_hash.delete @current.filename
54
- @external_requires = nil
55
- @synchronized = false
56
- end
57
- @current = source
58
- maybe_map @current
59
- catalog_inlock
59
+ if @current && (!source || @current.filename != source.filename) && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
60
+ source_map_hash.delete @current.filename
61
+ source_map_external_require_hash.delete @current.filename
62
+ @external_requires = nil
60
63
  end
64
+ changed = source && @current != source
65
+ @current = source
66
+ maybe_map @current
67
+ catalog if changed
61
68
  end
62
69
 
63
70
  # True if the specified file is currently attached.
@@ -95,15 +102,10 @@ module Solargraph
95
102
  # @param text [String] The contents of the file
96
103
  # @return [Boolean] True if the file was added to the workspace.
97
104
  def create filename, text
98
- result = false
99
- mutex.synchronize do
100
- next unless contain?(filename) || open?(filename)
101
- @synchronized = false
102
- source = Solargraph::Source.load_string(text, filename)
103
- workspace.merge(source)
104
- result = true
105
- end
106
- result
105
+ return false unless contain?(filename) || open?(filename)
106
+ source = Solargraph::Source.load_string(text, filename)
107
+ workspace.merge(source)
108
+ true
107
109
  end
108
110
 
109
111
  # Create file sources from files on disk. A file is ignored if it is
@@ -112,14 +114,11 @@ module Solargraph
112
114
  # @param filenames [Array<String>]
113
115
  # @return [Boolean] True if at least one file was added to the workspace.
114
116
  def create_from_disk *filenames
115
- result = false
116
- mutex.synchronize do
117
- sources = filenames
118
- .reject { |filename| File.directory?(filename) || !File.exist?(filename) }
119
- .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) }
120
- result = workspace.merge(*sources)
121
- sources.each { |source| maybe_map source }
122
- end
117
+ sources = filenames
118
+ .reject { |filename| File.directory?(filename) || !File.exist?(filename) }
119
+ .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) }
120
+ result = workspace.merge(*sources)
121
+ sources.each { |source| maybe_map source }
123
122
  result
124
123
  end
125
124
 
@@ -133,10 +132,7 @@ module Solargraph
133
132
  result = false
134
133
  filenames.each do |filename|
135
134
  detach filename
136
- mutex.synchronize do
137
- result ||= workspace.remove(filename)
138
- @synchronized = !result if synchronized?
139
- end
135
+ result ||= workspace.remove(filename)
140
136
  end
141
137
  result
142
138
  end
@@ -147,11 +143,10 @@ module Solargraph
147
143
  # @param filename [String]
148
144
  # @return [void]
149
145
  def close filename
150
- mutex.synchronize do
151
- @synchronized = false
152
- @current = nil if @current && @current.filename == filename
153
- catalog
154
- end
146
+ return unless @current&.filename == filename
147
+
148
+ @current = nil
149
+ catalog unless workspace.has_file?(filename)
155
150
  end
156
151
 
157
152
  # Get completion suggestions at the specified file and location.
@@ -162,9 +157,10 @@ module Solargraph
162
157
  # @return [SourceMap::Completion, nil]
163
158
  # @todo Take a Location instead of filename/line/column
164
159
  def completions_at filename, line, column
160
+ sync_catalog
165
161
  position = Position.new(line, column)
166
162
  cursor = Source::Cursor.new(read(filename), position)
167
- api_map.clip(cursor).complete
163
+ mutex.synchronize { api_map.clip(cursor).complete }
168
164
  rescue FileNotFoundError => e
169
165
  handle_file_not_found filename, e
170
166
  end
@@ -178,6 +174,7 @@ module Solargraph
178
174
  # @return [Array<Solargraph::Pin::Base>, nil]
179
175
  # @todo Take filename/position instead of filename/line/column
180
176
  def definitions_at filename, line, column
177
+ sync_catalog
181
178
  position = Position.new(line, column)
182
179
  cursor = Source::Cursor.new(read(filename), position)
183
180
  if cursor.comment?
@@ -187,13 +184,20 @@ module Solargraph
187
184
  rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i)
188
185
  if lft && rgt
189
186
  tag = (lft[1] + rgt[1]).sub(/:+$/, '')
190
- clip = api_map.clip(cursor)
187
+ clip = mutex.synchronize { api_map.clip(cursor) }
191
188
  clip.translate tag
192
189
  else
193
190
  []
194
191
  end
195
192
  else
196
- api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
193
+ mutex.synchronize do
194
+ clip = api_map.clip(cursor)
195
+ if cursor.assign?
196
+ [Pin::ProxyType.new(name: cursor.word, return_type: clip.infer)]
197
+ else
198
+ clip.define.map { |pin| pin.realize(api_map) }
199
+ end
200
+ end
197
201
  end
198
202
  rescue FileNotFoundError => e
199
203
  handle_file_not_found(filename, e)
@@ -208,9 +212,10 @@ module Solargraph
208
212
  # @return [Array<Solargraph::Pin::Base>, nil]
209
213
  # @todo Take filename/position instead of filename/line/column
210
214
  def type_definitions_at filename, line, column
215
+ sync_catalog
211
216
  position = Position.new(line, column)
212
217
  cursor = Source::Cursor.new(read(filename), position)
213
- api_map.clip(cursor).types
218
+ mutex.synchronize { api_map.clip(cursor).types }
214
219
  rescue FileNotFoundError => e
215
220
  handle_file_not_found filename, e
216
221
  end
@@ -224,9 +229,10 @@ module Solargraph
224
229
  # @return [Array<Solargraph::Pin::Base>]
225
230
  # @todo Take filename/position instead of filename/line/column
226
231
  def signatures_at filename, line, column
232
+ sync_catalog
227
233
  position = Position.new(line, column)
228
234
  cursor = Source::Cursor.new(read(filename), position)
229
- api_map.clip(cursor).signify
235
+ mutex.synchronize { api_map.clip(cursor).signify }
230
236
  end
231
237
 
232
238
  # @param filename [String]
@@ -237,8 +243,9 @@ module Solargraph
237
243
  # @return [Array<Solargraph::Range>]
238
244
  # @todo Take a Location instead of filename/line/column
239
245
  def references_from filename, line, column, strip: false, only: false
240
- cursor = api_map.cursor_at(filename, Position.new(line, column))
241
- clip = api_map.clip(cursor)
246
+ sync_catalog
247
+ cursor = Source::Cursor.new(read(filename), [line, column])
248
+ clip = mutex.synchronize { api_map.clip(cursor) }
242
249
  pin = clip.define.first
243
250
  return [] unless pin
244
251
  result = []
@@ -283,7 +290,8 @@ module Solargraph
283
290
  # @param location [Location]
284
291
  # @return [Array<Solargraph::Pin::Base>]
285
292
  def locate_pins location
286
- api_map.locate_pins(location).map { |pin| pin.realize(api_map) }
293
+ sync_catalog
294
+ mutex.synchronize { api_map.locate_pins(location).map { |pin| pin.realize(api_map) } }
287
295
  end
288
296
 
289
297
  # Match a require reference to a file.
@@ -316,19 +324,22 @@ module Solargraph
316
324
  # @param path [String]
317
325
  # @return [Enumerable<Solargraph::Pin::Base>]
318
326
  def get_path_pins path
319
- api_map.get_path_suggestions(path)
327
+ sync_catalog
328
+ mutex.synchronize { api_map.get_path_suggestions(path) }
320
329
  end
321
330
 
322
331
  # @param query [String]
323
332
  # @return [Enumerable<YARD::CodeObjects::Base>]
324
333
  def document query
325
- api_map.document query
334
+ sync_catalog
335
+ mutex.synchronize { api_map.document query }
326
336
  end
327
337
 
328
338
  # @param query [String]
329
339
  # @return [Array<String>]
330
340
  def search query
331
- api_map.search query
341
+ sync_catalog
342
+ mutex.synchronize { api_map.search query }
332
343
  end
333
344
 
334
345
  # Get an array of all symbols in the workspace that match the query.
@@ -336,7 +347,8 @@ module Solargraph
336
347
  # @param query [String]
337
348
  # @return [Array<Pin::Base>]
338
349
  def query_symbols query
339
- api_map.query_symbols query
350
+ sync_catalog
351
+ mutex.synchronize { api_map.query_symbols query }
340
352
  end
341
353
 
342
354
  # Get an array of document symbols.
@@ -348,13 +360,15 @@ module Solargraph
348
360
  # @param filename [String]
349
361
  # @return [Array<Solargraph::Pin::Base>]
350
362
  def document_symbols filename
351
- api_map.document_symbols(filename)
363
+ sync_catalog
364
+ mutex.synchronize { api_map.document_symbols(filename) }
352
365
  end
353
366
 
354
367
  # @param path [String]
355
368
  # @return [Enumerable<Solargraph::Pin::Base>]
356
369
  def path_pins path
357
- api_map.get_path_suggestions(path)
370
+ sync_catalog
371
+ mutex.synchronize { api_map.get_path_suggestions(path) }
358
372
  end
359
373
 
360
374
  # @return [Array<SourceMap>]
@@ -380,10 +394,10 @@ module Solargraph
380
394
  # everything in the workspace should get diagnosed, or if there should
381
395
  # be an option to do so.
382
396
  #
397
+ sync_catalog
383
398
  return [] unless open?(filename)
384
399
  result = []
385
400
  source = read(filename)
386
- catalog
387
401
  repargs = {}
388
402
  workspace.config.reporters.each do |line|
389
403
  if line == 'all!'
@@ -409,21 +423,7 @@ module Solargraph
409
423
  #
410
424
  # @return [void]
411
425
  def catalog
412
- mutex.synchronize do
413
- catalog_inlock
414
- end
415
- end
416
-
417
- # @return [void]
418
- private def catalog_inlock
419
- return if synchronized?
420
-
421
- logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
422
- api_map.catalog bench
423
- @synchronized = true
424
- logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
425
- logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
426
- cache_next_gemspec
426
+ @sync_count += 1
427
427
  end
428
428
 
429
429
  # @return [Bench]
@@ -461,13 +461,8 @@ module Solargraph
461
461
  # @param source [Source]
462
462
  # @return [Boolean] True if the source was merged into the workspace.
463
463
  def merge source
464
- Logging.logger.debug "Merging source: #{source.filename}"
465
- result = false
466
- mutex.synchronize do
467
- result = workspace.merge(source)
468
- maybe_map source
469
- end
470
- # catalog
464
+ result = workspace.merge(source)
465
+ maybe_map source
471
466
  result
472
467
  end
473
468
 
@@ -483,17 +478,13 @@ module Solargraph
483
478
  # @return [SourceMap, Boolean]
484
479
  def next_map
485
480
  return false if mapped?
486
- mutex.synchronize do
487
- @synchronized = false
488
- src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
489
- if src
490
- Logging.logger.debug "Mapping #{src.filename}"
491
- source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
492
- find_external_requires(source_map_hash[src.filename])
493
- source_map_hash[src.filename]
494
- else
495
- false
496
- end
481
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
482
+ if src
483
+ Logging.logger.debug "Mapping #{src.filename}"
484
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
485
+ source_map_hash[src.filename]
486
+ else
487
+ false
497
488
  end
498
489
  end
499
490
 
@@ -501,7 +492,7 @@ module Solargraph
501
492
  def map!
502
493
  workspace.sources.each do |src|
503
494
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
504
- find_external_requires(source_map_hash[src.filename])
495
+ find_external_requires source_map_hash[src.filename]
505
496
  end
506
497
  self
507
498
  end
@@ -575,30 +566,16 @@ module Solargraph
575
566
  end
576
567
  end
577
568
 
578
- # @param source [Source]
569
+ # @param source [Source, nil]
579
570
  # @return [void]
580
571
  def maybe_map source
581
572
  return unless source
582
573
  return unless @current == source || workspace.has_file?(source.filename)
583
574
  if source_map_hash.key?(source.filename)
584
- return if source_map_hash[source.filename].code == source.code &&
585
- source_map_hash[source.filename].source.synchronized? &&
586
- source.synchronized?
587
- if source.synchronized?
588
- new_map = Solargraph::SourceMap.map(source)
589
- unless source_map_hash[source.filename].try_merge!(new_map)
590
- source_map_hash[source.filename] = new_map
591
- find_external_requires(source_map_hash[source.filename])
592
- @synchronized = false
593
- end
594
- else
595
- # @todo Smelly instance variable access
596
- source_map_hash[source.filename].instance_variable_set(:@source, source)
597
- end
575
+ new_map = Solargraph::SourceMap.map(source)
576
+ source_map_hash[source.filename] = new_map
598
577
  else
599
578
  source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
600
- find_external_requires(source_map_hash[source.filename])
601
- @synchronized = false
602
579
  end
603
580
  end
604
581
 
@@ -609,24 +586,78 @@ module Solargraph
609
586
 
610
587
  # @return [void]
611
588
  def cache_next_gemspec
612
- return if @cache_pid
613
- spec = api_map.uncached_gemspecs.find { |spec| !cache_errors.include?(spec)}
614
- return unless spec
589
+ return if @cache_progress
590
+ spec = api_map.uncached_gemspecs.find { |spec| !cache_errors.include?(spec) }
591
+ return end_cache_progress unless spec
615
592
 
593
+ pending = api_map.uncached_gemspecs.length - cache_errors.length - 1
616
594
  logger.info "Caching #{spec.name} #{spec.version}"
617
595
  Thread.new do
618
- @cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
619
- Process.wait(@cache_pid)
596
+ cache_pid = Process.spawn(workspace.command_path, 'cache', spec.name, spec.version.to_s)
597
+ report_cache_progress spec.name, pending
598
+ Process.wait(cache_pid)
620
599
  logger.info "Cached #{spec.name} #{spec.version}"
621
- @synchronized = false
622
- rescue Errno::EINVAL => e
600
+ rescue Errno::EINVAL => _e
623
601
  logger.info "Cached #{spec.name} #{spec.version} with EINVAL"
624
- @synchronized = false
625
602
  rescue StandardError => e
626
603
  cache_errors.add spec
627
604
  Solargraph.logger.warn "Error caching gemspec #{spec.name} #{spec.version}: [#{e.class}] #{e.message}"
628
605
  ensure
629
- @cache_pid = nil
606
+ end_cache_progress
607
+ catalog
608
+ sync_catalog
609
+ end
610
+ end
611
+
612
+ # @param gem_name [String]
613
+ # @param pending [Integer]
614
+ # @return [void]
615
+ def report_cache_progress gem_name, pending
616
+ @total ||= pending
617
+ @total = pending if pending > @total
618
+ finished = @total - pending
619
+ pct = if @total.zero?
620
+ 0
621
+ else
622
+ ((finished.to_f / @total.to_f) * 100).to_i
623
+ end
624
+ message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}"
625
+ # "
626
+ if @cache_progress
627
+ @cache_progress.report(message, pct)
628
+ else
629
+ @cache_progress = LanguageServer::Progress.new('Caching gem')
630
+ # If we don't send both a begin and a report, the progress notification
631
+ # might get stuck in the status bar forever
632
+ @cache_progress.begin(message, pct)
633
+ changed
634
+ notify_observers @cache_progress
635
+ @cache_progress.report(message, pct)
636
+ end
637
+ changed
638
+ notify_observers @cache_progress
639
+ end
640
+
641
+ # @return [void]
642
+ def end_cache_progress
643
+ changed if @cache_progress&.finish('done')
644
+ notify_observers @cache_progress
645
+ @cache_progress = nil
646
+ @total = nil
647
+ end
648
+
649
+ def sync_catalog
650
+ return if @sync_count == 0
651
+
652
+ mutex.synchronize do
653
+ logger.warn "CATALOG"
654
+ logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
655
+ api_map.catalog bench
656
+ source_map_hash.values.each { |map| find_external_requires(map) }
657
+ logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
658
+ logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
659
+ cache_next_gemspec
660
+ @sync_count = 0
630
661
  end
631
662
  end
632
663
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- # A section of text identified by its filename and range.
4
+ # A pointer to a section of source text identified by its filename
5
+ # and Range.
5
6
  #
6
7
  class Location
7
8
  # @return [String]
@@ -17,6 +18,11 @@ module Solargraph
17
18
  @range = range
18
19
  end
19
20
 
21
+ # @param location [self]
22
+ def contain? location
23
+ range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename
24
+ end
25
+
20
26
  # @return [Hash]
21
27
  def to_hash
22
28
  {
@@ -25,6 +31,13 @@ module Solargraph
25
31
  }
26
32
  end
27
33
 
34
+ # @param node [Parser::AST::Node, nil]
35
+ def self.from_node(node)
36
+ return nil if node.nil? || node.loc.nil?
37
+ range = Range.from_node(node)
38
+ self.new(node.loc.expression.source_buffer.name, range)
39
+ end
40
+
28
41
  # @param other [BasicObject]
29
42
  def == other
30
43
  return false unless other.is_a?(Location)
@@ -13,7 +13,7 @@ module Solargraph
13
13
  # @return [Array<Pin::Base>]
14
14
  attr_reader :pins
15
15
 
16
- # @return [Array<Pin::Base>]
16
+ # @return [Array<Pin::BaseVariable>]
17
17
  attr_reader :locals
18
18
 
19
19
  # @param node [Parser::AST::Node]
@@ -62,7 +62,7 @@ module Solargraph
62
62
  end
63
63
 
64
64
  # @param position [Solargraph::Position]
65
- # @return [Pin::Base, nil]
65
+ # @return [Pin::Closure, nil]
66
66
  def named_path_pin position
67
67
  pins.select{|pin| pin.is_a?(Pin::Closure) && pin.path && !pin.path.empty? && pin.location.range.contain?(position)}.last
68
68
  end
@@ -71,6 +71,7 @@ module Solargraph
71
71
  # @param position [Solargraph::Position]
72
72
  # @return [Pin::Closure, nil]
73
73
  def block_pin position
74
+ # @todo determine if this can return a Pin::Block
74
75
  pins.select{|pin| pin.is_a?(Pin::Closure) && pin.location.range.contain?(position)}.last
75
76
  end
76
77
 
@@ -24,6 +24,7 @@ module Solargraph
24
24
  # @param node [Parser::AST::Node]
25
25
  # @param region [Region]
26
26
  # @param pins [Array<Pin::Base>]
27
+ # @param locals [Array<Pin::BaseVariable>]
27
28
  # @return [Array(Array<Pin::Base>, Array<Pin::Base>)]
28
29
  def self.process node, region = Region.new, pins = [], locals = []
29
30
  if pins.empty?
@@ -48,12 +48,6 @@ module Solargraph
48
48
  NodeProcessor.process(source.node, Region.new(source: source))
49
49
  end
50
50
 
51
- # @param node [Parser::AST::Node]
52
- # @return [Array<Parser::AST::Node>]
53
- def returns_from node
54
- NodeMethods.returns_from(node)
55
- end
56
-
57
51
  # @param source [Source]
58
52
  # @param name [String]
59
53
  # @return [Array<Location>]
@@ -62,10 +56,12 @@ module Solargraph
62
56
  reg = /#{Regexp.escape name[0..-2]}\s*=/
63
57
  # @param code [String]
64
58
  # @param offset [Integer]
59
+ # @return [Array(Integer, Integer), Array(nil, nil)]
65
60
  extract_offset = ->(code, offset) { reg.match(code, offset).offset(0) }
66
61
  else
67
62
  # @param code [String]
68
63
  # @param offset [Integer]
64
+ # @return [Array(Integer, Integer), Array(nil, nil)]
69
65
  extract_offset = ->(code, offset) { [soff = code.index(name, offset), soff + name.length] }
70
66
  end
71
67
  inner_node_references(name, source.node).map do |n|
@@ -115,7 +111,7 @@ module Solargraph
115
111
  NodeMethods.infer_literal_node_type node
116
112
  end
117
113
 
118
- # @return [void]
114
+ # @return [Integer]
119
115
  def version
120
116
  parser.version
121
117
  end
@@ -89,15 +89,21 @@ module Solargraph
89
89
  elsif n.type == :const
90
90
  const = unpack_name(n)
91
91
  result.push Chain::Constant.new(const)
92
- elsif [:lvar, :lvasgn].include?(n.type)
92
+ elsif [:lvasgn, :ivasgn, :gvasgn, :cvasgn].include?(n.type)
93
+ result.concat generate_links(n.children[1])
94
+ elsif n.type == :lvar
93
95
  result.push Chain::Call.new(n.children[0].to_s)
94
- elsif [:ivar, :ivasgn].include?(n.type)
95
- result.push Chain::InstanceVariable.new(n.children[0].to_s)
96
- elsif [:cvar, :cvasgn].include?(n.type)
97
- result.push Chain::ClassVariable.new(n.children[0].to_s)
98
- elsif [:gvar, :gvasgn].include?(n.type)
99
- result.push Chain::GlobalVariable.new(n.children[0].to_s)
96
+ elsif n.type == :ivar
97
+ result.push Chain::InstanceVariable.new(n.children[0].to_s)
98
+ elsif n.type == :cvar
99
+ result.push Chain::ClassVariable.new(n.children[0].to_s)
100
+ elsif n.type == :gvar
101
+ result.push Chain::GlobalVariable.new(n.children[0].to_s)
100
102
  elsif n.type == :or_asgn
103
+ # @todo: Need a new Link class here that evaluates the
104
+ # existing variable type with the RHS, and generates a
105
+ # union type of the LHS alone if never nil, or minus nil +
106
+ # RHS if it is nilable.
101
107
  result.concat generate_links n.children[1]
102
108
  elsif [:class, :module, :def, :defs].include?(n.type)
103
109
  # @todo Undefined or what?
@@ -181,10 +181,6 @@ module Solargraph
181
181
  elsif [:super, :zsuper].include?(node.type)
182
182
  result.push node
183
183
  node.children.each { |child| result.concat call_nodes_from(child) }
184
- elsif node.type == :masgn
185
- # @todo We're treating a mass assignment as a call node, but the
186
- # type checker still needs the logic to handle it.
187
- result.push node
188
184
  else
189
185
  node.children.each { |child| result.concat call_nodes_from(child) }
190
186
  end
@@ -315,7 +311,7 @@ module Solargraph
315
311
  # statements in value positions.
316
312
  module DeepInference
317
313
  class << self
318
- CONDITIONAL_ALL_BUT_FIRST = [:if, :unless, :or_asgn]
314
+ CONDITIONAL_ALL_BUT_FIRST = [:if, :unless]
319
315
  CONDITIONAL_ALL = [:or]
320
316
  ONLY_ONE_CHILD = [:return]
321
317
  FIRST_TWO_CHILDREN = [:rescue]