solargraph 0.60.4 → 0.62.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +24 -2
  3. data/.github/workflows/plugins.yml +19 -1
  4. data/.github/workflows/rspec.yml +17 -2
  5. data/.github/workflows/typecheck.yml +6 -0
  6. data/CHANGELOG.md +54 -0
  7. data/Gemfile +0 -3
  8. data/lib/solargraph/api_map/index.rb +28 -4
  9. data/lib/solargraph/api_map/store.rb +19 -0
  10. data/lib/solargraph/api_map.rb +91 -80
  11. data/lib/solargraph/cache_dir.rb +58 -0
  12. data/lib/solargraph/collection/base.rb +50 -0
  13. data/lib/solargraph/collection/core.rb +17 -0
  14. data/lib/solargraph/collection/gem.rb +59 -0
  15. data/lib/solargraph/collection/stdlib.rb +25 -0
  16. data/lib/solargraph/collection.rb +30 -0
  17. data/lib/solargraph/complex_type/unique_type.rb +13 -0
  18. data/lib/solargraph/complex_type.rb +23 -3
  19. data/lib/solargraph/convention.rb +5 -7
  20. data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
  21. data/lib/solargraph/diagnostics/type_check.rb +0 -1
  22. data/lib/solargraph/external.rb +187 -0
  23. data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
  24. data/lib/solargraph/language_server/host/dispatch.rb +4 -2
  25. data/lib/solargraph/language_server/host.rb +10 -2
  26. data/lib/solargraph/language_server/message/completion_item/resolve.rb +10 -3
  27. data/lib/solargraph/language_server/message/text_document/definition.rb +5 -2
  28. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
  29. data/lib/solargraph/library.rb +26 -53
  30. data/lib/solargraph/metagem.rb +80 -0
  31. data/lib/solargraph/parser/node_processor/base.rb +6 -0
  32. data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
  33. data/lib/solargraph/parser/parser_gem/node_methods.rb +29 -6
  34. data/lib/solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node.rb +98 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/class_definition.rb +54 -0
  36. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +1 -0
  37. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node.rb +65 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node.rb +97 -0
  39. data/lib/solargraph/parser/parser_gem/node_processors/data_definition.rb +110 -0
  40. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +1 -0
  41. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +2 -0
  42. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  43. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +185 -0
  44. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb +66 -0
  45. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb +108 -0
  46. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition.rb +170 -0
  47. data/lib/solargraph/parser/parser_gem/node_processors.rb +33 -29
  48. data/lib/solargraph/pin/base.rb +25 -1
  49. data/lib/solargraph/pin/base_variable.rb +31 -1
  50. data/lib/solargraph/pin/callable.rb +11 -2
  51. data/lib/solargraph/pin/closure.rb +17 -0
  52. data/lib/solargraph/pin/delegated_method.rb +5 -0
  53. data/lib/solargraph/pin/duck_method.rb +36 -16
  54. data/lib/solargraph/pin/factory_parameter.rb +84 -0
  55. data/lib/solargraph/pin/instance_variable.rb +45 -0
  56. data/lib/solargraph/pin/method.rb +10 -0
  57. data/lib/solargraph/pin/method_alias.rb +4 -0
  58. data/lib/solargraph/pin/parameter.rb +1 -1
  59. data/lib/solargraph/pin/reference/included_mixin.rb +22 -0
  60. data/lib/solargraph/pin/reference.rb +1 -0
  61. data/lib/solargraph/pin.rb +1 -0
  62. data/lib/solargraph/plugin/active_support.rb +100 -0
  63. data/lib/solargraph/plugin/addon.rb +16 -0
  64. data/lib/solargraph/plugin/base.rb +25 -0
  65. data/lib/solargraph/plugin/panel.rb +37 -0
  66. data/lib/solargraph/plugin.rb +26 -0
  67. data/lib/solargraph/rbs_collection.rb +119 -0
  68. data/lib/solargraph/rbs_map/base.rb +31 -0
  69. data/lib/solargraph/rbs_map/conversions.rb +109 -48
  70. data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
  71. data/lib/solargraph/rbs_map/core_fills.rb +1 -1
  72. data/lib/solargraph/rbs_map/gem.rb +16 -0
  73. data/lib/solargraph/rbs_map/helpers.rb +61 -0
  74. data/lib/solargraph/rbs_map/path.rb +19 -0
  75. data/lib/solargraph/rbs_map/stdlib.rb +24 -0
  76. data/lib/solargraph/rbs_map.rb +7 -198
  77. data/lib/solargraph/rbs_translator.rb +105 -70
  78. data/lib/solargraph/repo.rb +177 -0
  79. data/lib/solargraph/shell.rb +49 -99
  80. data/lib/solargraph/source/chain/call.rb +58 -14
  81. data/lib/solargraph/source/chain/literal.rb +17 -17
  82. data/lib/solargraph/source/chain/parameter.rb +79 -0
  83. data/lib/solargraph/source/chain.rb +8 -0
  84. data/lib/solargraph/source/node_map.rb +27 -0
  85. data/lib/solargraph/source/source_chainer.rb +40 -44
  86. data/lib/solargraph/source.rb +17 -0
  87. data/lib/solargraph/source_map/call.rb +18 -0
  88. data/lib/solargraph/source_map/clip.rb +5 -3
  89. data/lib/solargraph/source_map/mapper.rb +6 -2
  90. data/lib/solargraph/source_map.rb +28 -9
  91. data/lib/solargraph/type_checker/rules.rb +3 -2
  92. data/lib/solargraph/type_checker.rb +4 -1
  93. data/lib/solargraph/version.rb +1 -1
  94. data/lib/solargraph/workspace.rb +0 -1
  95. data/lib/solargraph/yard_map/cache.rb +1 -1
  96. data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
  97. data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
  98. data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
  99. data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
  100. data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
  101. data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
  102. data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
  103. data/lib/solargraph/yard_map/directives.rb +5 -2
  104. data/lib/solargraph/yard_map/helpers.rb +1 -1
  105. data/lib/solargraph/yard_map/macro.rb +85 -6
  106. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
  107. data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
  108. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
  109. data/lib/solargraph/yard_map/mapper.rb +2 -2
  110. data/lib/solargraph/yard_map.rb +1 -1
  111. data/lib/solargraph/yardoc.rb +43 -60
  112. data/lib/solargraph.rb +8 -3
  113. data/rbs/fills/boolean/0/boolean.rbs +12 -0
  114. data/solargraph.gemspec +2 -2
  115. metadata +41 -18
  116. data/lib/solargraph/convention/active_support_concern.rb +0 -111
  117. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +0 -61
  118. data/lib/solargraph/convention/data_definition/data_definition_node.rb +0 -93
  119. data/lib/solargraph/convention/data_definition.rb +0 -108
  120. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +0 -62
  121. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +0 -103
  122. data/lib/solargraph/convention/struct_definition.rb +0 -168
  123. data/lib/solargraph/doc_map.rb +0 -439
  124. data/lib/solargraph/gem_pins.rb +0 -87
  125. data/lib/solargraph/pin_cache.rb +0 -248
  126. data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
  127. data/lib/solargraph/workspace/gemspecs.rb +0 -373
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'rbs'
5
+
6
+ module Solargraph
7
+ module CacheDir
8
+ module_function
9
+
10
+ # The base directory for cached YARD documentation and serialized pins.
11
+ #
12
+ # @return [String]
13
+ def base_dir
14
+ ENV['SOLARGRAPH_CACHE'] ||
15
+ (ENV['XDG_CACHE_HOME'] ? File.join(ENV['XDG_CACHE_HOME'], 'solargraph') : nil) ||
16
+ File.join(Dir.home, '.cache', 'solargraph')
17
+ end
18
+
19
+ # The working directory for the current Ruby, RBS, and Solargraph versions.
20
+ #
21
+ # @return [String]
22
+ def work_dir
23
+ File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
24
+ end
25
+
26
+ # The current gem directory.
27
+ #
28
+ # @return [String]
29
+ def gem_dir
30
+ File.join(work_dir, 'gems')
31
+ end
32
+
33
+ # The current stdlib directory.
34
+ #
35
+ # @return [String]
36
+ def stdlib_dir
37
+ File.join(work_dir, 'stdlib')
38
+ end
39
+
40
+ # The current RBS collection directory.
41
+ #
42
+ # @return [String]
43
+ def rbs_dir
44
+ File.join(work_dir, 'rbs')
45
+ end
46
+
47
+ # The directory for the current YARD version.
48
+ #
49
+ # @return [String]
50
+ def yard_dir
51
+ File.join(base_dir, "yard-#{YARD::VERSION}", "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}")
52
+ end
53
+
54
+ def clear
55
+ FileUtils.rm_rf base_dir
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Solargraph
6
+ module Collection
7
+ # The base class for cacheable pin collections. Subclasses need to
8
+ # implement #pins and #cache_file.
9
+ #
10
+ # @!method pins
11
+ # The array of pins for this collection.
12
+ # @abstract
13
+ # @return [Array<Pin::Base>]
14
+ # @!method cache_file
15
+ # The file that will store serialized pins.
16
+ # @abstract
17
+ # @return [String]
18
+ class Base
19
+ # @return [Array<Pin::Base>]
20
+ def load
21
+ Collection.cached(cache_file) { file_cache_pins || generate_pin_caches }
22
+ end
23
+
24
+ # @return [Array<Pin::Base>]
25
+ def self.load(...)
26
+ new(...).load
27
+ end
28
+
29
+ def self.uncache(...)
30
+ cache_file = new(...).cache_file
31
+ FileUtils.rm_rf cache_file
32
+ Collection.mem_cache.delete cache_file
33
+ end
34
+
35
+ private
36
+
37
+ def file_cache_pins
38
+ Marshal.load(File.read(cache_file, mode: 'rb')) if File.exist?(cache_file) # rubocop:disable Security/MarshalLoad
39
+ end
40
+
41
+ def generate_pin_caches
42
+ Collection.mem_cache[cache_file] = pins
43
+ serial = Marshal.dump(pins)
44
+ FileUtils.mkdir_p File.dirname(cache_file)
45
+ File.write cache_file, serial, mode: 'wb'
46
+ pins
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable core pins.
6
+ #
7
+ class Core < Base
8
+ def cache_file
9
+ File.join CacheDir.work_dir, 'core.ser'
10
+ end
11
+
12
+ def pins
13
+ @pins ||= RbsMap::Core.pins
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable gem pins.
6
+ #
7
+ class Gem < Base
8
+ include Logging
9
+
10
+ attr_reader :metagem
11
+
12
+ @@path_source_cache = {}
13
+
14
+ # @param metagem [Metagem]
15
+ def initialize metagem
16
+ super()
17
+ @metagem = metagem
18
+ end
19
+
20
+ def cache_file
21
+ File.join CacheDir.gem_dir, "#{metagem.cache_name}.ser"
22
+ end
23
+
24
+ def load
25
+ return super if metagem.cacheable?
26
+ @@path_source_cache[metagem.full_path] ||= pins
27
+ end
28
+
29
+ def pins
30
+ @pins ||= without_yard
31
+ end
32
+
33
+ def self.cached? metagem
34
+ metagem.cacheable? && File.exist?(new(metagem).cache_file)
35
+ end
36
+
37
+ private
38
+
39
+ def with_yard
40
+ code_objects = Yardoc.load!(metagem)
41
+ yard_pins = YardMap::Mapper.new(code_objects, metagem).map
42
+ rbs_pins = RbsMap::Gem.pins(metagem)
43
+ RbsMap::Helpers.combine(yard_pins, rbs_pins)
44
+ end
45
+
46
+ def without_yard
47
+ files = metagem.require_paths.flat_map { |path| Dir.glob(File.join(metagem.full_path, path, '**', '*.rb')) }
48
+ source_maps = files.map { |file| Solargraph::SourceMap.load(file) }
49
+
50
+ # Generating an ApiMap is necessary for processing macros
51
+ bench = Bench.new(source_maps: source_maps)
52
+ source_pins = ApiMap.new.catalog(bench).pins.select { |pin| files.include?(pin.filename) }
53
+
54
+ rbs_pins = RbsMap::Gem.pins(metagem)
55
+ RbsMap::Helpers.combine(source_pins, rbs_pins)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable stdlib pins.
6
+ #
7
+ class Stdlib < Base
8
+ attr_reader :library
9
+
10
+ # @param library [String]
11
+ def initialize library
12
+ super()
13
+ @library = library
14
+ end
15
+
16
+ def pins
17
+ @pins ||= RbsMap::Stdlib.pins(library)
18
+ end
19
+
20
+ def cache_file
21
+ File.join CacheDir.stdlib_dir, "#{library}.ser"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ autoload :Base, 'solargraph/collection/base'
6
+ autoload :Core, 'solargraph/collection/core'
7
+ autoload :Gem, 'solargraph/collection/gem'
8
+ autoload :Stdlib, 'solargraph/collection/stdlib'
9
+
10
+ # A hash for storing pin collections in memory.
11
+ #
12
+ # @return [Hash{String => Array<Pin::Base>}]
13
+ def self.mem_cache
14
+ @mem_cache ||= {}
15
+ end
16
+
17
+ # The pins cached under a key, storing the block result on a miss.
18
+ #
19
+ # @param key [String]
20
+ # @return [Array<Pin::Base>]
21
+ def self.cached key
22
+ mem_cache.fetch(key) { mem_cache[key] = yield }
23
+ end
24
+
25
+ # @return [void]
26
+ def self.clear_mem_cache
27
+ mem_cache.clear
28
+ end
29
+ end
30
+ end
@@ -11,6 +11,13 @@ module Solargraph
11
11
 
