solargraph 0.54.4 → 0.57.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 (178) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +125 -0
  3. data/.github/workflows/plugins.yml +149 -5
  4. data/.github/workflows/rspec.yml +39 -4
  5. data/.github/workflows/typecheck.yml +8 -3
  6. data/.gitignore +7 -0
  7. data/.overcommit.yml +72 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +66 -0
  10. data/.rubocop_todo.yml +2627 -0
  11. data/.yardopts +1 -0
  12. data/CHANGELOG.md +104 -0
  13. data/README.md +20 -6
  14. data/Rakefile +125 -13
  15. data/lib/solargraph/api_map/cache.rb +3 -2
  16. data/lib/solargraph/api_map/constants.rb +218 -0
  17. data/lib/solargraph/api_map/index.rb +44 -42
  18. data/lib/solargraph/api_map/source_to_yard.rb +10 -4
  19. data/lib/solargraph/api_map/store.rb +165 -32
  20. data/lib/solargraph/api_map.rb +319 -243
  21. data/lib/solargraph/bench.rb +18 -1
  22. data/lib/solargraph/complex_type/type_methods.rb +7 -1
  23. data/lib/solargraph/complex_type/unique_type.rb +105 -16
  24. data/lib/solargraph/complex_type.rb +40 -7
  25. data/lib/solargraph/convention/active_support_concern.rb +111 -0
  26. data/lib/solargraph/convention/base.rb +20 -3
  27. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +61 -0
  28. data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -0
  29. data/lib/solargraph/convention/data_definition.rb +105 -0
  30. data/lib/solargraph/convention/gemspec.rb +3 -2
  31. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +61 -0
  32. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +102 -0
  33. data/lib/solargraph/convention/struct_definition.rb +164 -0
  34. data/lib/solargraph/convention.rb +35 -4
  35. data/lib/solargraph/diagnostics/rubocop.rb +6 -1
  36. data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -1
  37. data/lib/solargraph/doc_map.rb +313 -65
  38. data/lib/solargraph/environ.rb +9 -2
  39. data/lib/solargraph/gem_pins.rb +60 -38
  40. data/lib/solargraph/language_server/host/dispatch.rb +2 -0
  41. data/lib/solargraph/language_server/host/message_worker.rb +13 -7
  42. data/lib/solargraph/language_server/host.rb +14 -3
  43. data/lib/solargraph/language_server/message/base.rb +2 -1
  44. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
  45. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  46. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  47. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
  48. data/lib/solargraph/language_server/message/text_document/formatting.rb +16 -2
  49. data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -0
  50. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
  51. data/lib/solargraph/language_server/progress.rb +8 -0
  52. data/lib/solargraph/language_server/request.rb +1 -0
  53. data/lib/solargraph/library.rb +53 -32
  54. data/lib/solargraph/location.rb +23 -0
  55. data/lib/solargraph/logging.rb +12 -2
  56. data/lib/solargraph/page.rb +4 -0
  57. data/lib/solargraph/parser/comment_ripper.rb +20 -7
  58. data/lib/solargraph/parser/flow_sensitive_typing.rb +255 -0
  59. data/lib/solargraph/parser/node_methods.rb +16 -2
  60. data/lib/solargraph/parser/node_processor/base.rb +10 -5
  61. data/lib/solargraph/parser/node_processor.rb +26 -9
  62. data/lib/solargraph/parser/parser_gem/class_methods.rb +17 -15
  63. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
  64. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -11
  65. data/lib/solargraph/parser/parser_gem/node_methods.rb +8 -4
  66. data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
  67. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  68. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
  69. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +7 -4
  70. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -1
  71. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
  72. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
  73. data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
  74. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
  75. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +23 -0
  76. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
  77. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -1
  78. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +7 -1
  79. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +8 -7
  80. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +42 -0
  81. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  82. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +3 -1
  83. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
  84. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +63 -30
  85. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +3 -1
  86. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
  87. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
  88. data/lib/solargraph/parser/parser_gem/node_processors.rb +14 -0
  89. data/lib/solargraph/parser/region.rb +4 -1
  90. data/lib/solargraph/parser/snippet.rb +2 -0
  91. data/lib/solargraph/parser.rb +1 -0
  92. data/lib/solargraph/pin/base.rb +360 -30
  93. data/lib/solargraph/pin/base_variable.rb +16 -10
  94. data/lib/solargraph/pin/block.rb +2 -0
  95. data/lib/solargraph/pin/breakable.rb +9 -0
  96. data/lib/solargraph/pin/callable.rb +83 -3
  97. data/lib/solargraph/pin/closure.rb +20 -1
  98. data/lib/solargraph/pin/common.rb +10 -1
  99. data/lib/solargraph/pin/constant.rb +2 -0
  100. data/lib/solargraph/pin/delegated_method.rb +21 -1
  101. data/lib/solargraph/pin/documenting.rb +16 -0
  102. data/lib/solargraph/pin/keyword.rb +7 -2
  103. data/lib/solargraph/pin/local_variable.rb +18 -6
  104. data/lib/solargraph/pin/method.rb +175 -46
  105. data/lib/solargraph/pin/method_alias.rb +3 -0
  106. data/lib/solargraph/pin/namespace.rb +17 -9
  107. data/lib/solargraph/pin/parameter.rb +78 -19
  108. data/lib/solargraph/pin/proxy_type.rb +13 -6
  109. data/lib/solargraph/pin/reference/override.rb +24 -6
  110. data/lib/solargraph/pin/reference/require.rb +2 -2
  111. data/lib/solargraph/pin/reference/superclass.rb +5 -0
  112. data/lib/solargraph/pin/reference.rb +26 -0
  113. data/lib/solargraph/pin/search.rb +3 -1
  114. data/lib/solargraph/pin/signature.rb +44 -0
  115. data/lib/solargraph/pin/singleton.rb +1 -1
  116. data/lib/solargraph/pin/symbol.rb +8 -2
  117. data/lib/solargraph/pin/until.rb +18 -0
  118. data/lib/solargraph/pin/while.rb +18 -0
  119. data/lib/solargraph/pin.rb +4 -1
  120. data/lib/solargraph/pin_cache.rb +245 -0
  121. data/lib/solargraph/position.rb +11 -0
  122. data/lib/solargraph/range.rb +10 -0
  123. data/lib/solargraph/rbs_map/conversions.rb +226 -70
  124. data/lib/solargraph/rbs_map/core_fills.rb +32 -16
  125. data/lib/solargraph/rbs_map/core_map.rb +37 -11
  126. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  127. data/lib/solargraph/rbs_map.rb +88 -18
  128. data/lib/solargraph/shell.rb +20 -18
  129. data/lib/solargraph/source/chain/array.rb +11 -7
  130. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  131. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  132. data/lib/solargraph/source/chain/call.rb +53 -23
  133. data/lib/solargraph/source/chain/constant.rb +1 -1
  134. data/lib/solargraph/source/chain/hash.rb +4 -3
  135. data/lib/solargraph/source/chain/head.rb +1 -1
  136. data/lib/solargraph/source/chain/if.rb +1 -1
  137. data/lib/solargraph/source/chain/link.rb +12 -1
  138. data/lib/solargraph/source/chain/literal.rb +22 -2
  139. data/lib/solargraph/source/chain/or.rb +1 -1
  140. data/lib/solargraph/source/chain/z_super.rb +1 -1
  141. data/lib/solargraph/source/chain.rb +84 -47
  142. data/lib/solargraph/source/change.rb +2 -2
  143. data/lib/solargraph/source/cursor.rb +2 -3
  144. data/lib/solargraph/source/source_chainer.rb +3 -3
  145. data/lib/solargraph/source.rb +5 -2
  146. data/lib/solargraph/source_map/clip.rb +4 -2
  147. data/lib/solargraph/source_map/data.rb +4 -0
  148. data/lib/solargraph/source_map/mapper.rb +13 -7
  149. data/lib/solargraph/source_map.rb +21 -31
  150. data/lib/solargraph/type_checker/checks.rb +4 -0
  151. data/lib/solargraph/type_checker/param_def.rb +2 -0
  152. data/lib/solargraph/type_checker/rules.rb +8 -0
  153. data/lib/solargraph/type_checker.rb +208 -128
  154. data/lib/solargraph/version.rb +1 -1
  155. data/lib/solargraph/views/_method.erb +10 -10
  156. data/lib/solargraph/views/_namespace.erb +3 -3
  157. data/lib/solargraph/views/document.erb +10 -10
  158. data/lib/solargraph/workspace/config.rb +1 -3
  159. data/lib/solargraph/workspace/require_paths.rb +98 -0
  160. data/lib/solargraph/workspace.rb +38 -52
  161. data/lib/solargraph/yard_map/helpers.rb +29 -1
  162. data/lib/solargraph/yard_map/mapper/to_constant.rb +7 -5
  163. data/lib/solargraph/yard_map/mapper/to_method.rb +53 -18
  164. data/lib/solargraph/yard_map/mapper/to_namespace.rb +9 -7
  165. data/lib/solargraph/yard_map/mapper.rb +4 -3
  166. data/lib/solargraph/yard_map/to_method.rb +4 -2
  167. data/lib/solargraph/yardoc.rb +22 -10
  168. data/lib/solargraph.rb +34 -1
  169. data/rbs/fills/tuple.rbs +149 -0
  170. data/rbs_collection.yaml +19 -0
  171. data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  172. data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  173. data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
  174. data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
  175. data/solargraph.gemspec +15 -4
  176. metadata +157 -15
  177. data/lib/.rubocop.yml +0 -22
  178. data/lib/solargraph/cache.rb +0 -77
