solargraph 0.60.3 → 0.61.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +1 -5
  3. data/.github/workflows/rspec.yml +3 -1
  4. data/CHANGELOG.md +26 -0
  5. data/Gemfile +0 -3
  6. data/lib/solargraph/api_map/index.rb +15 -2
  7. data/lib/solargraph/api_map/store.rb +13 -0
  8. data/lib/solargraph/api_map.rb +60 -81
  9. data/lib/solargraph/cache_dir.rb +58 -0
  10. data/lib/solargraph/collection/base.rb +50 -0
  11. data/lib/solargraph/collection/core.rb +17 -0
  12. data/lib/solargraph/collection/gem.rb +57 -0
  13. data/lib/solargraph/collection/rbs.rb +49 -0
  14. data/lib/solargraph/collection/stdlib.rb +25 -0
  15. data/lib/solargraph/collection.rb +31 -0
  16. data/lib/solargraph/complex_type.rb +18 -5
  17. data/lib/solargraph/convention.rb +5 -3
  18. data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
  19. data/lib/solargraph/external.rb +159 -0
  20. data/lib/solargraph/language_server/message/text_document/definition.rb +4 -1
  21. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
  22. data/lib/solargraph/library.rb +11 -24
  23. data/lib/solargraph/metagem.rb +80 -0
  24. data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
  25. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +14 -5
  26. data/lib/solargraph/pin/closure.rb +17 -0
  27. data/lib/solargraph/pin/factory_parameter.rb +84 -0
  28. data/lib/solargraph/pin/method.rb +7 -4
  29. data/lib/solargraph/pin.rb +1 -0
  30. data/lib/solargraph/rbs_map/base.rb +31 -0
  31. data/lib/solargraph/rbs_map/conversions.rb +3 -1
  32. data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
  33. data/lib/solargraph/rbs_map/core_fills.rb +1 -1
  34. data/lib/solargraph/rbs_map/gem.rb +13 -0
  35. data/lib/solargraph/rbs_map/helpers.rb +60 -0
  36. data/lib/solargraph/rbs_map/path.rb +19 -0
  37. data/lib/solargraph/rbs_map/stdlib.rb +24 -0
  38. data/lib/solargraph/rbs_map.rb +7 -198
  39. data/lib/solargraph/repo.rb +177 -0
  40. data/lib/solargraph/shell.rb +49 -99
  41. data/lib/solargraph/source/chain/call.rb +190 -67
  42. data/lib/solargraph/source/chain/literal.rb +17 -17
  43. data/lib/solargraph/source/chain/parameter.rb +79 -0
  44. data/lib/solargraph/source/chain.rb +44 -5
  45. data/lib/solargraph/source/source_chainer.rb +40 -44
  46. data/lib/solargraph/source_map/clip.rb +5 -3
  47. data/lib/solargraph/source_map/mapper.rb +6 -2
  48. data/lib/solargraph/source_map.rb +10 -5
  49. data/lib/solargraph/type_checker/rules.rb +2 -1
  50. data/lib/solargraph/version.rb +1 -1
  51. data/lib/solargraph/workspace.rb +0 -1
  52. data/lib/solargraph/yard_map/cache.rb +1 -1
  53. data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
  54. data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
  55. data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
  56. data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
  57. data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
  58. data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
  59. data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
  60. data/lib/solargraph/yard_map/directives.rb +5 -2
  61. data/lib/solargraph/yard_map/helpers.rb +1 -1
  62. data/lib/solargraph/yard_map/macro.rb +34 -5
  63. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
  64. data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
  65. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
  66. data/lib/solargraph/yard_map/mapper.rb +37 -4
  67. data/lib/solargraph/yard_map.rb +1 -1
  68. data/lib/solargraph/yardoc.rb +43 -60
  69. data/lib/solargraph.rb +6 -3
  70. data/solargraph.gemspec +1 -1
  71. metadata +22 -9
  72. data/lib/solargraph/doc_map.rb +0 -439
  73. data/lib/solargraph/gem_pins.rb +0 -87
  74. data/lib/solargraph/pin_cache.rb +0 -248
  75. data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
  76. data/lib/solargraph/workspace/gemspecs.rb +0 -373
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a62c1ebc82583e5c0ee905b75e3f065a1c6185c8268e7b529115fe445afb1a6
4
- data.tar.gz: 8b4cc8722f4eb6b3aecb58aaec22d2db54c49a3b2f6477bc7019331de86fbfc7
3
+ metadata.gz: 7b386e8d7447871b7850f167cab1483351050f92debdc40bb4b2ee9538e820e1
4
+ data.tar.gz: 3b88afe53d55f28b96004941ff37c2c8c9254c7e83ed597d90122aa1b6e374c2
5
5
  SHA512:
6
- metadata.gz: 8882c0dffd4a6ac9c4d25c53fdb1789840951fa00860120a25237353c9aa4b2c3454bf04cd94921e56f52871a97978f3ea5b4ed38fba34841d8395eb1223a650
7
- data.tar.gz: 5ec51b9ab0cb3d3f445e3282c0c132641a7c9bf52643598a4476ddc5eb23d5527442f0bd434f772b9fcc94a437a956b38607f13d9dc74752df69fae75017963b
6
+ metadata.gz: 97f94421c920c20c8c5e2d5d8deae43ef7559cd082cedc022041ebc22252c04cb713e25fb8d5e96af1c258fe7aecffbc527c88829876944868911e31f20990ee
7
+ data.tar.gz: ad5d661d4340531998b669b8d499922f2c05c2fc94d04f1cdb9c2ed3ce207d0aa08875fd18e9a63f6e5a9c0688354e8ea8ada6bd865f76eaaa5a92393d2c6917
@@ -129,12 +129,8 @@ jobs:
129
129
  - name: clone https://github.com/lekemula/solargraph-rspec/
130
130
  run: |
131
131
  cd ..
132
- # git clone https://github.com/lekemula/solargraph-rspec.git
133
-
134
- # pending https://github.com/lekemula/solargraph-rspec/pull/31
135
- git clone https://github.com/apiology/solargraph-rspec.git
132
+ git clone https://github.com/lekemula/solargraph-rspec/
136
133
  cd solargraph-rspec
137
- git checkout test_solargraph_prereleases
138
134
  - name: Set up Ruby
139
135
  uses: ruby/setup-ruby@v1
140
136
  with:
@@ -25,7 +25,7 @@ jobs:
25
25
  # It currently 404s ("Unavailable version head for ruby"), failing CI:
26
26
  # https://github.com/castwide/solargraph/actions/runs/25863741955/job/76000137015?pr=1187
27
27
  ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0']
28
- rbs-version: ['3.10.0', '4.0.0', '4.0.1', '4.0.2']
28
+ rbs-version: ['3.10.4', '4.0.0', '4.0.1', '4.0.2']
29
29
  exclude:
30
30
  - ruby-version: '3.1'
31
31
  rbs-version: '4.0.0'
@@ -48,6 +48,8 @@ jobs:
48
48
  # starting from Ruby 3.6.0
49
49
  - name: Work around legacy rbs deprecation on ruby > 3.4
50
50
  run: echo "gem 'tsort'" >> .Gemfile
51
+ - name: Install bundle
52
+ run: bundle install
51
53
  - name: Update gems
52
54
  run: |