12
12
  attr_reader :all_params, :subtypes, :key_types
13
13
 
14
+ # @type [Hash{String => String}]
15
+ ANONYMOUS_NAME_BY_STARTING_TAG = {
16
+ '{' => 'Hash',
17
+ '(' => 'Array',
18
+ '<' => 'Array'
19
+ }.freeze
20
+
14
21
  # Create a UniqueType with the specified name and an optional substring.
15
22
  # The substring is the parameter section of a parametrized type, e.g.,
16
23
  # for the type `Array<String>`, the name is `Array` and the substring is
@@ -22,6 +29,9 @@ module Solargraph
22
29
  # @return [UniqueType]
23
30
  def self.parse name, substring = '', make_rooted: nil
24
31
  raise ComplexTypeError, "Illegal prefix: #{name}" if name.start_with?(':::')
32
+ # Anonymous shorthand (`<A>`, `(A)`, `{A=>B}`) defaults the
33
+ # omitted type name to Array or Hash, before the rooted check below.
34
+ name = ANONYMOUS_NAME_BY_STARTING_TAG.fetch(substring.chars.fetch(0)) if name.empty? && !substring.empty?
25
35
  if name.start_with?('::')
26
36
  name = name[2..]
27
37
  rooted = true
@@ -443,6 +453,9 @@ module Solargraph
443
453
  # @param context_type [ComplexType] The receiver type
