solargraph 0.54.1 → 0.56.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +2 -0
  3. data/.github/workflows/typecheck.yml +3 -1
  4. data/.gitignore +2 -0
  5. data/CHANGELOG.md +70 -0
  6. data/README.md +13 -3
  7. data/lib/solargraph/api_map/cache.rb +10 -1
  8. data/lib/solargraph/api_map/index.rb +175 -0
  9. data/lib/solargraph/api_map/store.rb +79 -126
  10. data/lib/solargraph/api_map.rb +247 -93
  11. data/lib/solargraph/bench.rb +17 -1
  12. data/lib/solargraph/complex_type/type_methods.rb +13 -1
  13. data/lib/solargraph/complex_type/unique_type.rb +118 -9
  14. data/lib/solargraph/complex_type.rb +48 -18
  15. data/lib/solargraph/convention/base.rb +3 -3
  16. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
  17. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  18. data/lib/solargraph/convention/struct_definition.rb +101 -0
  19. data/lib/solargraph/convention.rb +5 -3
  20. data/lib/solargraph/doc_map.rb +274 -56
  21. data/lib/solargraph/equality.rb +33 -0
  22. data/lib/solargraph/gem_pins.rb +53 -37
  23. data/lib/solargraph/language_server/host/message_worker.rb +31 -11
  24. data/lib/solargraph/language_server/host.rb +24 -13
  25. data/lib/solargraph/language_server/message/base.rb +19 -12
  26. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
  27. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  28. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  29. data/lib/solargraph/language_server/message/initialize.rb +3 -1
  30. data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
  31. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
  32. data/lib/solargraph/library.rb +13 -11
  33. data/lib/solargraph/location.rb +28 -0
  34. data/lib/solargraph/logging.rb +1 -0
  35. data/lib/solargraph/parser/comment_ripper.rb +12 -6
  36. data/lib/solargraph/parser/flow_sensitive_typing.rb +227 -0
  37. data/lib/solargraph/parser/node_methods.rb +15 -1
  38. data/lib/solargraph/parser/node_processor.rb +3 -1
  39. data/lib/solargraph/parser/parser_gem/class_methods.rb +16 -14
  40. data/lib/solargraph/parser/parser_gem/node_chainer.rb +17 -23
  41. data/lib/solargraph/parser/parser_gem/node_methods.rb +5 -3
  42. data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
  43. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  44. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
  45. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +4 -2
  46. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +23 -2
  47. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
  48. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
  49. data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
  50. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
  51. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  52. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
  53. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +4 -3
  54. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +29 -6
  55. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  56. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +2 -1
  57. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
  58. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +28 -16
  59. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +2 -1
  60. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
  61. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
  62. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  63. data/lib/solargraph/parser/region.rb +1 -1
  64. data/lib/solargraph/parser.rb +1 -0
  65. data/lib/solargraph/pin/base.rb +326 -43
  66. data/lib/solargraph/pin/base_variable.rb +18 -10
  67. data/lib/solargraph/pin/block.rb +3 -3
  68. data/lib/solargraph/pin/breakable.rb +9 -0
  69. data/lib/solargraph/pin/callable.rb +77 -6
  70. data/lib/solargraph/pin/closure.rb +18 -1
  71. data/lib/solargraph/pin/common.rb +5 -0
  72. data/lib/solargraph/pin/delegated_method.rb +20 -1
  73. data/lib/solargraph/pin/documenting.rb +16 -0
  74. data/lib/solargraph/pin/keyword.rb +7 -2
  75. data/lib/solargraph/pin/local_variable.rb +15 -6
  76. data/lib/solargraph/pin/method.rb +172 -42
  77. data/lib/solargraph/pin/namespace.rb +17 -9
  78. data/lib/solargraph/pin/parameter.rb +61 -11
  79. data/lib/solargraph/pin/proxy_type.rb +12 -6
  80. data/lib/solargraph/pin/reference/override.rb +10 -6
  81. data/lib/solargraph/pin/reference/require.rb +2 -2
  82. data/lib/solargraph/pin/signature.rb +42 -0
  83. data/lib/solargraph/pin/singleton.rb +1 -1
  84. data/lib/solargraph/pin/symbol.rb +3 -2
  85. data/lib/solargraph/pin/until.rb +18 -0
  86. data/lib/solargraph/pin/while.rb +18 -0
  87. data/lib/solargraph/pin.rb +4 -1
  88. data/lib/solargraph/pin_cache.rb +185 -0
  89. data/lib/solargraph/position.rb +16 -0
  90. data/lib/solargraph/range.rb +16 -0
  91. data/lib/solargraph/rbs_map/conversions.rb +199 -66
  92. data/lib/solargraph/rbs_map/core_fills.rb +32 -16
  93. data/lib/solargraph/rbs_map/core_map.rb +34 -11
  94. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  95. data/lib/solargraph/rbs_map.rb +74 -16
  96. data/lib/solargraph/shell.rb +19 -18
  97. data/lib/solargraph/source/chain/array.rb +12 -8
  98. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  99. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  100. data/lib/solargraph/source/chain/call.rb +62 -25
  101. data/lib/solargraph/source/chain/constant.rb +1 -1
  102. data/lib/solargraph/source/chain/hash.rb +9 -3
  103. data/lib/solargraph/source/chain/head.rb +1 -1
  104. data/lib/solargraph/source/chain/if.rb +6 -1
  105. data/lib/solargraph/source/chain/link.rb +19 -5
  106. data/lib/solargraph/source/chain/literal.rb +27 -2
  107. data/lib/solargraph/source/chain/or.rb +1 -1
  108. data/lib/solargraph/source/chain/z_super.rb +1 -1
  109. data/lib/solargraph/source/chain.rb +106 -62
  110. data/lib/solargraph/source/cursor.rb +1 -11
  111. data/lib/solargraph/source/source_chainer.rb +2 -2
  112. data/lib/solargraph/source.rb +2 -1
  113. data/lib/solargraph/source_map/clip.rb +4 -2
  114. data/lib/solargraph/source_map/mapper.rb +9 -5
  115. data/lib/solargraph/source_map.rb +0 -17
  116. data/lib/solargraph/type_checker/checks.rb +4 -0
  117. data/lib/solargraph/type_checker.rb +41 -14
  118. data/lib/solargraph/version.rb +1 -1
  119. data/lib/solargraph/views/_method.erb +10 -10
  120. data/lib/solargraph/views/_namespace.erb +3 -3
  121. data/lib/solargraph/views/document.erb +10 -10
  122. data/lib/solargraph/workspace/config.rb +7 -3
  123. data/lib/solargraph/workspace.rb +16 -6
  124. data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -2
  125. data/lib/solargraph/yard_map/mapper/to_method.rb +55 -15
  126. data/lib/solargraph/yard_map/mapper/to_namespace.rb +5 -2
  127. data/lib/solargraph/yard_map/mapper.rb +5 -3
  128. data/lib/solargraph/yard_map/to_method.rb +4 -2
  129. data/lib/solargraph/yardoc.rb +6 -9
  130. data/lib/solargraph.rb +20 -1
  131. data/rbs/fills/tuple.rbs +150 -0
  132. data/rbs_collection.yaml +19 -0
  133. data/solargraph.gemspec +6 -5
  134. metadata +56 -29
  135. data/lib/solargraph/cache.rb +0 -77
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+ require 'benchmark'
5
+
3
6
  module Solargraph