53
55
  bundle update rbs # use latest available for this Ruby version
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.61.0 - October 7, 2026
2
+ - Clips qualify constant pins for completion (#1368)
3
+ - Unused spec tags (#1370)
4
+ - External gem dependency management (#1369)
5
+ - Use a development branch of Solargraph::RSpec for plugin tests (#1371)
6
+ - Pin::Base spec changes (#1372)
7
+ - Consolidate cache and gems commands (#1373)
8
+ - Sleep timeouts (#1375)
9
+ - RbsMap loads minimal pins (#1377)
10
+ - Fix 10x slowdown when analyzing projects with many gems (#1374)
11
+ - Clone main solargraph-rspec repo in plugins workflow (#1388)
12
+ - Add new Pin::FactoryParameter (#1063)
13
+ - Support YARD `@!scope` directive (#1387)
14
+ - Add nil guard to Hover (#1389)
15
+ - Stop dropping union members that differ only by type parameter (#1273)
16
+
17
+ ## 0.60.4 - August 30, 2026
18
+ - Fix @yieldparam type lost on multi-overload block-form methods (#1289) (#1290)
19
+ - Read inline RBS superclass params only on the class line (#1301)
20
+ - Stop recording YARD's implicit Object superclass (#1315)
21
+ - Improve overload resolution and macro handling in Chain::Call (#1247)
22
+ - Fix Chain#nullable? leaking nil from earlier &. into later calls (#1271)
23
+ - Handle YARD bug with open singleton classes (#1279)
24
+ - Fix duck_types_match? to check the inferred type's own duck interface (#1295)
25
+ - Use the original repo (run_solargraph_rspec_specs job) (#1291)
26
+
1
27
  ## 0.60.3 - August 5, 2026
2
28
  - Pin rdoc to ~> 7.0 (#1216)
3
29
  - (tag: v0.60.2) Release 0.60.2 (rdoc fix)
data/Gemfile CHANGED
@@ -4,9 +4,6 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec name: 'solargraph'
6
6
 
7
- # Test fixture gems
8
- gem 'gem-with-yard-macros', path: 'spec/fixtures/gem-with-yard-macros'
9
-
10
7
  # Local gemfile for development tools, etc.
11
8
  local_gemfile = File.expand_path('.Gemfile', __dir__)
12
9
  instance_eval File.read local_gemfile if File.exist? local_gemfile
@@ -92,6 +92,11 @@ module Solargraph
92
92
  @superclass_references ||= Hash.new { |h, k| h[k] = [] }
93
93
  end
94
94
 
95
+ # @return [Hash{String => Array<Pin::FactoryParameter>}]
96
+ def factory_parameter_hash
97
+ @factory_parameter_hash ||= Hash.new { |h, k| h[k] = [] }
98
+ end
99
+
95
100
  # @param pins [Enumerable<Pin::Base>]
96
101
  # @return [self]
97
102
  def merge pins
@@ -101,8 +106,8 @@ module Solargraph
101
106
  protected
102
107
 
103
108
  attr_writer :pins, :pin_select_cache, :namespace_hash, :pin_class_hash, :path_pin_hash, :include_references,
104
- :extend_references, :prepend_references, :superclass_references, :macro_method_names,
105
- :macro_method_name_pins
109
+ :extend_references, :prepend_references, :superclass_references, :factory_parameter_hash,
110
+ :macro_method_names, :macro_method_name_pins
106
111
 
107
112
  # @return [self]
108
113
  def deep_clone
@@ -148,6 +153,7 @@ module Solargraph
148
153
  macro_pins = pins_by_class(Pin::Method).select { |pin| pin.macros.any? }
149
154
  @macro_method_names = macro_pins.to_set(&:name)
150
155
  @macro_method_name_pins = macro_pins.to_set.classify(&:name)
156
+ map_factory_parameters
151
157
  map_overrides
152
158
  pins_by_class(Pin::Reference::TypeAlias).each { |pin| alias_hash[pin.name] = pin.return_type }
153
159
  self
@@ -210,6 +216,13 @@ module Solargraph
210
216
  sig.instance_variable_set(:@return_type, ComplexType.try_parse(tag.type))
211
217
  end
212
218
  end
219
+
220
+ # @return [void]
221
+ def map_factory_parameters
222
+ pins_by_class(Pin::FactoryParameter).each do |fp|
223
+ factory_parameter_hash[fp.method_path] << fp
224
+ end
225
+ end
213
226
  end
214
227
  end
215
228
  end
@@ -261,6 +261,14 @@ module Solargraph
261
261
  ancestors.compact.uniq
262
262
  end
263
263
 
264
+ # Get factory parameters for a method pin.
265
+ #
266
+ # @param method_pin [Pin::Method]
267
+ # @return [Array<Pin::FactoryParameter>]
268
+ def factory_parameters_for_method method_pin
269
+ factory_parameter_hash[method_pin.path] || []
270
+ end
271
+
264
272
  # @param fqns [String]
265
273
  #
266
274
  # @return [Array<Solargraph::Pin::Reference>]
@@ -358,6 +366,11 @@ module Solargraph
358
366
  index.extend_references
359
367
  end
360
368
 
369
+ # @return [Hash{String => Array<Pin::FactoryParameter>}]
370
+ def factory_parameter_hash
371
+ index.factory_parameter_hash
372
+ end
373
+
361
374
  # @param name [String]
362
375
  # @return [Enumerable<Solargraph::Pin::Base>]
363
376
  def namespace_children name
@@ -15,13 +15,9 @@ module Solargraph
15
15
  autoload :Index, 'solargraph/api_map/index'
16
16
  autoload :Constants, 'solargraph/api_map/constants'
17
17
 
18
- # @return [Array<String>]
19
- attr_reader :unresolved_requires
20
-
21
- @@core_map = RbsMap::CoreMap.new
18
+ include Equality
22
19
 
23
- # @return [Array<String>]
24
- attr_reader :missing_docs
20
+ @@core_pins = Collection::Core.load
25
21
 
26
22
  # @param pins [Array<Solargraph::Pin::Base>]
27
23
  # @param loose_unions [Boolean] if true, a potential type can be
@@ -38,12 +34,6 @@ module Solargraph
38
34
  index pins
39
35
  end
40
36
 
41
- # @param out [StringIO, IO, nil] output stream for logging
42
- # @return [void]
43
- def self.reset_core out: nil
44
- @@core_map = RbsMap::CoreMap.new
45
- end
46
-
47
37
  #
48
38
  # This is a mutable object, which is cached in the Chain class -
49
39
  # if you add any fields which change the results of calls (not
@@ -86,7 +76,7 @@ module Solargraph
86
76
  @source_map_hash = {}
87
77
  conventions_environ.clear
88
78
  cache.clear
89
- store.update @@core_map.pins, pins
79
+ store.update @@core_pins, pins
90
80
  self
91
81
  end
92
82
 
@@ -106,6 +96,8 @@ module Solargraph
106
96
  # @param bench [Bench]
107
97
  # @return [self]
108
98
  def catalog bench
99
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
100
+ @workspace = bench.workspace
109
101
  @source_map_hash = bench.source_map_hash
110
102
  # @type [Array<Pin::Base>]
111
103
  iced_pins = bench.icebox.flat_map(&:pins)
@@ -114,24 +106,27 @@ module Solargraph
114
106
  source_map_hash.each_value do |map|
115
107
  conventions_environ.merge map.conventions_environ
116
108
  end
117
- unresolved_requires = (bench.external_requires + conventions_environ.requires + bench.workspace.config.required).to_a.compact.uniq
118
- recreate_docmap = @unresolved_requires != unresolved_requires ||
119
- # @sg-ignore Unresolved call to rbs_collection_path on Solargraph::Workspace, nil
120
- workspace.rbs_collection_path != bench.workspace.rbs_collection_path ||
121
- @doc_map.uncached_gemspecs.any?
122
-
123
- if recreate_docmap
124
- @doc_map = DocMap.new(unresolved_requires, bench.workspace, out: nil) # @todo Implement gem preferences
125
- @unresolved_requires = @doc_map.unresolved_requires
126
- end
127
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
128
- Solargraph.logger.info 'Cataloging ApiMap started'
129
- @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins) { process_macros }
130
- @missing_docs = [] # @todo Implement missing docs
109
+ # @todo Determine what needs to be sent to global conventions
110
+ conventions_environ.merge Convention.for_global(nil)
111
+ external_changed = external.update(bench.external_requires.to_a + conventions_environ.requires)
112
+ store_changed = store.update(@@core_pins, external.pins.clone, conventions_environ.pins, iced_pins, live_pins) { process_macros }
113
+ @cache.clear if external_changed || store_changed
131
114
  Solargraph.logger.info "Cataloging ApiMap finished in #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time} seconds"
132
115
  self
133
116
  end
134
117
 
118
+ def external
119
+ @external ||= External.new(workspace&.directory, [])
120
+ end
121
+
122
+ def unresolved_requires
123
+ external.unresolved_requires
124
+ end
125
+
126
+ def unloaded_gems
127
+ external.unloaded_gems
128
+ end
129
+
135
130
  # @return [Array<Pin::Base>]
136
131
  def process_macros
137
132
  macro_pins = []
@@ -144,45 +139,23 @@ module Solargraph
144
139
  method_candidates.each do |node|
145
140
  closure = source_map.locate_closure_pin(node.location.line, node.location.column)
146
141
  chain = Solargraph::Parser::ParserGem::NodeChainer.chain(node)
147
- if node.children[0].nil? && store.macro_method_name_pins.key?(node.children[1].to_s)
148
- match = store.macro_method_name_pins[node.children[1].to_s].find do |pin|
149
- get_complex_type_methods(closure.return_type).include?(pin)
150
- end
151
- if match
152
- match.macros.each do |macro|
153
- macro_pins.concat macro.generate_pins_from(chain, match, source_map)
154
- end
155
- next
156
- end
142
+ next unless node.children[0].nil? && store.macro_method_name_pins.key?(node.children[1].to_s)
143
+ match = store.macro_method_name_pins[node.children[1].to_s].find do |pin|
144
+ get_complex_type_methods(closure.return_type).include?(pin)
145
+ end
146
+ next unless match
147
+ match.macros.each do |macro|
148
+ macro_pins.concat macro.generate_pins_from(chain, match, source_map)
157
149
  end
150
+ next
158
151
  end
159
152
  end
160
153
  macro_pins
161
154
  end
162
155
 
163
- # @return [DocMap]
164
- def doc_map
165
- @doc_map ||= DocMap.new([], Workspace.new('.'))
166
- end
167
-
168
- # @return [::Array<Gem::Specification>]
169
- def uncached_gemspecs
170
- doc_map.uncached_gemspecs || []
171
- end
172
-
173
- # @return [::Array<Gem::Specification>]
174
- def uncached_rbs_collection_gemspecs
175
- @doc_map.uncached_rbs_collection_gemspecs
176
- end
177
-
178
- # @return [::Array<Gem::Specification>]
179
- def uncached_yard_gemspecs
180
- @doc_map.uncached_yard_gemspecs
181
- end
182
-
183
156
  # @return [Enumerable<Pin::Base>]
184
157
  def core_pins
185
- @@core_map.pins
158
+ @@core_pins
186
159
  end
187
160
 
188
161
  # @param name [String, nil]
@@ -237,21 +210,6 @@ module Solargraph
237
210
  api_map
238
211
  end
239
212
 
240
- # @param out [StringIO, IO, nil]
241
- # @param rebuild [Boolean] whether to rebuild the pins even if they are cached
242
- # @return [void]
243
- def cache_all_for_doc_map! out: $stderr, rebuild: false
244
- doc_map.cache_all!(out, rebuild: rebuild)
245
- end
246
-
247
- # @param gemspec [Gem::Specification]
248
- # @param rebuild [Boolean]
249
- # @param out [StringIO, IO, nil]
250
- # @return [void]
251
- def cache_gem gemspec, rebuild: false, out: nil
252
- doc_map.cache(gemspec, rebuild: rebuild, out: out)
253
- end
254
-
255
213
  class << self
256
214
  include Logging
257
215
  end
@@ -267,12 +225,12 @@ module Solargraph
267
225
  # @return [ApiMap]
268
226
  def self.load_with_cache directory, out = $stderr, loose_unions: true
269
227
  api_map = load(directory, loose_unions: loose_unions)
270
- if api_map.uncached_gemspecs.empty?
271
- logger.info { "All gems cached for #{directory}" }
272
- return api_map
273
- end
228
+ return api_map if api_map.external.unloaded_gems.empty?
274
229
 
275
- api_map.cache_all_for_doc_map!(out: out)
230
+ api_map.external.unloaded_gems.each do |metagem|
231
+ out&.puts "Caching gem #{metagem.name} (#{metagem.cache_name})"
232
+ Collection::Gem.load metagem
233
+ end
276
234
  load(directory, loose_unions: loose_unions)
277
235
  end
278
236
 
@@ -288,6 +246,21 @@ module Solargraph
288
246
  store.pins_by_class(Pin::Keyword)
289
247
  end
290
248
 
249
+ # An array of pins based on factory parameters.
250
+ #
251
+ # @return [Enumerable<Solargraph::Pin::FactoryParameter>]
252
+ def factory_parameter_pins
253
+ store.pins_by_class(Pin::FactoryParameter)
254
+ end
255
+
256
+ # Get factory parameters for a method pin.
257
+ #
258
+ # @param method_pin [Solargraph::Pin::Method]
259
+ # @return [Array<Solargraph::Pin::FactoryParameter>]
260
+ def factory_parameters_for_method method_pin
261
+ store.factory_parameters_for_method(method_pin)
262
+ end
263
+
291
264
  # @param name [String]
292
265
  # @return [ComplexType, nil]
293
266
  def unalias name
@@ -609,6 +582,9 @@ module Solargraph
609
582
 
610
583
  # Get an array of pins that match the specified path.
611
584
  #
585
+ # @example
586
+ # api_map.get_pins_by_path('String#split')
587
+ #
612
588
  # @param path [String]
613
589
  # @return [Array<Pin::Base>]
614
590
  def get_path_pins path
@@ -755,7 +731,7 @@ module Solargraph
755
731
 
756
732
  # @return [Workspace, nil]
757
733
  def workspace
758
- doc_map.workspace
734
+ @workspace
759
735
  end
760
736
 
761
737
  # @param fq_reference_tag [String] A fully qualified whose method should be pulled in
@@ -1051,8 +1027,11 @@ module Solargraph
1051
1027
  # that this overload of 'protected' will typecheck @sg-ignore
1052
1028
  # @sg-ignore
1053
1029
  def equality_fields
1054
- [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires, @missing_docs,
1055
- @loose_unions]
1030
+ # External is compared by identity and generation instead of by its pins:
1031
+ # Chain#infer hashes the ApiMap on every inference, and Array#hash would
1032
+ # walk every gem pin on each of those calls.
1033
+ [@source_map_hash, conventions_environ, @external, @external&.generation,
1034
+ @unresolved_requires, @loose_unions]
1056
1035
  end
1057
1036
  end
1058
1037
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'rbs'
5
+
6
+ module Solargraph
7
+ module CacheDir
8
+ module_function
9
+
10
+ # The base directory for cached YARD documentation and serialized pins.
11
+ #
12
+ # @return [String]
13
+ def base_dir
14
+ ENV['SOLARGRAPH_CACHE'] ||
15
+ (ENV['XDG_CACHE_HOME'] ? File.join(ENV['XDG_CACHE_HOME'], 'solargraph') : nil) ||
16
+ File.join(Dir.home, '.cache', 'solargraph')
17
+ end
18
+
19
+ # The working directory for the current Ruby, RBS, and Solargraph versions.
20
+ #
21
+ # @return [String]
22
+ def work_dir
23
+ File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
24
+ end
25
+
26
+ # The current gem directory.
27
+ #
28
+ # @return [String]
29
+ def gem_dir
30
+ File.join(work_dir, 'gems')
31
+ end
32
+
33
+ # The current stdlib directory.
34
+ #
35
+ # @return [String]
36
+ def stdlib_dir
37
+ File.join(work_dir, 'stdlib')
38
+ end
39
+
40
+ # The current RBS collection directory.
41
+ #
42
+ # @return [String]
43
+ def rbs_dir
44
+ File.join(work_dir, 'rbs')
45
+ end
46
+
47
+ # The directory for the current YARD version.
48
+ #
49
+ # @return [String]
50
+ def yard_dir
51
+ File.join(base_dir, "yard-#{YARD::VERSION}", "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}")
52
+ end
53
+
54
+ def clear
55
+ FileUtils.rm_rf base_dir
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Solargraph
6
+ module Collection
7
+ # The base class for cacheable pin collections. Subclasses need to
8
+ # implement #pins and #cache_file.
9
+ #
10
+ # @!method pins
11
+ # The array of pins for this collection.
12
+ # @abstract
13
+ # @return [Array<Pin::Base>]
14
+ # @!method cache_file
15
+ # The file that will store serialized pins.
16
+ # @abstract
17
+ # @return [String]
18
+ class Base
19
+ # @return [Array<Pin::Base>]
20
+ def load
21
+ Collection.cached(cache_file) { file_cache_pins || generate_pin_caches }
22
+ end
23
+
24
+ # @return [Array<Pin::Base>]
25
+ def self.load(...)
26
+ new(...).load
27
+ end
28
+
29
+ def self.uncache(...)
30
+ cache_file = new(...).cache_file
31
+ FileUtils.rm_rf cache_file
32
+ Collection.mem_cache.delete cache_file
33
+ end
34
+
35
+ private
36
+
37
+ def file_cache_pins
38
+ Marshal.load(File.read(cache_file, mode: 'rb')) if File.exist?(cache_file) # rubocop:disable Security/MarshalLoad
39
+ end
40
+
41
+ def generate_pin_caches
42
+ Collection.mem_cache[cache_file] = pins
43
+ serial = Marshal.dump(pins)
44
+ FileUtils.mkdir_p File.dirname(cache_file)
45
+ File.write cache_file, serial, mode: 'wb'
46
+ pins
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable core pins.
6
+ #
7
+ class Core < Base
8
+ def cache_file
9
+ File.join CacheDir.work_dir, 'core.ser'
10
+ end
11
+
12
+ def pins
13
+ @pins ||= RbsMap::Core.pins
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable gem pins.
6
+ #
7
+ class Gem < Base
8
+ include Logging
9
+
10
+ attr_reader :metagem
11
+
12
+ # @param metagem [Metagem]
13
+ def initialize metagem
14
+ super()
15
+ @metagem = metagem
16
+ end
17
+
18
+ def cache_file
19
+ File.join CacheDir.gem_dir, "#{metagem.cache_name}.ser"
20
+ end
21
+
22
+ def load
23
+ return pins unless metagem.cacheable?
24
+ super
25
+ end
26
+
27
+ def pins
28
+ @pins ||= if metagem.cacheable?
29
+ cacheable_pins
30
+ else
31
+ uncacheable_pins
32
+ end
33
+ end
34
+
35
+ def self.cached? metagem
36
+ metagem.cacheable? && File.exist?(new(metagem).cache_file)
37
+ end
38
+
39
+ private
40
+
41
+ def cacheable_pins
42
+ code_objects = Yardoc.load!(metagem)
43
+ yard_pins = YardMap::Mapper.new(code_objects, metagem).map
44
+ rbs_pins = RbsMap::Gem.pins(metagem)
45
+ RbsMap::Helpers.combine(yard_pins, rbs_pins)
46
+ end
47
+
48
+ def uncacheable_pins
49
+ files = metagem.require_paths.flat_map { |path| Dir.glob(File.join(metagem.full_path, path, '**', '*.rb')) }
50
+ source_maps = files.map { |file| Solargraph::SourceMap.load(file) }
51
+ source_pins = source_maps.flat_map(&:pins)
52
+ rbs_pins = RbsMap::Gem.pins(metagem)
53
+ RbsMap::Helpers.combine(source_pins, rbs_pins)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Solargraph
6
+ module Collection
7
+ # Cacheable pins from an RBS collection directory.
8
+ #
9
+ class Rbs < Base
10
+ # @return [String]
11
+ attr_reader :path
12
+
13
+ # @param path [String]
14
+ # @return [Array<Pin::Base>]
15
+ def self.load path
16
+ new(path).load
17
+ end
18
+
19
+ # @param path [String]
20
+ def initialize path
21
+ super()
22
+ @path = path
23
+ end
24
+
25
+ # @return [Array<Pin::Base>]
26
+ def pins
27
+ @pins ||= RbsMap::Path.pins(path)
28
+ end
29
+
30
+ # @return [String]
31
+ def cache_file
32
+ File.join CacheDir.rbs_dir, "#{key}.ser"
33
+ end
34
+
35
+ private
36
+
37
+ # A collection is rewritten in place by `rbs collection install`, so the
38
+ # newest signature mtime distinguishes one installation from the next.
39
+ #
40
+ # @return [String]
41
+ def key
42
+ @key ||= begin
43
+ stamp = Dir.glob(File.join(path, '**', '*.rbs')).map { |file| File.mtime(file).to_i }.max
44
+ format('%.32s', Digest::SHA256.hexdigest("#{File.expand_path(path)}:#{stamp}"))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ # Cacheable stdlib pins.
6
+ #
7
+ class Stdlib < Base
8
+ attr_reader :library
9
+
10
+ # @param library [String]
11
+ def initialize library
12
+ super()
13
+ @library = library
14
+ end
15
+
16
+ def pins
17
+ @pins ||= RbsMap::Stdlib.pins(library)
18
+ end
19
+
20
+ def cache_file
21
+ File.join CacheDir.stdlib_dir, "#{library}.ser"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Collection
5
+ autoload :Base, 'solargraph/collection/base'
6
+ autoload :Core, 'solargraph/collection/core'
7
+ autoload :Gem, 'solargraph/collection/gem'
8
+ autoload :Rbs, 'solargraph/collection/rbs'
9
+ autoload :Stdlib, 'solargraph/collection/stdlib'
10
+
11
+ # A hash for storing pin collections in memory.
12
+ #
13
+ # @return [Hash{String => Array<Pin::Base>}]
14
+ def self.mem_cache
15
+ @mem_cache ||= {}
16
+ end
17
+
18
+ # The pins cached under a key, storing the block result on a miss.
19
+ #
20
+ # @param key [String]
21
+ # @return [Array<Pin::Base>]
22
+ def self.cached key
23
+ mem_cache.fetch(key) { mem_cache[key] = yield }
24
+ end
25
+
26
+ # @return [void]
27
+ def self.clear_mem_cache
28
+ mem_cache.clear
29
+ end
30
+ end
31
+ end