solargraph 0.58.2 → 0.59.0.dev.2

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 (203) 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 +41 -34
  5. data/.github/workflows/rspec.yml +44 -23
  6. data/.github/workflows/typecheck.yml +2 -2
  7. data/.rubocop.yml +32 -5
  8. data/.rubocop_todo.yml +50 -966
  9. data/Gemfile +3 -1
  10. data/README.md +3 -3
  11. data/Rakefile +26 -23
  12. data/bin/solargraph +2 -1
  13. data/lib/solargraph/api_map/cache.rb +3 -3
  14. data/lib/solargraph/api_map/constants.rb +13 -3
  15. data/lib/solargraph/api_map/index.rb +23 -18
  16. data/lib/solargraph/api_map/source_to_yard.rb +22 -9
  17. data/lib/solargraph/api_map/store.rb +33 -28
  18. data/lib/solargraph/api_map.rb +150 -82
  19. data/lib/solargraph/bench.rb +44 -45
  20. data/lib/solargraph/complex_type/conformance.rb +176 -0
  21. data/lib/solargraph/complex_type/type_methods.rb +28 -17
  22. data/lib/solargraph/complex_type/unique_type.rb +218 -57
  23. data/lib/solargraph/complex_type.rb +170 -57
  24. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +61 -61
  25. data/lib/solargraph/convention/data_definition/data_definition_node.rb +7 -5
  26. data/lib/solargraph/convention/data_definition.rb +5 -2
  27. data/lib/solargraph/convention/gemfile.rb +15 -15
  28. data/lib/solargraph/convention/gemspec.rb +23 -23
  29. data/lib/solargraph/convention/rakefile.rb +17 -17
  30. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +2 -1
  31. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +4 -3
  32. data/lib/solargraph/convention/struct_definition.rb +8 -4
  33. data/lib/solargraph/convention.rb +78 -78
  34. data/lib/solargraph/converters/dd.rb +19 -17
  35. data/lib/solargraph/converters/dl.rb +17 -15
  36. data/lib/solargraph/converters/dt.rb +17 -15
  37. data/lib/solargraph/converters/misc.rb +3 -1
  38. data/lib/solargraph/diagnostics/require_not_found.rb +1 -0
  39. data/lib/solargraph/diagnostics/rubocop.rb +11 -10
  40. data/lib/solargraph/diagnostics/rubocop_helpers.rb +5 -3
  41. data/lib/solargraph/diagnostics/type_check.rb +11 -10
  42. data/lib/solargraph/diagnostics/update_errors.rb +37 -41
  43. data/lib/solargraph/doc_map.rb +133 -373
  44. data/lib/solargraph/equality.rb +4 -4
  45. data/lib/solargraph/gem_pins.rb +21 -20
  46. data/lib/solargraph/language_server/error_codes.rb +20 -20
  47. data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
  48. data/lib/solargraph/language_server/host/dispatch.rb +3 -3
  49. data/lib/solargraph/language_server/host/message_worker.rb +4 -3
  50. data/lib/solargraph/language_server/host/sources.rb +2 -1
  51. data/lib/solargraph/language_server/host.rb +30 -22
  52. data/lib/solargraph/language_server/message/base.rb +97 -97
  53. data/lib/solargraph/language_server/message/client/register_capability.rb +13 -15
  54. data/lib/solargraph/language_server/message/completion_item/resolve.rb +58 -60
  55. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +12 -18
  56. data/lib/solargraph/language_server/message/extended/document.rb +1 -0
  57. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -32
  58. data/lib/solargraph/language_server/message/extended/download_core.rb +20 -19
  59. data/lib/solargraph/language_server/message/extended/search.rb +20 -20
  60. data/lib/solargraph/language_server/message/initialize.rb +197 -191
  61. data/lib/solargraph/language_server/message/text_document/completion.rb +10 -8
  62. data/lib/solargraph/language_server/message/text_document/definition.rb +41 -32
  63. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +23 -16
  64. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +29 -19
  65. data/lib/solargraph/language_server/message/text_document/formatting.rb +8 -6
  66. data/lib/solargraph/language_server/message/text_document/hover.rb +5 -5
  67. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +18 -11
  68. data/lib/solargraph/language_server/message/text_document/references.rb +23 -16
  69. data/lib/solargraph/language_server/message/text_document/rename.rb +26 -19
  70. data/lib/solargraph/language_server/message/text_document/signature_help.rb +3 -2
  71. data/lib/solargraph/language_server/message/text_document/type_definition.rb +25 -17
  72. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +41 -35
  73. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +48 -40
  74. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +32 -26
  75. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +27 -17
  76. data/lib/solargraph/language_server/message.rb +94 -94
  77. data/lib/solargraph/language_server/request.rb +29 -27
  78. data/lib/solargraph/language_server/transport/data_reader.rb +72 -74
  79. data/lib/solargraph/language_server/uri_helpers.rb +49 -49
  80. data/lib/solargraph/library.rb +85 -44
  81. data/lib/solargraph/location.rb +17 -14
  82. data/lib/solargraph/logging.rb +24 -4
  83. data/lib/solargraph/page.rb +92 -92
  84. data/lib/solargraph/parser/comment_ripper.rb +19 -4
  85. data/lib/solargraph/parser/flow_sensitive_typing.rb +326 -108
  86. data/lib/solargraph/parser/node_processor/base.rb +34 -4
  87. data/lib/solargraph/parser/node_processor.rb +8 -7
  88. data/lib/solargraph/parser/parser_gem/class_methods.rb +32 -14
  89. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +19 -19
  90. data/lib/solargraph/parser/parser_gem/node_chainer.rb +50 -25
  91. data/lib/solargraph/parser/parser_gem/node_methods.rb +91 -70
  92. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +4 -4
  93. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +13 -11
  94. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +9 -0
  95. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +12 -12
  96. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +10 -3
  97. data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +38 -37
  98. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +36 -6
  99. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +5 -3
  100. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  101. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +3 -1
  102. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +3 -3
  103. data/lib/solargraph/parser/parser_gem/node_processors/or_node.rb +22 -0
  104. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -1
  105. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +2 -1
  106. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -5
  107. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +124 -113
  108. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -29
  109. data/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +23 -0
  110. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +6 -2
  111. data/lib/solargraph/parser/parser_gem/node_processors.rb +4 -0
  112. data/lib/solargraph/parser/parser_gem.rb +14 -12
  113. data/lib/solargraph/parser/region.rb +9 -3
  114. data/lib/solargraph/parser/snippet.rb +3 -1
  115. data/lib/solargraph/parser.rb +25 -23
  116. data/lib/solargraph/pin/base.rb +126 -80
  117. data/lib/solargraph/pin/base_variable.rb +273 -24
  118. data/lib/solargraph/pin/block.rb +29 -6
  119. data/lib/solargraph/pin/breakable.rb +7 -1
  120. data/lib/solargraph/pin/callable.rb +65 -21
  121. data/lib/solargraph/pin/closure.rb +7 -10
  122. data/lib/solargraph/pin/common.rb +24 -6
  123. data/lib/solargraph/pin/compound_statement.rb +55 -0
  124. data/lib/solargraph/pin/constant.rb +43 -45
  125. data/lib/solargraph/pin/conversions.rb +10 -4
  126. data/lib/solargraph/pin/delegated_method.rb +19 -8
  127. data/lib/solargraph/pin/documenting.rb +4 -2
  128. data/lib/solargraph/pin/instance_variable.rb +5 -1
  129. data/lib/solargraph/pin/keyword.rb +0 -4
  130. data/lib/solargraph/pin/local_variable.rb +15 -59
  131. data/lib/solargraph/pin/method.rb +153 -104
  132. data/lib/solargraph/pin/method_alias.rb +8 -0
  133. data/lib/solargraph/pin/namespace.rb +19 -12
  134. data/lib/solargraph/pin/parameter.rb +100 -36
  135. data/lib/solargraph/pin/proxy_type.rb +4 -1
  136. data/lib/solargraph/pin/reference/override.rb +1 -1
  137. data/lib/solargraph/pin/reference/superclass.rb +2 -0
  138. data/lib/solargraph/pin/reference.rb +19 -0
  139. data/lib/solargraph/pin/search.rb +3 -2
  140. data/lib/solargraph/pin/signature.rb +15 -12
  141. data/lib/solargraph/pin/symbol.rb +2 -1
  142. data/lib/solargraph/pin/until.rb +2 -4
  143. data/lib/solargraph/pin/while.rb +2 -4
  144. data/lib/solargraph/pin.rb +2 -0
  145. data/lib/solargraph/pin_cache.rb +490 -73
  146. data/lib/solargraph/position.rb +14 -10
  147. data/lib/solargraph/range.rb +16 -15
  148. data/lib/solargraph/rbs_map/conversions.rb +343 -214
  149. data/lib/solargraph/rbs_map/core_fills.rb +91 -84
  150. data/lib/solargraph/rbs_map/core_map.rb +24 -17
  151. data/lib/solargraph/rbs_map/stdlib_map.rb +33 -5
  152. data/lib/solargraph/rbs_map.rb +77 -32
  153. data/lib/solargraph/server_methods.rb +16 -16
  154. data/lib/solargraph/shell.rb +128 -73
  155. data/lib/solargraph/source/chain/array.rb +39 -37
  156. data/lib/solargraph/source/chain/call.rb +96 -56
  157. data/lib/solargraph/source/chain/class_variable.rb +13 -13
  158. data/lib/solargraph/source/chain/constant.rb +5 -1
  159. data/lib/solargraph/source/chain/global_variable.rb +13 -13
  160. data/lib/solargraph/source/chain/hash.rb +8 -5
  161. data/lib/solargraph/source/chain/if.rb +12 -10
  162. data/lib/solargraph/source/chain/instance_variable.rb +24 -1
  163. data/lib/solargraph/source/chain/link.rb +99 -109
  164. data/lib/solargraph/source/chain/literal.rb +9 -6
  165. data/lib/solargraph/source/chain/or.rb +10 -4
  166. data/lib/solargraph/source/chain/q_call.rb +13 -11
  167. data/lib/solargraph/source/chain/variable.rb +15 -13
  168. data/lib/solargraph/source/chain/z_super.rb +28 -30
  169. data/lib/solargraph/source/chain.rb +49 -38
  170. data/lib/solargraph/source/change.rb +12 -5
  171. data/lib/solargraph/source/cursor.rb +23 -17
  172. data/lib/solargraph/source/encoding_fixes.rb +6 -7
  173. data/lib/solargraph/source/source_chainer.rb +56 -32
  174. data/lib/solargraph/source/updater.rb +5 -1
  175. data/lib/solargraph/source.rb +59 -35
  176. data/lib/solargraph/source_map/clip.rb +48 -29
  177. data/lib/solargraph/source_map/data.rb +4 -1
  178. data/lib/solargraph/source_map/mapper.rb +71 -42
  179. data/lib/solargraph/source_map.rb +21 -9
  180. data/lib/solargraph/type_checker/problem.rb +3 -1
  181. data/lib/solargraph/type_checker/rules.rb +81 -8
  182. data/lib/solargraph/type_checker.rb +195 -120
  183. data/lib/solargraph/version.rb +1 -1
  184. data/lib/solargraph/workspace/config.rb +13 -10
  185. data/lib/solargraph/workspace/gemspecs.rb +367 -0
  186. data/lib/solargraph/workspace/require_paths.rb +1 -0
  187. data/lib/solargraph/workspace.rb +149 -30
  188. data/lib/solargraph/yard_map/helpers.rb +8 -3
  189. data/lib/solargraph/yard_map/mapper/to_method.rb +13 -7
  190. data/lib/solargraph/yard_map/mapper/to_namespace.rb +2 -1
  191. data/lib/solargraph/yard_map/mapper.rb +13 -8
  192. data/lib/solargraph/yard_tags.rb +20 -20
  193. data/lib/solargraph/yardoc.rb +33 -23
  194. data/lib/solargraph.rb +29 -8
  195. data/rbs/fills/rubygems/0/dependency.rbs +193 -0
  196. data/rbs/fills/tuple/tuple.rbs +28 -0
  197. data/rbs/shims/ast/0/node.rbs +1 -1
  198. data/rbs/shims/diff-lcs/1.5/diff-lcs.rbs +11 -0
  199. data/solargraph.gemspec +36 -34
  200. metadata +38 -33
  201. data/lib/solargraph/type_checker/checks.rb +0 -124
  202. data/lib/solargraph/type_checker/param_def.rb +0 -37
  203. data/lib/solargraph/yard_map/to_method.rb +0 -89
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.58.2'
4
+ VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.59.0.dev.2')
5
5
  end