4
7
  # A collection of pins generated from required gems.
5
8
  #
6
9
  class DocMap
10
+ include Logging
11
+
7
12
  # @return [Array<String>]
8
13
  attr_reader :requires
14
+ alias required requires
9
15
 
10
16
  # @return [Array<Gem::Specification>]
11
17
  attr_reader :preferences
@@ -14,31 +20,120 @@ module Solargraph
14
20
  attr_reader :pins
15
21
 
16
22
  # @return [Array<Gem::Specification>]
17
- attr_reader :uncached_gemspecs
23
+ def uncached_gemspecs
24
+ (uncached_yard_gemspecs + uncached_rbs_collection_gemspecs).sort.
25
+ uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
26
+ end
27
+
28
+ # @return [Array<Gem::Specification>]
29
+ attr_reader :uncached_yard_gemspecs
30
+
31
+ # @return [Array<Gem::Specification>]
32
+ attr_reader :uncached_rbs_collection_gemspecs
33
+
34
+ attr_reader :rbs_collection_path
35
+
36
+ attr_reader :rbs_collection_config_path
37
+
38
+ # @return [Workspace, nil]
39
+ attr_reader :workspace
40
+
41
+ # @return [Environ]
42
+ attr_reader :environ
18
43
 
19
44
  # @param requires [Array<String>]
