solargraph 0.54.4 → 0.56.2

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 +62 -0
  6. data/README.md +13 -3
  7. data/lib/solargraph/api_map/index.rb +24 -16
  8. data/lib/solargraph/api_map/store.rb +48 -23
  9. data/lib/solargraph/api_map.rb +175 -77
  10. data/lib/solargraph/bench.rb +17 -1
  11. data/lib/solargraph/complex_type/type_methods.rb +6 -1
  12. data/lib/solargraph/complex_type/unique_type.rb +98 -9
  13. data/lib/solargraph/complex_type.rb +35 -6
  14. data/lib/solargraph/convention/base.rb +3 -3
  15. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +60 -0
  16. data/lib/solargraph/convention/data_definition/data_definition_node.rb +89 -0
  17. data/lib/solargraph/convention/data_definition.rb +104 -0
  18. data/lib/solargraph/convention/gemspec.rb +2 -1
  19. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
  20. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  21. data/lib/solargraph/convention/struct_definition.rb +141 -0
  22. data/lib/solargraph/convention.rb +5 -3
  23. data/lib/solargraph/doc_map.rb +277 -57
  24. data/lib/solargraph/gem_pins.rb +53 -37
  25. data/lib/solargraph/language_server/host/message_worker.rb +10 -7
  26. data/lib/solargraph/language_server/host.rb +12 -2
  27. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
  28. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  29. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  30. data/lib/solargraph/library.rb +45 -17
  31. data/lib/solargraph/location.rb +21 -0
  32. data/lib/solargraph/logging.rb +1 -0
  33. data/lib/solargraph/parser/comment_ripper.rb +12 -6
  34. data/lib/solargraph/parser/flow_sensitive_typing.rb +227 -0
  35. data/lib/solargraph/parser/node_methods.rb +14 -0
  36. data/lib/solargraph/parser/node_processor/base.rb +9 -4
  37. data/lib/solargraph/parser/node_processor.rb +21 -8
  38. data/lib/solargraph/parser/parser_gem/class_methods.rb +16 -14
  39. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  40. data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
  41. data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
  42. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  43. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
  44. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +4 -2
  45. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -1
  46. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
  47. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
  48. data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
  49. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
  50. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  51. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
  52. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -1
  53. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +4 -1
  54. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +8 -7
  55. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +42 -0
  56. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  57. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +3 -1
  58. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
  59. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +28 -16
  60. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +3 -1
  61. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
  62. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
  63. data/lib/solargraph/parser/parser_gem/node_processors.rb +14 -0
  64. data/lib/solargraph/parser/region.rb +1 -1
  65. data/lib/solargraph/parser.rb +1 -0
  66. data/lib/solargraph/pin/base.rb +316 -28
  67. data/lib/solargraph/pin/base_variable.rb +16 -9
  68. data/lib/solargraph/pin/block.rb +2 -0
  69. data/lib/solargraph/pin/breakable.rb +9 -0
  70. data/lib/solargraph/pin/callable.rb +74 -3
  71. data/lib/solargraph/pin/closure.rb +18 -1
  72. data/lib/solargraph/pin/common.rb +5 -0
  73. data/lib/solargraph/pin/delegated_method.rb +20 -1
  74. data/lib/solargraph/pin/documenting.rb +16 -0
  75. data/lib/solargraph/pin/keyword.rb +7 -2
  76. data/lib/solargraph/pin/local_variable.rb +15 -6
  77. data/lib/solargraph/pin/method.rb +169 -43
  78. data/lib/solargraph/pin/namespace.rb +17 -9
  79. data/lib/solargraph/pin/parameter.rb +60 -11
  80. data/lib/solargraph/pin/proxy_type.rb +12 -6
  81. data/lib/solargraph/pin/reference/override.rb +10 -6
  82. data/lib/solargraph/pin/reference/require.rb +2 -2
  83. data/lib/solargraph/pin/signature.rb +42 -0
  84. data/lib/solargraph/pin/singleton.rb +1 -1
  85. data/lib/solargraph/pin/symbol.rb +3 -2
  86. data/lib/solargraph/pin/until.rb +18 -0
  87. data/lib/solargraph/pin/while.rb +18 -0
  88. data/lib/solargraph/pin.rb +4 -1
  89. data/lib/solargraph/pin_cache.rb +185 -0
  90. data/lib/solargraph/position.rb +9 -0
  91. data/lib/solargraph/range.rb +9 -0
  92. data/lib/solargraph/rbs_map/conversions.rb +221 -67
  93. data/lib/solargraph/rbs_map/core_fills.rb +32 -16
  94. data/lib/solargraph/rbs_map/core_map.rb +34 -11
  95. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  96. data/lib/solargraph/rbs_map.rb +74 -17
  97. data/lib/solargraph/shell.rb +17 -18
  98. data/lib/solargraph/source/chain/array.rb +11 -7
  99. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  100. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  101. data/lib/solargraph/source/chain/call.rb +53 -23
  102. data/lib/solargraph/source/chain/constant.rb +1 -1
  103. data/lib/solargraph/source/chain/hash.rb +4 -3
  104. data/lib/solargraph/source/chain/head.rb +1 -1
  105. data/lib/solargraph/source/chain/if.rb +1 -1
  106. data/lib/solargraph/source/chain/link.rb +2 -0
  107. data/lib/solargraph/source/chain/literal.rb +22 -2
  108. data/lib/solargraph/source/chain/or.rb +1 -1
  109. data/lib/solargraph/source/chain/z_super.rb +1 -1
  110. data/lib/solargraph/source/chain.rb +78 -48
  111. data/lib/solargraph/source/source_chainer.rb +2 -2
  112. data/lib/solargraph/source_map/clip.rb +3 -1
  113. data/lib/solargraph/source_map/mapper.rb +9 -5
  114. data/lib/solargraph/source_map.rb +0 -17
  115. data/lib/solargraph/type_checker/checks.rb +4 -0
  116. data/lib/solargraph/type_checker.rb +35 -8
  117. data/lib/solargraph/version.rb +1 -1
  118. data/lib/solargraph/views/_method.erb +10 -10
  119. data/lib/solargraph/views/_namespace.erb +3 -3
  120. data/lib/solargraph/views/document.erb +10 -10
  121. data/lib/solargraph/workspace/config.rb +1 -1
  122. data/lib/solargraph/workspace.rb +23 -5
  123. data/lib/solargraph/yard_map/helpers.rb +29 -1
  124. data/lib/solargraph/yard_map/mapper/to_constant.rb +7 -5
  125. data/lib/solargraph/yard_map/mapper/to_method.rb +53 -18
  126. data/lib/solargraph/yard_map/mapper/to_namespace.rb +9 -7
  127. data/lib/solargraph/yard_map/mapper.rb +4 -3
  128. data/lib/solargraph/yard_map/to_method.rb +4 -2
  129. data/lib/solargraph/yardoc.rb +7 -8
  130. data/lib/solargraph.rb +32 -1
  131. data/rbs/fills/tuple.rbs +150 -0
  132. data/rbs_collection.yaml +19 -0
  133. data/solargraph.gemspec +2 -1
  134. metadata +37 -9
  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,121 @@ 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.concat(uncached_rbs_collection_gemspecs)
