solargraph 0.55.4 → 0.55.5

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +2 -0
  3. data/.github/workflows/typecheck.yml +2 -0
  4. data/.gitignore +2 -0
  5. data/CHANGELOG.md +3 -0
  6. data/README.md +13 -3
  7. data/lib/solargraph/api_map/index.rb +23 -15
  8. data/lib/solargraph/api_map/store.rb +2 -1
  9. data/lib/solargraph/api_map.rb +53 -27
  10. data/lib/solargraph/complex_type/type_methods.rb +5 -1
  11. data/lib/solargraph/complex_type/unique_type.rb +7 -0
  12. data/lib/solargraph/convention/base.rb +3 -3
  13. data/lib/solargraph/convention.rb +3 -3
  14. data/lib/solargraph/doc_map.rb +189 -43
  15. data/lib/solargraph/gem_pins.rb +53 -38
  16. data/lib/solargraph/language_server/host.rb +9 -1
  17. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -0
  18. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  19. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  20. data/lib/solargraph/library.rb +7 -4
  21. data/lib/solargraph/location.rb +13 -0
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +5 -8
  23. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -2
  24. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +2 -2
  25. data/lib/solargraph/pin/base.rb +268 -24
  26. data/lib/solargraph/pin/base_variable.rb +9 -8
  27. data/lib/solargraph/pin/callable.rb +69 -0
  28. data/lib/solargraph/pin/closure.rb +12 -0
  29. data/lib/solargraph/pin/local_variable.rb +8 -5
  30. data/lib/solargraph/pin/method.rb +134 -17
  31. data/lib/solargraph/pin/parameter.rb +43 -6
  32. data/lib/solargraph/pin/signature.rb +38 -0
  33. data/lib/solargraph/pin_cache.rb +185 -0
  34. data/lib/solargraph/position.rb +9 -0
  35. data/lib/solargraph/range.rb +9 -0
  36. data/lib/solargraph/rbs_map/conversions.rb +19 -8
  37. data/lib/solargraph/rbs_map/core_map.rb +31 -9
  38. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  39. data/lib/solargraph/rbs_map.rb +74 -17
  40. data/lib/solargraph/shell.rb +11 -15
  41. data/lib/solargraph/source_map.rb +0 -17
  42. data/lib/solargraph/version.rb +1 -1
  43. data/lib/solargraph/views/_method.erb +10 -10
  44. data/lib/solargraph/views/_namespace.erb +3 -3
  45. data/lib/solargraph/views/document.erb +10 -10
  46. data/lib/solargraph/workspace.rb +15 -5
  47. data/lib/solargraph/yardoc.rb +3 -11
  48. data/lib/solargraph.rb +10 -12
  49. data/rbs_collection.yaml +19 -0
  50. data/solargraph.gemspec +1 -0
  51. metadata +19 -7
  52. data/lib/solargraph/cache.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e00ddc5e4b665c4527099b496d2caf414a0fe5c1cdb44e71b4af16a7c5521d34
4
- data.tar.gz: 494b003c5260150a4c66952c0e4c512374197a52b528249027100b48a3deb4c5
3
+ metadata.gz: 9e015963ad868b3671d88892483ec3c06a3e7c96757f2013ba56c514af35eb0e
4
+ data.tar.gz: 22a6383d6f179ec708a930fbf8cb81bea8cbbee694de559f43208c859240a0cd
5
5
  SHA512:
6
- metadata.gz: b56ff99aecf4a656722a8a55cdcf521b9dba1066c53e22dd344840f173dfb302fb560daba2b828af01ad0789f7defab7976511509484613df13687854b90bf77
7
- data.tar.gz: 2cc74269454a5290c0c98e89f55942bdc36f916b622bfc37a42d4aeaf30d023f497a6be585f725b4397991a5083fdc43dedf90c05526a3739bb2c4a78266daf0
6
+ metadata.gz: cd7b151efc8a584829cdc5c4424c81dd3703dd22f8287c8edfcfc32d65b8409e8be9d8354ed5369c6fa0955cc1ccd534014f6782aa71ca7b0a89c5df8a0d9b06
7
+ data.tar.gz: 818390cee111c7948e40904e1eb48dd407dd8f15045a29b96881e6bb520022438d9fca6a7ec678dbc6d793f74b3bcdaf17d4b152e86bb5b1f563f1703423a7ed
@@ -34,6 +34,8 @@ jobs:
34
34
  bundle exec solargraph config