20
45
  # @param preferences [Array<Gem::Specification>]
21
- # @param rbs_path [String, Pathname, nil]
22
- def initialize(requires, preferences, rbs_path = nil)
46
+ # @param workspace [Workspace, nil]
47
+ def initialize(requires, preferences, workspace = nil)
23
48
  @requires = requires.compact
24
49
  @preferences = preferences.compact
25
- @rbs_path = rbs_path
26
- generate
50
+ @workspace = workspace
51
+ @rbs_collection_path = workspace&.rbs_collection_path
52
+ @rbs_collection_config_path = workspace&.rbs_collection_config_path
53
+ @environ = Convention.for_global(self)
54
+ load_serialized_gem_pins
55
+ pins.concat @environ.pins
56
+ end
57
+
58
+ def cache_all!(out)
59
+ # if we log at debug level:
60
+ if logger.info?
61
+ gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ')
62
+ logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty?
63
+ end
64
+ logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" }
65
+ logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" }
66
+ load_serialized_gem_pins
67
+ uncached_gemspecs.each do |gemspec|
68
+ cache(gemspec, out: out)
69
+ end
70
+ load_serialized_gem_pins
71
+ @uncached_rbs_collection_gemspecs = []
72
+ @uncached_yard_gemspecs = []
73
+ end
74
+
75
+ def cache_yard_pins(gemspec, out)
76
+ pins = GemPins.build_yard_pins(gemspec)
77
+ PinCache.serialize_yard_gem(gemspec, pins)
78
+ logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
79
+ end
80
+
81
+ def cache_rbs_collection_pins(gemspec, out)
82
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
83
+ pins = rbs_map.pins
84
+ rbs_version_cache_key = rbs_map.cache_key
85
+ # cache pins even if result is zero, so we don't retry building pins
86
+ pins ||= []
87
+ PinCache.serialize_rbs_collection_gem(gemspec, rbs_version_cache_key, pins)
88
+ logger.info { "Cached #{pins.length} RBS collection pins for gem #{gemspec.name} #{gemspec.version} with cache_key #{rbs_version_cache_key.inspect}" unless pins.empty? }
89
+ end
90
+
91
+ # @param gemspec [Gem::Specification]
92
+ def cache(gemspec, rebuild: false, out: nil)
93
+ build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild
94
+ build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
95
+ if build_yard || build_rbs_collection
96
+ type = []
97
+ type << 'YARD' if build_yard
98
+ type << 'RBS collection' if build_rbs_collection
99
+ out.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") if out
100
+ end
101
+ cache_yard_pins(gemspec, out) if build_yard
102
+ cache_rbs_collection_pins(gemspec, out) if build_rbs_collection
27
103
  end
28
104
 
29
105
  # @return [Array<Gem::Specification>]
30
106
  def gemspecs