25
+ .sort
26
+ .uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
27
+ end
28
+
29
+ # @return [Array<Gem::Specification>]
30
+ attr_reader :uncached_yard_gemspecs
31
+
32
+ # @return [Array<Gem::Specification>]
33
+ attr_reader :uncached_rbs_collection_gemspecs
34
+
35
+ attr_reader :rbs_collection_path
36
+
37
+ attr_reader :rbs_collection_config_path
38
+
39
+ # @return [Workspace, nil]
40
+ attr_reader :workspace
41
+
42
+ # @return [Environ]
43
+ attr_reader :environ
18
44
 
19
45
  # @param requires [Array<String>]
20
46
  # @param preferences [Array<Gem::Specification>]
21
- # @param rbs_path [String, Pathname, nil]
22
- def initialize(requires, preferences, rbs_path = nil)
47
+ # @param workspace [Workspace, nil]
48
+ def initialize(requires, preferences, workspace = nil)
23
49
  @requires = requires.compact
24
50
  @preferences = preferences.compact
25
- @rbs_path = rbs_path
26
- generate
51
+ @workspace = workspace
52
+ @rbs_collection_path = workspace&.rbs_collection_path
53
+ @rbs_collection_config_path = workspace&.rbs_collection_config_path
54
+ @environ = Convention.for_global(self)
55
+ load_serialized_gem_pins
56
+ pins.concat @environ.pins
57
+ end
58
+
59
+ def cache_all!(out)
60
+ # if we log at debug level:
61
+ if logger.info?
62
+ gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ')
63
+ logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty?
64
+ end
65
+ logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" }
66
+ logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" }
67
+ load_serialized_gem_pins
68
+ uncached_gemspecs.each do |gemspec|
69
+ cache(gemspec, out: out)
70
+ end
71
+ load_serialized_gem_pins
72
+ @uncached_rbs_collection_gemspecs = []
73
+ @uncached_yard_gemspecs = []
74
+ end
75
+
76
+ def cache_yard_pins(gemspec, out)
77
+ pins = GemPins.build_yard_pins(gemspec)
78
+ PinCache.serialize_yard_gem(gemspec, pins)
79
+ logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
80
+ end
81
+
82
+ def cache_rbs_collection_pins(gemspec, out)
83
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
84
+ pins = rbs_map.pins
85
+ rbs_version_cache_key = rbs_map.cache_key
86
+ # cache pins even if result is zero, so we don't retry building pins
87
+ pins ||= []
88
+ PinCache.serialize_rbs_collection_gem(gemspec, rbs_version_cache_key, pins)
89
+ 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? }
90
+ end
91
+
92
+ # @param gemspec [Gem::Specification]
93
+ def cache(gemspec, rebuild: false, out: nil)
94
+ build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild
95
+ build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
96
+ if build_yard || build_rbs_collection
97
+ type = []
98
+ type << 'YARD' if build_yard
99
+ type << 'RBS collection' if build_rbs_collection
100
+ out.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") if out
101
+ end
102
+ cache_yard_pins(gemspec, out) if build_yard
103
+ cache_rbs_collection_pins(gemspec, out) if build_rbs_collection
27
104
  end
