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
@@ -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
+ gemspecs = 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
@@ -1,97 +1,98 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
-
5
- module Solargraph
6
- # A workspace consists of the files in a project's directory and the
7
- # project's configuration. It provides a Source for each file to be used
8
- # in an associated Library or ApiMap.
9
- #
10
- class Workspace
11
- # Manages determining which gemspecs are available in a workspace
12
- class RequirePaths
13
- attr_reader :directory, :config
14
-
15
- # @param directory [String, nil]
16
- # @param config [Config, nil]
17
- def initialize directory, config
18
- @directory = directory
19
- @config = config
20
- end
21
-
22
- # Generate require paths from gemspecs if they exist or assume the default
23
- # lib directory.
24
- #
25
- # @return [Array<String>]
26
- def generate
27
- result = require_paths_from_gemspec_files
28
- return configured_require_paths if result.empty?
29
- result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config
30
- result
31
- end
32
-
33
- private
34
-
35
- # @return [Array<String>]
36
- def require_paths_from_gemspec_files
37
- results = []
38
- gemspec_file_paths.each do |gemspec_file_path|
39
- results.concat require_path_from_gemspec_file(gemspec_file_path)
40
- end
41
- results
42
- end
43
-
44
- # Get an array of all gemspec files in the workspace.
45
- #
46
- # @return [Array<String>]
47
- def gemspec_file_paths
48
- return [] if directory.nil?
49
- @gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
50
- config.nil? || config.allow?(gs)
51
- end
52
- end
53
-
54
- # Get additional require paths defined in the configuration.
55
- #
56
- # @return [Array<String>]
57
- def configured_require_paths
58
- return ['lib'] unless directory
59
- return [File.join(directory, 'lib')] if !config || config.require_paths.empty?
60
- config.require_paths.map { |p| File.join(directory, p) }
61
- end
62
-
63
- # Generate require paths from gemspecs if they exist or assume the default
64
- # lib directory.
65
- #
66
- # @param gemspec_file_path [String]
67
- # @return [Array<String>]
68
- def require_path_from_gemspec_file gemspec_file_path
69
- base = File.dirname(gemspec_file_path)
70
- # HACK: Evaluating gemspec files violates the goal of not running
71
- # workspace code, but this is how Gem::Specification.load does it
72
- # anyway.
73
- cmd = ['ruby', '-e',
74
- "require 'rubygems'; " \
75
- "require 'json'; " \
76
- "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
77
- 'return unless Gem::Specification === spec; ' \
78
- 'puts({name: spec.name, paths: spec.require_paths}.to_json)']
79
- o, e, s = Open3.capture3(*cmd)
80
- if s.success?
81
- begin
82
- hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
83
- return [] if hash.empty?
84
- hash['paths'].map { |path| File.join(base, path) }
85
- rescue StandardError => e
86
- Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}"
87
- []
88
- end
89
- else
90
- Solargraph.logger.warn "Error reading #{gemspec_file_path}"
91
- Solargraph.logger.warn e
92
- []
93
- end
94
- end
95
- end
96
- end
97
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Solargraph
6
+ # A workspace consists of the files in a project's directory and the
7
+ # project's configuration. It provides a Source for each file to be used
8
+ # in an associated Library or ApiMap.
9
+ #
10
+ class Workspace
11
+ # Manages determining which gemspecs are available in a workspace
12
+ class RequirePaths
13
+ attr_reader :directory, :config
14
+
15
+ # @param directory [String, nil]
16
+ # @param config [Config, nil]
17
+ def initialize directory, config
18
+ @directory = directory
19
+ @config = config
20
+ end
21
+
22
+ # Generate require paths from gemspecs if they exist or assume the default
23
+ # lib directory.
24
+ #
25
+ # @return [Array<String>]
26
+ def generate
27
+ result = require_paths_from_gemspec_files
28
+ return configured_require_paths if result.empty?
29
+ result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Array<String>]
36
+ def require_paths_from_gemspec_files
37
+ results = []
38
+ gemspec_file_paths.each do |gemspec_file_path|
39
+ results.concat require_path_from_gemspec_file(gemspec_file_path)
40
+ end
41
+ results
42
+ end
43
+
44
+ # Get an array of all gemspec files in the workspace.
45
+ #
46
+ # @return [Array<String>]
47
+ def gemspec_file_paths
48
+ return [] if directory.nil?
49
+ @gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
50
+ config.nil? || config.allow?(gs)
51
+ end
52
+ end
53
+
54
+ # Get additional require paths defined in the configuration.
55
+ #
56
+ # @return [Array<String>]
57
+ def configured_require_paths
58
+ return ['lib'] unless directory
59
+ return [File.join(directory, 'lib')] if !config || config.require_paths.empty?
60
+ config.require_paths.map { |p| File.join(directory, p) }
61
+ end
62
+
63
+ # Generate require paths from gemspecs if they exist or assume the default
64
+ # lib directory.
65
+ #
66
+ # @param gemspec_file_path [String]
67
+ # @return [Array<String>]
68
+ def require_path_from_gemspec_file gemspec_file_path
69
+ base = File.dirname(gemspec_file_path)
70
+ # HACK: Evaluating gemspec files violates the goal of not running
71
+ # workspace code, but this is how Gem::Specification.load does it
72
+ # anyway.
73
+ cmd = ['ruby', '-e',
74
+ "require 'rubygems'; " \
75
+ "require 'json'; " \
76
+ "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
77
+ 'return unless Gem::Specification === spec; ' \
78
+ 'puts({name: spec.name, paths: spec.require_paths}.to_json)']
79
+ o, e, s = Open3.capture3(*cmd)
80
+ if s.success?
81
+ begin
82
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
83
+ return [] if hash.empty?
84
+ hash['paths'].map { |path| File.join(base, path) }
85
+ rescue StandardError => e
86
+ # @sg-ignore flow sensitive typing should be able to handle redefinition
87
+ Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}"
88
+ []
89
+ end
90
+ else
91
+ Solargraph.logger.warn "Error reading #{gemspec_file_path}"
92
+ Solargraph.logger.warn e
93
+ []
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end