31
- @gemspecs ||= required_gem_map.values.compact
107
+ @gemspecs ||= required_gems_map.values.compact.flatten
32
108
  end
33
109
 
34
110
  # @return [Array<String>]
35
111
  def unresolved_requires
36
- @unresolved_requires ||= required_gem_map.select { |_, gemspec| gemspec.nil? }.keys
112
+ @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
113
+ end
114
+
115
+ def self.all_yard_gems_in_memory
116
+ @yard_gems_in_memory ||= {}
37
117
  end
38
118
 
39
- # @return [Hash{Gem::Specification => Array[Pin::Base]}]
40
- def self.gems_in_memory
41
- @gems_in_memory ||= {}
119
+ def self.all_rbs_collection_gems_in_memory
120
+ @rbs_collection_gems_in_memory ||= {}
121
+ end
122
+
123
+ def yard_pins_in_memory
124
+ self.class.all_yard_gems_in_memory
125
+ end
126
+
127
+ def rbs_collection_pins_in_memory
128
+ self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {}
129
+ end
130
+
131
+ def self.all_combined_pins_in_memory
132
+ @combined_pins_in_memory ||= {}
133
+ end
134
+
135
+ def combined_pins_in_memory
136
+ self.class.all_combined_pins_in_memory
42
137
  end
43
138
 
44
139
  # @return [Set<Gem::Specification>]
@@ -49,23 +144,34 @@ module Solargraph
49
144
  private
50
145
 
51
146
  # @return [void]
52
- def generate
147
+ def load_serialized_gem_pins
53
148
  @pins = []
54
- @uncached_gemspecs = []
55
- required_gem_map.each do |path, gemspec|
56
- if gemspec
57
- try_cache gemspec
58
- else
59
- try_stdlib_map path
149
+ @uncached_yard_gemspecs = []
150
+ @uncached_rbs_collection_gemspecs = []
151
+ with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
152
+ paths = Hash[without_gemspecs].keys
153
+ gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a
154
+
155
+ paths.each do |path|
156
+ rbs_pins = deserialize_stdlib_rbs_map path
157
+ end
158
+
159
+ logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." }
160
+ time = Benchmark.measure do
161
+ gemspecs.each do |gemspec|
162
+ pins = deserialize_combined_pin_cache gemspec
163
+ @pins.concat pins if pins
60
164
  end
61
165
  end
62
- dependencies.each { |dep| try_cache dep }
63
- @uncached_gemspecs.uniq!
166
+ logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" }
167
+ @uncached_yard_gemspecs.uniq!
168
+ @uncached_rbs_collection_gemspecs.uniq!
169
+ nil
64
170
  end
65
171
 
66
- # @return [Hash{String => Gem::Specification, nil}]
67
- def required_gem_map
68
- @required_gem_map ||= requires.to_h { |path| [path, resolve_path_to_gemspec(path)] }
172
+ # @return [Hash{String => Array<Gem::Specification>}]
173
+ def required_gems_map
174
+ @required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
69
175
  end
70
176
 
71
177
  # @return [Hash{String => Gem::Specification}]
@@ -73,32 +179,97 @@ module Solargraph
73
179
  @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
74
180
  end
75
181
 
182
+ # @param gemspec [Gem::Specification]
183
+ # @return [Array<Pin::Base>]
184
+ def deserialize_yard_pin_cache gemspec
185
+ if yard_pins_in_memory.key?([gemspec.name, gemspec.version])
186
+ return yard_pins_in_memory[[gemspec.name, gemspec.version]]
187
+ end
188
+
189
+ cached = PinCache.deserialize_yard_gem(gemspec)
190
+ if cached
191
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
192
+ yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached
193
+ cached
194
+ else
195
+ logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
196
+ @uncached_yard_gemspecs.push gemspec
197
+ nil
198
+ end
199
+ end
200
+
76
201
  # @param gemspec [Gem::Specification]
77
202
  # @return [void]