28
105
 
29
106
  # @return [Array<Gem::Specification>]
30
107
  def gemspecs
31
- @gemspecs ||= required_gem_map.values.compact
108
+ @gemspecs ||= required_gems_map.values.compact.flatten
32
109
  end
33
110
 
34
111
  # @return [Array<String>]
35
112
  def unresolved_requires
36
- @unresolved_requires ||= required_gem_map.select { |_, gemspec| gemspec.nil? }.keys
113
+ @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
114
+ end
115
+
116
+ def self.all_yard_gems_in_memory
117
+ @yard_gems_in_memory ||= {}
118
+ end
119
+
120
+ def self.all_rbs_collection_gems_in_memory
121
+ @rbs_collection_gems_in_memory ||= {}
122
+ end
123
+
124
+ def yard_pins_in_memory
125
+ self.class.all_yard_gems_in_memory
126
+ end
127
+
128
+ def rbs_collection_pins_in_memory
129
+ self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {}
130
+ end
131
+
132
+ def self.all_combined_pins_in_memory
133
+ @combined_pins_in_memory ||= {}
37
134
  end
38
135
 
39
- # @return [Hash{Gem::Specification => Array[Pin::Base]}]
40
- def self.gems_in_memory
41
- @gems_in_memory ||= {}
136
+ def combined_pins_in_memory
137
+ self.class.all_combined_pins_in_memory
42
138
  end
43
139
 
44
140
  # @return [Set<Gem::Specification>]
@@ -49,23 +145,34 @@ module Solargraph
49
145
  private
50
146
 
51
147
  # @return [void]
52
- def generate
148
+ def load_serialized_gem_pins
53
149
  @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
150
+ @uncached_yard_gemspecs = []
151
+ @uncached_rbs_collection_gemspecs = []
152
+ with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
153
+ paths = Hash[without_gemspecs].keys
154
+ gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a
155
+
156
+ paths.each do |path|
157
+ rbs_pins = deserialize_stdlib_rbs_map path
158
+ end
159
+
160
+ logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." }
161
+ time = Benchmark.measure do
162
+ gemspecs.each do |gemspec|
163
+ pins = deserialize_combined_pin_cache gemspec
164
+ @pins.concat pins if pins
60
165
  end
