solargraph 0.60.4 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +1 -1
  3. data/.github/workflows/rspec.yml +3 -1
  4. data/CHANGELOG.md +16 -0
  5. data/Gemfile +0 -3
  6. data/lib/solargraph/api_map/index.rb +15 -2
  7. data/lib/solargraph/api_map/store.rb +13 -0
  8. data/lib/solargraph/api_map.rb +60 -81
  9. data/lib/solargraph/cache_dir.rb +58 -0
  10. data/lib/solargraph/collection/base.rb +50 -0
  11. data/lib/solargraph/collection/core.rb +17 -0
  12. data/lib/solargraph/collection/gem.rb +57 -0
  13. data/lib/solargraph/collection/rbs.rb +49 -0
  14. data/lib/solargraph/collection/stdlib.rb +25 -0
  15. data/lib/solargraph/collection.rb +31 -0
  16. data/lib/solargraph/convention.rb +5 -3
  17. data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
  18. data/lib/solargraph/external.rb +159 -0
  19. data/lib/solargraph/language_server/message/text_document/definition.rb +4 -1
  20. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
  21. data/lib/solargraph/library.rb +11 -24
  22. data/lib/solargraph/metagem.rb +80 -0
  23. data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
  24. data/lib/solargraph/pin/closure.rb +17 -0
  25. data/lib/solargraph/pin/factory_parameter.rb +84 -0
  26. data/lib/solargraph/pin.rb +1 -0
  27. data/lib/solargraph/rbs_map/base.rb +31 -0
  28. data/lib/solargraph/rbs_map/conversions.rb +3 -1
  29. data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
  30. data/lib/solargraph/rbs_map/core_fills.rb +1 -1
  31. data/lib/solargraph/rbs_map/gem.rb +13 -0
  32. data/lib/solargraph/rbs_map/helpers.rb +60 -0
  33. data/lib/solargraph/rbs_map/path.rb +19 -0
  34. data/lib/solargraph/rbs_map/stdlib.rb +24 -0
  35. data/lib/solargraph/rbs_map.rb +7 -198
  36. data/lib/solargraph/repo.rb +177 -0
  37. data/lib/solargraph/shell.rb +49 -99
  38. data/lib/solargraph/source/chain/call.rb +29 -4
  39. data/lib/solargraph/source/chain/literal.rb +17 -17
  40. data/lib/solargraph/source/chain/parameter.rb +79 -0
  41. data/lib/solargraph/source/chain.rb +8 -0
  42. data/lib/solargraph/source/source_chainer.rb +40 -44
  43. data/lib/solargraph/source_map/clip.rb +5 -3
  44. data/lib/solargraph/source_map/mapper.rb +6 -2
  45. data/lib/solargraph/source_map.rb +10 -5
  46. data/lib/solargraph/type_checker/rules.rb +2 -1
  47. data/lib/solargraph/version.rb +1 -1
  48. data/lib/solargraph/workspace.rb +0 -1
  49. data/lib/solargraph/yard_map/cache.rb +1 -1
  50. data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
  51. data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
  52. data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
  53. data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
  54. data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
  55. data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
  56. data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
  57. data/lib/solargraph/yard_map/directives.rb +5 -2
  58. data/lib/solargraph/yard_map/helpers.rb +1 -1
  59. data/lib/solargraph/yard_map/macro.rb +34 -5
  60. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
  61. data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
  62. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
  63. data/lib/solargraph/yard_map/mapper.rb +2 -2
  64. data/lib/solargraph/yard_map.rb +1 -1
  65. data/lib/solargraph/yardoc.rb +43 -60
  66. data/lib/solargraph.rb +6 -3
  67. data/solargraph.gemspec +1 -1
  68. metadata +22 -9
  69. data/lib/solargraph/doc_map.rb +0 -439
  70. data/lib/solargraph/gem_pins.rb +0 -87
  71. data/lib/solargraph/pin_cache.rb +0 -248
  72. data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
  73. data/lib/solargraph/workspace/gemspecs.rb +0 -373