@@ -1,11 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+ require 'benchmark'
5
+ require 'open3'
6
+
3
7
  module Solargraph
4
8
  # A collection of pins generated from required gems.
5
9
  #
6
10
  class DocMap
11
+ include Logging
12
+
7
13
  # @return [Array<String>]
8
14
  attr_reader :requires
15
+ alias required requires
9
16
 
10
17
  # @return [Array<Gem::Specification>]
11
18
  attr_reader :preferences
@@ -14,31 +21,147 @@ module Solargraph
14
21
  attr_reader :pins
15
22
 
16
23
  # @return [Array<Gem::Specification>]
17
- attr_reader :uncached_gemspecs
24
+ def uncached_gemspecs
25
+ uncached_yard_gemspecs.concat(uncached_rbs_collection_gemspecs)
26
+ .sort
27
+ .uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
28
+ end
29
+
30
+ # @return [Array<Gem::Specification>]
31
+ attr_reader :uncached_yard_gemspecs
32
+
33
+ # @return [Array<Gem::Specification>]
34
+ attr_reader :uncached_rbs_collection_gemspecs
35
+
36
+ # @return [String, nil]
37
+ attr_reader :rbs_collection_path
38
+
39
+ # @return [String, nil]
40
+ attr_reader :rbs_collection_config_path
41
+
42
+ # @return [Workspace, nil]
43
+ attr_reader :workspace
44
+
45
+ # @return [Environ]
46
+ attr_reader :environ
18
47
 