61
166
  end
62
- dependencies.each { |dep| try_cache dep }
63
- @uncached_gemspecs.uniq!
167
+ logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" }
168
+ @uncached_yard_gemspecs.uniq!
169
+ @uncached_rbs_collection_gemspecs.uniq!
170
+ nil
64
171
  end
65
172
 
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)] }
173
+ # @return [Hash{String => Array<Gem::Specification>}]
174
+ def required_gems_map
175
+ @required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
69
176
  end
70
177
 
71
178
  # @return [Hash{String => Gem::Specification}]
@@ -73,32 +180,97 @@ module Solargraph
73
180
  @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
74
181
  end
75
182
 
183
+ # @param gemspec [Gem::Specification]
184
+ # @return [Array<Pin::Base>]
185
+ def deserialize_yard_pin_cache gemspec
186
+ if yard_pins_in_memory.key?([gemspec.name, gemspec.version])
187
+ return yard_pins_in_memory[[gemspec.name, gemspec.version]]
188
+ end
189
+
190
+ cached = PinCache.deserialize_yard_gem(gemspec)
191
+ if cached
192
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
193
+ yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached
194
+ cached
195
+ else
196
+ logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
197
+ @uncached_yard_gemspecs.push gemspec
198
+ nil
199
+ end
200
+ end
201
+
76
202
  # @param gemspec [Gem::Specification]
77
203
  # @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
204
+ def deserialize_combined_pin_cache(gemspec)
205
+ unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil?
206
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
207
+ end
208
+
209
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
210
+ rbs_version_cache_key = rbs_map.cache_key
211
+
212
+ cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key)
213
+ if cached
214
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
215
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached
216
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
217
+ end
218
+
219
+ rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
220
+
221
+ yard_pins = deserialize_yard_pin_cache gemspec
222
+
223
+ if !rbs_collection_pins.nil? && !yard_pins.nil?
224
+ logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" }
225
+ combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
226
+ PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
227
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins
228
+ logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" }
229
+ return combined_pins
230
+ end
231
+
232
+ if !yard_pins.nil?
233
+ logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" }
234
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins
235
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
236
+ elsif !rbs_collection_pins.nil?
237
+ logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" }
238
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins
239
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
86
240
  else
87
- Solargraph.logger.debug "No pin cache for #{gemspec.name} #{gemspec.version}"
88
- @uncached_gemspecs.push gemspec
241
+ logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" }
242
+ return nil
89
243
  end
90
244
  end
91
245
 
92
246
  # @param path [String] require path that might be in the RBS stdlib collection
93
247
  # @return [void]
94
- def try_stdlib_map path
248
+ def deserialize_stdlib_rbs_map path
95
249
  map = RbsMap::StdlibMap.load(path)
96
250
  if map.resolved?
97
- Solargraph.logger.debug "Loading stdlib pins for #{path}"
251
+ logger.debug { "Loading stdlib pins for #{path}" }
98
252
  @pins.concat map.pins
253
+ logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" }
254
+ map.pins
99
255
  else
100
256
  # @todo Temporarily ignoring unresolved `require 'set'`
101
- Solargraph.logger.debug "Require path #{path} could not be resolved" unless path == 'set'
257
+ logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
258
+ nil
259
+ end
260
+ end
261
+
262
+ # @return [Array<Pin::Base>, nil]
263
+ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
264
+ return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
265
+ cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key)
266
+ if cached
267
+ logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty?
268
+ rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached
269
+ cached
270
+ else
271
+ logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}"
272
+ @uncached_rbs_collection_gemspecs.push gemspec
273
+ nil
102
274
  end
103
275
  end
104
276
 
@@ -112,22 +284,11 @@ module Solargraph
112
284
  true
113
285
  end
114
286
 
115
- # @param gemspec [Gem::Specification]
116
- def update_from_collection gemspec, gempins
117
- return gempins unless @rbs_path && File.directory?(@rbs_path)
118
- return gempins if RbsMap.new(gemspec.name, gemspec.version).resolved?
119
-
120
- rbs_map = RbsMap.new(gemspec.name, gemspec.version, directories: [@rbs_path])
121
- return gempins unless rbs_map.resolved?
122
-
123
- Solargraph.logger.info "Updating #{gemspec.name} #{gemspec.version} from collection"
124
- GemPins.combine(gempins, rbs_map)
125
- end
126
-
127
287
  # @param path [String]
