solargraph 0.40.3 → 0.42.0

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