19
48
  # @param requires [Array<String>]
20
49
  # @param preferences [Array<Gem::Specification>]
21
- # @param rbs_path [String, Pathname, nil]
22
- def initialize(requires, preferences, rbs_path = nil)
50
+ # @param workspace [Workspace, nil]
51
+ def initialize(requires, preferences, workspace = nil)
23
52
  @requires = requires.compact
24
53
  @preferences = preferences.compact
25
- @rbs_path = rbs_path
26
- generate
54
+ @workspace = workspace
55
+ @rbs_collection_path = workspace&.rbs_collection_path
56
+ @rbs_collection_config_path = workspace&.rbs_collection_config_path
57
+ @environ = Convention.for_global(self)
58
+ @requires.concat @environ.requires if @environ
59
+ load_serialized_gem_pins
60
+ pins.concat @environ.pins
61
+ end
62
+
63
+ # @param out [IO]
64
+ # @return [void]
65
+ def cache_all!(out)
66
+ # if we log at debug level:
67
+ if logger.info?
68
+ gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ')
69
+ logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty?
70
+ end
71
+ logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" }
72
+ logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" }
73
+ load_serialized_gem_pins
74
+ uncached_gemspecs.each do |gemspec|
75
+ cache(gemspec, out: out)
76
+ end
77
+ load_serialized_gem_pins
78
+ @uncached_rbs_collection_gemspecs = []
79
+ @uncached_yard_gemspecs = []
80
+ end
81
+
82
+ # @param gemspec [Gem::Specification]
83
+ # @param out [IO]
84
+ # @return [void]
85
+ def cache_yard_pins(gemspec, out)
86
+ pins = GemPins.build_yard_pins(yard_plugins, gemspec)
87
+ PinCache.serialize_yard_gem(gemspec, pins)
88
+ logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
89
+ end
90
+
91
+ # @param gemspec [Gem::Specification]
92
+ # @param out [IO]
93
+ # @return [void]
94
+ def cache_rbs_collection_pins(gemspec, out)
95
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
96
+ pins = rbs_map.pins
97
+ rbs_version_cache_key = rbs_map.cache_key
98
+ # cache pins even if result is zero, so we don't retry building pins
99
+ pins ||= []
100
+ PinCache.serialize_rbs_collection_gem(gemspec, rbs_version_cache_key, pins)
101
+ 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? }
102
+ end
103
+
104
+ # @param gemspec [Gem::Specification]
105
+ # @param rebuild [Boolean] whether to rebuild the pins even if they are cached
106
+ # @param out [IO, nil] output stream for logging
107
+ # @return [void]
108
+ def cache(gemspec, rebuild: false, out: nil)
109
+ build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild
110
+ build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
111
+ if build_yard || build_rbs_collection
112
+ type = []
113
+ type << 'YARD' if build_yard
114
+ type << 'RBS collection' if build_rbs_collection
115
+ out.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") if out
116
+ end
117
+ cache_yard_pins(gemspec, out) if build_yard
118
+ cache_rbs_collection_pins(gemspec, out) if build_rbs_collection
27
119
  end