128
- # @return [Gem::Specification, nil]
129
- def resolve_path_to_gemspec path
288
+ # @return [::Array<Gem::Specification>, nil]
289
+ def resolve_path_to_gemspecs path
130
290
  return nil if path.empty?
291
+ return gemspecs_required_from_bundler if path == 'bundler/require'
131
292
 
132
293
  gemspec = Gem::Specification.find_by_path(path)
133
294
  if gemspec.nil?
@@ -141,17 +302,18 @@ module Solargraph
141
302
  file = "lib/#{path}.rb"
142
303
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
143
304
  rescue Gem::MissingSpecError
144
- Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
145
- nil
305
+ logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" }
306
+ []
146
307
  end
147
308
  end
148
- gemspec_or_preference gemspec
309
+ return nil if gemspec.nil?
310
+ [gemspec_or_preference(gemspec)]
149
311
  end
150
312
 
151
- # @param gemspec [Gem::Specification, nil]
152
- # @return [Gem::Specification, nil]
313
+ # @param gemspec [Gem::Specification]
314
+ # @return [Gem::Specification]
153
315
  def gemspec_or_preference gemspec
154
- return gemspec unless gemspec && preference_map.key?(gemspec.name)
316
+ return gemspec unless preference_map.key?(gemspec.name)
155
317
  return gemspec if gemspec.version == preference_map[gemspec.name].version
156
318
 
157
319
  change_gemspec_version gemspec, preference_map[by_path.name].version
@@ -170,12 +332,15 @@ module Solargraph
170
332
  # @param gemspec [Gem::Specification]
171
333
  # @return [Array<Gem::Specification>]
172
334
  def fetch_dependencies gemspec
335
+ # @param spec [Gem::Dependency]
173
336
  only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
174
337
  Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
175
- dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
338
+ dep = Gem.loaded_specs[spec.name]
339
+ # @todo is next line necessary?
340
+ dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
176
341
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
177
342
  rescue Gem::MissingSpecError
178
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
343
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems."
179
344
  end.to_a
180
345
  end
181
346
 
@@ -184,5 +349,60 @@ module Solargraph
184
349
  def only_runtime_dependencies gemspec
185
350
  gemspec.dependencies - gemspec.development_dependencies
186
351
  end
352
+
353
+
354
+ def inspect
355
+ self.class.inspect
356
+ end
357
+
358
+ def gemspecs_required_from_bundler
359
+ # @todo Handle projects with custom Bundler/Gemfile setups
360
+ return unless workspace.gemfile?
361
+
362
+ if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
363
+ # Find only the gems bundler is now using
364
+ Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
365
+ logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}"
366
+ [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
367
+ rescue Gem::MissingSpecError => e
368
+ logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
369
+ # can happen in local filesystem references
370
+ specs = resolve_path_to_gemspecs lazy_spec.name
371
+ logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
372
+ next specs
373
+ end.compact
374
+ else
375
+ logger.info 'Fetching gemspecs required from Bundler (bundler/require)'
376
+ gemspecs_required_from_external_bundle
377
+ end
378
+ end
379
+
380
+ def gemspecs_required_from_external_bundle
381
+ logger.info 'Fetching gemspecs required from external bundle'
382
+ return [] unless workspace&.directory
383
+
384
+ Solargraph.with_clean_env do
385
+ cmd = [
386
+ 'ruby', '-e',
387
+ "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }"
388
+ ]
389
+ o, e, s = Open3.capture3(*cmd)
390
+ if s.success?
391
+ Solargraph.logger.debug "External bundle: #{o}"
392
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
393
+ hash.flat_map do |name, version|
394
+ Gem::Specification.find_by_name(name, version)
395
+ rescue Gem::MissingSpecError => e
396
+ logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess")
397
+ # can happen in local filesystem references
398
+ specs = resolve_path_to_gemspecs name
399
+ logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil?
400
+ next specs
401
+ end.compact
402
+ else
403
+ Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}"
404
+ end
405
+ end
406
+ end
187
407
  end