78
- def try_cache gemspec
79
- return if try_gem_in_memory(gemspec)
80
- cache_file = File.join('gems', "#{gemspec.name}-#{gemspec.version}.ser")
81
- if Cache.exist?(cache_file)
82
- cached = Cache.load(cache_file)
83
- gempins = update_from_collection(gemspec, cached)
84
- self.class.gems_in_memory[gemspec] = gempins
85
- @pins.concat gempins
203
+ def deserialize_combined_pin_cache(gemspec)
204
+ unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil?
205
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
206
+ end
207
+
208
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
209
+ rbs_version_cache_key = rbs_map.cache_key
210
+
211
+ cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key)
212
+ if cached
213
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
214
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached
215
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
216
+ end
217
+
218
+ rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
219
+
220
+ yard_pins = deserialize_yard_pin_cache gemspec
221
+
222
+ if !rbs_collection_pins.nil? && !yard_pins.nil?
223
+ logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" }
224
+ combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
225
+ PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
226
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins
227
+ logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" }
228
+ return combined_pins
229
+ end
230
+
231
+ if !yard_pins.nil?
232
+ logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" }
233
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins
234
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
235
+ elsif !rbs_collection_pins.nil?
236
+ logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" }
237
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins
238
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
86
239
  else
87
- Solargraph.logger.debug "No pin cache for #{gemspec.name} #{gemspec.version}"
88
- @uncached_gemspecs.push gemspec
240
+ logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" }
241
+ return nil
89
242
  end
90
243
  end
91
244
 
92
245
  # @param path [String] require path that might be in the RBS stdlib collection
93
246
  # @return [void]
94
- def try_stdlib_map path
247
+ def deserialize_stdlib_rbs_map path
95
248
  map = RbsMap::StdlibMap.load(path)
96
249
  if map.resolved?
97
- Solargraph.logger.debug "Loading stdlib pins for #{path}"
250
+ logger.debug { "Loading stdlib pins for #{path}" }
98
251
  @pins.concat map.pins
252
+ logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" }
253
+ map.pins
99
254
  else
100
255
  # @todo Temporarily ignoring unresolved `require 'set'`
101
- Solargraph.logger.debug "Require path #{path} could not be resolved" unless path == 'set'
256
+ logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
257
+ nil
258
+ end
259
+ end
260
+
261
+ # @return [Array<Pin::Base>, nil]
262
+ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
263
+ return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
264
+ cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key)
265
+ if cached
266
+ logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty?
267
+ rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached
268
+ cached
269
+ else
270
+ logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}"
271
+ @uncached_rbs_collection_gemspecs.push gemspec
272
+ nil
102
273
  end
103
274
  end
104
275
 
@@ -112,21 +283,11 @@ module Solargraph
112
283
  true
113
284
  end
114
285
 
115
- def update_from_collection gemspec, gempins
116
- return gempins unless @rbs_path && File.directory?(@rbs_path)
117
- return gempins if RbsMap.new(gemspec.name, gemspec.version).resolved?
118
-
119
- rbs_map = RbsMap.new(gemspec.name, gemspec.version, directories: [@rbs_path])
120
- return gempins unless rbs_map.resolved?
121
-
122
- Solargraph.logger.info "Updating #{gemspec.name} #{gemspec.version} from collection"
123
- GemPins.combine(gempins, rbs_map)
124
- end
125
-
126
286
  # @param path [String]
127
- # @return [Gem::Specification, nil]
128
- def resolve_path_to_gemspec path
287
+ # @return [::Array<Gem::Specification>, nil]
288
+ def resolve_path_to_gemspecs path
129
289
  return nil if path.empty?
290
+ return gemspecs_required_from_bundler if path == 'bundler/require'
130
291
 
131
292
  gemspec = Gem::Specification.find_by_path(path)
132
293
  if gemspec.nil?
@@ -140,17 +301,18 @@ module Solargraph
140
301
  file = "lib/#{path}.rb"