444
454
  # @return [UniqueType, ComplexType]
445
455
  def resolve_generics definitions, context_type
456
+ # Method-scoped generics bind per call site via
457
+ # resolve_generics_from_context, not against a receiver's type params.
458
+ return self if definitions.is_a?(Pin::Callable)
446
459
  return self if definitions.nil? || definitions.generics.empty?
447
460
 
448
461
  transform(name) do |t|
@@ -50,9 +50,10 @@ module Solargraph
50
50
  def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {}
51
51
  return self unless generic?
52
52
 
53
- ComplexType.new(@items.map do |i|
54
- i.resolve_generics_from_context(generics_to_resolve, context_type,
55
- resolved_generic_values: resolved_generic_values)
53
+ ComplexType.new(@items.map do |unique_type|
54
+ unique_type.resolve_generics_from_context(generics_to_resolve,
55
+ destructure_context_type(unique_type, context_type),
56
+ resolved_generic_values: resolved_generic_values)
56
57
  end)
57
58
  end
58
59
 
@@ -561,6 +562,25 @@ module Solargraph
561
562
 
562
563
  private
563
564
 
565
+ # Narrows what a generic member binds against: context members this type
566
+ # already matches concretely are dropped. Nothing left over means no value
567
+ # can reach the generic, which is bot.
568
+ #
569
+ # @param unique_type [UniqueType] A member of @items
570
+ # @param context_type [ComplexType, UniqueType, nil]
571
+ # @return [ComplexType, UniqueType, nil]
572
+ def destructure_context_type unique_type, context_type
573
+ return context_type unless unique_type.generic? && context_type.is_a?(ComplexType) && @items.length > 1
574
+
575
+ concrete_items = @items.reject(&:generic?)
576
+ return context_type if concrete_items.empty?
577
+
578
+ remaining_items = context_type.items.reject { |ct| concrete_items.any? { |ci| ci.name == ct.name } }
579
+ return BOT if remaining_items.empty?
580
+
581
+ ComplexType.new(remaining_items)
582
+ end
583
+
564
584
  # @todo This is a quick and dirty hack that forces `self` keywords
565
585
  # to reference an instance of their class and never the class itself.
566
586
  # This behavior may change depending on which result is expected
@@ -9,9 +9,6 @@ module Solargraph
9
9
  autoload :Gemfile, 'solargraph/convention/gemfile'
10
10
  autoload :Gemspec, 'solargraph/convention/gemspec'
11
11
  autoload :Rakefile, 'solargraph/convention/rakefile'
12
- autoload :StructDefinition, 'solargraph/convention/struct_definition'
13
- autoload :DataDefinition, 'solargraph/convention/data_definition'
14
- autoload :ActiveSupportConcern, 'solargraph/convention/active_support_concern'
15
12
 
16
13
  # @type [Set<Convention::Base>]
17
14
  @@conventions = Set.new
@@ -38,12 +35,14 @@ module Solargraph
38
35
  result
39
36
  end
40
37
 
41
- # @param doc_map [DocMap]
38
+ # @todo Determine what argument needs to be passed here
39
+ #
40
+ # @param object [Object]
42
41
  # @return [Environ]
43
- def self.for_global doc_map
42
+ def self.for_global object
44
43
  result = Environ.new
45
44
  @@conventions.each do |conv|
46
- result.merge conv.global(doc_map)
45
+ result.merge conv.global(object)
47
46
  end
48
47
  result
49
48
  end
@@ -73,6 +72,5 @@ module Solargraph
73
72
  register Gemfile
74
73
  register Gemspec
75
74
  register Rakefile
76
- register ActiveSupportConcern
77
75
  end
78
76
  end
@@ -13,10 +13,6 @@ module Solargraph
13
13
  # @sg-ignore Need to add nil check here
14
14
  map = api_map.source_map(source.filename)
15
15
  map.requires.each { |ref| refs[ref.name] = ref }
16
- api_map.missing_docs.each do |r|
17
- next unless refs.key?(r)
18
- result.push docs_error(r, refs[r].location)
19
- end
20
16
  api_map.unresolved_requires.each do |r|
21
17
  next unless refs.key?(r)
22
18
  result.push require_error(r, refs[r].location)
@@ -11,7 +11,6 @@ module Solargraph
11
11
  # return [] unless args.include?('always') || api_map.workspaced?(source.filename)
12
12
  severity = Diagnostics::Severities::ERROR
13
13
  level = args.reverse.find { |a| %w[normal typed strict strong].include?(a) } || :normal
14
- # @sg-ignore sensitive typing needs to handle || on nil types
15
14
  checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym)
