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