35
35
  yq -yi '.plugins += ["solargraph-rails"]' .solargraph.yml
36
36
  yq -yi '.plugins += ["solargraph-rspec"]' .solargraph.yml
37
+ - name: Install gem types
38
+ run: bundle exec rbs collection install
37
39
  - name: Ensure typechecking still works
38
40
  run: bundle exec solargraph typecheck --level typed
39
41
  - name: Ensure specs still run
@@ -30,5 +30,7 @@ jobs:
30
30
  bundler-cache: false
31
31
  - name: Install gems
32
32
  run: bundle install
33
+ - name: Install gem types
34
+ run: bundle exec rbs collection install
33
35
  - name: Typecheck self
34
36
  run: SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ /.gem_rbs_collection
2
+ /rbs_collection.lock.yaml
1
3
  /Gemfile.lock
2
4
  .Gemfile
3
5
  .idea
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.55.5 - July 1, 2025
2
+ - Ignore directory paths in Workspace#would_require? (#988)
3
+
1
4
  ## 0.55.4 - June 27, 2025
2
5
  - Flatten results of DocMap external bundle query (#981)
3
6
 
data/README.md CHANGED
@@ -63,12 +63,22 @@ The RSpec framework is supported via [solargraph-rspec](https://github.com/lekem
63
63
 
64
64
  **Note: Before version 0.53.0, it was recommended to run `yard gems` periodically or automate it with `yard config` to ensure that Solargraph had access to gem documentation. These steps are no longer necessary. Solargraph maintains its own gem documentation cache independent of the yardocs in your gem installations.**
65
65
 
66
- Solargraph automatically generates code maps from installed gems. You can also manage your cached gem documentation with the `solargraph gems` command.
67
-
68
- When editing code, a `require` call that references a gem will pull the documentation into the code maps and include the gem's API in code completion and intellisense.
66
+ When editing code, a `require` call that references a gem will pull the documentation into the code maps and include the gem's API in code completion and intellisense. Solargraph automatically generates code maps from installed gems, based on the YARD or RBS type information inside the gem. You can also eagerly cache gem documentation with the `solargraph gems` command.
69
67
 
70
68
  If your project automatically requires bundled gems (e.g., `require 'bundler/require'`), Solargraph will add all of the Gemfile's default dependencies to the map.
71
69
 
70
+ To ensure you have types for gems which contain neither RBS nor YARD
71
+ information, use
72
+ [gem\_rbs\_collection](https://github.com/ruby/gem_rbs_collection) to
73
+ install a community-supported set of RBS types for various gems:
74
+
75
+ ```sh
76
+ bundle exec rbs collection init
77
+ bundle exec rbs collection install
78
+ ```
79
+
80
+ Once installed, you can also insert your own local overrides and definitions in RBS in a directory configured in the `rbs_collection.yaml` that the above commands create.
81
+
72
82
  ### Type Checking
73
83
 
74
84
  As of version 0.33.0, Solargraph includes a [type checker](https://github.com/castwide/solargraph/issues/192) that uses a combination of YARD tags and code analysis to report missing type definitions. In strict mode, it performs type inference to determine whether the tags match the types it detects from code.
@@ -3,6 +3,8 @@
3
3
  module Solargraph
4
4
  class ApiMap
5
5
  class Index
6
+ include Logging
7
+
6
8
  # @param pins [Array<Pin::Base>]
7
9
  def initialize pins = []
8
10
  catalog pins
@@ -132,21 +134,24 @@ module Solargraph
132
134
  # @return [void]
133
135
  def map_overrides
134
136
  pins_by_class(Pin::Reference::Override).each do |ovr|
135
- pin = path_pin_hash[ovr.name].first
136
- next if pin.nil?
137
- new_pin = if pin.path.end_with?('#initialize')
138
- path_pin_hash[pin.path.sub(/#initialize/, '.new')].first
139
- end
140
- (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag|
141
- pin.docstring.delete_tags tag
142
- new_pin.docstring.delete_tags tag if new_pin
143
- end
144
- ovr.tags.each do |tag|
145
- pin.docstring.add_tag(tag)
146
- redefine_return_type pin, tag
147
- if new_pin
148
- new_pin.docstring.add_tag(tag)
149
- redefine_return_type new_pin, tag
137
+ logger.debug { "ApiMap::Index#map_overrides: Looking at override #{ovr} for #{ovr.name}" }
138
+ pins = path_pin_hash[ovr.name]
139
+ logger.debug { "ApiMap::Index#map_overrides: pins for path=#{ovr.name}: #{pins}" }
140
+ pins.each do |pin|
141
+ new_pin = if pin.path.end_with?('#initialize')
142
+ path_pin_hash[pin.path.sub(/#initialize/, '.new')].first
143
+ end
144
+ (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag|
145
+ pin.docstring.delete_tags tag
146
+ new_pin.docstring.delete_tags tag if new_pin
147
+ end
148
+ ovr.tags.each do |tag|
149
+ pin.docstring.add_tag(tag)
150
+ redefine_return_type pin, tag
151
+ if new_pin
152
+ new_pin.docstring.add_tag(tag)
153
+ redefine_return_type new_pin, tag
154
+ end
150
155
  end
151
156
  end
152
157
  end
@@ -156,11 +161,14 @@ module Solargraph
156
161
  # @param tag [YARD::Tags::Tag]
157
162
  # @return [void]
158
163
  def redefine_return_type pin, tag
164
+ # @todo can this be made to not mutate existing pins and use
165
+ # proxy() / proxy_with_signatures() instead?
159
166
  return unless pin && tag.tag_name == 'return'
160
167
  pin.instance_variable_set(:@return_type, ComplexType.try_parse(tag.type))
161
168
  pin.signatures.each do |sig|
162
169
  sig.instance_variable_set(:@return_type, ComplexType.try_parse(tag.type))
163
170
  end
171
+ pin.reset_generated!
164
172
  end
165
173
  end
166
174
  end
@@ -61,9 +61,10 @@ module Solargraph
61
61
  # @param visibility [Array<Symbol>]
62
62
  # @return [Enumerable<Solargraph::Pin::Method>]
63
63
  def get_methods fqns, scope: :instance, visibility: [:public]
64
- namespace_children(fqns).select do |pin|
64
+ all_pins = namespace_children(fqns).select do |pin|
65
65
  pin.is_a?(Pin::Method) && pin.scope == scope && visibility.include?(pin.visibility)
66
66
  end
67
+ GemPins.combine_method_pins_by_path(all_pins)
67
68
  end
68
69
 
69
70
  # @param fq_tag [String]
@@ -93,9 +93,13 @@ module Solargraph
93
93
  implicit.merge map.environ
94
94
  end
95
95
  unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq
96
- if @unresolved_requires != unresolved_requires || @doc_map&.uncached_gemspecs&.any?
96
+ recreate_docmap = @unresolved_requires != unresolved_requires ||
97
+ @doc_map&.uncached_yard_gemspecs&.any? ||
98
+ @doc_map&.uncached_rbs_collection_gemspecs&.any? ||
99
+ @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path
100
+ if recreate_docmap
97
101
  @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences
98
- @unresolved_requires = unresolved_requires
102
+ @unresolved_requires = @doc_map.unresolved_requires
99
103
  end
100
104
  @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
101
105
  @missing_docs = [] # @todo Implement missing docs
@@ -109,11 +113,25 @@ module Solargraph
109
113
  [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
110
114
  end
111
115
 
116
+ def doc_map
117
+ @doc_map ||= DocMap.new([], [])
118
+ end
119
+
112
120
  # @return [::Array<Gem::Specification>]
113
121
  def uncached_gemspecs
114
122
  @doc_map&.uncached_gemspecs || []
115
123
  end
116
124
 
125
+ # @return [::Array<Gem::Specification>]
126
+ def uncached_rbs_collection_gemspecs
127
+ @doc_map.uncached_rbs_collection_gemspecs
128
+ end
129
+
130
+ # @return [::Array<Gem::Specification>]
131
+ def uncached_yard_gemspecs
132
+ @doc_map.uncached_yard_gemspecs
133
+ end
134
+
117
135
  # @return [Array<Pin::Base>]
118
136
  def core_pins
119
137
  @@core_map.pins
@@ -168,6 +186,18 @@ module Solargraph
168
186
  api_map
169
187
  end
170
188
 
189
+ def cache_all!(out)
190
+ @doc_map.cache_all!(out)
191
+ end
192
+
193
+ def cache_gem(gemspec, rebuild: false, out: nil)
194
+ @doc_map.cache(gemspec, rebuild: rebuild, out: out)
195
+ end
196
+
197
+ class << self
198
+ include Logging
199
+ end
200
+
171
201
  # Create an ApiMap with a workspace in the specified directory and cache
172
202
  # any missing gems.
173
203
  #
@@ -178,15 +208,14 @@ module Solargraph
178
208
  # @param directory [String]
179
209
  # @param out [IO] The output stream for messages
180
210
  # @return [ApiMap]
181
- def self.load_with_cache directory, out = IO::NULL
211
+ def self.load_with_cache directory, out
182
212
  api_map = load(directory)
183
- return api_map if api_map.uncached_gemspecs.empty?
184
-
185
- api_map.uncached_gemspecs.each do |gemspec|
186
- out.puts "Caching gem #{gemspec.name} #{gemspec.version}"
187
- pins = GemPins.build(gemspec)
188
- Solargraph::Cache.save('gems', "#{gemspec.name}-#{gemspec.version}.ser", pins)
213
+ if api_map.uncached_gemspecs.empty?
214
+ logger.info { "All gems cached for #{directory}" }
215
+ return api_map
189
216
  end
217
+
218
+ api_map.cache_all!(out)
190
219
  load(directory)
191
220
  end
192
221
 
@@ -527,13 +556,8 @@ module Solargraph
527
556
  .select { |path| path.downcase.include?(query.downcase) }
528
557
  end
529
558
 
530
- # Get YARD documentation for the specified path.
531
- #
532
- # @example
533
- # api_map.document('String#split')
534
- #
535
- # @todo This method is likely superfluous. Calling get_path_pins directly
536
- # should be sufficient.
559
+ # @deprecated This method is likely superfluous. Calling #get_path_pins
560
+ # directly should be sufficient.
537
561
  #
538
562
  # @param path [String] The path to find
539
563
  # @return [Enumerable<Pin::Base>]
@@ -627,6 +651,19 @@ module Solargraph
627
651
  store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns)
628
652
  end
629
653
 
654
+ # @param pins [Enumerable<Pin::Base>]
655
+ # @param visibility [Enumerable<Symbol>]
656
+ # @return [Array<Pin::Base>]
657
+ def resolve_method_aliases pins, visibility = [:public, :private, :protected]
658
+ with_resolved_aliases = pins.map do |pin|
659
+ resolved = resolve_method_alias(pin)
660
+ next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
661
+ resolved
662
+ end.compact
663
+ logger.debug { "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" }
664
+ GemPins.combine_method_pins_by_path(with_resolved_aliases)
665
+ end
666
+
630
667
  private
631
668
 
632
669
  # A hash of source maps with filename keys.
@@ -859,17 +896,6 @@ module Solargraph
859
896
  result + nil_pins
860
897
  end
861
898
 
862
- # @param pins [Enumerable<Pin::Base>]
863
- # @param visibility [Enumerable<Symbol>]
864
- # @return [Array<Pin::Base>]
865
- def resolve_method_aliases pins, visibility = [:public, :private, :protected]
866
- pins.map do |pin|
867
- resolved = resolve_method_alias(pin)
868
- next pin if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
869
- resolved
870
- end.compact
871
- end
872
-
873
899
  # @param pin [Pin::MethodAlias, Pin::Base]
874
900
  # @return [Pin::Method]
875
901
  def resolve_method_alias pin
@@ -172,7 +172,11 @@ module Solargraph
172
172
  elsif fixed_parameters?
173
173
  "(#{subtypes_str})"
174
174
  else
175
- "<#{subtypes_str}>"
175
+ if name == 'Hash'
176
+ "<#{key_types_str}, #{subtypes_str}>"
177
+ else
178
+ "<#{key_types_str}#{subtypes_str}>"
179
+ end
176
180
  end
177
181
  end
178
182
 
@@ -55,6 +55,13 @@ module Solargraph
55
55
  key_types.concat(subs[0].map { |u| ComplexType.new([u]) })
56
56
  # @sg-ignore
57
57
  subtypes.concat(subs[1].map { |u| ComplexType.new([u]) })
58
+ elsif parameters_type == :list && name == 'Hash'
59
+ # Treat Hash<A, B> as Hash{A => B}
60
+ if subs.length != 2
61
+ raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring} - must have exactly two parameters"
62
+ end
63
+ key_types.concat(subs[0].map { |u| ComplexType.new([u]) })
64
+ subtypes.concat(subs[1].map { |u| ComplexType.new([u]) })
58
65
  else
59
66
  subtypes.concat subs
60
67
  end
@@ -20,12 +20,12 @@ module Solargraph
20
20
  EMPTY_ENVIRON
21
21
  end
22
22
 
23
- # The Environ for a YARD map.
23
+ # The Environ for a DocMap.
24
24
  # Subclasses can override this method.
25
25
  #
26
- # @param yard_map [YardMap]
26
+ # @param doc_map [DocMap]
27
27
  # @return [Environ]
28
- def global yard_map
28
+ def global doc_map
29
29
  EMPTY_ENVIRON
30
30
  end
31
31
  end
@@ -31,12 +31,12 @@ module Solargraph
31
31
  result
32
32
  end
33
33
 
34
- # @param yard_map [YardMap]
34
+ # @param yard_map [DocMap]
35
35
  # @return [Environ]
36
- def self.for_global(yard_map)
36
+ def self.for_global(doc_map)
37
37
  result = Environ.new
38
38
  @@conventions.each do |conv|
39
- result.merge conv.global(yard_map)
39
+ result.merge conv.global(doc_map)
40
40
  end
41
41
  result
42
42
  end
@@ -1,5 +1,8 @@
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
  #
@@ -8,6 +11,7 @@ module Solargraph
8
11
 
9
12
  # @return [Array<String>]
10
13
  attr_reader :requires
14
+ alias required requires
11
15
 
12
16
  # @return [Array<Gem::Specification>]
13
17
  attr_reader :preferences
@@ -16,11 +20,27 @@ module Solargraph
16
20
  attr_reader :pins
17
21
 
18
22
  # @return [Array<Gem::Specification>]
19
- 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
20
37
 
21
38
  # @return [Workspace, nil]
22
39
  attr_reader :workspace
23
40
 
41
+ # @return [Environ]
42
+ attr_reader :environ
43
+
24
44
  # @param requires [Array<String>]
25
45
  # @param preferences [Array<Gem::Specification>]
26
46
  # @param workspace [Workspace, nil]
@@ -28,7 +48,51 @@ module Solargraph
28
48
  @requires = requires.compact
29
49
  @preferences = preferences.compact
30
50
  @workspace = workspace
31
- generate
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
+ out.puts("Caching pins for gem #{gemspec.name}:#{gemspec.version}") if out
94
+ cache_yard_pins(gemspec, out) if uncached_yard_gemspecs.include?(gemspec) || rebuild
95
+ cache_rbs_collection_pins(gemspec, out) if uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
32
96
  end
33
97
 
34
98
  # @return [Array<Gem::Specification>]
@@ -41,9 +105,24 @@ module Solargraph
41
105
  @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
42
106
  end
43
107
 
44
- # @return [Hash{Gem::Specification => Array[Pin::Base]}]
45
- def self.gems_in_memory
46
- @gems_in_memory ||= {}
108
+ def self.all_yard_gems_in_memory
109
+ @yard_gems_in_memory ||= {}
110
+ end
111
+
112
+ def self.all_rbs_collection_gems_in_memory
113
+ @rbs_collection_gems_in_memory ||= {}
114
+ end
115
+
116
+ def yard_pins_in_memory
117
+ self.class.all_yard_gems_in_memory
118
+ end
119
+
120
+ def rbs_collection_pins_in_memory
121
+ self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {}
122
+ end
123
+
124
+ def combined_pins_in_memory
125
+ @combined_pins_in_memory ||= {}
47
126
  end
48
127
 
49
128
  # @return [Set<Gem::Specification>]
@@ -54,20 +133,29 @@ module Solargraph
54
133
  private
55
134
 
56
135
  # @return [void]
57
- def generate
136
+ def load_serialized_gem_pins
58
137
  @pins = []
59
- @uncached_gemspecs = []
60
- required_gems_map.each do |path, gemspecs|
61
- if gemspecs.nil?
62
- try_stdlib_map path
63
- else
64
- gemspecs.each do |gemspec|
65
- try_cache gemspec
66
- end
138
+ @uncached_yard_gemspecs = []
139
+ @uncached_rbs_collection_gemspecs = []
140
+ with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
141
+ paths = Hash[without_gemspecs].keys
142
+ gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a
143
+
144
+ paths.each do |path|
145
+ rbs_pins = deserialize_stdlib_rbs_map path
146
+ end
147
+
148
+ logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." }
149
+ time = Benchmark.measure do
150
+ gemspecs.each do |gemspec|
151
+ pins = deserialize_combined_pin_cache gemspec
152
+ @pins.concat pins if pins
67
153
  end
68
154
  end
69
- dependencies.each { |dep| try_cache dep }
70
- @uncached_gemspecs.uniq!
155
+ logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" }
156
+ @uncached_yard_gemspecs.uniq!
157
+ @uncached_rbs_collection_gemspecs.uniq!
158
+ nil
71
159
  end
72
160
 
73
161
  # @return [Hash{String => Array<Gem::Specification>}]
@@ -80,32 +168,97 @@ module Solargraph
80
168
  @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
81
169
  end
82
170
 
171
+ # @param gemspec [Gem::Specification]
172
+ # @return [Array<Pin::Base>]
173
+ def deserialize_yard_pin_cache gemspec
174
+ if yard_pins_in_memory.key?([gemspec.name, gemspec.version])
175
+ return yard_pins_in_memory[[gemspec.name, gemspec.version]]
176
+ end
177
+
178
+ cached = PinCache.deserialize_yard_gem(gemspec)
179
+ if cached
180
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
181
+ yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached
182
+ cached
183
+ else
184
+ logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
185
+ @uncached_yard_gemspecs.push gemspec
186
+ nil
187
+ end
188
+ end
189
+
83
190
  # @param gemspec [Gem::Specification]
84
191
  # @return [void]
85
- def try_cache gemspec
86
- return if try_gem_in_memory(gemspec)
87
- cache_file = File.join('gems', "#{gemspec.name}-#{gemspec.version}.ser")
88
- if Cache.exist?(cache_file)
89
- cached = Cache.load(cache_file)
90
- gempins = update_from_collection(gemspec, cached)
91
- self.class.gems_in_memory[gemspec] = gempins
92
- @pins.concat gempins
192
+ def deserialize_combined_pin_cache(gemspec)
193
+ unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil?
194
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
195
+ end
196
+
197
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
198
+ rbs_version_cache_key = rbs_map.cache_key
199
+
200
+ cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key)
201
+ if cached
202
+ logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
203
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached
204
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
205
+ end
206
+
207
+ rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
208
+
209
+ yard_pins = deserialize_yard_pin_cache gemspec
210
+
211
+ if !rbs_collection_pins.nil? && !yard_pins.nil?
212
+ logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" }
213
+ combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
214
+ PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
215
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins
216
+ logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" }
217
+ return combined_pins
218
+ end
219
+
220
+ if !yard_pins.nil?
221
+ logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" }
222
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins
223
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
224
+ elsif !rbs_collection_pins.nil?
225
+ logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" }
226
+ combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins
227
+ return combined_pins_in_memory[[gemspec.name, gemspec.version]]
93
228
  else
94
- Solargraph.logger.debug "No pin cache for #{gemspec.name} #{gemspec.version}"
95
- @uncached_gemspecs.push gemspec
229
+ logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" }
230
+ return nil
96
231
  end
97
232
  end
98
233
 
99
234
  # @param path [String] require path that might be in the RBS stdlib collection
100
235
  # @return [void]
101
- def try_stdlib_map path
236
+ def deserialize_stdlib_rbs_map path
102
237
  map = RbsMap::StdlibMap.load(path)
103
238
  if map.resolved?
104
- Solargraph.logger.debug "Loading stdlib pins for #{path}"
239
+ logger.debug { "Loading stdlib pins for #{path}" }
105
240
  @pins.concat map.pins
241
+ logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" }
242
+ map.pins
106
243
  else
107
244
  # @todo Temporarily ignoring unresolved `require 'set'`
108
- Solargraph.logger.debug "Require path #{path} could not be resolved" unless path == 'set'
245
+ logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
246
+ nil
247
+ end
248
+ end
249
+
250
+ # @return [Array<Pin::Base>, nil]
251
+ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
252
+ return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
253
+ cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key)
254
+ if cached
255
+ logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty?
256
+ rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached
257
+ cached
258
+ else
259
+ logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}"
260
+ @uncached_rbs_collection_gemspecs.push gemspec
261
+ nil
109
262
  end
110
263
  end
111
264
 
@@ -119,18 +272,6 @@ module Solargraph
119
272
  true
120
273
  end
121
274
 
122
- # @param gemspec [Gem::Specification]
123
- def update_from_collection gemspec, gempins
124
- return gempins unless workspace&.rbs_collection_path && File.directory?(workspace&.rbs_collection_path)
125
- return gempins if RbsMap.new(gemspec.name, gemspec.version).resolved?
126
-
127
- rbs_map = RbsMap.new(gemspec.name, gemspec.version, directories: [workspace&.rbs_collection_path])
128
- return gempins unless rbs_map.resolved?
129
-
130
- Solargraph.logger.info "Updating #{gemspec.name} #{gemspec.version} from collection"
131
- GemPins.combine(gempins, rbs_map)
132
- end
133
-
134
275
  # @param path [String]
135
276
  # @return [::Array<Gem::Specification>, nil]
136
277
  def resolve_path_to_gemspecs path
@@ -149,7 +290,7 @@ module Solargraph
149
290
  file = "lib/#{path}.rb"
150
291
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
151
292
  rescue Gem::MissingSpecError
152
- Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
293
+ logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" }
153
294
  []
154
295
  end
155
296
  end
@@ -187,7 +328,7 @@ module Solargraph
187
328
  dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
188
329
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
189
330
  rescue Gem::MissingSpecError
190
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found."
331
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems."
191
332
  end.to_a
192
333
  end
193
334
 
@@ -197,6 +338,11 @@ module Solargraph
197
338
  gemspec.dependencies - gemspec.development_dependencies
198
339
  end
199
340
 
341
+
342
+ def inspect
343
+ self.class.inspect
344
+ end
345
+
200
346
  def gemspecs_required_from_bundler
201
347
  if workspace&.directory && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
202
348
  # Find only the gems bundler is now using