16
15
  checker.problems
17
16
  .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line }
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Solargraph
6
+ # @todo This class might need a way to track changes to the repo, e.g.,
7
+ # bundle or dependency updates
8
+ #
9
+ class External
10
+ # @return [String]
11
+ attr_reader :directory
12
+
13
+ # @return [Array<String>]
14
+ attr_reader :requires
15
+
16
+ attr_reader :extra_gem_names
17
+
18
+ # @param directory [String]
19
+ # @param requires [Array<String>]
20
+ # @param extra_gem_names [Array<String>]
21
+ def initialize directory, requires, extra_gem_names = []
22
+ @repo = Repo.new(directory)
23
+ @directory = directory
24
+ @requires = requires
25
+ @extra_gem_names = extra_gem_names
26
+ update!
27
+ end
28
+
29
+ def configured_gem_names
30
+ loaded_gems.map(&:name) + unloaded_gems.map(&:name) + extra_gem_names
31
+ end
32
+
33
+ def unresolved_requires
34
+ @unresolved_requires ||= []
35
+ end
36
+
37
+ def unresolved_dependencies
38
+ @unresolved_dependencies ||= []
39
+ end
40
+
41
+ def loaded_gems
42
+ @loaded_gems ||= Set.new
43
+ end
44
+
45
+ def unloaded_gems
46
+ @unloaded_gems ||= Set.new
47
+ end
48
+
49
+ def unfound_gem_names
50
+ @unfound_gem_names ||= []
51
+ end
52
+
53
+ def loaded_stdlibs
54
+ @loaded_stdlibs ||= Set.new
55
+ end
56
+
57
+ def pins
58
+ @pins ||= []
59
+ end
60
+
61
+ # @return [Integer] incremented whenever the external pin set is rebuilt
62
+ def generation
63
+ @generation ||= 0
64
+ end
65
+
66
+ # @param new_requires [Array<String>]
67
+ # @return [Boolean]
68
+ def update new_requires, new_gem_names = []
69
+ return false if requires == new_requires && new_gem_names == extra_gem_names && !cache_changed?
70
+
71
+ requires.replace new_requires
72
+ extra_gem_names.replace new_gem_names
73
+ update!
74
+ true
75
+ end
76
+
77
+ # @return [Array<String>]
78
+ def rbs_collection_paths
79
+ @rbs_collection_paths ||= read_rbs_collection_paths
80
+ end
81
+
82
+ # @return [String, nil]
83
+ def rbs_collection_config_path
84
+ # @todo Get rid of the '*' case
85
+ @rbs_collection_config_path ||= unless directory.nil? || directory.empty? || directory == '*'
86
+ yaml_file = File.join(directory, 'rbs_collection.yaml')
87
+ yaml_file if File.file?(yaml_file)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def update!
94
+ @generation = generation + 1
95
+ clear_all
96
+ load_requires
97
+ load_gem_names
98
+ # load_rbs_collection
99
+ end
100
+
101
+ def cache_changed?
102
+ unloaded_gems.any? { |gem| Collection::Gem.cached?(gem) }
103
+ end
104
+
105
+ def load_requires
106
+ bundler_require = false
107
+
108
+ requires.uniq.each do |path|
109
+ if path == 'bundler/require'
110
+ bundler_require = true
111
+ end
112
+ if RbsMap::Stdlib.has?(path)
113
+ pins.concat Collection::Stdlib.load(path) if loaded_stdlibs.add?(path)
114
+ else
115
+ metagem = @repo.find_by_path(path)
116
+ next unresolved_requires.push(path) unless metagem
117
+ process_gem metagem
118
+ end
119
+ end
120
+
121
+ return unless bundler_require
122
+
123
+ @repo.find_by_group(:default).each { |metagem| process_gem metagem }
124
+ end
125
+
126
+ def load_gem_names
127
+ extra_gem_names.uniq.each do |gem|
128
+ metagem = @repo.find_by_name(gem)
129
+ next unfound_gem_names.push(gem) unless metagem
130
+ process_gem metagem
131
+ end
132
+ end
133
+
134
+ def rbs_collection
135
+ @rbs_collection ||= RbsCollection.new(rbs_collection_lockfile)
136
+ end
137
+
138
+ def rbs_collection_lockfile
139
+ return if directory.nil? || directory.empty? || directory == '*'
140
+ lockfile = File.join(directory, 'rbs_collection.lock.yaml')
141
+ lockfile if File.file?(lockfile)
142
+ end
143
+
144
+ def clear_all
145
+ pins.clear
146
+ unresolved_requires.clear
147
+ unresolved_dependencies.clear
148
+ loaded_gems.clear
149
+ unloaded_gems.clear
150
+ loaded_stdlibs.clear
151
+ unfound_gem_names.clear
152
+ end
153
+
154
+ def process_gem metagem
155
+ return if loaded_gems.include?(metagem) || unloaded_gems.include?(metagem)
156
+
157
+ if metagem.cacheable? && !Collection::Gem.cached?(metagem)
158
+ unloaded_gems.add metagem
159
+ else
160
+ loaded_gems.add metagem
161
+ base_pins = Collection::Gem.load(metagem)
162
+ rbsc_pins = rbs_collection.load(metagem)
163
+ if rbsc_pins.empty?
164
+ pins.concat base_pins
165
+ else
166
+ pins.concat RbsMap::Helpers.combine(base_pins, rbsc_pins)
167
+ end
168
+ # Gems might not declare stdlib dependencies as gem dependencies
169
+ # (e.g., `activesupport` requires `date`)
170
+ base_pins.select { |pin| pin.is_a?(Pin::Reference::Require) && RbsMap::Stdlib.has?(pin.name) }
171
+ .each do |pin|
172
+ pins.concat Collection::Stdlib.load(pin.name) if loaded_stdlibs.add?(pin.name)
173
+ end
174
+ end
175
+ load_dependencies metagem
176
+ end
177
+
178
+ def load_dependencies parent
179
+ parent.dependencies.each do |name|
180
+ next if loaded_gems.map(&:name).include?(name) || unloaded_gems.map(&:name).include?(name)
181
+ metagem = @repo.find_by_name(name)
182
+ next unresolved_dependencies.push(name) unless metagem
183
+ process_gem metagem
184
+ end
185
+ end
186
+ end
187
+ end
@@ -61,7 +61,7 @@ module Solargraph
61
61
  return