@@ -14,7 +14,7 @@ module Solargraph
14
14
  # @return [String]
15
15
  attr_reader :directory
16
16
 
17
- # @todo To make JSON strongly typed we'll need a record syntax
17
+ # @todo Need to validate config
18
18
  # @return [Hash{String => undefined, nil}]
19
19
  attr_reader :raw_data
20
20
 
@@ -54,7 +54,9 @@ module Solargraph
54
54
  #
55
55
  # @return [Array<String>]
56
56
  def calculated
57
- Solargraph.logger.info "Indexing workspace files in #{directory}" unless @calculated || directory.empty? || directory == '*'
57
+ unless @calculated || directory.empty? || directory == '*'
58
+ Solargraph.logger.info "Indexing workspace files in #{directory}"
59
+ end
58
60
  @calculated ||= included - excluded
59
61
  end
60
62
 
@@ -78,7 +80,9 @@ module Solargraph
78
80
 
79
81
  # An array of load paths for required paths.
80
82
  #
83
+ # @sg-ignore Need to validate config
81
84
  # @return [Array<String>]
85
+ # @sg-ignore Need to validate config
82
86
  def require_paths
83
87
  raw_data['require_paths'] || []
84
88
  end
@@ -144,7 +148,7 @@ module Solargraph
144
148
  global_config = read_config(global_config_path)
