solargraph 0.58.1 → 0.59.0.dev.1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +3 -0
  3. data/.github/workflows/linting.yml +4 -5
  4. data/.github/workflows/plugins.yml +40 -36
  5. data/.github/workflows/rspec.yml +45 -13
  6. data/.github/workflows/typecheck.yml +2 -2
  7. data/.rubocop_todo.yml +27 -49
  8. data/README.md +3 -3
  9. data/Rakefile +1 -0
  10. data/lib/solargraph/api_map/cache.rb +110 -110
  11. data/lib/solargraph/api_map/constants.rb +289 -279
  12. data/lib/solargraph/api_map/index.rb +204 -193
  13. data/lib/solargraph/api_map/source_to_yard.rb +109 -97
  14. data/lib/solargraph/api_map/store.rb +387 -384
  15. data/lib/solargraph/api_map.rb +1000 -945
  16. data/lib/solargraph/complex_type/conformance.rb +176 -0
  17. data/lib/solargraph/complex_type/type_methods.rb +242 -228
  18. data/lib/solargraph/complex_type/unique_type.rb +632 -482
  19. data/lib/solargraph/complex_type.rb +549 -444
  20. data/lib/solargraph/convention/data_definition/data_definition_node.rb +93 -91
  21. data/lib/solargraph/convention/data_definition.rb +108 -105
  22. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +62 -61
  23. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +103 -102
  24. data/lib/solargraph/convention/struct_definition.rb +168 -164
  25. data/lib/solargraph/diagnostics/require_not_found.rb +54 -53
  26. data/lib/solargraph/diagnostics/rubocop.rb +119 -118
  27. data/lib/solargraph/diagnostics/rubocop_helpers.rb +70 -68
  28. data/lib/solargraph/diagnostics/type_check.rb +56 -55
  29. data/lib/solargraph/doc_map.rb +200 -439
  30. data/lib/solargraph/equality.rb +34 -34
  31. data/lib/solargraph/gem_pins.rb +97 -98
  32. data/lib/solargraph/language_server/host/dispatch.rb +131 -130
  33. data/lib/solargraph/language_server/host/message_worker.rb +113 -112
  34. data/lib/solargraph/language_server/host/sources.rb +100 -99
  35. data/lib/solargraph/language_server/host.rb +883 -878
  36. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +109 -114
  37. data/lib/solargraph/language_server/message/extended/document.rb +24 -23
  38. data/lib/solargraph/language_server/message/text_document/completion.rb +58 -56
  39. data/lib/solargraph/language_server/message/text_document/definition.rb +42 -40
  40. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +28 -26
  41. data/lib/solargraph/language_server/message/text_document/formatting.rb +150 -148
  42. data/lib/solargraph/language_server/message/text_document/hover.rb +60 -58
  43. data/lib/solargraph/language_server/message/text_document/signature_help.rb +25 -24
  44. data/lib/solargraph/language_server/message/text_document/type_definition.rb +27 -25
  45. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +25 -23
  46. data/lib/solargraph/library.rb +729 -683
  47. data/lib/solargraph/location.rb +87 -82
  48. data/lib/solargraph/logging.rb +57 -37
  49. data/lib/solargraph/parser/comment_ripper.rb +76 -69
  50. data/lib/solargraph/parser/flow_sensitive_typing.rb +483 -255
  51. data/lib/solargraph/parser/node_processor/base.rb +122 -92
  52. data/lib/solargraph/parser/node_processor.rb +63 -62
  53. data/lib/solargraph/parser/parser_gem/class_methods.rb +167 -149
  54. data/lib/solargraph/parser/parser_gem/node_chainer.rb +191 -166
  55. data/lib/solargraph/parser/parser_gem/node_methods.rb +506 -486
  56. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
  57. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +61 -59
  58. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +24 -15
  59. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
  60. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +60 -53
  61. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +53 -23
  62. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +41 -40
  63. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +30 -29
  64. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +61 -59
  65. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
  66. data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
  68. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +39 -38
  69. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +53 -52
  70. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +296 -291
  71. data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
  72. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +33 -29
  73. data/lib/solargraph/parser/parser_gem/node_processors.rb +74 -70
  74. data/lib/solargraph/parser/region.rb +75 -69
  75. data/lib/solargraph/parser/snippet.rb +17 -17
  76. data/lib/solargraph/pin/base.rb +761 -729
  77. data/lib/solargraph/pin/base_variable.rb +418 -126
  78. data/lib/solargraph/pin/block.rb +126 -104
  79. data/lib/solargraph/pin/breakable.rb +13 -9
  80. data/lib/solargraph/pin/callable.rb +278 -231
  81. data/lib/solargraph/pin/closure.rb +68 -72
  82. data/lib/solargraph/pin/common.rb +94 -79
  83. data/lib/solargraph/pin/compound_statement.rb +55 -0
  84. data/lib/solargraph/pin/conversions.rb +124 -123
  85. data/lib/solargraph/pin/delegated_method.rb +131 -120
  86. data/lib/solargraph/pin/documenting.rb +115 -114
  87. data/lib/solargraph/pin/instance_variable.rb +38 -34
  88. data/lib/solargraph/pin/keyword.rb +16 -20
  89. data/lib/solargraph/pin/local_variable.rb +31 -75
  90. data/lib/solargraph/pin/method.rb +720 -672
  91. data/lib/solargraph/pin/method_alias.rb +42 -34
  92. data/lib/solargraph/pin/namespace.rb +121 -115
  93. data/lib/solargraph/pin/parameter.rb +338 -275
  94. data/lib/solargraph/pin/proxy_type.rb +40 -39
  95. data/lib/solargraph/pin/reference/override.rb +47 -47
  96. data/lib/solargraph/pin/reference/superclass.rb +17 -15
  97. data/lib/solargraph/pin/reference.rb +41 -39
  98. data/lib/solargraph/pin/search.rb +62 -61
  99. data/lib/solargraph/pin/signature.rb +69 -61
  100. data/lib/solargraph/pin/symbol.rb +53 -53
  101. data/lib/solargraph/pin/until.rb +18 -18
  102. data/lib/solargraph/pin/while.rb +18 -18
  103. data/lib/solargraph/pin.rb +46 -44
  104. data/lib/solargraph/pin_cache.rb +665 -245
  105. data/lib/solargraph/position.rb +118 -119
  106. data/lib/solargraph/range.rb +112 -112
  107. data/lib/solargraph/rbs_map/conversions.rb +846 -823
  108. data/lib/solargraph/rbs_map/core_map.rb +65 -58
  109. data/lib/solargraph/rbs_map/stdlib_map.rb +72 -43
  110. data/lib/solargraph/rbs_map.rb +217 -163
  111. data/lib/solargraph/shell.rb +397 -352
  112. data/lib/solargraph/source/chain/call.rb +372 -337
  113. data/lib/solargraph/source/chain/constant.rb +28 -26
  114. data/lib/solargraph/source/chain/hash.rb +35 -34
  115. data/lib/solargraph/source/chain/if.rb +29 -28
  116. data/lib/solargraph/source/chain/instance_variable.rb +34 -13
  117. data/lib/solargraph/source/chain/literal.rb +53 -48
  118. data/lib/solargraph/source/chain/or.rb +31 -23
  119. data/lib/solargraph/source/chain.rb +294 -291
  120. data/lib/solargraph/source/change.rb +89 -82
  121. data/lib/solargraph/source/cursor.rb +172 -166
  122. data/lib/solargraph/source/source_chainer.rb +204 -194
  123. data/lib/solargraph/source/updater.rb +59 -55
  124. data/lib/solargraph/source.rb +524 -498
  125. data/lib/solargraph/source_map/clip.rb +237 -226
  126. data/lib/solargraph/source_map/data.rb +37 -34
  127. data/lib/solargraph/source_map/mapper.rb +282 -259
  128. data/lib/solargraph/source_map.rb +220 -212
  129. data/lib/solargraph/type_checker/problem.rb +34 -32
  130. data/lib/solargraph/type_checker/rules.rb +157 -84
  131. data/lib/solargraph/type_checker.rb +895 -814
  132. data/lib/solargraph/version.rb +1 -1
  133. data/lib/solargraph/workspace/config.rb +257 -255
  134. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  135. data/lib/solargraph/workspace/require_paths.rb +98 -97
  136. data/lib/solargraph/workspace.rb +362 -220
  137. data/lib/solargraph/yard_map/helpers.rb +45 -44
  138. data/lib/solargraph/yard_map/mapper/to_method.rb +134 -130
  139. data/lib/solargraph/yard_map/mapper/to_namespace.rb +32 -31
  140. data/lib/solargraph/yard_map/mapper.rb +84 -79
  141. data/lib/solargraph/yardoc.rb +97 -87
  142. data/lib/solargraph.rb +126 -105
  143. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  144. data/rbs/fills/tuple/tuple.rbs +28 -0
  145. data/rbs/shims/ast/0/node.rbs +5 -0
  146. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  147. data/rbs_collection.yaml +1 -1
  148. data/solargraph.gemspec +2 -1
  149. metadata +22 -17
  150. data/lib/solargraph/type_checker/checks.rb +0 -124
  151. data/lib/solargraph/type_checker/param_def.rb +0 -37
  152. data/lib/solargraph/yard_map/to_method.rb +0 -89
  153. data/sig/shims/ast/0/node.rbs +0 -5
  154. /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
  155. /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
  156. /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
  157. /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
  158. /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
  159. /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
  160. /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
  161. /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
  162. /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