28
120
 
29
121
  # @return [Array<Gem::Specification>]
30
122
  def gemspecs
31
- @gemspecs ||= required_gem_map.values.compact
123
+ @gemspecs ||= required_gems_map.values.compact.flatten
32
124
  end
33
125
 
34
126
  # @return [Array<String>]
35
127
  def unresolved_requires
36
- @unresolved_requires ||= required_gem_map.select { |_, gemspec| gemspec.nil? }.keys
128
+ @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
129
+ end
130
+
131
+ # @return [Hash{Array(String, String) => Array<Gem::Specification>}] Indexed by gemspec name and version
132
+ def self.all_yard_gems_in_memory
133
+ @yard_gems_in_memory ||= {}
134
+ end
135
+
136
+ # @return [Hash{String => Hash{Array(String, String) => Array<Pin::Base>}}] stored by RBS collection path
137
+ def self.all_rbs_collection_gems_in_memory
138
+ @rbs_collection_gems_in_memory ||= {}
37
139
  end
38
140
 
39
- # @return [Hash{Gem::Specification => Array[Pin::Base]}]
40
- def self.gems_in_memory
41
- @gems_in_memory ||= {}
141
+ # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version
142
+ def yard_pins_in_memory
143
+ self.class.all_yard_gems_in_memory
144
+ end
145
+
146
+ # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version
147
+ def rbs_collection_pins_in_memory
148
+ self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {}
149
+ end
150
+
151
+ # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version
152
+ def self.all_combined_pins_in_memory
153
+ @combined_pins_in_memory ||= {}
154
+ end
155
+
156
+ # @todo this should also include an index by the hash of the RBS collection
157
+ # @return [Hash{Array(String, String) => Array<Pin::Base>}] Indexed by gemspec name and version
158
+ def combined_pins_in_memory
159
+ self.class.all_combined_pins_in_memory
160
+ end
161
+
162
+ # @return [Array<String>]
163
+ def yard_plugins
164
+ @environ.yard_plugins
42
165
  end