141
302
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
142
303
  rescue Gem::MissingSpecError
143
- Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
144
- nil
304
+ logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" }
305
+ []
145
306
  end
146
307
  end
147
- gemspec_or_preference gemspec
308
+ return nil if gemspec.nil?
309
+ [gemspec_or_preference(gemspec)]
148
310
  end
149
311
 
150
- # @param gemspec [Gem::Specification, nil]
151
- # @return [Gem::Specification, nil]
312
+ # @param gemspec [Gem::Specification]
313
+ # @return [Gem::Specification]
152
314
  def gemspec_or_preference gemspec
153
- return gemspec unless gemspec && preference_map.key?(gemspec.name)
315
+ return gemspec unless preference_map.key?(gemspec.name)
154
316
  return gemspec if gemspec.version == preference_map[gemspec.name].version
155
317
 
156
318
  change_gemspec_version gemspec, preference_map[by_path.name].version
@@ -169,12 +331,15 @@ module Solargraph
169
331
  # @param gemspec [Gem::Specification]
170
332
  # @return [Array<Gem::Specification>]
171
333
  def fetch_dependencies gemspec
334
+ # @param spec [Gem::Dependency]
172
335
  only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
173
336
  Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
174
- dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
337
+ dep = Gem.loaded_specs[spec.name]
338
+ # @todo is next line necessary?
339
+ dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
175
340
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
176
341
  rescue Gem::MissingSpecError
177
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
342
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems."
178
343
  end.to_a
179
344
  end
180
345
 
@@ -183,5 +348,58 @@ module Solargraph
183
348
  def only_runtime_dependencies gemspec
184
349
  gemspec.dependencies - gemspec.development_dependencies
185
350
  end