@@ -1,245 +1,665 @@
1
- require 'yard-activesupport-concern'
2
- require 'fileutils'
3
- require 'rbs'
4
-
5
- module Solargraph
6
- module PinCache
7
- class << self
8
- include Logging
9
-
10
- # The base directory where cached YARD documentation and serialized pins are serialized
11
- #
12
- # @return [String]
13
- def base_dir
14
- # The directory is not stored in a variable so it can be overridden
15
- # in specs.
16
- ENV['SOLARGRAPH_CACHE'] ||
17
- (ENV['XDG_CACHE_HOME'] ? File.join(ENV['XDG_CACHE_HOME'], 'solargraph') : nil) ||
18
- File.join(Dir.home, '.cache', 'solargraph')
19
- end
20
-
21
- # The working directory for the current Ruby, RBS, and Solargraph versions.
22
- #
23
- # @return [String]
24
- def work_dir
25
- # The directory is not stored in a variable so it can be overridden
26
- # in specs.
27
- File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
28
- end
29
-
30
- # @param gemspec [Gem::Specification]
31
- # @return [String]
32
- def yardoc_path gemspec
33
- File.join(base_dir,
34
- "yard-#{YARD::VERSION}",
35
- "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}",
36
- "#{gemspec.name}-#{gemspec.version}.yardoc")
37
- end
38
-
39
- # @return [String]
40
- def stdlib_path
41
- File.join(work_dir, 'stdlib')
42
- end
43
-
44
- # @param require [String]
45
- # @return [String]
46
- def stdlib_require_path require
47
- File.join(stdlib_path, "#{require}.ser")
48
- end
49
-
50
- # @param require [String]
51
- # @return [Array<Pin::Base>, nil]
52
- def deserialize_stdlib_require require
53
- load(stdlib_require_path(require))
54
- end
55
-
56
- # @param require [String]
57
- # @param pins [Array<Pin::Base>]
58
- # @return [void]
59
- def serialize_stdlib_require require, pins
60
- save(stdlib_require_path(require), pins)
61
- end
62
-
63
- # @return [String]
64
- def core_path
65
- File.join(work_dir, 'core.ser')
66
- end
67
-
68
- # @return [Array<Pin::Base>, nil]
69
- def deserialize_core
70
- load(core_path)
71
- end
72
-
73
- # @param pins [Array<Pin::Base>]
74
- # @return [void]
75
- def serialize_core pins
76
- save(core_path, pins)
77
- end
78
-
79
- # @param gemspec [Gem::Specification]
80
- # @return [String]
81
- def yard_gem_path gemspec
82
- File.join(work_dir, 'yard', "#{gemspec.name}-#{gemspec.version}.ser")
83
- end
84
-
85
- # @param gemspec [Gem::Specification]
86
- # @return [Array<Pin::Base>, nil]
87
- def deserialize_yard_gem(gemspec)
88
- load(yard_gem_path(gemspec))
89
- end
90
-
91
- # @param gemspec [Gem::Specification]
92
- # @param pins [Array<Pin::Base>]
93
- # @return [void]
94
- def serialize_yard_gem(gemspec, pins)
95
- save(yard_gem_path(gemspec), pins)
96
- end
97
-
98
- # @param gemspec [Gem::Specification]
99
- # @return [Boolean]
100
- def has_yard?(gemspec)
101
- exist?(yard_gem_path(gemspec))
102
- end
103
-
104
- # @param gemspec [Gem::Specification]
105
- # @param hash [String, nil]
106
- # @return [String]
107
- def rbs_collection_path(gemspec, hash)
108
- File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
109
- end
110
-
111
- # @param gemspec [Gem::Specification]
112
- # @return [String]
113
- def rbs_collection_path_prefix(gemspec)
114
- File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-")
115
- end
116
-
117
- # @param gemspec [Gem::Specification]
118
- # @param hash [String, nil]
119
- # @return [Array<Pin::Base>, nil]
120
- def deserialize_rbs_collection_gem(gemspec, hash)
121
- load(rbs_collection_path(gemspec, hash))
122
- end
123
-
124
- # @param gemspec [Gem::Specification]
125
- # @param hash [String, nil]
126
- # @param pins [Array<Pin::Base>]n
127
- # @return [void]
128
- def serialize_rbs_collection_gem(gemspec, hash, pins)
129
- save(rbs_collection_path(gemspec, hash), pins)
130
- end
131
-
132
- # @param gemspec [Gem::Specification]
133
- # @param hash [String, nil]
134
- # @return [String]
135
- def combined_path(gemspec, hash)
136
- File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
137
- end
138
-
139
- # @param gemspec [Gem::Specification]
140
- # @return [String]
141
- def combined_path_prefix(gemspec)
142
- File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-")
143
- end
144
-
145
- # @param gemspec [Gem::Specification]
146
- # @param hash [String, nil]
147
- # @param pins [Array<Pin::Base>]
148
- # @return [void]
149
- def serialize_combined_gem(gemspec, hash, pins)
150
- save(combined_path(gemspec, hash), pins)
151
- end
152
-
153
- # @param gemspec [Gem::Specification]
154
- # @param hash [String, nil]
155
- # @return [Array<Pin::Base>, nil]
156
- def deserialize_combined_gem gemspec, hash
157
- load(combined_path(gemspec, hash))
158
- end
159
-
160
- # @param gemspec [Gem::Specification]
161
- # @param hash [String, nil]
162
- # @return [Boolean]
163
- def has_rbs_collection?(gemspec, hash)
164
- exist?(rbs_collection_path(gemspec, hash))
165
- end
166
-
167
- # @return [void]
168
- def uncache_core
169
- uncache(core_path)
170
- end
171
-
172
- # @return [void]
173
- def uncache_stdlib
174
- uncache(stdlib_path)
175
- end
176
-
177
- # @param gemspec [Gem::Specification]
178
- # @param out [IO, nil]
179
- # @return [void]
180
- def uncache_gem(gemspec, out: nil)
181
- uncache(yardoc_path(gemspec), out: out)
182
- uncache_by_prefix(rbs_collection_path_prefix(gemspec), out: out)
183
- uncache(yard_gem_path(gemspec), out: out)
184
- uncache_by_prefix(combined_path_prefix(gemspec), out: out)
185
- end
186
-
187
- # @return [void]
188
- def clear
189
- FileUtils.rm_rf base_dir, secure: true
190
- end
191
-
192
- private
193
-
194
- # @param file [String]
195
- # @return [Array<Solargraph::Pin::Base>, nil]
196
- def load file
197
- return nil unless File.file?(file)
198
- Marshal.load(File.read(file, mode: 'rb'))
199
- rescue StandardError => e
200
- Solargraph.logger.warn "Failed to load cached file #{file}: [#{e.class}] #{e.message}"
201
- FileUtils.rm_f file
202
- nil
203
- end
204
-
205
- # @param path [String]
206
- def exist? *path
207
- File.file? File.join(*path)
208
- end
209
-
210
- # @param file [String]
211
- # @param pins [Array<Pin::Base>]
212
- # @return [void]
213
- def save file, pins
214
- base = File.dirname(file)
215
- FileUtils.mkdir_p base unless File.directory?(base)
216
- ser = Marshal.dump(pins)
217
- File.write file, ser, mode: 'wb'
218
- logger.debug { "Cache#save: Saved #{pins.length} pins to #{file}" }
219
- end
220
-
221
- # @param path_segments [Array<String>]
222
- # @return [void]
223
- def uncache *path_segments, out: nil
224
- path = File.join(*path_segments)
225
- if File.exist?(path)
226
- FileUtils.rm_rf path, secure: true
227
- out.puts "Clearing pin cache in #{path}" unless out.nil?
228
- end
229
- end
230
-
231
- # @return [void]
232
- # @param path_segments [Array<String>]
233
- def uncache_by_prefix *path_segments, out: nil
234
- path = File.join(*path_segments)
235
- glob = "#{path}*"
236
- out.puts "Clearing pin cache in #{glob}" unless out.nil?
237
- Dir.glob(glob).each do |file|
238
- next unless File.file?(file)
239
- FileUtils.rm_rf file, secure: true
240
- out.puts "Clearing pin cache in #{file}" unless out.nil?
241
- end
242
- end
243
- end
244
- end
245
- end
1
+ require 'fileutils'
2
+ require 'rbs'
3
+ require 'rubygems'
4
+
5
+ module Solargraph
6
+ class PinCache
7
+ include Logging
8
+
9
+ attr_reader :directory, :rbs_collection_path, :rbs_collection_config_path, :yard_plugins
10
+
11
+ # @param rbs_collection_path [String, nil]
12
+ # @param rbs_collection_config_path [String, nil]
13
+ # @param directory [String, nil]
14
+ # @param yard_plugins [Array<String>]
15
+ def initialize rbs_collection_path:, rbs_collection_config_path:,
16
+ directory:,
17
+ yard_plugins:
18
+ @rbs_collection_path = rbs_collection_path
19
+ @rbs_collection_config_path = rbs_collection_config_path
20
+ @directory = directory
21
+ @yard_plugins = yard_plugins
22
+ end
23
+
24
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
25
+ def cached? gemspec
26
+ rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec)
27
+ combined_gem?(gemspec, rbs_version_cache_key)
28
+ end
29
+
30
+ # @param gemspec [Gem::Specification]
31
+ # @param rebuild [Boolean] whether to rebuild the cache regardless of whether it already exists
32
+ # @param out [StringIO, IO, nil] output stream for logging
33
+ # @return [void]
34
+ def cache_gem gemspec:, rebuild: false, out: nil
35
+ rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec)
36
+
37
+ build_yard, build_rbs_collection, build_combined =
38
+ calculate_build_needs(gemspec,
39
+ rebuild: rebuild,
40
+ rbs_version_cache_key: rbs_version_cache_key)
41
+
42
+ return unless build_yard || build_rbs_collection || build_combined
43
+
44
+ build_combine_and_cache(gemspec,
45
+ rbs_version_cache_key,
46
+ build_yard: build_yard,
47
+ build_rbs_collection: build_rbs_collection,
48
+ build_combined: build_combined,
49
+ out: out)
50
+ end
51
+
52
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
53
+ # @param rbs_version_cache_key [String, nil]
54
+ def suppress_yard_cache? gemspec, rbs_version_cache_key
55
+ if gemspec.name == 'parser' && rbs_version_cache_key != RbsMap::CACHE_KEY_UNRESOLVED
56
+ # parser takes forever to build YARD pins, but has excellent RBS collection pins
57
+ return true
58
+ end
59
+ false
60
+ end
61
+
62
+ # @param out [StringIO, IO, nil] output stream for logging
63
+ # @param rebuild [Boolean] build pins regardless of whether we
64
+ # have cached them already
65
+ #
66
+ # @return [void]
67
+ def cache_all_stdlibs rebuild: false, out: $stderr
68
+ possible_stdlibs.each do |stdlib|
69
+ RbsMap::StdlibMap.new(stdlib, rebuild: rebuild, out: out)
70
+ end
71
+ end
72
+
73
+ # @param path [String] require path that might be in the RBS stdlib collection
74
+ # @return [void]
75
+ def cache_stdlib_rbs_map path
76
+ # these are held in memory in RbsMap::StdlibMap
77
+ map = RbsMap::StdlibMap.load(path)
78
+ if map.resolved?
79
+ logger.debug { "Loading stdlib pins for #{path}" }
80
+ pins = map.pins
81
+ logger.debug { "Loaded #{pins.length} stdlib pins for #{path}" }
82
+ pins
83
+ else
84
+ # @todo Temporarily ignoring unresolved `require 'set'`
85
+ logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
86
+ nil
87
+ end
88
+ end
89
+
90
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
91
+ #
92
+ # @return [String]
93
+ def lookup_rbs_version_cache_key gemspec
94
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
95
+ rbs_map.cache_key
96
+ end
97
+
98
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
99
+ # @param rbs_version_cache_key [String, nil]
100
+ # @param yard_pins [Array<Pin::Base>]
101
+ # @param rbs_collection_pins [Array<Pin::Base>]
102
+ # @return [void]
103
+ def cache_combined_pins gemspec, rbs_version_cache_key, yard_pins, rbs_collection_pins
104
+ combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
105
+ serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
106
+ end
107
+
108
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
109
+ # @return [Array<Pin::Base>, nil]
110
+ def deserialize_combined_pin_cache gemspec
111
+ rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec)
112
+
113
+ load_combined_gem(gemspec, rbs_version_cache_key)
114
+ end
115
+
116
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
117
+ # @param out [StringIO, IO, nil]
118
+ # @return [void]
119
+ def uncache_gem gemspec, out: nil
120
+ PinCache.uncache(yardoc_path(gemspec), out: out)
121
+ PinCache.uncache(yard_gem_path(gemspec), out: out)
122
+ uncache_by_prefix(rbs_collection_pins_path_prefix(gemspec), out: out)
123
+ uncache_by_prefix(combined_path_prefix(gemspec), out: out)
124
+ rbs_version_cache_key = lookup_rbs_version_cache_key(gemspec)
125
+ combined_pins_in_memory.delete([gemspec.name, gemspec.version, rbs_version_cache_key])
126
+ end
127
+
128
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
129
+ def yardoc_processing? gemspec
130
+ Yardoc.processing?(yardoc_path(gemspec))
131
+ end
132
+
133
+ # @return [Array<String>] a list of possible standard library names
134
+ def possible_stdlibs
135
+ # all dirs and .rb files in Gem::RUBYGEMS_DIR
136
+ Dir.glob(File.join(Gem::RUBYGEMS_DIR, '*')).map do |file_or_dir|
137
+ basename = File.basename(file_or_dir)
138
+ # remove .rb
139
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
140
+ basename = basename[0..-4] if basename.end_with?('.rb')
141
+ basename
142
+ end.sort.uniq
143
+ rescue StandardError => e
144
+ logger.info { "Failed to get possible stdlibs: #{e.message}" }
145
+ # @sg-ignore Need to add nil check here
146
+ logger.debug { e.backtrace.join("\n") }
147
+ []
148
+ end
149
+
150
+ private
151
+
152
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
153
+ # @param rebuild [Boolean] whether to rebuild the cache regardless of whether it already exists
154
+ # @param rbs_version_cache_key [String, nil] the cache key for the gem in the RBS collection
155
+ #
156
+ # @return [Array(Boolean, Boolean, Boolean)] whether to build YARD
157
+ # pins, RBS collection pins, and combined pins
158
+ def calculate_build_needs gemspec, rebuild:, rbs_version_cache_key:
159
+ if rebuild
160
+ build_yard = true
161
+ build_rbs_collection = true
162
+ build_combined = true
163
+ else
164
+ build_yard = !yard_gem?(gemspec)
165
+ build_rbs_collection = !rbs_collection_pins?(gemspec, rbs_version_cache_key)
166
+ # @sg-ignore Need to add nil check here
167
+ build_combined = !combined_gem?(gemspec, rbs_version_cache_key) || build_yard || build_rbs_collection
168
+ end
169
+
170
+ build_yard = false if suppress_yard_cache?(gemspec, rbs_version_cache_key)
171
+
172
+ [build_yard, build_rbs_collection, build_combined]
173
+ end
174
+
175
+ # @param gemspec [Gem::Specification]
176
+ # @param rbs_version_cache_key [String, nil]
177
+ # @param build_yard [Boolean]
178
+ # @param build_rbs_collection [Boolean]
179
+ # @param build_combined [Boolean]
180
+ # @param out [StringIO, IO, nil]
181
+ #
182
+ # @return [void]
183
+ def build_combine_and_cache gemspec,
184
+ rbs_version_cache_key,
185
+ build_yard:,
186
+ build_rbs_collection:,
187
+ build_combined:,
188
+ out:
189
+ log_cache_info(gemspec, rbs_version_cache_key,
190
+ build_yard: build_yard,
191
+ build_rbs_collection: build_rbs_collection,
192
+ build_combined: build_combined,
193
+ out: out)
194
+ cache_yard_pins(gemspec, out) if build_yard
195
+ # this can be nil even if we aren't told to build it - see suppress_yard_cache?
196
+ yard_pins = deserialize_yard_pin_cache(gemspec) || []
197
+ cache_rbs_collection_pins(gemspec, out) if build_rbs_collection
198
+ rbs_collection_pins = deserialize_rbs_collection_cache(gemspec, rbs_version_cache_key) || []
199
+ cache_combined_pins(gemspec, rbs_version_cache_key, yard_pins, rbs_collection_pins) if build_combined
200
+ end
201
+
202
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
203
+ # @param rbs_version_cache_key [String, nil]
204
+ # @param build_yard [Boolean]
205
+ # @param build_rbs_collection [Boolean]
206
+ # @param build_combined [Boolean]
207
+ # @param out [StringIO, IO, nil]
208
+ #
209
+ # @return [void]
210
+ def log_cache_info gemspec,
211
+ rbs_version_cache_key,
212
+ build_yard:,
213
+ build_rbs_collection:,
214
+ build_combined:,
215
+ out:
216
+ type = []
217
+ type << 'YARD' if build_yard
218
+ rbs_source_desc = RbsMap.rbs_source_desc(rbs_version_cache_key)
219
+ type << rbs_source_desc if build_rbs_collection && !rbs_source_desc.nil?
220
+ # we'll build it anyway, but it won't take long to build with
221
+ # only a single source
222
+
223
+ # 'combining' is awkward terminology in this case
224
+ just_yard = build_yard && rbs_source_desc.nil?
225
+
226
+ type << 'combined' if build_combined && !just_yard
227
+ out&.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}")
228
+ end
229
+
230
+ # @param gemspec [Gem::Specification]
231
+ # @param out [StringIO, IO, nil]
232
+ # @return [Array<Pin::Base>]
233
+ def cache_yard_pins gemspec, out
234
+ gem_yardoc_path = yardoc_path(gemspec)
235
+ Yardoc.build_docs(gem_yardoc_path, yard_plugins, gemspec) unless Yardoc.docs_built?(gem_yardoc_path)
236
+ pins = Yardoc.build_pins(gem_yardoc_path, gemspec, out: out)
237
+ serialize_yard_gem(gemspec, pins)
238
+ logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
239
+ pins
240
+ end
241
+
242
+ # @return [Hash{Array(String, String, String) => Array<Pin::Base>}]
243
+ def combined_pins_in_memory
244
+ PinCache.all_combined_pins_in_memory[yard_plugins] ||= {}
245
+ end
246
+
247
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
248
+ # @param _out [StringIO, IO, nil]
249
+ # @return [Array<Pin::Base>]
250
+ def cache_rbs_collection_pins gemspec, _out
251
+ rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
252
+ pins = rbs_map.pins
253
+ rbs_version_cache_key = rbs_map.cache_key
254
+ # cache pins even if result is zero, so we don't retry building pins
255
+ pins ||= []
256
+ serialize_rbs_collection_pins(gemspec, rbs_version_cache_key, pins)
257
+ logger.info do
258
+ unless pins.empty?
259
+ "Cached #{pins.length} RBS collection pins for gem #{gemspec.name} #{gemspec.version} with " \
260
+ "cache_key #{rbs_version_cache_key.inspect}"
261
+ end
262
+ end
263
+ pins
264
+ end
265
+
266
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
267
+ # @return [Array<Pin::Base>, nil]
268
+ def deserialize_yard_pin_cache gemspec
269
+ cached = load_yard_gem(gemspec)
270
+ if cached
271
+ cached
272
+ else
273
+ logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
274
+ nil
275
+ end
276
+ end
277
+
278
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
279
+ # @param rbs_version_cache_key [String, nil]
280
+ # @return [Array<Pin::Base>, nil]
281
+ def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
282
+ cached = load_rbs_collection_pins(gemspec, rbs_version_cache_key)
283
+ Solargraph.assert_or_log(:pin_cache_rbs_collection, 'Asked for non-existent rbs collection') if cached.nil?
284
+ logger.info do
285
+ "Loaded #{cached&.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}"
286
+ end
287
+ cached
288
+ end
289
+
290
+ # @return [Array<String>]
291
+ def yard_path_components
292
+ ["yard-#{YARD::VERSION}",
293
+ yard_plugins.sort.uniq.join('-')]
294
+ end
295
+
296
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
297
+ # @return [String]
298
+ def yardoc_path gemspec
299
+ File.join(PinCache.base_dir,
300
+ *yard_path_components,
301
+ "#{gemspec.name}-#{gemspec.version}.yardoc")
302
+ end
303
+
304
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
305
+ # @return [String]
306
+ def yard_gem_path gemspec
307
+ File.join(PinCache.work_dir, *yard_path_components, "#{gemspec.name}-#{gemspec.version}.ser")
308
+ end
309
+
310
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
311
+ # @return [Array<Pin::Base>, nil]
312
+ def load_yard_gem gemspec
313
+ PinCache.load(yard_gem_path(gemspec))
314
+ end
315
+
316
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
317
+ # @param pins [Array<Pin::Base>]
318
+ # @return [void]
319
+ def serialize_yard_gem gemspec, pins
320
+ PinCache.save(yard_gem_path(gemspec), pins)
321
+ end
322
+
323
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
324
+ # @return [Boolean]
325
+ def yard_gem? gemspec
326
+ exist?(yard_gem_path(gemspec))
327
+ end
328
+
329
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
330
+ # @param hash [String, nil]
331
+ # @return [String]
332
+ def rbs_collection_pins_path gemspec, hash
333
+ rbs_collection_pins_path_prefix(gemspec) + "#{hash || 0}.ser"
334
+ end
335
+
336
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
337
+ # @return [String]
338
+ def rbs_collection_pins_path_prefix gemspec
339
+ File.join(PinCache.work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-")
340
+ end
341
+
342
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
343
+ # @param hash [String, nil]
344
+ #
345
+ # @return [Array<Pin::Base>, nil]
346
+ def load_rbs_collection_pins gemspec, hash
347
+ PinCache.load(rbs_collection_pins_path(gemspec, hash))
348
+ end
349
+
350
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
351
+ # @param hash [String, nil]
352
+ # @param pins [Array<Pin::Base>]
353
+ # @return [void]
354
+ def serialize_rbs_collection_pins gemspec, hash, pins
355
+ PinCache.save(rbs_collection_pins_path(gemspec, hash), pins)
356
+ end
357
+
358
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
359
+ # @param hash [String, nil]
360
+ # @return [String]
361
+ def combined_path gemspec, hash
362
+ File.join(combined_path_prefix(gemspec) + "-#{hash || 0}.ser")
363
+ end
364
+
365
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
366
+ # @return [String]
367
+ def combined_path_prefix gemspec
368
+ File.join(PinCache.work_dir, 'combined', yard_plugins.sort.join('-'), "#{gemspec.name}-#{gemspec.version}")
369
+ end
370
+
371
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
372
+ # @param hash [String, nil]
373
+ # @param pins [Array<Pin::Base>]
374
+ # @return [void]
375
+ def serialize_combined_gem gemspec, hash, pins
376
+ PinCache.save(combined_path(gemspec, hash), pins)
377
+ end
378
+
379
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
380
+ # @param hash [String]
381
+ def combined_gem? gemspec, hash
382
+ exist?(combined_path(gemspec, hash))
383
+ end
384
+
385
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
386
+ # @param hash [String, nil]
387
+ # @return [Array<Pin::Base>, nil]
388
+ def load_combined_gem gemspec, hash
389
+ cached = combined_pins_in_memory[[gemspec.name, gemspec.version, hash]]
390
+ return cached if cached
391
+ loaded = PinCache.load(combined_path(gemspec, hash))
392
+ combined_pins_in_memory[[gemspec.name, gemspec.version, hash]] = loaded if loaded
393
+ loaded
394
+ end
395
+
396
+ # @param gemspec [Gem::Specification, Bundler::LazySpecification]
397
+ # @param hash [String, nil]
398
+ def rbs_collection_pins? gemspec, hash
399
+ exist?(rbs_collection_pins_path(gemspec, hash))
400
+ end
401
+
402
+ include Logging
403
+
404
+ # @param path [String]
405
+ def exist? *path
406
+ File.file? File.join(*path)
407
+ end
408
+
409
+ # @return [void]
410
+ # @param path_segments [Array<String>]
411
+ def uncache_by_prefix *path_segments, out: nil
412
+ path = File.join(*path_segments)
413
+ glob = "#{path}*"
414
+ out&.puts "Clearing pin cache in #{glob}"
415
+ Dir.glob(glob).each do |file|
416
+ next unless File.file?(file)
417
+ FileUtils.rm_rf file, secure: true
418
+ out&.puts "Clearing pin cache in #{file}"
419
+ end
420
+ end
421
+
422
+ class << self
423
+ include Logging
424
+
425
+ # @return [Hash{Array<String> => Hash{Array(String, String) =>
426
+ # Array<Pin::Base>}}] yard plugins, then gemspec name and
427
+ # version
428
+ def all_combined_pins_in_memory
429
+ @all_combined_pins_in_memory ||= {}
430
+ end
431
+
432
+ # The base directory where cached YARD documentation and serialized pins are serialized
433
+ #
434
+ # @return [String]
435
+ def base_dir
436
+ # The directory is not stored in a variable so it can be overridden
437
+ # in specs.
438
+ ENV['SOLARGRAPH_CACHE'] ||
439
+ (ENV['XDG_CACHE_HOME'] ? File.join(ENV['XDG_CACHE_HOME'], 'solargraph') : nil) ||
440
+ File.join(Dir.home, '.cache', 'solargraph')
441
+ end
442
+
443
+ # @param path_segments [Array<String>]
444
+ # @param out [IO, nil]
445
+ # @return [void]
446
+ def uncache *path_segments, out: nil
447
+ path = File.join(*path_segments)
448
+ if File.exist?(path)
449
+ FileUtils.rm_rf path, secure: true
450
+ out.puts "Clearing pin cache in #{path}" unless out.nil?
451
+ else
452
+ out&.puts "Pin cache file #{path} does not exist"
453
+ end
454
+ end
455
+
456
+ # @return [void]
457
+ # @param out [IO, nil]
458
+ # @param path_segments [Array<String>]
459
+ def uncache_by_prefix *path_segments, out: nil
460
+ path = File.join(*path_segments)
461
+ glob = "#{path}*"
462
+ out.puts "Clearing pin cache in #{glob}" unless out.nil?
463
+ Dir.glob(glob).each do |file|
464
+ next unless File.file?(file)
465
+ FileUtils.rm_rf file, secure: true
466
+ out.puts "Clearing pin cache in #{file}" unless out.nil?
467
+ end
468
+ end
469
+
470
+ # @param out [StringIO, IO, nil]
471
+ # @return [void]
472
+ def uncache_core out: nil
473
+ uncache(core_path, out: out)
474
+ # ApiMap keep this in memory
475
+ ApiMap.reset_core(out: out)
476
+ end
477
+
478
+ # @param out [StringIO, IO, nil]
479
+ # @return [void]
480
+ def uncache_stdlib out: nil
481
+ uncache(stdlib_path, out: out)
482
+ end
483
+
484
+ # The working directory for the current Ruby, RBS, and Solargraph versions.
485
+ #
486
+ # @return [String]
487
+ def work_dir
488
+ # The directory is not stored in a variable so it can be overridden
489
+ # in specs.
490
+ File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
491
+ end
492
+
493
+ # @return [String]
494
+ def stdlib_path
495
+ File.join(work_dir, 'stdlib')
496
+ end
497
+
498
+ # @param require [String]
499
+ # @return [String]
500
+ def stdlib_require_path require
501
+ File.join(stdlib_path, "#{require}.ser")
502
+ end
503
+
504
+ # @param require [String]
505
+ # @return [Array<Pin::Base>, nil]
506
+ def deserialize_stdlib_require require
507
+ load(stdlib_require_path(require))
508
+ end
509
+
510
+ # @param require [String]
511
+ # @param pins [Array<Pin::Base>]
512
+ # @return [void]
513
+ def serialize_stdlib_require require, pins
514
+ save(stdlib_require_path(require), pins)
515
+ end
516
+
517
+ # @return [String]
518
+ def core_path
519
+ File.join(work_dir, 'core.ser')
520
+ end
521
+
522
+ # @return [Array<Pin::Base>, nil]
523
+ def deserialize_core
524
+ load(core_path)
525
+ end
526
+
527
+ # @param pins [Array<Pin::Base>]
528
+ # @return [void]
529
+ def serialize_core pins
530
+ save(core_path, pins)
531
+ end
532
+
533
+ # @param gemspec [Gem::Specification]
534
+ # @return [String]
535
+ def yard_gem_path gemspec
536
+ File.join(work_dir, 'yard', "#{gemspec.name}-#{gemspec.version}.ser")
537
+ end
538
+
539
+ # @param gemspec [Gem::Specification]
540
+ # @return [Array<Pin::Base>, nil]
541
+ def deserialize_yard_gem(gemspec)
542
+ load(yard_gem_path(gemspec))
543
+ end
544
+
545
+ # @param gemspec [Gem::Specification]
546
+ # @param pins [Array<Pin::Base>]
547
+ # @return [void]
548
+ def serialize_yard_gem(gemspec, pins)
549
+ save(yard_gem_path(gemspec), pins)
550
+ end
551
+
552
+ # @param gemspec [Gem::Specification]
553
+ # @return [Boolean]
554
+ def has_yard?(gemspec)
555
+ exist?(yard_gem_path(gemspec))
556
+ end
557
+
558
+ # @param gemspec [Gem::Specification]
559
+ # @param hash [String, nil]
560
+ # @return [String]
561
+ def rbs_collection_path(gemspec, hash)
562
+ File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
563
+ end
564
+
565
+ # @param gemspec [Gem::Specification]
566
+ # @return [String]
567
+ def rbs_collection_path_prefix(gemspec)
568
+ File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-")
569
+ end
570
+
571
+ # @param gemspec [Gem::Specification]
572
+ # @param hash [String, nil]
573
+ # @return [Array<Pin::Base>, nil]
574
+ def deserialize_rbs_collection_gem(gemspec, hash)
575
+ load(rbs_collection_path(gemspec, hash))
576
+ end
577
+
578
+ # @param gemspec [Gem::Specification]
579
+ # @param hash [String, nil]
580
+ # @param pins [Array<Pin::Base>]n
581
+ # @return [void]
582
+ def serialize_rbs_collection_gem(gemspec, hash, pins)
583
+ save(rbs_collection_path(gemspec, hash), pins)
584
+ end
585
+
586
+ # @param gemspec [Gem::Specification]
587
+ # @param hash [String, nil]
588
+ # @return [String]
589
+ def combined_path(gemspec, hash)
590
+ File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
591
+ end
592
+
593
+ # @param gemspec [Gem::Specification]
594
+ # @return [String]
595
+ def combined_path_prefix(gemspec)
596
+ File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-")
597
+ end
598
+
599
+ # @param gemspec [Gem::Specification]
600
+ # @param hash [String, nil]
601
+ # @param pins [Array<Pin::Base>]
602
+ # @return [void]
603
+ def serialize_combined_gem(gemspec, hash, pins)
604
+ save(combined_path(gemspec, hash), pins)
605
+ end
606
+
607
+ # @param gemspec [Gem::Specification]
608
+ # @param hash [String, nil]
609
+ # @return [Array<Pin::Base>, nil]
610
+ def deserialize_combined_gem gemspec, hash
611
+ load(combined_path(gemspec, hash))
612
+ end
613
+
614
+ # @param gemspec [Gem::Specification]
615
+ # @param hash [String, nil]
616
+ # @return [Boolean]
617
+ def has_rbs_collection?(gemspec, hash)
618
+ exist?(rbs_collection_path(gemspec, hash))
619
+ end
620
+
621
+ # @return [void]
622
+ def clear
623
+ FileUtils.rm_rf base_dir, secure: true
624
+ end
625
+
626
+ # @param file [String]
627
+ # @sg-ignore Marshal.load evaluates to boolean here which is wrong
628
+ # @return [Array<Solargraph::Pin::Base>, nil]
629
+ def load file
630
+ return nil unless File.file?(file)
631
+ Marshal.load(File.read(file, mode: 'rb'))
632
+ rescue StandardError => e
633
+ Solargraph.logger.warn "Failed to load cached file #{file}: [#{e.class}] #{e.message}"
634
+ FileUtils.rm_f file
635
+ nil
636
+ end
637
+
638
+ # @param file [String]
639
+ # @param pins [Array<Pin::Base>]
640
+ # @return [void]
641
+ def save file, pins
642
+ base = File.dirname(file)
643
+ FileUtils.mkdir_p base unless File.directory?(base)
644
+ ser = Marshal.dump(pins)
645
+ File.write file, ser, mode: 'wb'
646
+ logger.debug { "Cache#save: Saved #{pins.length} pins to #{file}" }
647
+ end
648
+
649
+ def core?
650
+ File.file?(core_path)
651
+ end
652
+
653
+ # @param out [StringIO, IO, nil]
654
+ # @return [Array<Pin::Base>]
655
+ def cache_core out: $stderr
656
+ RbsMap::CoreMap.new.cache_core(out: out)
657
+ end
658
+
659
+ # @param path [String]
660
+ def exist? *path
661
+ File.file? File.join(*path)
662
+ end
663
+ end
664
+ end
665
+ end