solargraph 0.40.3 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -5
  3. data/CHANGELOG.md +34 -0
  4. data/README.md +15 -0
  5. data/SPONSORS.md +1 -0
  6. data/lib/.rubocop.yml +4 -3
  7. data/lib/solargraph.rb +8 -7
  8. data/lib/solargraph/api_map.rb +40 -111
  9. data/lib/solargraph/api_map/store.rb +5 -0
  10. data/lib/solargraph/bench.rb +13 -16
  11. data/lib/solargraph/compat.rb +15 -1
  12. data/lib/solargraph/diagnostics/rubocop.rb +10 -2
  13. data/lib/solargraph/diagnostics/rubocop_helpers.rb +18 -0
  14. data/lib/solargraph/diagnostics/type_check.rb +1 -1
  15. data/lib/solargraph/language_server/host.rb +108 -7
  16. data/lib/solargraph/language_server/host/diagnoser.rb +9 -1
  17. data/lib/solargraph/language_server/host/sources.rb +1 -1
  18. data/lib/solargraph/language_server/message/completion_item/resolve.rb +1 -0
  19. data/lib/solargraph/language_server/message/extended/environment.rb +3 -3
  20. data/lib/solargraph/language_server/message/initialize.rb +37 -35
  21. data/lib/solargraph/language_server/message/text_document/formatting.rb +28 -7
  22. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  23. data/lib/solargraph/library.rb +132 -22
  24. data/lib/solargraph/parser/rubyvm/node_chainer.rb +0 -1
  25. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +11 -12
  26. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +1 -6
  27. data/lib/solargraph/shell.rb +5 -1
  28. data/lib/solargraph/source.rb +1 -1
  29. data/lib/solargraph/source/chain/head.rb +0 -16
  30. data/lib/solargraph/source/source_chainer.rb +1 -0
  31. data/lib/solargraph/source_map/mapper.rb +0 -5
  32. data/lib/solargraph/type_checker.rb +2 -2
  33. data/lib/solargraph/type_checker/checks.rb +4 -4
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/workspace.rb +1 -0
  36. data/lib/solargraph/workspace/config.rb +4 -3
  37. data/lib/solargraph/yard_map.rb +41 -39
  38. data/lib/solargraph/yard_map/core_fills.rb +1 -0
  39. data/solargraph.gemspec +1 -0
  40. metadata +16 -2
@@ -17,7 +17,7 @@ module Solargraph
17
17
  if !this_link.nil? && this_link != last_link
18
18
  parts.push this_link
19
19
  end
20
- parts.push pin.detail.gsub(':', '\\:') unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
20
+ parts.push "`#{pin.detail}`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil?
21
21
  parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty?
22
22
  unless parts.empty?
23
23
  data = parts.join("\n\n")
@@ -20,9 +20,7 @@ module Solargraph
20
20
  def initialize workspace = Solargraph::Workspace.new, name = nil
21
21
  @workspace = workspace
22
22
  @name = name
23
- api_map.catalog bench
24
- @synchronized = true
25
- @catalog_mutex = Mutex.new
23
+ @synchronized = false
26
24
  end
27
25
 
28
26
  def inspect
@@ -48,9 +46,15 @@ module Solargraph
48
46
  # @return [void]
49
47
  def attach source
50
48
  mutex.synchronize do
51
- @synchronized = (@current == source) if synchronized?
49
+ if @current && @current.filename != source.filename && source_map_hash.key?(@current.filename) && !workspace.has_file?(@current.filename)
50
+ source_map_hash.delete @current.filename
51
+ source_map_external_require_hash.delete @current.filename
52
+ @external_requires = nil
53
+ @synchronized = false
54
+ end
52
55
  @current = source
53
- catalog
56
+ maybe_map @current
57
+ api_map.catalog bench unless synchronized?
54
58
  end
55
59
  end
56
60
 
@@ -110,9 +114,9 @@ module Solargraph
110
114
  mutex.synchronize do
111
115
  next if File.directory?(filename) || !File.exist?(filename)
112
116
  next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
113
- @synchronized = false
114
117
  source = Solargraph::Source.load_string(File.read(filename), filename)
115
118
  workspace.merge(source)
119
+ maybe_map source
116
120
  result = true
117
121
  end
118
122
  result
@@ -158,6 +162,8 @@ module Solargraph
158
162
  position = Position.new(line, column)
159
163
  cursor = Source::Cursor.new(read(filename), position)
160
164
  api_map.clip(cursor).complete
165
+ rescue FileNotFoundError => e
166
+ handle_file_not_found filename, e
161
167
  end
162
168
 
163
169
  # Get definition suggestions for the expression at the specified file and
@@ -186,6 +192,8 @@ module Solargraph
186
192
  else
187
193
  api_map.clip(cursor).define.map { |pin| pin.realize(api_map) }