145
149
 
146
150
  defaults = default_config
147
- defaults.merge({'exclude' => []}) unless workspace_config.nil?
151
+ defaults.merge({ 'exclude' => [] }) unless workspace_config.nil?
148
152
 
149
153
  defaults
150
154
  .merge(global_config || {})
@@ -158,7 +162,7 @@ module Solargraph
158
162
  def read_config config_path = ''
159
163
  return nil if config_path.empty?
160
164
  return nil unless File.file?(config_path)
161
- YAML.safe_load(File.read(config_path))
165
+ YAML.safe_load_file(config_path)
162
166
  end
163
167
 
164
168
  # @return [Hash{String => Array, Hash, Integer}]
@@ -174,11 +178,11 @@ module Solargraph
174
178
  'cops' => 'safe',
175
179
  'except' => [],
176
180
  'only' => [],
177
- 'extra_args' =>[]
181
+ 'extra_args' => []
178
182
  }
179
183
  },
180
184
  'type_checker' => {
181
- 'rules' => { }
185
+ 'rules' => {}
182
186
  },
183
187
  'require_paths' => [],
184
188
  'plugins' => [],
@@ -191,12 +195,11 @@ module Solargraph
191
195
  # @param globs [Array<String>]
192
196
  # @return [Array<String>]