351
+
352
+
353
+ def inspect
354
+ self.class.inspect
355
+ end
356
+
357
+ def gemspecs_required_from_bundler
358
+ if workspace&.directory && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
359
+ # Find only the gems bundler is now using
360
+ Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
361
+ logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}"
362
+ [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
363
+ rescue Gem::MissingSpecError => e
364
+ logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
365
+ # can happen in local filesystem references
366
+ specs = resolve_path_to_gemspecs lazy_spec.name
367
+ logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
368
+ next specs
369
+ end.compact
370
+ else
371
+ logger.info 'Fetching gemspecs required from Bundler (bundler/require)'
372
+ gemspecs_required_from_external_bundle
373
+ end
374
+ end
375
+
376
+ def gemspecs_required_from_external_bundle
377
+ logger.info 'Fetching gemspecs required from external bundle'
378
+ return [] unless workspace&.directory
379
+
380
+ Solargraph.with_clean_env do
381
+ cmd = [
382
+ 'ruby', '-e',
383
+ "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }"
384
+ ]
385
+ o, e, s = Open3.capture3(*cmd)
386
+ if s.success?
387
+ Solargraph.logger.debug "External bundle: #{o}"
388
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
389
+ hash.flat_map do |name, version|
390
+ Gem::Specification.find_by_name(name, version)
391
+ rescue Gem::MissingSpecError => e
392
+ logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess")
393
+ # can happen in local filesystem references
394
+ specs = resolve_path_to_gemspecs name
395
+ logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil?
396
+ next specs
397
+ end.compact
398
+ else
399
+ Solargraph.logger.warn e
400
+ raise BundleNotFoundError, "Failed to load gems from bundle at #{workspace&.directory}"
401
+ end
402
+ end
403
+ end
186
404
  end
187
405
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ # @abstract This mixin relies on these -
5
+ # methods:
6
+ # equality_fields()
7
+ module Equality
8
+ # @!method equality_fields
9
+ # @return [Array]
10
+
11
+ # @param other [Object]
12
+ # @return [Boolean]
13
+ def eql?(other)
14
+ self.class.eql?(other.class) &&
15
+ equality_fields.eql?(other.equality_fields)
16
+ end
17
+
18
+ # @param other [Object]
19
+ # @return [Boolean]
20
+ def ==(other)
21
+ self.eql?(other)
22
+ end
23
+
24
+ def hash
25
+ equality_fields.hash
26
+ end
27
+
28
+ def freeze
29
+ equality_fields.each(&:freeze)
30
+ super
31
+ end
32
+ end
33
+ end
@@ -7,59 +7,75 @@ module Solargraph
7
7
  # documentation.
8
8
  #
9
9
  module GemPins
10
- # Build an array of pins from a gem specification. The process starts with
11
- # YARD, enhances the resulting pins with RBS definitions, and appends RBS
12
- # pins that don't exist in the YARD mapping.
13
- #
10
+ class << self
11
+ include Logging
12
+ end
13
+
14
14
  # @param gemspec [Gem::Specification]
15
15
  # @return [Array<Pin::Base>]
16
- def self.build(gemspec)
17
- yard_pins = build_yard_pins(gemspec)
18
- rbs_map = RbsMap.from_gemspec(gemspec)
19
- combine yard_pins, rbs_map
16
+ def self.build_yard_pins(gemspec)
17
+ Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec)
18
+ yardoc = Yardoc.load!(gemspec)
19
+ YardMap::Mapper.new(yardoc, gemspec).map
20
+ end
21
+
22
+ # @param pins [Array<Pin::Base>]
23
+ def self.combine_method_pins_by_path(pins)
24
+ # bad_pins = pins.select { |pin| pin.is_a?(Pin::Method) && pin.path == 'StringIO.open' && pin.source == :rbs }; raise "wtf: #{bad_pins}" if bad_pins.length > 1
25
+ method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method }
26
+ by_path = method_pins.group_by(&:path)
27
+ by_path.transform_values! do |pins|
28
+ GemPins.combine_method_pins(*pins)
29
+ end
30
+ by_path.values + alias_pins
31
+ end
32
+
33
+ def self.combine_method_pins(*pins)
34
+ out = pins.reduce(nil) do |memo, pin|
35
+ next pin if memo.nil?
36
+ if memo == pin && memo.source != :combined
37
+ # @todo we should track down situations where we are handled
38
+ # the same pin from the same source here and eliminate them -
39
+ # this is an efficiency workaround for now
40
+ next memo
41
+ end
42
+ memo.combine_with(pin)
43
+ end
44
+ logger.debug { "GemPins.combine_method_pins(pins.length=#{pins.length}, pins=#{pins}) => #{out.inspect}" }
45
+ out
20
46
  end
21
47
 
22
48
  # @param yard_pins [Array<Pin::Base>]
23
49
  # @param rbs_map [RbsMap]
24
50
  # @return [Array<Pin::Base>]
25
- def self.combine(yard_pins, rbs_map)
51
+ def self.combine(yard_pins, rbs_pins)
26
52
  in_yard = Set.new
27
- combined = yard_pins.map do |yard|
28
- in_yard.add yard.path
29
- next yard unless yard.is_a?(Pin::Method)
53
+ rbs_api_map = Solargraph::ApiMap.new(pins: rbs_pins)
54
+ combined = yard_pins.map do |yard_pin|
55
+ in_yard.add yard_pin.path
56
+ rbs_pin = rbs_api_map.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first
57
+ next yard_pin unless rbs_pin && yard_pin.class == Pin::Method
30
58
 
31
- rbs = rbs_map.path_pin(yard.path, Pin::Method)
32
- next yard unless rbs
59
+ unless rbs_pin
60
+ logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" }
61
+ next yard_pin
62
+ end
33
63
 