188
194
  end
195
+ rescue FileNotFoundError => e
196
+ handle_file_not_found(filename, e)
189
197
  end
190
198
 
191
199
  # Get signature suggestions for the method at the specified file and
@@ -246,7 +254,18 @@ module Solargraph
246
254
  end
247
255
 
248
256
  def locate_ref location
249
- api_map.require_reference_at location
257
+ map = source_map_hash[location.filename]
258
+ return if map.nil?
259
+ pin = map.requires.select { |p| p.location.range.contain?(location.range.start) }.first
260
+ return nil if pin.nil?
261
+ workspace.require_paths.each do |path|
262
+ full = Pathname.new(path).join("#{pin.name}.rb").to_s
263
+ next unless source_map_hash.key?(full)
264
+ return Location.new(full, Solargraph::Range.from_to(0, 0, 0, 0))
265
+ end
266
+ api_map.yard_map.require_reference(pin.name)
267
+ rescue FileNotFoundError
268
+ nil
250
269
  end
251
270
 
252
271
  # Get an array of pins that match a path.
@@ -260,14 +279,12 @@ module Solargraph
260
279
  # @param query [String]
261
280
  # @return [Array<YARD::CodeObjects::Base>]
262
281
  def document query
263
- catalog
264
282
  api_map.document query
265
283
  end
266
284
 
267
285
  # @param query [String]
268
286
  # @return [Array<String>]
269
287
  def search query
270
- catalog
271
288
  api_map.search query
272
289
  end
273
290
 
@@ -276,7 +293,6 @@ module Solargraph
276
293
  # @param query [String]
277
294
  # @return [Array<Pin::Base>]
278
295
  def query_symbols query
279
- catalog
280
296
  api_map.query_symbols query
281
297
  end
282
298
 
@@ -295,10 +311,13 @@ module Solargraph
295
311
  # @param path [String]
296
312
  # @return [Array<Solargraph::Pin::Base>]
297
313
  def path_pins path
298
- catalog
299
314
  api_map.get_path_suggestions(path)
300
315
  end
301
316
 
317
+ def source_maps
318
+ source_map_hash.values
319
+ end
320
+
302
321
  # Get the current text of a file in the library.
303
322
  #
304
323
  # @param filename [String]
@@ -318,9 +337,9 @@ module Solargraph
318
337
  # be an option to do so.
319
338
  #
320
339
  return [] unless open?(filename)
321
- catalog
322
340
  result = []
323
341
  source = read(filename)
342
+ catalog
324
343
  repargs = {}
325
344
  workspace.config.reporters.each do |line|
326
345
  if line == 'all!'
@@ -346,7 +365,7 @@ module Solargraph
346
365
  #
347
366
  # @return [void]
348
367
  def catalog
349
- @catalog_mutex.synchronize do
368
+ mutex.synchronize do
350
369
  break if synchronized?
351
370
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
352
371
  api_map.catalog bench
@@ -355,6 +374,14 @@ module Solargraph
355
374
  end
356
375
  end
357
376
 
377
+ def bench
378
+ Bench.new(
379
+ source_maps: source_map_hash.values,
380
+ workspace: workspace,
381
+ external_requires: external_requires
382
+ )
383
+ end
384
+
358
385
  # Get an array of foldable ranges for the specified file.
359
386
  #
360
387
  # @deprecated The library should not need to handle folding ranges. The
@@ -381,16 +408,75 @@ module Solargraph
381
408
  # @param source [Source]
382
409
  # @return [Boolean] True if the source was merged into the workspace.
383
410
  def merge source
411
+ Logging.logger.debug "Merging source: #{source.filename}"
384
412
  result = false
385
413
  mutex.synchronize do
386
414
  result = workspace.merge(source)
387
- @synchronized = !result if synchronized?
415
+ maybe_map source
388
416
  end
417
+ # catalog
389
418
  result
390
419
  end
391
420
 
421
+ def source_map_hash
422
+ @source_map_hash ||= {}
423
+ end
424
+
425
+ def mapped?
426
+ (workspace.filenames - source_map_hash.keys).empty?
427
+ end
428
+
429
+ def next_map
430
+ return false if mapped?
431
+ mutex.synchronize do
432
+ @synchronized = false
433
+ src = workspace.sources.find { |s| !source_map_hash.key?(s.filename) }
434
+ if src
435
+ Logging.logger.debug "Mapping #{src.filename}"
436
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
437
+ find_external_requires(source_map_hash[src.filename])
438
+ source_map_hash[src.filename]
439
+ else
440
+ false
441
+ end
442
+ end
443
+ end
444
+
445
+ def map!
446
+ workspace.sources.each do |src|
447
+ source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
448
+ find_external_requires(source_map_hash[src.filename])
449
+ end
450
+ self
451
+ end
452
+
453
+ def pins
454
+ @pins ||= []
455
+ end
456
+
457
+ def external_requires
458
+ @external_requires ||= source_map_external_require_hash.values.flatten.to_set
459
+ end
460
+
392
461
  private