188
408
  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>]
@@ -7,9 +7,16 @@ module Solargraph
7
7
  #
8
8
  class MessageWorker
9
9
  UPDATE_METHODS = [
10
- 'textDocument/didOpen',
11
10
  'textDocument/didChange',
12
- 'workspace/didChangeWatchedFiles'
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'
13
20
  ].freeze
14
21
 
15
22
  # @param host [Host]
@@ -84,11 +91,7 @@ module Solargraph
84
91
  idx = messages.find_index do |msg|
85
92
  UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
86
93
  end
87
- return messages.shift unless idx
88
-
89
- msg = messages[idx]
90
- messages.delete_at idx
91
- msg
94
+ idx ? messages.delete_at(idx) : messages.shift
92
95
  end
93
96
 
94
97
  # True if the message requires a previous update to have executed in
@@ -299,6 +299,10 @@ module Solargraph
299
299
  end
300
300
  end
301
301
 
302
+ def command_path
303
+ options['commandPath'] || 'solargraph'
304
+ end
305
+
302
306
  # Prepare multiple folders.
303
307
  #
304
308
  # @param array [Array<Hash{String => String}>]
@@ -501,7 +505,8 @@ module Solargraph
501
505
  parameters: pin.parameters,
502
506
  return_type: ComplexType.try_parse(params['data']['path']),
503
507
  comments: pin.comments,
504
- closure: pin.closure
508
+ closure: pin.closure,
509
+ source: :solargraph
505
510
  )
506
511
  end)
507
512
  end
@@ -597,7 +602,11 @@ module Solargraph
597
602
  # @return [Array]
598
603
  def document query
599
604
  result = []
600
- libraries.each { |lib| result.concat lib.document(query) }
605
+ if libraries.empty?
606
+ result.concat generic_library.document(query)
607
+ else
608
+ libraries.each { |lib| result.concat lib.document(query) }
609
+ end
601
610
  result
602
611
  end
603
612
 
@@ -749,6 +758,7 @@ module Solargraph
749
758
  return change if source.code.length + 1 != change['text'].length
750
759
  diffs = Diff::LCS.diff(source.code, change['text'])
751
760
  return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
761
+ # @sg-ignore push this upstream
752
762
  # @type [Diff::LCS::Change]
753
763
  diff = diffs.first.first
754
764
  return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
@@ -83,6 +83,8 @@ module Solargraph
83
83
  @fetched = true
84
84
  begin
85
85
  @available ||= begin
86
+ # @sg-ignore
87
+ # @type [Gem::Dependency, nil]
86
88
  tuple = CheckGemVersion.fetcher.search_for_dependency(Gem::Dependency.new('solargraph')).flatten.first
87
89
  if tuple.nil?
88
90
  @error = 'An error occurred fetching the gem data'
@@ -6,12 +6,15 @@ module Solargraph
6
6
  module Extended
7
7
  class Document < Base
8
8
  def process
9
- objects = host.document(params['query'])
9
+ api_map, pins = host.document(params['query'])
10
10
  page = Solargraph::Page.new(host.options['viewsPath'])
11
- content = page.render('document', layout: true, locals: {objects: objects})
11
+ content = page.render('document', layout: true, locals: { api_map: api_map, pins: pins })
12
12
  set_result(
13
13
  content: content
14
14
  )
15
+ rescue StandardError => e
16
+ Solargraph.logger.warn "Error processing document: [#{e.class}] #{e.message}"
17
+ Solargraph.logger.debug e.backtrace.join("\n")
15
18
  end
16
19
  end
17
20
  end
@@ -11,9 +11,9 @@ module Solargraph
11
11
  #
12
12
  class DocumentGems < Base
13
13
  def process
14
- cmd = "yard gems"
15
- cmd += " --rebuild" if params['rebuild']
16
- o, s = Open3.capture2(cmd)
14
+ cmd = [host.command_path, 'gems']
15
+ cmd.push '--rebuild' if params['rebuild']
16
+ o, s = Open3.capture2(*cmd)
17
17
  if s != 0
18
18
  host.show_message "An error occurred while building gem documentation.", LanguageServer::MessageTypes::ERROR
19
19
  set_result({