193
197
  def process_globs globs
194
- result = globs.flat_map do |glob|
198
+ globs.flat_map do |glob|
195
199
  Dir[File.absolute_path(glob, directory)]
196
- .map{ |f| f.gsub(/\\/, '/') }
200
+ .map { |f| f.gsub('\\', '/') }
197
201
  .select { |f| File.file?(f) }
198
202
  end
199
- result
200
203
  end
201
204
 
202
205
  # Modify the included files based on excluded directories and get an
@@ -239,7 +242,7 @@ module Solargraph
239
242
  # @param glob [String]
240
243
  # @return [String]
241
244
  def glob_to_directory glob
242
- glob.gsub(/(\/\*|\/\*\*\/\*\*?)$/, '')
245
+ glob.gsub(%r{(/\*|/\*\*/\*\*?)$}, '')
243
246
  end
244
247
 
245
248
  # @return [Array<String>]
@@ -0,0 +1,367 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ module Solargraph
7
+ class Workspace
8
+ # Manages determining which gemspecs are available in a workspace
9
+ class Gemspecs
10
+ include Logging
11
+
12
+ attr_reader :directory, :preferences
13
+
14
+ # @param directory [String, nil] If nil, assume no bundle is present
15
+ # @param preferences [Array<Gem::Specification>]
16
+ def initialize directory, preferences: []
17
+ # @todo an issue with both external bundles and the potential
18
+ # preferences feature is that bundler gives you a 'clean'
19
+ # rubygems environment with only the specified versions
20
+ # installed. Possible alternatives:
21
+ #
22
+ # *) prompt the user to run solargraph outside of bundler
23
+ # and treat all bundles as external
24
+ # *) reinstall the needed gems dynamically each time
25
+ # *) manipulate the rubygems/bundler environment
26
+ @directory = directory && File.absolute_path(directory)
27
+ # @todo implement preferences as a config-exposed feature
28
+ @preferences = preferences
29
+ end
30
+
31
+ # Take the path given to a 'require' statement in a source file
32
+ # and return the Gem::Specifications which will be brought into
33
+ # scope with it, so we can load pins for them.
34
+ #
35
+ # @param require [String] The string sent to 'require' in the code to resolve, e.g. 'rails', 'bundler/require'
36
+ # @return [::Array<Gem::Specification>, nil]
37
+ def resolve_require require
38
+ return nil if require.empty?
39
+
40
+ # This is added in the parser when it sees 'Bundler.require' -
41
+ # see https://bundler.io/guides/bundler_setup.html '
42
+ #
43
+ # @todo handle different arguments to Bundler.require
44
+ return auto_required_gemspecs_from_bundler if require == 'bundler/require'
45
+
46
+ # Determine gem name based on the require path
47
+ file = "lib/#{require}.rb"
48
+ spec_with_path = Gem::Specification.find_by_path(file)
49
+
50
+ all_gemspecs = all_gemspecs_from_bundle
51
+
52
+ gem_names_to_try = [
53
+ spec_with_path&.name,
54
+ require.tr('/', '-'),
55
+ require.split('/').first
56
+ ].compact.uniq
57
+ # @param gem_name [String]
58
+ gem_names_to_try.each do |gem_name|
59
+ # @sg-ignore Unresolved call to == on Boolean
60
+ gemspec = all_gemspecs.find { |gemspec| gemspec.name == gem_name }
61
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
62
+ return [gemspec_or_preference(gemspec)] if gemspec
63
+
64
+ begin
65
+ gemspec = Gem::Specification.find_by_name(gem_name)
66
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
67
+ return [gemspec_or_preference(gemspec)] if gemspec
68
+ rescue Gem::MissingSpecError
69
+ logger.debug do
70
+ "Require path #{require} could not be resolved to a gem via find_by_path or guess of #{gem_name}"
71
+ end
72
+ end
73
+
74
+ # look ourselves just in case this is hanging out somewhere
75
+ # that find_by_path doesn't index
76
+ gemspec = all_gemspecs.find do |spec|
77
+ spec = to_gem_specification(spec) unless spec.respond_to?(:files)
78
+
79
+ # @sg-ignore Translate to something flow sensitive typing understands
80
+ spec&.files&.any? { |gemspec_file| file == gemspec_file }
81
+ end
82
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
83
+ return [gemspec_or_preference(gemspec)] if gemspec
84
+ end
85
+
86
+ nil
87
+ end
88
+
89
+ # @param stdlib_name [String]
90
+ #
91
+ # @return [Array<String>]
92
+ def stdlib_dependencies stdlib_name
93
+ deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || []
94
+ deps.map { |dep| dep['name'] }.compact
95
+ end
96
+
97
+ # @param name [String]
98
+ # @param version [String, nil]
99
+ # @param out [IO, nil] output stream for logging
100
+ #
101
+ # @return [Gem::Specification, nil]
102
+ def find_gem name, version = nil, out: $stderr
103
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
104
+ specish = all_gemspecs_from_bundle.find { |specish| specish.name == name && specish.version == version }
105
+ return to_gem_specification specish if specish
106
+
107
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
108
+ specish = all_gemspecs_from_bundle.find { |specish| specish.name == name }
109
+ # @sg-ignore flow sensitive typing needs to create separate ranges for postfix if
110
+ return to_gem_specification specish if specish
111
+
112
+ resolve_gem_ignoring_local_bundle name, version, out: out
113
+ end
114
+
115
+ # @param gemspec [Gem::Specification]
116
+ # @param out[IO, nil] output stream for logging
117
+ #
118
+ # @return [Array<Gem::Specification>]
119
+ def fetch_dependencies gemspec, out: $stderr
120
+ all_gemspecs_from_bundle
121
+
122
+ # @type [Hash{String => Gem::Specification}]
123
+ deps_so_far = {}
124
+
125
+ # @param runtime_dep [Gem::Dependency]
126
+ # @param deps [Hash{String => Gem::Specification}]
127
+ gem_dep_gemspecs = only_runtime_dependencies(gemspec).each_with_object(deps_so_far) do |runtime_dep, deps|
128
+ dep = find_gem(runtime_dep.name, runtime_dep.requirement)
129
+ next unless dep
130
+
131
+ fetch_dependencies(dep, out: out).each { |sub_dep| deps[sub_dep.name] ||= sub_dep }
132
+
133
+ deps[dep.name] ||= dep
134
+ end
135
+
136
+ # RBS tracks implicit dependencies, like how the YAML standard
137
+ # library implies pulling in the psych library.
138
+ stdlib_deps = RbsMap::StdlibMap.stdlib_dependencies(gemspec.name, gemspec.version) || []
139
+ stdlib_dep_gemspecs = stdlib_deps.map { |dep| find_gem(dep['name'], dep['version']) }.compact
140
+ (gem_dep_gemspecs.values.compact + stdlib_dep_gemspecs).uniq(&:name)
141
+ end
142
+
143
+ # Returns all gemspecs directly depended on by this workspace's
144
+ # bundle (does not include transitive dependencies).
145
+ #
146
+ # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
147
+ def all_gemspecs_from_bundle
148
+ return [] unless directory
149
+
150
+ @all_gemspecs_from_bundle ||=
151
+ if in_this_bundle?
152
+ all_gemspecs_from_this_bundle
153
+ else
154
+ all_gemspecs_from_external_bundle
155
+ end
156
+ end
157
+
158
+ # @return [Hash{Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification => Gem::Specification}]
159
+ def self.gem_specification_cache
160
+ @gem_specification_cache ||= {}
161
+ end
162
+
163
+ private
164
+
165
+ # @param specish [Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification]
166
+ #
167
+ # @return [Gem::Specification, nil]
168
+ # @sg-ignore flow sensitive typing needs better handling of ||= on lvars
169
+ def to_gem_specification specish
170
+ # print time including milliseconds
171
+ self.class.gem_specification_cache[specish] ||= case specish
172
+ when Gem::Specification
173
+ specish
174
+ when Bundler::LazySpecification
175
+ # materializing didn't work. Let's look in the local
176
+ # rubygems without bundler's help
177
+ resolve_gem_ignoring_local_bundle specish.name,
178
+ specish.version
179
+ when Bundler::StubSpecification
180
+ # turns a Bundler::StubSpecification into a
181
+ # Gem::StubSpecification if we can
182
+ if specish.respond_to?(:stub)
183
+ # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName'
184
+ to_gem_specification specish.stub
185
+ else
186
+ # A Bundler::StubSpecification is a Bundler::
187
+ # RemoteSpecification which ought to proxy a Gem::
188
+ # Specification
189
+ specish
190
+ end
191
+ # @sg-ignore Unresolved constant Gem::StubSpecification
192
+ when Gem::StubSpecification
193
+ # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName'
194
+ specish.to_spec
195
+ else
196
+ raise "Unexpected type while resolving gem: #{specish.class}"
197
+ end
198
+ end
199
+
200
+ # @param command [String] The expression to evaluate in the external bundle
201
+ # @sg-ignore Need a JSON type
202
+ # @yield [undefined, nil]
203
+ def query_external_bundle command
204
+ Solargraph.with_clean_env do
205
+ cmd = [
206
+ 'ruby', '-e',
207
+ "require 'bundler'; require 'json'; Dir.chdir('#{directory}') { puts begin; #{command}; end.to_json }"
208
+ ]
209
+ o, e, s = Open3.capture3(*cmd)
210
+ if s.success?
211
+ Solargraph.logger.debug "External bundle: #{o}"
212
+ o && !o.empty? ? JSON.parse(o.split("\n").last) : nil
213
+ else
214
+ Solargraph.logger.warn e
215
+ raise BundleNotFoundError, "Failed to load gems from bundle at #{directory}"
216
+ end
217
+ end
218
+ end
219
+
220
+ # @sg-ignore need boolish support for ? methods
221
+ def in_this_bundle?
222
+ Bundler.definition&.lockfile&.to_s&.start_with?(directory)
223
+ end
224
+
225
+ # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
226
+ def all_gemspecs_from_this_bundle
227
+ # Find only the gems bundler is now using
228
+ specish_objects = Bundler.definition.locked_gems.specs
229
+ if specish_objects.first.respond_to?(:materialize_for_installation)
230
+ specish_objects = specish_objects.map(&:materialize_for_installation)
231
+ end
232
+ specish_objects.map do |specish|
233
+ if specish.respond_to?(:name) && specish.respond_to?(:version) && specish.respond_to?(:gem_dir)
234
+ # duck type is good enough for outside uses!
235
+ specish
236
+ else
237
+ to_gem_specification(specish)
238
+ end
239
+ end.compact
240
+ end
241
+
242
+ # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
243
+ def auto_required_gemspecs_from_bundler
244
+ return [] unless directory
245
+
246
+ logger.info 'Fetching gemspecs autorequired from Bundler (bundler/require)'
247
+ @auto_required_gemspecs_from_bundler ||=
248
+ if in_this_bundle?
249
+ auto_required_gemspecs_from_this_bundle
250
+ else
251
+ auto_required_gemspecs_from_external_bundle
252
+ end
253
+ end
254
+
255
+ # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
256
+ def auto_required_gemspecs_from_this_bundle
257
+ # Adapted from require() in lib/bundler/runtime.rb
258
+ dep_names = Bundler.definition.dependencies.select do |dep|
259
+ dep.groups.include?(:default) && dep.should_include?
260
+ end.map(&:name)
261
+
262
+ all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
263
+ end
264
+
265
+ # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
266
+ def auto_required_gemspecs_from_external_bundle
267
+ @auto_required_gemspecs_from_external_bundle ||=
268
+ begin
269
+ logger.info 'Fetching auto-required gemspecs from Bundler (bundler/require)'
270
+ command =
271
+ 'Bundler.definition.dependencies' \
272
+ '.select { |dep| dep.groups.include?(:default) && dep.should_include? }' \
273
+ '.map(&:name)'
274
+ # @sg-ignore
275
+ # @type [Array<String>]
276
+ dep_names = query_external_bundle command
277
+
278
+ all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
279
+ end
280
+ end
281
+
282
+ # @param gemspec [Gem::Specification]
283
+ # @return [Array<Gem::Dependency>]
284
+ def only_runtime_dependencies gemspec
285
+ unless gemspec.respond_to?(:dependencies) && gemspec.respond_to?(:development_dependencies)
286
+ gemspec = to_gem_specification(gemspec)
287
+ end
288
+ return [] if gemspec.nil?
289
+
290
+ gemspec.dependencies - gemspec.development_dependencies
291
+ end
292
+
293
+ # @todo Should this be using Gem::SpecFetcher and pull them automatically?
294
+ #
295
+ # @param name [String]
296
+ # @param version_or_requirement [String, nil]
297
+ # @param out [IO, nil] output stream for logging
298
+ #
299
+ # @return [Gem::Specification, nil]
300
+ def resolve_gem_ignoring_local_bundle name, version_or_requirement = nil, out: $stderr
301
+ Gem::Specification.find_by_name(name, version_or_requirement)
302
+ rescue Gem::MissingSpecError
303
+ begin
304
+ Gem::Specification.find_by_name(name)
305
+ rescue Gem::MissingSpecError
306
+ stdlibmap = RbsMap::StdlibMap.new(name)
307
+ unless stdlibmap.resolved?
308
+ gem_desc = name
309
+ gem_desc += ":#{version_or_requirement}" if version_or_requirement
310
+ out&.puts "Please install the gem #{gem_desc} in Solargraph's Ruby environment"
311
+ end
312
+ nil # either not here or in stdlib
313
+ end
314
+ end
315
+
316
+ # @sg-ignore flow sensitive typing needs better handling of ||= on lvars
317
+ # @return [Array<Gem::Specification>]
318
+ def all_gemspecs_from_external_bundle
319
+ @all_gemspecs_from_external_bundle ||=
320
+ begin
321
+ logger.info 'Fetching gemspecs required from external bundle'
322
+
323
+ command = 'specish_objects = Bundler.definition.locked_gems&.specs; ' \
324
+ 'if specish_objects.first.respond_to?(:materialize_for_installation);' \
325
+ 'specish_objects = specish_objects.map(&:materialize_for_installation);' \
326
+ 'end;' \
327
+ 'specish_objects.map { |specish| [specish.name, specish.version] }'
328
+ # @type [Array<Gem::Specification>]
329
+ query_external_bundle(command).map do |name, version|
330
+ resolve_gem_ignoring_local_bundle(name, version)
331
+ end.compact
332
+ rescue Solargraph::BundleNotFoundError => e
333
+ Solargraph.logger.info e.message
334
+ # @sg-ignore Need to add nil check here
335
+ Solargraph.logger.debug e.backtrace.join("\n")
336
+ []
337
+ end
338
+ end
339
+
340
+ # @return [Hash{String => Gem::Specification}]
341
+ def preference_map
342
+ @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
343
+ end
344
+
345
+ # @param gemspec [Gem::Specification]
346
+ #
347
+ # @return [Gem::Specification]
348
+ def gemspec_or_preference gemspec
349
+ return gemspec unless preference_map.key?(gemspec.name)
350
+ return gemspec if gemspec.version == preference_map[gemspec.name].version
351
+
352
+ change_gemspec_version gemspec, preference_map[gemspec.name].version
353
+ end
354
+
355
+ # @param gemspec [Gem::Specification]
356
+ # @param version [String]
357
+ # @return [Gem::Specification]
358
+ def change_gemspec_version gemspec, version
359
+ Gem::Specification.find_by_name(gemspec.name, "= #{version}")
360
+ rescue Gem::MissingSpecError
361
+ Solargraph.logger.info "Gem #{gemspec.name} version #{version.inspect} not found. " \
362
+ "Using #{gemspec.version} instead"
363
+ gemspec
364
+ end
365
+ end
366
+ end
367
+ end
@@ -83,6 +83,7 @@ module Solargraph
83
83
  return [] if hash.empty?
84
84
  hash['paths'].map { |path| File.join(base, path) }
85
85
  rescue StandardError => e
86
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
86
87
  Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}"
87
88
  []
88
89
  end