43
166
 
44
167
  # @return [Set<Gem::Specification>]
@@ -49,23 +172,38 @@ module Solargraph
49
172
  private
50
173
 
51
174
  # @return [void]
52
- def generate
175
+ def load_serialized_gem_pins
53
176
  @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
177
+ @uncached_yard_gemspecs = []
178
+ @uncached_rbs_collection_gemspecs = []
179
+ with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
180
+ # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash<Array(String, Array<Gem::Specification>), undefined>, received Array<Array(String, Array<Gem::Specification>)>
181
+ # @type [Array<String>]
182
+ paths = Hash[without_gemspecs].keys
183
+ # @sg-ignore Wrong argument type for Hash.[]: arg_0 expected _ToHash<Array(String, Array<Gem::Specification>), undefined>, received Array<Array(String, Array<Gem::Specification>)>
184
+ # @type [Array<Gem::Specification>]
185
+ gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a
186
+
187
+ paths.each do |path|
188
+ rbs_pins = deserialize_stdlib_rbs_map path
189
+ end
190
+
191
+ logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." }
192
+ time = Benchmark.measure do
193
+ gemspecs.each do |gemspec|
194
+ pins = deserialize_combined_pin_cache gemspec
195
+ @pins.concat pins if pins
60
196
  end
61
197
  end
62
- dependencies.each { |dep| try_cache dep }
63
- @uncached_gemspecs.uniq!
198
+ logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" }
199
+ @uncached_yard_gemspecs.uniq!
200
+ @uncached_rbs_collection_gemspecs.uniq!
201
+ nil
64
202
  end
65
203
 
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)] }
204
+ # @return [Hash{String => Array<Gem::Specification>}]
205
+ def required_gems_map
206
+ @required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
69
207
  end
70
208
 
71
209
  # @return [Hash{String => Gem::Specification}]
@@ -73,62 +211,109 @@ module Solargraph
73
211
  @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
74
212
  end
75
213
 
214
+ # @param gemspec [Gem::Specification]
215
+ # @return [Array<Pin::Base>]
216
+ def deserialize_yard_pin_cache gemspec
217
+ if yard_pins_in_memory.key?([gemspec.name, gemspec.version])
218
+ return yard_pins_in_memory[[gemspec.name, gemspec.version]]
219
+ end
220
+
221
+ cached = PinCache.deserialize_yard_gem(gemspec)
222
+ if cached
223
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
224
+ yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached
225
+ cached
226
+ else
227
+ logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
228
+ @uncached_yard_gemspecs.push gemspec
229
+ nil
230
+ end
231
+ end
232
+
76
233
  # @param gemspec [Gem::Specification]
77
234
  # @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
235
+ def deserialize_combined_pin_cache(gemspec)
236
+ unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil?
237
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
238
+ end
239
+
240
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
241
+ rbs_version_cache_key = rbs_map.cache_key
242
+
243
+ cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key)
244
+ if cached
245
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
246
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached
247
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
248
+ end
249
+
250
+ rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
251
+
252
+ yard_pins = deserialize_yard_pin_cache gemspec
253
+
254
+ if !rbs_collection_pins.nil? && !yard_pins.nil?
255
+ logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" }
256
+ combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
257
+ PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
258
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins
259
+ logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" }
260
+ return combined_pins
261
+ end
262
+
263
+ if !yard_pins.nil?
264
+ logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" }
265
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins
266
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
267
+ elsif !rbs_collection_pins.nil?
268
+ logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" }
269
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins
270
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
86
271
  else
87
- Solargraph.logger.debug "No pin cache for #{gemspec.name} #{gemspec.version}"
88
- @uncached_gemspecs.push gemspec
272
+ logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" }
273
+ return nil
89
274
  end