62
62
  end
63
63
  current = mutex.synchronize { queue.shift }
64
- return if queue.include?(current)
64
+ return if current.nil? || queue.include?(current)
65
65
  begin
66
66
  host.diagnose current
67
67
  rescue InvalidOffsetError
@@ -118,11 +118,13 @@ module Solargraph
118
118
  .tap { |lib| lib.add_observer self }
119
119
  end
120
120
 
121
- # @param progress [Solargraph::LanguageServer::Progress, nil]
121
+ # The Library observer callback that #generic_library registers.
122
122
  #
123
+ # @abstract
124
+ # @param progress [Solargraph::LanguageServer::Progress, nil]
123
125
  # @return [void]
124
126
  def update progress
125
- progress&.send(self)
127
+ raise NotImplementedError, 'Host::Dispatch requires an update method'
126
128
  end
127
129
  end
128
130
  end
@@ -495,7 +495,7 @@ module Solargraph
495
495
  )
496
496
  result.concat(library.locate_pins(location).select { |pin| pin.name == params['label'] })
497
497
  end
498
- if params['data']['path']
498
+ if params['data']['path'] && !params['data']['path'].empty?
499
499
  result.concat library.path_pins(params['data']['path'])
500
500
  # @todo This exception is necessary because `Library#path_pins` does