393
462
 
463
+ def source_map_external_require_hash
464
+ @source_map_external_require_hash ||= {}
465
+ end
466
+
467
+ # @param source_map [SourceMap]
468
+ def find_external_requires source_map
469
+ new_set = source_map.requires.map(&:name).to_set
470
+ # return if new_set == source_map_external_require_hash[source_map.filename]
471
+ source_map_external_require_hash[source_map.filename] = new_set.reject do |path|
472
+ workspace.require_paths.any? do |base|
473
+ full = Pathname.new(base).join("#{path}.rb").to_s
474
+ workspace.filenames.include?(full)
475
+ end
476
+ end
477
+ @external_requires = nil
478
+ end
479
+
394
480
  # @return [Mutex]
395
481
  def mutex
396
482
  @mutex ||= Mutex.new
@@ -401,14 +487,6 @@ module Solargraph
401
487
  @api_map ||= Solargraph::ApiMap.new
402
488
  end
403
489
 
404
- # @return [Bench]
405
- def bench
406
- Bench.new(
407
- workspace: workspace,
408
- opened: @current ? [@current] : []
409
- )
410
- end
411
-
412
490
  # Get the source for an open file or create a new source if the file
413
491
  # exists on disk. Sources created from disk are not added to the open
414
492
  # workspace files, i.e., the version on disk remains the authoritative
@@ -422,5 +500,37 @@ module Solargraph
422
500
  raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
423
501
  workspace.source(filename)
424
502
  end
503
+
504
+ def handle_file_not_found filename, error
505
+ if workspace.source(filename)
506
+ Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap"
507
+ nil
508
+ else
509
+ raise error
510
+ end
511
+ end
512
+
513
+ def maybe_map source
514
+ if source_map_hash.key?(source.filename)
515
+ return if source_map_hash[source.filename].code == source.code &&
516
+ source_map_hash[source.filename].source.synchronized? &&
517
+ source.synchronized?
518
+ if source.synchronized?
519
+ new_map = Solargraph::SourceMap.map(source)
520
+ unless source_map_hash[source.filename].try_merge!(new_map)
521
+ source_map_hash[source.filename] = new_map
522
+ find_external_requires(source_map_hash[source.filename])
523
+ @synchronized = false
524
+ end
525
+ else
526
+ # @todo Smelly instance variable access
527
+ source_map_hash[source.filename].instance_variable_set(:@source, source)
528
+ end
529
+ else
530
+ source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
531
+ find_external_requires(source_map_hash[source.filename])
532
+ @synchronized = false
533
+ end
534
+ end
425
535
  end
426
536
  end
@@ -109,7 +109,6 @@ module Solargraph
109
109
  end
110
110
 
111
111
  def node_to_argchains node
112
- # @todo Process array, splat, argscat
113
112
  return [] unless Parser.is_ast_node?(node)
114
113
  if [:ZARRAY, :ARRAY, :LIST].include?(node.type)
115
114
  node.children[0..-2].map { |c| NodeChainer.chain(c) }
@@ -32,7 +32,6 @@ module Solargraph
32
32
  region.closure.parameters.push locals.last
33
33
  end
34
34
  end
35
- # @todo Optional args, keyword args, etc.
36
35
  if node.children[6]
37
36
  locals.push Solargraph::Pin::Parameter.new(
38
37
  location: region.closure.location,
@@ -55,19 +54,19 @@ module Solargraph
55
54
  )
56
55
  region.closure.parameters.push locals.last
57
56
  end
58
- if node.children.last
59
- locals.push Solargraph::Pin::Parameter.new(
60
- location: region.closure.location,
61
- closure: region.closure,
62
- comments: comments_for(node),
63
- name: node.children.last.to_s,
64
- presence: region.closure.location.range,
65
- decl: :blockarg
66
- )
67
- region.closure.parameters.push locals.last
68
- end
69
57
  end
70
58
  process_children
59
+ if node.children.last
60
+ locals.push Solargraph::Pin::Parameter.new(
61
+ location: region.closure.location,
62
+ closure: region.closure,
63
+ comments: comments_for(node),
64
+ name: node.children.last.to_s,
65
+ presence: region.closure.location.range,
66
+ decl: :blockarg
67
+ )
68
+ region.closure.parameters.push locals.last
69
+ end
71
70
  end
72
71
 
73
72
  private
@@ -16,12 +16,7 @@ module Solargraph
16
16
  presence: region.closure.location.range,
17
17
  decl: :optarg
18
18
  )