90
275
  end
91
276
 
92
277
  # @param path [String] require path that might be in the RBS stdlib collection
93
278
  # @return [void]
94
- def try_stdlib_map path
279
+ def deserialize_stdlib_rbs_map path
95
280
  map = RbsMap::StdlibMap.load(path)
96
281
  if map.resolved?
97
- Solargraph.logger.debug "Loading stdlib pins for #{path}"
282
+ logger.debug { "Loading stdlib pins for #{path}" }
98
283
  @pins.concat map.pins
284
+ logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" }
285
+ map.pins
99
286
  else
100
287
  # @todo Temporarily ignoring unresolved `require 'set'`
101
- Solargraph.logger.debug "Require path #{path} could not be resolved" unless path == 'set'
288
+ logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
289
+ nil
102
290
  end
103
291
  end
104
292
 
105
293
  # @param gemspec [Gem::Specification]
106
- # @return [Boolean]
107
- def try_gem_in_memory gemspec
108
- gempins = DocMap.gems_in_memory[gemspec]
109
- return false unless gempins
110
- Solargraph.logger.debug "Found #{gemspec.name} #{gemspec.version} in memory"
111
- @pins.concat gempins
112
- true
113
- end
114
-
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)
294
+ # @param rbs_version_cache_key [String]
295
+ # @return [Array<Pin::Base>, nil]
296
+ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
297
+ return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
298
+ cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key)
299
+ if cached
300
+ logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty?
301
+ rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached
302
+ cached
303
+ else
304
+ logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}"
305
+ @uncached_rbs_collection_gemspecs.push gemspec
306
+ nil
307
+ end
125
308
  end
126
309
 
127
310
  # @param path [String]
128
- # @return [Gem::Specification, nil]
129
- def resolve_path_to_gemspec path
311
+ # @return [::Array<Gem::Specification>, nil]
312
+ def resolve_path_to_gemspecs path
130
313
  return nil if path.empty?
314
+ return gemspecs_required_from_bundler if path == 'bundler/require'
131
315
 
316
+ # @type [Gem::Specification, nil]
132
317
  gemspec = Gem::Specification.find_by_path(path)
133
318
  if gemspec.nil?
134
319
  gem_name_guess = path.split('/').first
@@ -141,20 +326,23 @@ module Solargraph
141
326
  file = "lib/#{path}.rb"
142
327
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
143
328
  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
329
+ logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" }
330
+ []
146
331
  end
147
332
  end
148
- gemspec_or_preference gemspec
333
+ return nil if gemspec.nil?
334
+ [gemspec_or_preference(gemspec)]
149
335
  end
150
336
 
151
- # @param gemspec [Gem::Specification, nil]
152
- # @return [Gem::Specification, nil]
337
+ # @param gemspec [Gem::Specification]
338
+ # @return [Gem::Specification]
153
339
  def gemspec_or_preference gemspec
154
- return gemspec unless gemspec && preference_map.key?(gemspec.name)
340
+ # :nocov: dormant feature
341
+ return gemspec unless preference_map.key?(gemspec.name)
155
342
  return gemspec if gemspec.version == preference_map[gemspec.name].version
156
343
 
157
- change_gemspec_version gemspec, preference_map[by_path.name].version
344
+ change_gemspec_version gemspec, preference_map[gemspec.name].version
345
+ # :nocov:
158
346
  end
159
347
 
160
348
  # @param gemspec [Gem::Specification]
@@ -170,12 +358,15 @@ module Solargraph
170
358
  # @param gemspec [Gem::Specification]
171
359
  # @return [Array<Gem::Specification>]
172
360
  def fetch_dependencies gemspec
361
+ # @param spec [Gem::Dependency]
173
362
  only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
174
363
  Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
175
- dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
364
+ dep = Gem.loaded_specs[spec.name]
365
+ # @todo is next line necessary?
366
+ dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
176
367
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
177
368
  rescue Gem::MissingSpecError