501
501
  # not perform a namespace method query, so the implicit `.new` pin
@@ -706,6 +706,15 @@ module Solargraph
706
706
  client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
707
707
  end
708
708
 
709
+ # The Library observer callback that sends a library's progress to
710
+ # the client.
711
+ #
712
+ # @param progress [Solargraph::LanguageServer::Progress, nil]
713
+ # @return [void]
714
+ def update progress
715
+ progress&.send(self)
716
+ end
717
+
709
718
  private
710
719
 
711
720
  # @return [Array<Integer>]
@@ -736,7 +745,6 @@ module Solargraph
736
745
  # @return [String]
737
746
  def normalize_separators path
738
747
  return path if File::ALT_SEPARATOR.nil?
739
- # @sg-ignore flow sensitive typing needs to handle constants
740
748
  path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
741
749
  end
742
750
 
@@ -7,6 +7,12 @@ module Solargraph
7
7
  # completionItem/resolve message handler
8
8
  #
9
9
  class Resolve < Base
10
+ # Properties that must not be changed during completionItem/resolve
11
+ # according to the LSP specification.
12
+ #
13
+ # @see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion
14
+ IMMUTABLE_FIELDS = %i[label kind sortText filterText insertText textEdit data].freeze
15
+
10
16
  def process
11
17
  pins = host.locate_pins(params)
