solargraph 0.54.1 → 0.56.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 +2 -0
- data/.github/workflows/typecheck.yml +3 -1
- data/.gitignore +2 -0
- data/CHANGELOG.md +70 -0
- data/README.md +13 -3
- data/lib/solargraph/api_map/cache.rb +10 -1
- data/lib/solargraph/api_map/index.rb +175 -0
- data/lib/solargraph/api_map/store.rb +79 -126
- data/lib/solargraph/api_map.rb +247 -93
- data/lib/solargraph/bench.rb +17 -1
- data/lib/solargraph/complex_type/type_methods.rb +13 -1
- data/lib/solargraph/complex_type/unique_type.rb +118 -9
- data/lib/solargraph/complex_type.rb +48 -18
- data/lib/solargraph/convention/base.rb +3 -3
- data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
- data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
- data/lib/solargraph/convention/struct_definition.rb +101 -0
- data/lib/solargraph/convention.rb +5 -3
- data/lib/solargraph/doc_map.rb +274 -56
- data/lib/solargraph/equality.rb +33 -0
- data/lib/solargraph/gem_pins.rb +53 -37
- data/lib/solargraph/language_server/host/message_worker.rb +31 -11
- data/lib/solargraph/language_server/host.rb +24 -13
- data/lib/solargraph/language_server/message/base.rb +19 -12
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +2 -0
- data/lib/solargraph/language_server/message/extended/document.rb +5 -2
- data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
- data/lib/solargraph/language_server/message/initialize.rb +3 -1
- data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
- data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
- data/lib/solargraph/library.rb +13 -11
- data/lib/solargraph/location.rb +28 -0
- data/lib/solargraph/logging.rb +1 -0
- data/lib/solargraph/parser/comment_ripper.rb +12 -6
- data/lib/solargraph/parser/flow_sensitive_typing.rb +227 -0
- data/lib/solargraph/parser/node_methods.rb +15 -1
- data/lib/solargraph/parser/node_processor.rb +3 -1
- data/lib/solargraph/parser/parser_gem/class_methods.rb +16 -14
- data/lib/solargraph/parser/parser_gem/node_chainer.rb +17 -23
- data/lib/solargraph/parser/parser_gem/node_methods.rb +5 -3
- data/lib/solargraph/parser/parser_gem/node_processors/alias_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +23 -2
- data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -3
- data/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
- data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +4 -2
- data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +4 -3
- data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +29 -6
- data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
- data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +4 -3
- data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +28 -16
- data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +2 -1
- data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +29 -0
- data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -0
- data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
- data/lib/solargraph/parser/region.rb +1 -1
- data/lib/solargraph/parser.rb +1 -0
- data/lib/solargraph/pin/base.rb +326 -43
- data/lib/solargraph/pin/base_variable.rb +18 -10
- data/lib/solargraph/pin/block.rb +3 -3
- data/lib/solargraph/pin/breakable.rb +9 -0
- data/lib/solargraph/pin/callable.rb +77 -6
- data/lib/solargraph/pin/closure.rb +18 -1
- data/lib/solargraph/pin/common.rb +5 -0
- data/lib/solargraph/pin/delegated_method.rb +20 -1
- data/lib/solargraph/pin/documenting.rb +16 -0
- data/lib/solargraph/pin/keyword.rb +7 -2
- data/lib/solargraph/pin/local_variable.rb +15 -6
- data/lib/solargraph/pin/method.rb +172 -42
- data/lib/solargraph/pin/namespace.rb +17 -9
- data/lib/solargraph/pin/parameter.rb +61 -11
- data/lib/solargraph/pin/proxy_type.rb +12 -6
- data/lib/solargraph/pin/reference/override.rb +10 -6
- data/lib/solargraph/pin/reference/require.rb +2 -2
- data/lib/solargraph/pin/signature.rb +42 -0
- data/lib/solargraph/pin/singleton.rb +1 -1
- data/lib/solargraph/pin/symbol.rb +3 -2
- data/lib/solargraph/pin/until.rb +18 -0
- data/lib/solargraph/pin/while.rb +18 -0
- data/lib/solargraph/pin.rb +4 -1
- data/lib/solargraph/pin_cache.rb +185 -0
- data/lib/solargraph/position.rb +16 -0
- data/lib/solargraph/range.rb +16 -0
- data/lib/solargraph/rbs_map/conversions.rb +199 -66
- data/lib/solargraph/rbs_map/core_fills.rb +32 -16
- data/lib/solargraph/rbs_map/core_map.rb +34 -11
- data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
- data/lib/solargraph/rbs_map.rb +74 -16
- data/lib/solargraph/shell.rb +19 -18
- data/lib/solargraph/source/chain/array.rb +12 -8
- data/lib/solargraph/source/chain/block_symbol.rb +1 -1
- data/lib/solargraph/source/chain/block_variable.rb +1 -1
- data/lib/solargraph/source/chain/call.rb +62 -25
- data/lib/solargraph/source/chain/constant.rb +1 -1
- data/lib/solargraph/source/chain/hash.rb +9 -3
- data/lib/solargraph/source/chain/head.rb +1 -1
- data/lib/solargraph/source/chain/if.rb +6 -1
- data/lib/solargraph/source/chain/link.rb +19 -5
- data/lib/solargraph/source/chain/literal.rb +27 -2
- data/lib/solargraph/source/chain/or.rb +1 -1
- data/lib/solargraph/source/chain/z_super.rb +1 -1
- data/lib/solargraph/source/chain.rb +106 -62
- data/lib/solargraph/source/cursor.rb +1 -11
- data/lib/solargraph/source/source_chainer.rb +2 -2
- data/lib/solargraph/source.rb +2 -1
- data/lib/solargraph/source_map/clip.rb +4 -2
- data/lib/solargraph/source_map/mapper.rb +9 -5
- data/lib/solargraph/source_map.rb +0 -17
- data/lib/solargraph/type_checker/checks.rb +4 -0
- data/lib/solargraph/type_checker.rb +41 -14
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/views/_method.erb +10 -10
- data/lib/solargraph/views/_namespace.erb +3 -3
- data/lib/solargraph/views/document.erb +10 -10
- data/lib/solargraph/workspace/config.rb +7 -3
- data/lib/solargraph/workspace.rb +16 -6
- data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -2
- data/lib/solargraph/yard_map/mapper/to_method.rb +55 -15
- data/lib/solargraph/yard_map/mapper/to_namespace.rb +5 -2
- data/lib/solargraph/yard_map/mapper.rb +5 -3
- data/lib/solargraph/yard_map/to_method.rb +4 -2
- data/lib/solargraph/yardoc.rb +6 -9
- data/lib/solargraph.rb +20 -1
- data/rbs/fills/tuple.rbs +150 -0
- data/rbs_collection.yaml +19 -0
- data/solargraph.gemspec +6 -5
- metadata +56 -29
- data/lib/solargraph/cache.rb +0 -77
data/lib/solargraph/doc_map.rb
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'benchmark'
|
|
5
|
+
|
|
3
6
|
module Solargraph
|
|
4
7
|
# A collection of pins generated from required gems.
|
|
5
8
|
#
|
|
6
9
|
class DocMap
|
|
10
|
+
include Logging
|
|
11
|
+
|
|
7
12
|
# @return [Array<String>]
|
|
8
13
|
attr_reader :requires
|
|
14
|
+
alias required requires
|
|
9
15
|
|
|
10
16
|
# @return [Array<Gem::Specification>]
|
|
11
17
|
attr_reader :preferences
|
|
@@ -14,31 +20,120 @@ module Solargraph
|
|
|
14
20
|
attr_reader :pins
|
|
15
21
|
|
|
16
22
|
# @return [Array<Gem::Specification>]
|
|
17
|
-
|
|
23
|
+
def uncached_gemspecs
|
|
24
|
+
(uncached_yard_gemspecs + uncached_rbs_collection_gemspecs).sort.
|
|
25
|
+
uniq { |gemspec| "#{gemspec.name}:#{gemspec.version}" }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Array<Gem::Specification>]
|
|
29
|
+
attr_reader :uncached_yard_gemspecs
|
|
30
|
+
|
|
31
|
+
# @return [Array<Gem::Specification>]
|
|
32
|
+
attr_reader :uncached_rbs_collection_gemspecs
|
|
33
|
+
|
|
34
|
+
attr_reader :rbs_collection_path
|
|
35
|
+
|
|
36
|
+
attr_reader :rbs_collection_config_path
|
|
37
|
+
|
|
38
|
+
# @return [Workspace, nil]
|
|
39
|
+
attr_reader :workspace
|
|
40
|
+
|
|
41
|
+
# @return [Environ]
|
|
42
|
+
attr_reader :environ
|
|
18
43
|
|
|
19
44
|
# @param requires [Array<String>]
|
|
20
45
|
# @param preferences [Array<Gem::Specification>]
|
|
21
|
-
# @param
|
|
22
|
-
def initialize(requires, preferences,
|
|
46
|
+
# @param workspace [Workspace, nil]
|
|
47
|
+
def initialize(requires, preferences, workspace = nil)
|
|
23
48
|
@requires = requires.compact
|
|
24
49
|
@preferences = preferences.compact
|
|
25
|
-
@
|
|
26
|
-
|
|
50
|
+
@workspace = workspace
|
|
51
|
+
@rbs_collection_path = workspace&.rbs_collection_path
|
|
52
|
+
@rbs_collection_config_path = workspace&.rbs_collection_config_path
|
|
53
|
+
@environ = Convention.for_global(self)
|
|
54
|
+
load_serialized_gem_pins
|
|
55
|
+
pins.concat @environ.pins
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cache_all!(out)
|
|
59
|
+
# if we log at debug level:
|
|
60
|
+
if logger.info?
|
|
61
|
+
gem_desc = uncached_gemspecs.map { |gemspec| "#{gemspec.name}:#{gemspec.version}" }.join(', ')
|
|
62
|
+
logger.info "Caching pins for gems: #{gem_desc}" unless uncached_gemspecs.empty?
|
|
63
|
+
end
|
|
64
|
+
logger.debug { "Caching for YARD: #{uncached_yard_gemspecs.map(&:name)}" }
|
|
65
|
+
logger.debug { "Caching for RBS collection: #{uncached_rbs_collection_gemspecs.map(&:name)}" }
|
|
66
|
+
load_serialized_gem_pins
|
|
67
|
+
uncached_gemspecs.each do |gemspec|
|
|
68
|
+
cache(gemspec, out: out)
|
|
69
|
+
end
|
|
70
|
+
load_serialized_gem_pins
|
|
71
|
+
@uncached_rbs_collection_gemspecs = []
|
|
72
|
+
@uncached_yard_gemspecs = []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def cache_yard_pins(gemspec, out)
|
|
76
|
+
pins = GemPins.build_yard_pins(gemspec)
|
|
77
|
+
PinCache.serialize_yard_gem(gemspec, pins)
|
|
78
|
+
logger.info { "Cached #{pins.length} YARD pins for gem #{gemspec.name}:#{gemspec.version}" } unless pins.empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def cache_rbs_collection_pins(gemspec, out)
|
|
82
|
+
rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
|
|
83
|
+
pins = rbs_map.pins
|
|
84
|
+
rbs_version_cache_key = rbs_map.cache_key
|
|
85
|
+
# cache pins even if result is zero, so we don't retry building pins
|
|
86
|
+
pins ||= []
|
|
87
|
+
PinCache.serialize_rbs_collection_gem(gemspec, rbs_version_cache_key, pins)
|
|
88
|
+
logger.info { "Cached #{pins.length} RBS collection pins for gem #{gemspec.name} #{gemspec.version} with cache_key #{rbs_version_cache_key.inspect}" unless pins.empty? }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param gemspec [Gem::Specification]
|
|
92
|
+
def cache(gemspec, rebuild: false, out: nil)
|
|
93
|
+
build_yard = uncached_yard_gemspecs.include?(gemspec) || rebuild
|
|
94
|
+
build_rbs_collection = uncached_rbs_collection_gemspecs.include?(gemspec) || rebuild
|
|
95
|
+
if build_yard || build_rbs_collection
|
|
96
|
+
type = []
|
|
97
|
+
type << 'YARD' if build_yard
|
|
98
|
+
type << 'RBS collection' if build_rbs_collection
|
|
99
|
+
out.puts("Caching #{type.join(' and ')} pins for gem #{gemspec.name}:#{gemspec.version}") if out
|
|
100
|
+
end
|
|
101
|
+
cache_yard_pins(gemspec, out) if build_yard
|
|
102
|
+
cache_rbs_collection_pins(gemspec, out) if build_rbs_collection
|
|
27
103
|
end
|
|
28
104
|
|
|
29
105
|
# @return [Array<Gem::Specification>]
|
|
30
106
|
def gemspecs
|
|
31
|
-
@gemspecs ||=
|
|
107
|
+
@gemspecs ||= required_gems_map.values.compact.flatten
|
|
32
108
|
end
|
|
33
109
|
|
|
34
110
|
# @return [Array<String>]
|
|
35
111
|
def unresolved_requires
|
|
36
|
-
@unresolved_requires ||=
|
|
112
|
+
@unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.all_yard_gems_in_memory
|
|
116
|
+
@yard_gems_in_memory ||= {}
|
|
37
117
|
end
|
|
38
118
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
119
|
+
def self.all_rbs_collection_gems_in_memory
|
|
120
|
+
@rbs_collection_gems_in_memory ||= {}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def yard_pins_in_memory
|
|
124
|
+
self.class.all_yard_gems_in_memory
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def rbs_collection_pins_in_memory
|
|
128
|
+
self.class.all_rbs_collection_gems_in_memory[rbs_collection_path] ||= {}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.all_combined_pins_in_memory
|
|
132
|
+
@combined_pins_in_memory ||= {}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def combined_pins_in_memory
|
|
136
|
+
self.class.all_combined_pins_in_memory
|
|
42
137
|
end
|
|
43
138
|
|
|
44
139
|
# @return [Set<Gem::Specification>]
|
|
@@ -49,23 +144,34 @@ module Solargraph
|
|
|
49
144
|
private
|
|
50
145
|
|
|
51
146
|
# @return [void]
|
|
52
|
-
def
|
|
147
|
+
def load_serialized_gem_pins
|
|
53
148
|
@pins = []
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
149
|
+
@uncached_yard_gemspecs = []
|
|
150
|
+
@uncached_rbs_collection_gemspecs = []
|
|
151
|
+
with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v }
|
|
152
|
+
paths = Hash[without_gemspecs].keys
|
|
153
|
+
gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies.to_a
|
|
154
|
+
|
|
155
|
+
paths.each do |path|
|
|
156
|
+
rbs_pins = deserialize_stdlib_rbs_map path
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
logger.debug { "DocMap#load_serialized_gem_pins: Combining pins..." }
|
|
160
|
+
time = Benchmark.measure do
|
|
161
|
+
gemspecs.each do |gemspec|
|
|
162
|
+
pins = deserialize_combined_pin_cache gemspec
|
|
163
|
+
@pins.concat pins if pins
|
|
60
164
|
end
|
|
61
165
|
end
|
|
62
|
-
|
|
63
|
-
@
|
|
166
|
+
logger.info { "DocMap#load_serialized_gem_pins: Loaded and processed serialized pins together in #{time.real} seconds" }
|
|
167
|
+
@uncached_yard_gemspecs.uniq!
|
|
168
|
+
@uncached_rbs_collection_gemspecs.uniq!
|
|
169
|
+
nil
|
|
64
170
|
end
|
|
65
171
|
|
|
66
|
-
# @return [Hash{String => Gem::Specification
|
|
67
|
-
def
|
|
68
|
-
@
|
|
172
|
+
# @return [Hash{String => Array<Gem::Specification>}]
|
|
173
|
+
def required_gems_map
|
|
174
|
+
@required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
|
|
69
175
|
end
|
|
70
176
|
|
|
71
177
|
# @return [Hash{String => Gem::Specification}]
|
|
@@ -73,32 +179,97 @@ module Solargraph
|
|
|
73
179
|
@preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
|
|
74
180
|
end
|
|
75
181
|
|
|
182
|
+
# @param gemspec [Gem::Specification]
|
|
183
|
+
# @return [Array<Pin::Base>]
|
|
184
|
+
def deserialize_yard_pin_cache gemspec
|
|
185
|
+
if yard_pins_in_memory.key?([gemspec.name, gemspec.version])
|
|
186
|
+
return yard_pins_in_memory[[gemspec.name, gemspec.version]]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
cached = PinCache.deserialize_yard_gem(gemspec)
|
|
190
|
+
if cached
|
|
191
|
+
logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
|
|
192
|
+
yard_pins_in_memory[[gemspec.name, gemspec.version]] = cached
|
|
193
|
+
cached
|
|
194
|
+
else
|
|
195
|
+
logger.debug "No YARD pin cache for #{gemspec.name}:#{gemspec.version}"
|
|
196
|
+
@uncached_yard_gemspecs.push gemspec
|
|
197
|
+
nil
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
76
201
|
# @param gemspec [Gem::Specification]
|
|
77
202
|
# @return [void]
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
203
|
+
def deserialize_combined_pin_cache(gemspec)
|
|
204
|
+
unless combined_pins_in_memory[[gemspec.name, gemspec.version]].nil?
|
|
205
|
+
return combined_pins_in_memory[[gemspec.name, gemspec.version]]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
rbs_map = RbsMap.from_gemspec(gemspec, rbs_collection_path, rbs_collection_config_path)
|
|
209
|
+
rbs_version_cache_key = rbs_map.cache_key
|
|
210
|
+
|
|
211
|
+
cached = PinCache.deserialize_combined_gem(gemspec, rbs_version_cache_key)
|
|
212
|
+
if cached
|
|
213
|
+
logger.info { "Loaded #{cached.length} cached YARD pins from #{gemspec.name}:#{gemspec.version}" }
|
|
214
|
+
combined_pins_in_memory[[gemspec.name, gemspec.version]] = cached
|
|
215
|
+
return combined_pins_in_memory[[gemspec.name, gemspec.version]]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
rbs_collection_pins = deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
|
|
219
|
+
|
|
220
|
+
yard_pins = deserialize_yard_pin_cache gemspec
|
|
221
|
+
|
|
222
|
+
if !rbs_collection_pins.nil? && !yard_pins.nil?
|
|
223
|
+
logger.debug { "Combining pins for #{gemspec.name}:#{gemspec.version}" }
|
|
224
|
+
combined_pins = GemPins.combine(yard_pins, rbs_collection_pins)
|
|
225
|
+
PinCache.serialize_combined_gem(gemspec, rbs_version_cache_key, combined_pins)
|
|
226
|
+
combined_pins_in_memory[[gemspec.name, gemspec.version]] = combined_pins
|
|
227
|
+
logger.info { "Generated #{combined_pins_in_memory[[gemspec.name, gemspec.version]].length} combined pins for #{gemspec.name} #{gemspec.version}" }
|
|
228
|
+
return combined_pins
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if !yard_pins.nil?
|
|
232
|
+
logger.debug { "Using only YARD pins for #{gemspec.name}:#{gemspec.version}" }
|
|
233
|
+
combined_pins_in_memory[[gemspec.name, gemspec.version]] = yard_pins
|
|
234
|
+
return combined_pins_in_memory[[gemspec.name, gemspec.version]]
|
|
235
|
+
elsif !rbs_collection_pins.nil?
|
|
236
|
+
logger.debug { "Using only RBS collection pins for #{gemspec.name}:#{gemspec.version}" }
|
|
237
|
+
combined_pins_in_memory[[gemspec.name, gemspec.version]] = rbs_collection_pins
|
|
238
|
+
return combined_pins_in_memory[[gemspec.name, gemspec.version]]
|
|
86
239
|
else
|
|
87
|
-
|
|
88
|
-
|
|
240
|
+
logger.debug { "Pins not yet cached for #{gemspec.name}:#{gemspec.version}" }
|
|
241
|
+
return nil
|
|
89
242
|
end
|
|
90
243
|
end
|
|
91
244
|
|
|
92
245
|
# @param path [String] require path that might be in the RBS stdlib collection
|
|
93
246
|
# @return [void]
|
|
94
|
-
def
|
|
247
|
+
def deserialize_stdlib_rbs_map path
|
|
95
248
|
map = RbsMap::StdlibMap.load(path)
|
|
96
249
|
if map.resolved?
|
|
97
|
-
|
|
250
|
+
logger.debug { "Loading stdlib pins for #{path}" }
|
|
98
251
|
@pins.concat map.pins
|
|
252
|
+
logger.debug { "Loaded #{map.pins.length} stdlib pins for #{path}" }
|
|
253
|
+
map.pins
|
|
99
254
|
else
|
|
100
255
|
# @todo Temporarily ignoring unresolved `require 'set'`
|
|
101
|
-
|
|
256
|
+
logger.debug { "Require path #{path} could not be resolved in RBS" } unless path == 'set'
|
|
257
|
+
nil
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# @return [Array<Pin::Base>, nil]
|
|
262
|
+
def deserialize_rbs_collection_cache gemspec, rbs_version_cache_key
|
|
263
|
+
return if rbs_collection_pins_in_memory.key?([gemspec, rbs_version_cache_key])
|
|
264
|
+
cached = PinCache.deserialize_rbs_collection_gem(gemspec, rbs_version_cache_key)
|
|
265
|
+
if cached
|
|
266
|
+
logger.info { "Loaded #{cached.length} pins from RBS collection cache for #{gemspec.name}:#{gemspec.version}" } unless cached.empty?
|
|
267
|
+
rbs_collection_pins_in_memory[[gemspec, rbs_version_cache_key]] = cached
|
|
268
|
+
cached
|
|
269
|
+
else
|
|
270
|
+
logger.debug "No RBS collection pin cache for #{gemspec.name} #{gemspec.version}"
|
|
271
|
+
@uncached_rbs_collection_gemspecs.push gemspec
|
|
272
|
+
nil
|
|
102
273
|
end
|
|
103
274
|
end
|
|
104
275
|
|
|
@@ -112,21 +283,11 @@ module Solargraph
|
|
|
112
283
|
true
|
|
113
284
|
end
|
|
114
285
|
|
|
115
|
-
def update_from_collection gemspec, gempins
|
|
116
|
-
return gempins unless @rbs_path && File.directory?(@rbs_path)
|
|
117
|
-
return gempins if RbsMap.new(gemspec.name, gemspec.version).resolved?
|
|
118
|
-
|
|
119
|
-
rbs_map = RbsMap.new(gemspec.name, gemspec.version, directories: [@rbs_path])
|
|
120
|
-
return gempins unless rbs_map.resolved?
|
|
121
|
-
|
|
122
|
-
Solargraph.logger.info "Updating #{gemspec.name} #{gemspec.version} from collection"
|
|
123
|
-
GemPins.combine(gempins, rbs_map)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
286
|
# @param path [String]
|
|
127
|
-
# @return [Gem::Specification
|
|
128
|
-
def
|
|
287
|
+
# @return [::Array<Gem::Specification>, nil]
|
|
288
|
+
def resolve_path_to_gemspecs path
|
|
129
289
|
return nil if path.empty?
|
|
290
|
+
return gemspecs_required_from_bundler if path == 'bundler/require'
|
|
130
291
|
|
|
131
292
|
gemspec = Gem::Specification.find_by_path(path)
|
|
132
293
|
if gemspec.nil?
|
|
@@ -140,17 +301,18 @@ module Solargraph
|
|
|
140
301
|
file = "lib/#{path}.rb"
|
|
141
302
|
gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
|
|
142
303
|
rescue Gem::MissingSpecError
|
|
143
|
-
|
|
144
|
-
|
|
304
|
+
logger.debug { "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}" }
|
|
305
|
+
[]
|
|
145
306
|
end
|
|
146
307
|
end
|
|
147
|
-
|
|
308
|
+
return nil if gemspec.nil?
|
|
309
|
+
[gemspec_or_preference(gemspec)]
|
|
148
310
|
end
|
|
149
311
|
|
|
150
|
-
# @param gemspec [Gem::Specification
|
|
151
|
-
# @return [Gem::Specification
|
|
312
|
+
# @param gemspec [Gem::Specification]
|
|
313
|
+
# @return [Gem::Specification]
|
|
152
314
|
def gemspec_or_preference gemspec
|
|
153
|
-
return gemspec unless
|
|
315
|
+
return gemspec unless preference_map.key?(gemspec.name)
|
|
154
316
|
return gemspec if gemspec.version == preference_map[gemspec.name].version
|
|
155
317
|
|
|
156
318
|
change_gemspec_version gemspec, preference_map[by_path.name].version
|
|
@@ -169,12 +331,15 @@ module Solargraph
|
|
|
169
331
|
# @param gemspec [Gem::Specification]
|
|
170
332
|
# @return [Array<Gem::Specification>]
|
|
171
333
|
def fetch_dependencies gemspec
|
|
334
|
+
# @param spec [Gem::Dependency]
|
|
172
335
|
only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
|
|
173
336
|
Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
|
|
174
|
-
dep = Gem
|
|
337
|
+
dep = Gem.loaded_specs[spec.name]
|
|
338
|
+
# @todo is next line necessary?
|
|
339
|
+
dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
|
|
175
340
|
deps.merge fetch_dependencies(dep) if deps.add?(dep)
|
|
176
341
|
rescue Gem::MissingSpecError
|
|
177
|
-
Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.
|
|
342
|
+
Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found in RubyGems."
|
|
178
343
|
end.to_a
|
|
179
344
|
end
|
|
180
345
|
|
|
@@ -183,5 +348,58 @@ module Solargraph
|
|
|
183
348
|
def only_runtime_dependencies gemspec
|
|
184
349
|
gemspec.dependencies - gemspec.development_dependencies
|
|
185
350
|
end
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def inspect
|
|
354
|
+
self.class.inspect
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def gemspecs_required_from_bundler
|
|
358
|
+
if workspace&.directory && Bundler.definition&.lockfile&.to_s&.start_with?(workspace.directory)
|
|
359
|
+
# Find only the gems bundler is now using
|
|
360
|
+
Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
|
|
361
|
+
logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version}"
|
|
362
|
+
[Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
|
|
363
|
+
rescue Gem::MissingSpecError => e
|
|
364
|
+
logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
|
|
365
|
+
# can happen in local filesystem references
|
|
366
|
+
specs = resolve_path_to_gemspecs lazy_spec.name
|
|
367
|
+
logger.warn "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
|
|
368
|
+
next specs
|
|
369
|
+
end.compact
|
|
370
|
+
else
|
|
371
|
+
logger.info 'Fetching gemspecs required from Bundler (bundler/require)'
|
|
372
|
+
gemspecs_required_from_external_bundle
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def gemspecs_required_from_external_bundle
|
|
377
|
+
logger.info 'Fetching gemspecs required from external bundle'
|
|
378
|
+
return [] unless workspace&.directory
|
|
379
|
+
|
|
380
|
+
Solargraph.with_clean_env do
|
|
381
|
+
cmd = [
|
|
382
|
+
'ruby', '-e',
|
|
383
|
+
"require 'bundler'; require 'json'; Dir.chdir('#{workspace&.directory}') { puts Bundler.definition.locked_gems.specs.map { |spec| [spec.name, spec.version] }.to_h.to_json }"
|
|
384
|
+
]
|
|
385
|
+
o, e, s = Open3.capture3(*cmd)
|
|
386
|
+
if s.success?
|
|
387
|
+
Solargraph.logger.debug "External bundle: #{o}"
|
|
388
|
+
hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
|
|
389
|
+
hash.flat_map do |name, version|
|
|
390
|
+
Gem::Specification.find_by_name(name, version)
|
|
391
|
+
rescue Gem::MissingSpecError => e
|
|
392
|
+
logger.info("Could not find #{name}:#{version} with find_by_name, falling back to guess")
|
|
393
|
+
# can happen in local filesystem references
|
|
394
|
+
specs = resolve_path_to_gemspecs name
|
|
395
|
+
logger.warn "Gem #{name} #{version} from bundle not found: #{e}" if specs.nil?
|
|
396
|
+
next specs
|
|
397
|
+
end.compact
|
|
398
|
+
else
|
|
399
|
+
Solargraph.logger.warn e
|
|
400
|
+
raise BundleNotFoundError, "Failed to load gems from bundle at #{workspace&.directory}"
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
186
404
|
end
|
|
187
405
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Solargraph
|
|
4
|
+
# @abstract This mixin relies on these -
|
|
5
|
+
# methods:
|
|
6
|
+
# equality_fields()
|
|
7
|
+
module Equality
|
|
8
|
+
# @!method equality_fields
|
|
9
|
+
# @return [Array]
|
|
10
|
+
|
|
11
|
+
# @param other [Object]
|
|
12
|
+
# @return [Boolean]
|
|
13
|
+
def eql?(other)
|
|
14
|
+
self.class.eql?(other.class) &&
|
|
15
|
+
equality_fields.eql?(other.equality_fields)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param other [Object]
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def ==(other)
|
|
21
|
+
self.eql?(other)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def hash
|
|
25
|
+
equality_fields.hash
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def freeze
|
|
29
|
+
equality_fields.each(&:freeze)
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/solargraph/gem_pins.rb
CHANGED
|
@@ -7,59 +7,75 @@ module Solargraph
|
|
|
7
7
|
# documentation.
|
|
8
8
|
#
|
|
9
9
|
module GemPins
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
class << self
|
|
11
|
+
include Logging
|
|
12
|
+
end
|
|
13
|
+
|
|
14
14
|
# @param gemspec [Gem::Specification]
|
|
15
15
|
# @return [Array<Pin::Base>]
|
|
16
|
-
def self.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
def self.build_yard_pins(gemspec)
|
|
17
|
+
Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec)
|
|
18
|
+
yardoc = Yardoc.load!(gemspec)
|
|
19
|
+
YardMap::Mapper.new(yardoc, gemspec).map
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param pins [Array<Pin::Base>]
|
|
23
|
+
def self.combine_method_pins_by_path(pins)
|
|
24
|
+
# bad_pins = pins.select { |pin| pin.is_a?(Pin::Method) && pin.path == 'StringIO.open' && pin.source == :rbs }; raise "wtf: #{bad_pins}" if bad_pins.length > 1
|
|
25
|
+
method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method }
|
|
26
|
+
by_path = method_pins.group_by(&:path)
|
|
27
|
+
by_path.transform_values! do |pins|
|
|
28
|
+
GemPins.combine_method_pins(*pins)
|
|
29
|
+
end
|
|
30
|
+
by_path.values + alias_pins
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.combine_method_pins(*pins)
|
|
34
|
+
out = pins.reduce(nil) do |memo, pin|
|
|
35
|
+
next pin if memo.nil?
|
|
36
|
+
if memo == pin && memo.source != :combined
|
|
37
|
+
# @todo we should track down situations where we are handled
|
|
38
|
+
# the same pin from the same source here and eliminate them -
|
|
39
|
+
# this is an efficiency workaround for now
|
|
40
|
+
next memo
|
|
41
|
+
end
|
|
42
|
+
memo.combine_with(pin)
|
|
43
|
+
end
|
|
44
|
+
logger.debug { "GemPins.combine_method_pins(pins.length=#{pins.length}, pins=#{pins}) => #{out.inspect}" }
|
|
45
|
+
out
|
|
20
46
|
end
|
|
21
47
|
|
|
22
48
|
# @param yard_pins [Array<Pin::Base>]
|
|
23
49
|
# @param rbs_map [RbsMap]
|
|
24
50
|
# @return [Array<Pin::Base>]
|
|
25
|
-
def self.combine(yard_pins,
|
|
51
|
+
def self.combine(yard_pins, rbs_pins)
|
|
26
52
|
in_yard = Set.new
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
rbs_api_map = Solargraph::ApiMap.new(pins: rbs_pins)
|
|
54
|
+
combined = yard_pins.map do |yard_pin|
|
|
55
|
+
in_yard.add yard_pin.path
|
|
56
|
+
rbs_pin = rbs_api_map.get_path_pins(yard_pin.path).filter { |pin| pin.is_a? Pin::Method }.first
|
|
57
|
+
next yard_pin unless rbs_pin && yard_pin.class == Pin::Method
|
|
30
58
|
|
|
31
|
-
|
|
32
|
-
|
|
59
|
+
unless rbs_pin
|
|
60
|
+
logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" }
|
|
61
|
+
next yard_pin
|
|
62
|
+
end
|
|
33
63
|
|
|
34
|
-
|
|
35
|
-
yard.
|
|
36
|
-
|
|
37
|
-
closure: yard.closure,
|
|
38
|
-
name: yard.name,
|
|
39
|
-
comments: yard.comments,
|
|
40
|
-
scope: yard.scope,
|
|
41
|
-
parameters: rbs.parameters,
|
|
42
|
-
generics: rbs.generics,
|
|
43
|
-
node: yard.node,
|
|
44
|
-
signatures: yard.signatures,
|
|
45
|
-
return_type: best_return_type(rbs.return_type, yard.return_type)
|
|
46
|
-
)
|
|
64
|
+
out = combine_method_pins(rbs_pin, yard_pin)
|
|
65
|
+
logger.debug { "GemPins.combine: Combining yard.path=#{yard_pin.path} - rbs=#{rbs_pin.inspect} with yard=#{yard_pin.inspect} into #{out}" }
|
|
66
|
+
out
|
|
47
67
|
end
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
in_rbs_only = rbs_pins.select do |pin|
|
|
69
|
+
pin.path.nil? || !in_yard.include?(pin.path)
|
|
70
|
+
end
|
|
71
|
+
out = combined + in_rbs_only
|
|
72
|
+
logger.debug { "GemPins#combine: Returning #{out.length} combined pins" }
|
|
73
|
+
out
|
|
50
74
|
end
|
|
51
75
|
|
|
52
76
|
class << self
|
|
53
77
|
private
|
|
54
78
|
|
|
55
|
-
# @param gemspec [Gem::Specification]
|
|
56
|
-
# @return [Array<Pin::Base>]
|
|
57
|
-
def build_yard_pins(gemspec)
|
|
58
|
-
Yardoc.cache(gemspec) unless Yardoc.cached?(gemspec)
|
|
59
|
-
yardoc = Yardoc.load!(gemspec)
|
|
60
|
-
YardMap::Mapper.new(yardoc, gemspec).map
|
|
61
|
-
end
|
|
62
|
-
|
|
63
79
|
# Select the first defined type.
|
|
64
80
|
#
|
|
65
81
|
# @param choices [Array<ComplexType>]
|
|
@@ -3,12 +3,21 @@
|
|
|
3
3
|
module Solargraph
|
|
4
4
|
module LanguageServer
|
|
5
5
|
class Host
|
|
6
|
-
# A serial worker Thread to handle
|
|
7
|
-
#
|
|
8
|
-
# this make check pending message possible, and maybe cancelled to speedup process
|
|
6
|
+
# A serial worker Thread to handle incoming messages.
|
|
9
7
|
#
|
|
10
8
|
class MessageWorker
|
|
11
|
-
UPDATE_METHODS = [
|
|
9
|
+
UPDATE_METHODS = [
|
|
10
|
+
'textDocument/didChange',
|
|
11
|
+
'textDocument/didClose',
|
|
12
|
+
'textDocument/didOpen',
|
|
13
|
+
'textDocument/didSave',
|
|
14
|
+
'workspace/didChangeConfiguration',
|
|
15
|
+
'workspace/didChangeWatchedFiles',
|
|
16
|
+
'workspace/didCreateFiles',
|
|
17
|
+
'workspace/didChangeWorkspaceFolders',
|
|
18
|
+
'workspace/didDeleteFiles',
|
|
19
|
+
'workspace/didRenameFiles'
|
|
20
|
+
].freeze
|
|
12
21
|
|
|
13
22
|
# @param host [Host]
|
|
14
23
|
def initialize(host)
|
|
@@ -64,22 +73,33 @@ module Solargraph
|
|
|
64
73
|
private
|
|
65
74
|
|
|
66
75
|
def next_message
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
return
|
|
76
|
+
cancel_message || next_priority
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def cancel_message
|
|
80
|
+
# Handle cancellations first
|
|
81
|
+
idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' }
|
|
82
|
+
return unless idx
|
|
74
83
|
|
|
75
84
|
msg = messages[idx]
|
|
76
85
|
messages.delete_at idx
|
|
77
86
|
msg
|
|
78
87
|
end
|
|
79
88
|
|
|
89
|
+
def next_priority
|
|
90
|
+
# Prioritize updates and version-dependent messages for performance
|
|
91
|
+
idx = messages.find_index do |msg|
|
|
92
|
+
UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
|
|
93
|
+
end
|
|
94
|
+
idx ? messages.delete_at(idx) : messages.shift
|
|
95
|
+
end
|
|
96
|
+
|
|
80
97
|
# True if the message requires a previous update to have executed in
|
|
81
98
|
# order to work correctly.
|
|
82
99
|
#
|
|
100
|
+
# @param msg [Hash{String => Object}]
|
|
101
|
+
# @todo need boolish type from RBS
|
|
102
|
+
# @return [Object]
|
|
83
103
|
def version_dependent? msg
|
|
84
104
|
msg['textDocument'] && msg['position']
|
|
85
105
|
end
|