178
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
369
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems."
179
370
  end.to_a
180
371
  end
181
372
 
@@ -184,5 +375,62 @@ module Solargraph
184
375
  def only_runtime_dependencies gemspec
185
376
  gemspec.dependencies - gemspec.development_dependencies
186
377
  end
378
+
379
+
380
+ def inspect
381
+ self.class.inspect
382
+ end
383
+
384
+ # @return [Array<Gem::Specification>]
385
+ def gemspecs_required_from_bundler
386
+ # @todo Handle projects with custom Bundler/Gemfile setups
387
+ return unless workspace.gemfile?
388
+
389
+ if workspace.gemfile? && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
390
+ # Find only the gems bundler is now using
391
+ Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
392
+ logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}"
393
+ [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
394
+ rescue Gem::MissingSpecError => e
395
+ logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
396
+ # can happen in local filesystem references
397
+ specs = resolve_path_to_gemspecs lazy_spec.name
398
+ logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
399
+ next specs
400
+ end.compact
401
+ else
402
+ logger.info 'Fetching gemspecs required from Bundler (bundler/require)'
403
+ gemspecs_required_from_external_bundle
404
+ end
405
+ end
406
+
407
+ # @return [Array<Gem::Specification>]
408
+ def gemspecs_required_from_external_bundle
409
+ logger.info 'Fetching gemspecs required from external bundle'
410
+ return [] unless workspace&.directory
411
+
412
+ Solargraph.with_clean_env do
413
+ cmd = [
414
+ 'ruby', '-e',
415
+ "require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }"
416
+ ]
417
+ o, e, s = Open3.capture3(*cmd)
418
+ if s.success?
419
+ Solargraph.logger.debug "External bundle: #{o}"
420
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
421
+ hash.flat_map do |name, version|
422
+ Gem::Specification.find_by_name(name, version)
423
+ rescue Gem::MissingSpecError => e
424
+ logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess")
425
+ # can happen in local filesystem references
426
+ specs = resolve_path_to_gemspecs name
427
+ logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil?
428
+ next specs
429
+ end.compact
430
+ else
431
+ Solargraph.logger.warn "Failed to load gems from bundle at #{workspace&.directory}: #{e}"
432
+ end
433
+ end
434
+ end
187
435
  end
188
436
  end
@@ -13,16 +13,21 @@ module Solargraph
13
13
  # @return [Array<String>]
14
14
  attr_reader :domains
15
15
 
16
- # @return [Array<Pin::Reference::Override>]
16
+ # @return [Array<Pin::Base>]
17
17
  attr_reader :pins
18
18
 
19
+ # @return [Array<String>]
20
+ attr_reader :yard_plugins
21
+
19
22
  # @param requires [Array<String>]
20
23
  # @param domains [Array<String>]
21
24
  # @param pins [Array<Pin::Base>]
22
- def initialize requires: [], domains: [], pins: []
25
+ # @param yard_plugins[Array<String>]
26
+ def initialize requires: [], domains: [], pins: [], yard_plugins: []
23
27
  @requires = requires
24
28
  @domains = domains
25
29
  @pins = pins
30
+ @yard_plugins = yard_plugins
26
31
  end
27
32
 
28
33
  # @return [self]
@@ -30,6 +35,7 @@ module Solargraph
30
35
  domains.clear
31
36
  requires.clear
32
37
  pins.clear
38
+ yard_plugins.clear
33
39
  self
34
40
  end
35
41
 
@@ -39,6 +45,7 @@ module Solargraph
39
45
  domains.concat other.domains
40
46
  requires.concat other.requires
41
47
  pins.concat other.pins
48
+ yard_plugins.concat other.yard_plugins
42
49
  self
43
50
  end
44
51
  end
@@ -7,59 +7,81 @@ 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
+ # @param pins [Array<Pin::Base>]
15
+ # @return [Array<Pin::Base>]
16
+ def self.combine_method_pins_by_path(pins)
17
+ method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method }
18
+ by_path = method_pins.group_by(&:path)
19
+ by_path.transform_values! do |pins|
20
+ GemPins.combine_method_pins(*pins)
21
+ end
22
+ by_path.values + alias_pins
23
+ end
24
+
25
+ # @param pins [Array<Pin::Method>]
26
+ # @return [Pin::Method, nil]
27
+ def self.combine_method_pins(*pins)
28
+ # @type [Pin::Method, nil]
29
+ combined_pin = nil
30
+ out = pins.reduce(combined_pin) do |memo, pin|
31
+ next pin if memo.nil?
32
+ if memo == pin && memo.source != :combined
33
+ # @todo we should track down situations where we are handled
34
+ # the same pin from the same source here and eliminate them -
35
+ # this is an efficiency workaround for now
36
+ next memo
37
+ end
38
+ memo.combine_with(pin)
39
+ end
40
+ logger.debug { "GemPins.combine_method_pins(pins.length=#{pins.length}, pins=#{pins}) => #{out.inspect}" }
41
+ out
42
+ end
43
+
44
+ # @param yard_plugins [Array<String>] The names of YARD plugins to use.
14
45
  # @param gemspec [Gem::Specification]
