solargraph 0.60.3 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/plugins.yml +1 -5
- data/.github/workflows/rspec.yml +3 -1
- data/CHANGELOG.md +26 -0
- data/Gemfile +0 -3
- data/lib/solargraph/api_map/index.rb +15 -2
- data/lib/solargraph/api_map/store.rb +13 -0
- data/lib/solargraph/api_map.rb +60 -81
- data/lib/solargraph/cache_dir.rb +58 -0
- data/lib/solargraph/collection/base.rb +50 -0
- data/lib/solargraph/collection/core.rb +17 -0
- data/lib/solargraph/collection/gem.rb +57 -0
- data/lib/solargraph/collection/rbs.rb +49 -0
- data/lib/solargraph/collection/stdlib.rb +25 -0
- data/lib/solargraph/collection.rb +31 -0
- data/lib/solargraph/complex_type.rb +18 -5
- data/lib/solargraph/convention.rb +5 -3
- data/lib/solargraph/diagnostics/require_not_found.rb +0 -4
- data/lib/solargraph/external.rb +159 -0
- data/lib/solargraph/language_server/message/text_document/definition.rb +4 -1
- data/lib/solargraph/language_server/message/text_document/hover.rb +1 -2
- data/lib/solargraph/library.rb +11 -24
- data/lib/solargraph/metagem.rb +80 -0
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +22 -3
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +14 -5
- data/lib/solargraph/pin/closure.rb +17 -0
- data/lib/solargraph/pin/factory_parameter.rb +84 -0
- data/lib/solargraph/pin/method.rb +7 -4
- data/lib/solargraph/pin.rb +1 -0
- data/lib/solargraph/rbs_map/base.rb +31 -0
- data/lib/solargraph/rbs_map/conversions.rb +3 -1
- data/lib/solargraph/rbs_map/{core_map.rb → core.rb} +18 -39
- data/lib/solargraph/rbs_map/core_fills.rb +1 -1
- data/lib/solargraph/rbs_map/gem.rb +13 -0
- data/lib/solargraph/rbs_map/helpers.rb +60 -0
- data/lib/solargraph/rbs_map/path.rb +19 -0
- data/lib/solargraph/rbs_map/stdlib.rb +24 -0
- data/lib/solargraph/rbs_map.rb +7 -198
- data/lib/solargraph/repo.rb +177 -0
- data/lib/solargraph/shell.rb +49 -99
- data/lib/solargraph/source/chain/call.rb +190 -67
- data/lib/solargraph/source/chain/literal.rb +17 -17
- data/lib/solargraph/source/chain/parameter.rb +79 -0
- data/lib/solargraph/source/chain.rb +44 -5
- data/lib/solargraph/source/source_chainer.rb +40 -44
- data/lib/solargraph/source_map/clip.rb +5 -3
- data/lib/solargraph/source_map/mapper.rb +6 -2
- data/lib/solargraph/source_map.rb +10 -5
- data/lib/solargraph/type_checker/rules.rb +2 -1
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +0 -1
- data/lib/solargraph/yard_map/cache.rb +1 -1
- data/lib/solargraph/yard_map/directives/attribute_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/domain_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/method_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/override_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/parse_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives/scope_directive.rb +178 -0
- data/lib/solargraph/yard_map/directives/visibility_directive.rb +1 -1
- data/lib/solargraph/yard_map/directives.rb +5 -2
- data/lib/solargraph/yard_map/helpers.rb +1 -1
- data/lib/solargraph/yard_map/macro.rb +34 -5
- data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_method.rb +1 -1
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -1
- data/lib/solargraph/yard_map/mapper.rb +37 -4
- data/lib/solargraph/yard_map.rb +1 -1
- data/lib/solargraph/yardoc.rb +43 -60
- data/lib/solargraph.rb +6 -3
- data/solargraph.gemspec +1 -1
- metadata +22 -9
- data/lib/solargraph/doc_map.rb +0 -439
- data/lib/solargraph/gem_pins.rb +0 -87
- data/lib/solargraph/pin_cache.rb +0 -248
- data/lib/solargraph/rbs_map/stdlib_map.rb +0 -71
- data/lib/solargraph/workspace/gemspecs.rb +0 -373
data/lib/solargraph/pin_cache.rb
DELETED
|
@@ -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
|