12
18
  set_result merge(pins)
@@ -17,14 +23,15 @@ module Solargraph
17
23
  # @param pins [Array<Pin::Base>]
18
24
  # @return [Hash]
19
25
  def merge pins
20
- return params if pins.empty?
26
+ original = params.transform_keys(&:to_sym)
27
+ return original if pins.empty?
21
28
  docs = pins
22
29
  .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? }
23
- result = params
24
- .transform_keys(&:to_sym)
30
+ result = original
25
31
  .merge(pins.first.resolve_completion_item)
26
32
  .merge(documentation: markup_content(join_docs(docs)))
27
33
  result[:detail] = pins.first.detail
34
+ IMMUTABLE_FIELDS.each { |field| result[field] = original[field] }
28
35
  result
29
36
  end
30
37
 
@@ -15,7 +15,7 @@ module Solargraph
15
15
 
16
16
  # @return [Array<Hash>, nil]
17
17
  def code_location
18
- suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column)
18
+ suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) || []
19
19
  # @sg-ignore Need to add nil check here
20
20
  return nil if suggestions.empty?
21
21
  # @sg-ignore Need to add nil check here
@@ -29,7 +29,10 @@ module Solargraph
29
29
 
30
30
  # @return [Array<Hash>, nil]
31
31
  def require_location
32
- # @todo Terrible hack
32
+ # @todo Terrible hack - move this logic to [Solargraph::Source::Chain::Parameter]
33
+ # @example
34
+ # require 'click_me'
35
+ # ^^^^^^^^^^
33
36
  lib = host.library_for(params['textDocument']['uri'])
34
37
  rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']),
35
38
  Solargraph::Range.from_to(@line, @column, @line, @column))
@@ -11,8 +11,7 @@ module Solargraph
11
11
  contents = []
12
12
  suggestions = host.definitions_at(params['textDocument']['uri'], line, col)
13
13
  last_link = nil
14
- # @sg-ignore Need to add nil check here
15
- suggestions.each do |pin|
14
+ suggestions&.each do |pin|
16
15
  parts = []
17
16
  this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation
18
17
  parts.push this_link if !this_link.nil? && this_link != last_link