34
- # @sg-ignore
35
- yard.class.new(
36
- location: yard.location,
37
- closure: yard.closure,
38
- name: yard.name,
39
- comments: yard.comments,
40
- scope: yard.scope,
41
- parameters: rbs.parameters,
42
- generics: rbs.generics,
43
- node: yard.node,
44
- signatures: yard.signatures,
45
- return_type: best_return_type(rbs.return_type, yard.return_type)
46
- )
64
+ out = combine_method_pins(rbs_pin, yard_pin)
65
+ logger.debug { "GemPins.combine: Combining yard.path=#{yard_pin.path} - rbs=#{rbs_pin.inspect} with yard=#{yard_pin.inspect} into #{out}" }
66
+ out
47
67
  end
48
- in_rbs = rbs_map.pins.reject { |pin| in_yard.include?(pin.path) }
49
- combined + in_rbs
68
+ in_rbs_only = rbs_pins.select do |pin|
69
+ pin.path.nil? || !in_yard.include?(pin.path)
70
+ end
71
+ out = combined + in_rbs_only
72
+ logger.debug { "GemPins#combine: Returning #{out.length} combined pins" }
73
+ out
50
74
  end
51
75
 
52
76
  class << self
53
77
  private
54
78
 
55
- # @param gemspec [Gem::Specification]
56
- # @return [Array<Pin::Base>]
57
- def build_yard_pins(gemspec)
58
- Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec)
59
- yardoc = Yardoc.load!(gemspec)
60
- YardMap::Mapper.new(yardoc, gemspec).map
61
- end
62
-
63
79
  # Select the first defined type.
64
80
  #
65
81
  # @param choices [Array<ComplexType>]
@@ -3,12 +3,21 @@
3
3
  module Solargraph
4
4
  module LanguageServer
5
5
  class Host
6
- # A serial worker Thread to handle message.
7
- #
8
- # this make check pending message possible, and maybe cancelled to speedup process
6
+ # A serial worker Thread to handle incoming messages.
9
7
  #
10
8
  class MessageWorker
11
- UPDATE_METHODS = ['textDocument/didOpen', 'textDocument/didChange', 'workspace/didChangeWatchedFiles'].freeze
9
+ UPDATE_METHODS = [
10
+ 'textDocument/didChange',
11
+ 'textDocument/didClose',
12
+ 'textDocument/didOpen',
13
+ 'textDocument/didSave',
14
+ 'workspace/didChangeConfiguration',
15
+ 'workspace/didChangeWatchedFiles',
16
+ 'workspace/didCreateFiles',
17
+ 'workspace/didChangeWorkspaceFolders',
18
+ 'workspace/didDeleteFiles',
19
+ 'workspace/didRenameFiles'
20
+ ].freeze
12
21
 
13
22
  # @param host [Host]
14
23
  def initialize(host)
@@ -64,22 +73,33 @@ module Solargraph
64
73
  private
65
74
 
66
75
  def next_message
67
- # Prioritize updates and version-dependent messages for performance
68
- idx = messages.find_index do |msg|
69
- UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
70
- end
71
- # @todo We might want to clear duplicate instances of this message
72
- # that occur before the next update
73
- return messages.shift unless idx
76
+ cancel_message || next_priority
77
+ end
78
+
79
+ def cancel_message
80
+ # Handle cancellations first
81
+ idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' }
82
+ return unless idx
74
83
 
75
84
  msg = messages[idx]
76
85
  messages.delete_at idx
77
86
  msg
78
87
  end
79
88
 
89
+ def next_priority
90
+ # Prioritize updates and version-dependent messages for performance
91
+ idx = messages.find_index do |msg|
92
+ UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
93
+ end
94
+ idx ? messages.delete_at(idx) : messages.shift
95
+ end
96
+
80
97
  # True if the message requires a previous update to have executed in
81
98
  # order to work correctly.
82
99
  #
100
+ # @param msg [Hash{String => Object}]
101
+ # @todo need boolish type from RBS
102
+ # @return [Object]
83
103
  def version_dependent? msg
84
104
  msg['textDocument'] && msg['position']
85
105
  end