19
- idx = region.closure.parameters.find_index { |par| par.decl != :arg }
20
- if idx
21
- region.closure.parameters.insert idx, locals.last
22
- else
23
- region.closure.parameters.push locals.last
24
- end
19
+ region.closure.parameters.push locals.last
25
20
  node.children[1] && NodeProcessor.process(node.children[1], region, pins, locals)
26
21
  end
27
22
  end
@@ -74,7 +74,11 @@ module Solargraph
74
74
  desc 'download-core [VERSION]', 'Download core documentation'
75
75
  def download_core version = nil
76
76
  ver = version || Solargraph::YardMap::CoreDocs.best_download
77
- puts "Downloading docs for #{ver}..."
77
+ if RUBY_VERSION != ver
78
+ puts "Documentation for #{RUBY_VERSION} is not available. Reverting to closest match..."
79
+ else
80
+ puts "Downloading docs for #{ver}..."
81
+ end
78
82
  Solargraph::YardMap::CoreDocs.download ver
79
83
  # Clear cached documentation if it exists
80
84
  FileUtils.rm_rf Dir.glob(File.join(Solargraph::YardMap::CoreDocs.cache_dir, ver, '*.ser'))
@@ -175,7 +175,7 @@ module Solargraph
175
175
  synced
176
176
  end
177
177
 
178
- # @param position [Position]
178
+ # @param position [Position, Array(Integer, Integer)]
179
179
  # @return [Source::Cursor]
180
180
  def cursor_at position
181
181
  Cursor.new(self, position)
@@ -13,22 +13,6 @@ module Solargraph
13
13
  # return super_pins(api_map, name_pin) if word == 'super'
14
14
  []
15
15
  end
16
-
17
- # @todo This is temporary. Chain heads need to handle arguments to
18
- # `super`.
19
- # def arguments
20
- # []
21
- # end
22
-
23
- private
24
-
25
- # # @param api_map [ApiMap]
26
- # # @param name_pin [Pin::Base]
27
- # # @return [Array<Pin::Base>]
28
- # def super_pins api_map, name_pin
29
- # pins = api_map.get_method_stack(name_pin.namespace, name_pin.name, scope: name_pin.scope)
30
- # pins.reject{|p| p.path == name_pin.path}
31
- # end
32
16
  end
33
17
  end
34
18
  end
@@ -34,6 +34,7 @@ module Solargraph
34
34
  # Special handling for files that end with an integer and a period
35
35
  return Chain.new([Chain::Literal.new('Integer'), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
36
  return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
37
+ return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i)
37
38
  begin
38
39
  return Chain.new([]) if phrase.end_with?('..')
39
40
  node = nil
@@ -71,11 +71,6 @@ module Solargraph
71
71
  pos = Solargraph::Position.new(comment_position.line + line_num - 1, comment_position.column)
72
72
  process_directive(source_position, pos, d)
73
73
  last_line = line_num + 1
74
- # @todo The below call assumes the topmost comment line. The above
75
- # process occasionally emits incorrect comment positions due to
76
- # blank lines in comment blocks, but at least it processes all the
77
- # directives.
78
- # process_directive(source_position, comment_position, d)
79
74
  end
80
75
  end
81
76
 
@@ -95,7 +95,7 @@ module Solargraph
95
95
  result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
96
96
  end
97
97
  elsif rules.validate_tags?
98
- unless pin.node.nil? || declared.void? || macro_pin?(pin) || abstract?(pin)
98
+ unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
99
99
  inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
100
100
  if inferred.undefined?
101
101
  unless rules.ignore_all_undefined? || external?(pin)
@@ -111,7 +111,7 @@ module Solargraph
111
111
  result
112
112
  end
113
113
 
114
- def macro_pin? pin
114
+ def virtual_pin? pin
115
115
  pin.location && source_map.source.comment_at?(pin.location.range.ending)
116
116
  end
117
117
 
@@ -75,7 +75,7 @@ module Solargraph
75
75
  true
76
76
  end
77
77
 
78
- # @param type [ComplexType]
78
+ # @param type [ComplexType::UniqueType]
79
79
  # @return [String]
80
80
  def fuzz type
81
81
  if type.parameters?
@@ -86,13 +86,13 @@ module Solargraph
86
86
  end
87
87
 
88
88
  # @param api_map [ApiMap]
89
- # @param cls1 [ComplexType]
90
- # @param cls2 [ComplexType]
89
+ # @param cls1 [ComplexType::UniqueType]
90
+ # @param cls2 [ComplexType::UniqueType]
91
91
  # @return [Boolean]
92
92
  def either_way?(api_map, cls1, cls2)
93
93
  f1 = fuzz(cls1)
94
94
  f2 = fuzz(cls2)
95
- api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1)
95
+ api_map.type_include?(f1, f2) || api_map.super_and_sub?(f1, f2) || api_map.super_and_sub?(f2, f1)
96
96
  end
97
97
  end
98
98
  end