@@ -1,248 +0,0 @@
1
- require 'yard-activesupport-concern'
2
- require 'fileutils'
3
- require 'pathname' # @todo Required by RBS but not loaded in some use cases
4
- require 'rbs'
5
-
6
- module Solargraph
7
- module PinCache
8
- class << self
9
- include Logging
10
-
11
- # The base directory where cached YARD documentation and serialized pins are serialized
12
- #
13
- # @return [String]
14
- def base_dir
15
- # The directory is not stored in a variable so it can be overridden
16
- # in specs.
17
- ENV['SOLARGRAPH_CACHE'] ||
18
- (ENV['XDG_CACHE_HOME'] ? File.join(ENV['XDG_CACHE_HOME'], 'solargraph') : nil) ||
19
- File.join(Dir.home, '.cache', 'solargraph')
20
- end
21
-
22
- # The working directory for the current Ruby, RBS, and Solargraph versions.
23
- #
24
- # @return [String]
25
- def work_dir
26
- # The directory is not stored in a variable so it can be overridden
27
- # in specs.
28
- File.join(base_dir, "ruby-#{RUBY_VERSION}", "rbs-#{RBS::VERSION}", "solargraph-#{Solargraph::VERSION}")
29
- end
30
-
31
- # @param gemspec [Gem::Specification]
32
- # @return [String]
33
- def yardoc_path gemspec
34
- File.join(base_dir,
35
- "yard-#{YARD::VERSION}",
36
- "yard-activesupport-concern-#{YARD::ActiveSupport::Concern::VERSION}",
37
- "#{gemspec.name}-#{gemspec.version}.yardoc")
38
- end
39
-
40
- # @return [String]
41
- def stdlib_path
42
- File.join(work_dir, 'stdlib')
43
- end
44
-
45
- # @param require [String]
46
- # @return [String]
47
- def stdlib_require_path require
48
- File.join(stdlib_path, "#{require}.ser")
49
- end
50
-
51
- # @param require [String]
52
- # @return [Array<Pin::Base>, nil]
53
- def deserialize_stdlib_require require
54
- load(stdlib_require_path(require))
55
- end
56
-
57
- # @param require [String]
58
- # @param pins [Array<Pin::Base>]
59
- # @return [void]
60
- def serialize_stdlib_require require, pins
61
- save(stdlib_require_path(require), pins)
62
- end
63
-
64
- # @return [String]
65
- def core_path
66
- File.join(work_dir, 'core.ser')
67
- end
68
-
69
- # @return [Array<Pin::Base>, nil]
70
- def deserialize_core
71
- load(core_path)
72
- end
73
-
74
- # @param pins [Array<Pin::Base>]
75
- # @return [void]
76
- def serialize_core pins
77
- save(core_path, pins)
78
- end
79
-
80
- # @param gemspec [Gem::Specification]
81
- # @return [String]
82
- def yard_gem_path gemspec
83
- File.join(work_dir, 'yard', "#{gemspec.name}-#{gemspec.version}.ser")
84
- end
85
-
86
- # @param gemspec [Gem::Specification]
87
- # @return [Array<Pin::Base>, nil]
88
- def deserialize_yard_gem gemspec
89
- load(yard_gem_path(gemspec))
90
- end
91
-
92
- # @param gemspec [Gem::Specification]
93
- # @param pins [Array<Pin::Base>]
94
- # @return [void]
95
- def serialize_yard_gem gemspec, pins
96
- save(yard_gem_path(gemspec), pins)
97
- end
98
-
99
- # @param gemspec [Gem::Specification]
100
- # @return [Boolean]
101
- def has_yard? gemspec
102
- exist?(yard_gem_path(gemspec))
103
- end
104
-
105
- # @param gemspec [Gem::Specification]
106
- # @param hash [String, nil]
107
- # @return [String]
108
- def rbs_collection_path gemspec, hash
109
- File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
110
- end
111
-
112
- # @param gemspec [Gem::Specification]
113
- # @return [String]
114
- def rbs_collection_path_prefix gemspec
115
- File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-")
116
- end
117
-
118
- # @param gemspec [Gem::Specification]
119
- # @param hash [String, nil]
120
- # @return [Array<Pin::Base>, nil]
121
- def deserialize_rbs_collection_gem gemspec, hash
122
- load(rbs_collection_path(gemspec, hash))
123
- end
124
-
125
- # @param gemspec [Gem::Specification]
126
- # @param hash [String, nil]
127
- # @param pins [Array<Pin::Base>]n
128
- # @return [void]
129
- def serialize_rbs_collection_gem gemspec, hash, pins
130
- save(rbs_collection_path(gemspec, hash), pins)
131
- end
132
-
133
- # @param gemspec [Gem::Specification]
134
- # @param hash [String, nil]
135
- # @return [String]
136
- def combined_path gemspec, hash
137
- File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser")
138
- end
139
-
140
- # @param gemspec [Gem::Specification]
141
- # @return [String]
142
- def combined_path_prefix gemspec
143
- File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-")
144
- end
145
-
146
- # @param gemspec [Gem::Specification]
147
- # @param hash [String, nil]
148
- # @param pins [Array<Pin::Base>]
149
- # @return [void]
150
- def serialize_combined_gem gemspec, hash, pins
151
- save(combined_path(gemspec, hash), pins)
152
- end
153
-
154
- # @param gemspec [Gem::Specification]
155
- # @param hash [String, nil]
156
- # @return [Array<Pin::Base>, nil]
157
- def deserialize_combined_gem gemspec, hash
158
- load(combined_path(gemspec, hash))
159
- end
160
-
161
- # @param gemspec [Gem::Specification]
162
- # @param hash [String, nil]
163
- # @return [Boolean]
164
- def has_rbs_collection? gemspec, hash
165
- exist?(rbs_collection_path(gemspec, hash))
166
- end
167
-
168
- # @return [void]
169
- def uncache_core
170
- uncache(core_path)
171
- end
172
-
173
- # @return [void]
174
- def uncache_stdlib
175
- uncache(stdlib_path)
176
- end
177
-
178
- # @param gemspec [Gem::Specification]
179
- # @param out [IO, StringIO, nil]
180
- # @return [void]
181
- def uncache_gem gemspec, out: nil
182
- uncache(yardoc_path(gemspec), out: out)
183
- uncache_by_prefix(rbs_collection_path_prefix(gemspec), out: out)
184
- uncache(yard_gem_path(gemspec), out: out)
185
- uncache_by_prefix(combined_path_prefix(gemspec), out: out)
186
- end
187
-
188
- # @return [void]
189
- def clear
190
- FileUtils.rm_rf base_dir, secure: true
191
- end
192
-
193
- private
194
-
195
- # @param file [String]
196
- # @sg-ignore Marshal.load returns Object; we know it's Array<Pin::Base>
197
- # @return [Array<Solargraph::Pin::Base>, nil]
198
- def load file
199
- return nil unless File.file?(file)
200
- Marshal.load(File.read(file, mode: 'rb'))
201
- rescue StandardError => e
202
- Solargraph.logger.warn "Failed to load cached file #{file}: [#{e.class}] #{e.message}"
203
- FileUtils.rm_f file
204
- nil
205
- end
206
-
207
- # @param path [String]
208
- def exist? *path
209
- File.file? File.join(*path)
210
- end
211
-
212
- # @param file [String]
213
- # @param pins [Array<Pin::Base>]
214
- # @return [void]
215
- def save file, pins
216
- base = File.dirname(file)
217
- FileUtils.mkdir_p base unless File.directory?(base)
218
- ser = Marshal.dump(pins)
219
- File.write file, ser, mode: 'wb'
220
- logger.debug { "Cache#save: Saved #{pins.length} pins to #{file}" }
221
- end
222
-
223
- # @param path_segments [Array<String>]
224
- # @return [void]
225
- # @param [Object, nil] out
226
- def uncache *path_segments, out: nil
227
- path = File.join(*path_segments)
228
- return unless File.exist?(path)
229
- FileUtils.rm_rf path, secure: true
230
- out&.puts "Clearing pin cache in #{path}"
231
- end
232
-
233
- # @return [void]
234
- # @param path_segments [Array<String>]
235
- # @param [Object, nil] out
236
- def uncache_by_prefix *path_segments, out: nil
237
- path = File.join(*path_segments)
238
- glob = "#{path}*"
239
- out&.puts "Clearing pin cache in #{glob}"
240
- Dir.glob(glob).each do |file|
241
- next unless File.file?(file)
242
- FileUtils.rm_rf file, secure: true
243
- out&.puts "Clearing pin cache in #{file}"
244
- end
245
- end
246
- end
247
- end
248
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rbs'
4
-
5
- module Solargraph
6
- class RbsMap
7
- # Ruby stdlib pins
8
- #
9
- class StdlibMap < RbsMap
10
- include Logging
11
-
12
- # @type [Hash{String => RbsMap}]
13
- @stdlib_maps_hash = {}
14
-
15
- # @param rebuild [Boolean] build pins regardless of whether we
16
- # have cached them already
17
- # @param library [String]
18
- # @param out [StringIO, IO, nil] where to log messages
19
- def initialize library, rebuild: false, out: $stderr
20
- cached_pins = PinCache.deserialize_stdlib_require library
21
- if cached_pins && !rebuild
22
- @pins = cached_pins
23
- @resolved = true
24
- @loaded = true
25
- logger.debug { "Deserialized #{cached_pins.length} cached pins for stdlib require #{library.inspect}" }
26
- elsif self.class.source.has? library, nil
27
- super(library, out: out)
28
- unless resolved?
29
- @pins = []
30
- logger.debug { "StdlibMap could not resolve #{library.inspect}" }
31
- return
32
- end
33
- generated_pins = pins
34
- logger.debug { "Found #{generated_pins.length} pins for stdlib library #{library}" }
35
- PinCache.serialize_stdlib_require library, generated_pins
36
- end
37
- end
38
-
39
- # @return [RBS::Collection::Sources::Stdlib]
40
- def self.source
41
- @source ||= RBS::Collection::Sources::Stdlib.instance
42
- end
43
-
44
- # @param name [String]
45
- # @param version [String, nil]
46
- # @return [Array<Hash{String => String}>, nil]
47
- def self.stdlib_dependencies name, version = nil
48
- if source.has?(name, version)
49
- # @sg-ignore we are relying on undocumented behavior where
50
- # passing version=nil gives the latest version it has
51
- source.dependencies_of(name, version)
52
- else
53
- []
54
- end
55
- end
56
-
57
- def resolve_dependencies?
58
- # there are 'virtual' dependencies for stdlib gems in RBS that
59
- # aren't represented in the actual gemspecs that we'd
60
- # otherwise use
61
- true
62
- end
63
-
64
- # @param library [String]
65
- # @return [StdlibMap]
66
- def self.load library
67
- @stdlib_maps_hash[library] ||= StdlibMap.new(library)
68
- end
69
- end
70
- end
71
- end
@@ -1,373 +0,0 @@
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 Unresolved call to to_spec on Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification
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
- rescue Bundler::GemfileNotFound
224
- # Solargraph itself isn't running under a discoverable Gemfile
225
- # (e.g. installed and invoked as a standalone gem), so it can't
226
- # be "this bundle" - fall back to treating the workspace as an
227
- # external bundle.
228
- false
229
- end
230
-
231
- # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
232
- def all_gemspecs_from_this_bundle
233
- # Find only the gems bundler is now using
234
- specish_objects = Bundler.definition.locked_gems.specs
235
- if specish_objects.first.respond_to?(:materialize_for_installation)
236
- specish_objects = specish_objects.map(&:materialize_for_installation)
237
- end
238
- specish_objects.map do |specish|
239
- if specish.respond_to?(:name) && specish.respond_to?(:version) && specish.respond_to?(:gem_dir)
240
- # duck type is good enough for outside uses!
241
- specish
242
- else
243
- to_gem_specification(specish)
244
- end
245
- end.compact
246
- end
247
-
248
- # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
249
- def auto_required_gemspecs_from_bundler
250
- return [] unless directory
251
-
252
- logger.info 'Fetching gemspecs autorequired from Bundler (bundler/require)'
253
- @auto_required_gemspecs_from_bundler ||=
254
- if in_this_bundle?
255
- auto_required_gemspecs_from_this_bundle
256
- else
257
- auto_required_gemspecs_from_external_bundle
258
- end
259
- end
260
-
261
- # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
262
- def auto_required_gemspecs_from_this_bundle
263
- # Adapted from require() in lib/bundler/runtime.rb
264
- dep_names = Bundler.definition.dependencies.select do |dep|
265
- dep.groups.include?(:default) && dep.should_include?
266
- end.map(&:name)
267
-
268
- all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
269
- end
270
-
271
- # @return [Array<Gem::Specification, Bundler::LazySpecification, Bundler::StubSpecification>]
272
- def auto_required_gemspecs_from_external_bundle
273
- @auto_required_gemspecs_from_external_bundle ||=
274
- begin
275
- logger.info 'Fetching auto-required gemspecs from Bundler (bundler/require)'
276
- command =
277
- 'Bundler.definition.dependencies' \
278
- '.select { |dep| dep.groups.include?(:default) && dep.should_include? }' \
279
- '.map(&:name)'
280
- # @sg-ignore
281
- # @type [Array<String>]
282
- dep_names = query_external_bundle command
283
-
284
- all_gemspecs_from_bundle.select { |gemspec| dep_names.include?(gemspec.name) }
285
- end
286
- end
287
-
288
- # @param gemspec [Gem::Specification]
289
- # @return [Array<Gem::Dependency>]
290
- def only_runtime_dependencies gemspec
291
- unless gemspec.respond_to?(:dependencies) && gemspec.respond_to?(:development_dependencies)
292
- gemspec = to_gem_specification(gemspec)
293
- end
294
- return [] if gemspec.nil?
295
-
296
- gemspec.dependencies - gemspec.development_dependencies
297
- end
298
-
299
- # @todo Should this be using Gem::SpecFetcher and pull them automatically?
300
- #
301
- # @param name [String]
302
- # @param version_or_requirement [String, nil]
303
- # @param out [IO, nil] output stream for logging
304
- #
305
- # @return [Gem::Specification, nil]
306
- def resolve_gem_ignoring_local_bundle name, version_or_requirement = nil, out: $stderr
307
- Gem::Specification.find_by_name(name, version_or_requirement)
308
- rescue Gem::MissingSpecError
309
- begin
310
- Gem::Specification.find_by_name(name)
311
- rescue Gem::MissingSpecError
312
- stdlibmap = RbsMap::StdlibMap.new(name)
313
- unless stdlibmap.resolved?
314
- gem_desc = name
315
- gem_desc += ":#{version_or_requirement}" if version_or_requirement
316
- out&.puts "Please install the gem #{gem_desc} in Solargraph's Ruby environment"
317
- end
318
- nil # either not here or in stdlib
319
- end
320
- end
321
-
322
- # @sg-ignore return type could not be inferred
323
- # @return [Array<Gem::Specification>]
324
- def all_gemspecs_from_external_bundle
325
- @all_gemspecs_from_external_bundle ||=
326
- begin
327
- logger.info 'Fetching gemspecs required from external bundle'
328
-
329
- command = 'specish_objects = Bundler.definition.locked_gems&.specs; ' \
330
- 'if specish_objects.first.respond_to?(:materialize_for_installation);' \
331
- 'specish_objects = specish_objects.map(&:materialize_for_installation);' \
332
- 'end;' \
333
- 'specish_objects.map { |specish| [specish.name, specish.version] }'
334
- # @type [Array<Gem::Specification>]
335
- query_external_bundle(command).map do |name, version|
336
- resolve_gem_ignoring_local_bundle(name, version)
337
- end.compact
338
- rescue Solargraph::BundleNotFoundError => e
339
- Solargraph.logger.info e.message
340
- # @sg-ignore Need to add nil check here
341
- Solargraph.logger.debug e.backtrace.join("\n")
342
- []
343
- end
344
- end
345
-
346
- # @return [Hash{String => Gem::Specification}]
347
- def preference_map
348
- @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
349
- end
350
-
351
- # @param gemspec [Gem::Specification]
352
- #
353
- # @return [Gem::Specification]
354
- def gemspec_or_preference gemspec
355
- return gemspec unless preference_map.key?(gemspec.name)
356
- return gemspec if gemspec.version == preference_map[gemspec.name].version
357
-
358
- change_gemspec_version gemspec, preference_map[gemspec.name].version
359
- end
360
-
361
- # @param gemspec [Gem::Specification]
362
- # @param version [String]
363
- # @return [Gem::Specification]
364
- def change_gemspec_version gemspec, version
365
- Gem::Specification.find_by_name(gemspec.name, "= #{version}")
366
- rescue Gem::MissingSpecError
367
- Solargraph.logger.info "Gem #{gemspec.name} version #{version.inspect} not found. " \
368
- "Using #{gemspec.version} instead"
369
- gemspec
370
- end
371
- end
372
- end
373
- end