15
46
  # @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
47
+ def self.build_yard_pins(yard_plugins, gemspec)
48
+ Yardoc.cache(yard_plugins, gemspec) unless Yardoc.cached?(gemspec)
49
+ yardoc = Yardoc.load!(gemspec)
50
+ YardMap::Mapper.new(yardoc, gemspec).map
20
51
  end
21
52
 
22
53
  # @param yard_pins [Array<Pin::Base>]
23
- # @param rbs_map [RbsMap]
54
+ # @param rbs_pins [Array<Pin::Base>]
55
+ #
24
56
  # @return [Array<Pin::Base>]
25
- def self.combine(yard_pins, rbs_map)
57
+ def self.combine(yard_pins, rbs_pins)
26
58
  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)
59
+ rbs_api_map = Solargraph::ApiMap.new(pins: rbs_pins)
60
+ combined = yard_pins.map do |yard_pin|
61
+ in_yard.add yard_pin.path
62
+ rbs_pin = rbs_api_map.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first
63
+ next yard_pin unless rbs_pin && yard_pin.class == Pin::Method
30
64
 
31
- rbs = rbs_map.path_pin(yard.path, Pin::Method)
32
- next yard unless rbs
65
+ unless rbs_pin
66
+ 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})" }
67
+ next yard_pin
68
+ end
33
69
 
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
- )
70
+ out = combine_method_pins(rbs_pin, yard_pin)
71
+ logger.debug { "GemPins.combine: Combining yard.path=#{yard_pin.path} - rbs=#{rbs_pin.inspect} with yard=#{yard_pin.inspect} into #{out}" }
72
+ out
47
73
  end
48
- in_rbs = rbs_map.pins.reject { |pin| in_yard.include?(pin.path) }
49
- combined + in_rbs
74
+ in_rbs_only = rbs_pins.select do |pin|
75
+ pin.path.nil? || !in_yard.include?(pin.path)
76
+ end
77
+ out = combined + in_rbs_only
78
+ logger.debug { "GemPins#combine: Returning #{out.length} combined pins" }
79
+ out
50
80
  end
51
81
 
52
82
  class << self
53
83
  private
54
84
 
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
85
  # Select the first defined type.
64
86
  #
65
87
  # @param choices [Array<ComplexType>]
@@ -95,6 +95,7 @@ module Solargraph
95
95
  nil
96
96
  end
97
97
 
98
+ # @return [Hash{String => undefined}]
98
99
  def options
99
100
  @options ||= {}.freeze
100
101
  end
@@ -118,6 +119,7 @@ module Solargraph
118
119
  end
119
120
 
120
121
  # @param library [Solargraph::Library]
122
+ # @param progress [Solargraph::LanguageServer::Progress, nil]
121
123
  # @return [void]
122
124
  def update progress
123
125
  progress&.send(self)