solargraph 0.53.0 → 0.54.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/source_to_yard.rb +17 -10
  5. data/lib/solargraph/api_map/store.rb +11 -6
  6. data/lib/solargraph/api_map.rb +80 -38
  7. data/lib/solargraph/bench.rb +1 -0
  8. data/lib/solargraph/complex_type/type_methods.rb +62 -30
  9. data/lib/solargraph/complex_type/unique_type.rb +149 -75
  10. data/lib/solargraph/complex_type.rb +41 -25
  11. data/lib/solargraph/doc_map.rb +65 -24
  12. data/lib/solargraph/gem_pins.rb +10 -2
  13. data/lib/solargraph/language_server/host/dispatch.rb +13 -2
  14. data/lib/solargraph/language_server/host/sources.rb +1 -61
  15. data/lib/solargraph/language_server/host.rb +41 -69
  16. data/lib/solargraph/language_server/message/base.rb +1 -1
  17. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  18. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  19. data/lib/solargraph/language_server/progress.rb +118 -0
  20. data/lib/solargraph/language_server.rb +1 -0
  21. data/lib/solargraph/library.rb +149 -97
  22. data/lib/solargraph/page.rb +6 -0
  23. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  24. data/lib/solargraph/parser/node_processor.rb +1 -0
  25. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  26. data/lib/solargraph/parser/parser_gem/node_methods.rb +0 -4
  27. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -19
  28. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +5 -3
  30. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  31. data/lib/solargraph/pin/base_variable.rb +35 -6
  32. data/lib/solargraph/pin/block.rb +70 -32
  33. data/lib/solargraph/pin/delegated_method.rb +5 -1
  34. data/lib/solargraph/pin/documenting.rb +2 -0
  35. data/lib/solargraph/pin/method.rb +4 -2
  36. data/lib/solargraph/pin/parameter.rb +5 -28
  37. data/lib/solargraph/rbs_map/conversions.rb +16 -47
  38. data/lib/solargraph/rbs_map/core_fills.rb +12 -6
  39. data/lib/solargraph/rbs_map/core_map.rb +2 -13
  40. data/lib/solargraph/rbs_map.rb +17 -7
  41. data/lib/solargraph/shell.rb +18 -13
  42. data/lib/solargraph/source/chain.rb +20 -0
  43. data/lib/solargraph/source/updater.rb +1 -0
  44. data/lib/solargraph/source.rb +0 -44
  45. data/lib/solargraph/source_map/clip.rb +3 -2
  46. data/lib/solargraph/source_map/mapper.rb +3 -2
  47. data/lib/solargraph/source_map.rb +8 -6
  48. data/lib/solargraph/type_checker.rb +58 -13
  49. data/lib/solargraph/version.rb +1 -1
  50. data/lib/solargraph/workspace/config.rb +2 -1
  51. data/lib/solargraph/workspace.rb +27 -1
  52. data/lib/solargraph/yard_map/mapper/to_method.rb +5 -2
  53. metadata +4 -4
  54. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
  55. data/lib/solargraph/rbs_map/core_signs.rb +0 -35
@@ -8,7 +8,7 @@ module Solargraph
8
8
  class UniqueType
9
9
  include TypeMethods
10
10
 
11
- attr_reader :all_params
11
+ attr_reader :all_params, :subtypes, :key_types
12
12
 
13
13
  # Create a UniqueType with the specified name and an optional substring.
14
14
  # The substring is the parameter section of a parametrized type, e.g.,
@@ -17,43 +17,61 @@ module Solargraph
17
17
  #
18
18
  # @param name [String] The name of the type
19
19
  # @param substring [String] The substring of the type
20
- def initialize name, substring = ''
20
+ # @param make_rooted [Boolean, nil]
21
+ # @return [UniqueType]
22
+ def self.parse name, substring = '', make_rooted: nil
23
+ if name.start_with?(':::')
24
+ raise "Illegal prefix: #{name}"
25
+ end
21
26
  if name.start_with?('::')
22
- @name = name[2..-1]
23
- @rooted = true
27
+ name = name[2..-1]
28
+ rooted = true
24
29
  else
25
- @name = name
26
- @rooted = false
30
+ rooted = false
27
31
  end
28
- @substring = substring
29
- @tag = @name + substring
30
- # @type [Array<ComplexType>]
31
- @key_types = []
32
+ rooted = make_rooted unless make_rooted.nil?
33
+
32
34
  # @type [Array<ComplexType>]
33
- @subtypes = []
35
+ key_types = []
34
36
  # @type [Array<ComplexType>]
35
- @all_params = []
36
- return unless parameters?
37
- # @todo we should be able to probe the type of 'subs' without
38
- # hoisting the definition outside of the if statement
39
- subs = if @substring.start_with?('<(') && @substring.end_with?(')>')
40
- ComplexType.parse(substring[2..-3], partial: true)
41
- else
42
- ComplexType.parse(substring[1..-2], partial: true)
43
- end
44
- if hash_parameters?
45
- raise ComplexTypeError, "Bad hash type" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
46
- # @todo should be able to resolve map; both types have it
47
- # with same return type
48
- # @sg-ignore
49
- @key_types.concat subs[0].map { |u| ComplexType.new([u]) }
50
- # @sg-ignore
51
- @subtypes.concat subs[1].map { |u| ComplexType.new([u]) }
52
- else
53
- @subtypes.concat subs
37
+ subtypes = []
38
+ parameters_type = nil
39
+ unless substring.empty?
40
+ subs = ComplexType.parse(substring[1..-2], partial: true)
41
+ parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0])
42
+ if parameters_type == :hash
43
+ raise ComplexTypeError, "Bad hash type" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType)
44
+ # @todo should be able to resolve map; both types have it
45
+ # with same return type
46
+ # @sg-ignore
47
+ key_types.concat(subs[0].map { |u| ComplexType.new([u]) })
48
+ # @sg-ignore
49
+ subtypes.concat(subs[1].map { |u| ComplexType.new([u]) })
50
+ else
51
+ subtypes.concat subs
52
+ end
53
+ end
54
+ new(name, key_types, subtypes, rooted: rooted, parameters_type: parameters_type)
55
+ end
56
+
57
+ # @param name [String]
58
+ # @param key_types [Array<ComplexType>]
59
+ # @param subtypes [Array<ComplexType>]
60
+ # @param rooted [Boolean]
61
+ # @param parameters_type [Symbol, nil]
62
+ def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: nil)
63
+ if parameters_type.nil?
64
+ raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty?
54
65
  end
55
- @all_params.concat @key_types
56
- @all_params.concat @subtypes
66
+ raise "Please remove leading :: and set rooted instead - #{name}" if name.start_with?('::')
67
+ @name = name
68
+ @key_types = key_types
69
+ @subtypes = subtypes
70
+ @rooted = rooted
71
+ @all_params = []
72
+ @all_params.concat key_types
73
+ @all_params.concat subtypes
74
+ @parameters_type = parameters_type
57
75
  end
58
76
 
59
77
  def to_s
@@ -76,7 +94,17 @@ module Solargraph
76
94
 
77
95
  # @return [String]
78
96
  def to_rbs
79
- if ['Tuple', 'Array'].include?(name) && fixed_parameters?
97
+ if duck_type?
98
+ 'untyped'
99
+ elsif name == 'Boolean'
100
+ 'bool'
101
+ elsif name.downcase == 'nil'
102
+ 'nil'
103
+ elsif name == GENERIC_TAG_NAME
104
+ all_params.first.name
105
+ elsif ['Class', 'Module'].include?(name)
106
+ rbs_name
107
+ elsif ['Tuple', 'Array'].include?(name) && fixed_parameters?
80
108
  # tuples don't have a name; they're just [foo, bar, baz].
81
109
  if substring == '()'
82
110
  # but there are no zero element tuples, so we go with an array
@@ -90,28 +118,45 @@ module Solargraph
90
118
  end
91
119
  end
92
120
 
121
+ # @return [Boolean]
122
+ def parameters?
123
+ !all_params.empty?
124
+ end
125
+
126
+ # @param types [Array<UniqueType, ComplexType>]
127
+ # @return [String]
128
+ def rbs_union(types)
129
+ if types.length == 1
130
+ types.first.to_rbs
131
+ else
132
+ "(#{types.map(&:to_rbs).join(' | ')})"
133
+ end
134
+ end
135
+
93
136
  # @return [String]
94
137
  def parameters_as_rbs
95
- parameters? ? "[#{all_params.map { |s| s.to_rbs }.join(', ')}]" : ''
138
+ return '' unless parameters?
139
+
140
+ return "[#{all_params.map(&:to_rbs).join(', ')}]" if key_types.empty?
141
+
142
+ # handle, e.g., Hash[K, V] case
143
+ key_types_str = rbs_union(key_types)
144
+ subtypes_str = rbs_union(subtypes)
145
+ "[#{key_types_str}, #{subtypes_str}]"
96
146
  end
97
147
 
98
148
  def generic?
99
149
  name == GENERIC_TAG_NAME || all_params.any?(&:generic?)
100
150
  end
101
151
 
102
-
103
152
  # @param generics_to_resolve [Enumerable<String>]
104
153
  # @param context_type [UniqueType, nil]
105
154
  # @param resolved_generic_values [Hash{String => ComplexType}] Added to as types are encountered or resolved
106
155
  # @return [UniqueType, ComplexType]
107
156
  def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {}
108
- transform(name) do |t|
109
- next t unless t.name == ComplexType::GENERIC_TAG_NAME
110
-
111
- new_binding = false
112
-
113
- type_param = t.subtypes.first&.name
114
- next t unless generics_to_resolve.include? type_param
157
+ if name == ComplexType::GENERIC_TAG_NAME
158
+ type_param = subtypes.first&.name
159
+ return self unless generics_to_resolve.include? type_param
115
160
  unless context_type.nil? || !resolved_generic_values[type_param].nil?
116
161
  new_binding = true
117
162
  resolved_generic_values[type_param] = context_type
@@ -121,7 +166,34 @@ module Solargraph
121
166
  complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values)
122
167
  end
123
168
  end
124
- resolved_generic_values[type_param] || t
169
+ return resolved_generic_values[type_param] || self
170
+ end
171
+
172
+ # @todo typechecking should complain when the method being called has no @yieldparam tag
173
+ new_key_types = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:key_types)
174
+ new_subtypes = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:subtypes)
175
+ recreate(new_key_types: new_key_types, new_subtypes: new_subtypes)
176
+ end
177
+
178
+ # @param generics_to_resolve [Enumerable<String>]
179
+ # @param context_type [UniqueType]
180
+ # @param resolved_generic_values [Hash{String => ComplexType}]
181
+ # @yieldreturn [Array<ComplexType>]
182
+ # @return [Array<ComplexType>]
183
+ def resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values)
184
+ types = yield self
185
+ types.each_with_index.flat_map do |ct, i|
186
+ ct.items.flat_map do |ut|
187
+ context_params = yield context_type if context_type
188
+ if context_params && context_params[i]
189
+ type_arg = context_params[i]
190
+ type_arg.map do |new_unique_context_type|
191
+ ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, resolved_generic_values: resolved_generic_values
192
+ end
193
+ else
194
+ ut.resolve_generics_from_context generics_to_resolve, nil, resolved_generic_values: resolved_generic_values
195
+ end
196
+ end
125
197
  end
126
198
  end
127
199
 
@@ -159,39 +231,34 @@ module Solargraph
159
231
  end
160
232
 
161
233
  # @param new_name [String, nil]
234
+ # @param make_rooted [Boolean, nil]
162
235
  # @param new_key_types [Array<UniqueType>, nil]
236
+ # @param rooted [Boolean, nil]
163
237
  # @param new_subtypes [Array<UniqueType>, nil]
164
238
  # @return [self]
165
- def recreate(new_name: nil, new_key_types: nil, new_subtypes: nil)
239
+ def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil)
240
+ raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::')
166
241
  new_name ||= name
167
242
  new_key_types ||= @key_types
168
243
  new_subtypes ||= @subtypes
169
- if new_key_types.none?(&:defined?) && new_subtypes.none?(&:defined?)
170
- # if all subtypes are undefined, erase down to the non-parametric type
171
- UniqueType.new(new_name)
172
- elsif new_key_types.empty? && new_subtypes.empty?
173
- UniqueType.new(new_name)
174
- elsif hash_parameters?
175
- UniqueType.new(new_name, "{#{new_key_types.join(', ')} => #{new_subtypes.join(', ')}}")
176
- elsif @substring.start_with?('<(')
177
- # @todo This clause is probably wrong, and if so, fixing it
178
- # will be some level of breaking change. Probably best
179
- # handled before real tuple support is rolled out and
180
- # folks start relying on it more.
181
- #
182
- # (String) is a one element tuple in https://yardoc.org/types
183
- # <String> is an array of zero or more Strings in https://yardoc.org/types
184
- # Array<(String)> could be an Array of one-element tuples or a
185
- # one element tuple. https://yardoc.org/types treats it
186
- # as the former.
187
- # Array<(String), Integer> is not ambiguous if we accept
188
- # (String) as a tuple type, but not currently understood
189
- # by Solargraph.
190
- UniqueType.new(new_name, "<(#{new_subtypes.join(', ')})>")
191
- elsif fixed_parameters?
192
- UniqueType.new(new_name, "(#{new_subtypes.join(', ')})")
193
- else
194
- UniqueType.new(new_name, "<#{new_subtypes.join(', ')}>")
244
+ make_rooted = @rooted if make_rooted.nil?
245
+ UniqueType.new(new_name, new_key_types, new_subtypes, rooted: make_rooted, parameters_type: parameters_type)
246
+ end
247
+
248
+ # @return [String]
249
+ def rooted_tags
250
+ rooted_tag
251
+ end
252
+
253
+ # @return [String]
254
+ def tags
255
+ tag
256
+ end
257
+
258
+ # @return [self]
259
+ def force_rooted
260
+ transform do |t|
261
+ t.recreate(make_rooted: true)
195
262
  end
196
263
  end
197
264
 
@@ -202,9 +269,16 @@ module Solargraph
202
269
  # @yieldreturn [self]
203
270
  # @return [self]
204
271
  def transform(new_name = nil, &transform_type)
205
- new_key_types = @key_types.flat_map { |ct| ct.map { |ut| ut.transform(&transform_type) } }.compact
206
- new_subtypes = @subtypes.flat_map { |ct| ct.map { |ut| ut.transform(&transform_type) } }.compact
207
- new_type = recreate(new_name: new_name || rooted_name, new_key_types: new_key_types, new_subtypes: new_subtypes)
272
+ raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::')
273
+ if name == ComplexType::GENERIC_TAG_NAME
274
+ # doesn't make sense to manipulate the name of the generic
275
+ new_key_types = @key_types
276
+ new_subtypes = @subtypes
277
+ else
278
+ new_key_types = @key_types.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
279
+ new_subtypes = @subtypes.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
280
+ end
281
+ new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes)
208
282
  yield new_type
209
283
  end
210
284
 
@@ -222,8 +296,8 @@ module Solargraph
222
296
  @name == 'self' || @key_types.any?(&:selfy?) || @subtypes.any?(&:selfy?)
223
297
  end
224
298
 
225
- UNDEFINED = UniqueType.new('undefined')
226
- BOOLEAN = UniqueType.new('Boolean')
299
+ UNDEFINED = UniqueType.new('undefined', rooted: false)
300
+ BOOLEAN = UniqueType.new('Boolean', rooted: true)
227
301
  end
228
302
  end
229
303
  end
@@ -11,7 +11,7 @@ module Solargraph
11
11
  autoload :TypeMethods, 'solargraph/complex_type/type_methods'
12
12
  autoload :UniqueType, 'solargraph/complex_type/unique_type'
13
13
 
14
- # @param types [Array<[UniqueType, ComplexType]>]
14
+ # @param types [Array<UniqueType, ComplexType>]
15
15
  def initialize types = [UniqueType::UNDEFINED]
16
16
  # @todo @items here should not need an annotation
17
17
  # @type [Array<UniqueType>]
@@ -86,6 +86,10 @@ module Solargraph
86
86
  @items
87
87
  end
88
88
 
89
+ def tags
90
+ @items.map(&:tag).join(', ')
91
+ end
92
+
89
93
  # @param index [Integer]
90
94
  # @return [UniqueType]
91
95
  def [](index)
@@ -126,6 +130,10 @@ module Solargraph
126
130
  map(&:tag).join(', ')
127
131
  end
128
132
 
133
+ def rooted_tags
134
+ map(&:rooted_tag).join(', ')
135
+ end
136
+
129
137
  def all? &block
130
138
  @items.all? &block
131
139
  end
@@ -147,15 +155,23 @@ module Solargraph
147
155
  # @yieldreturn [UniqueType]
148
156
  # @return [ComplexType]
149
157
  def transform(new_name = nil, &transform_type)
158
+ raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::')
150
159
  ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) })
151
160
  end
152
161
 
162
+ # @return [self]
163
+ def force_rooted
164
+ transform do |t|
165
+ t.recreate(make_rooted: true)
166
+ end
167
+ end
168
+
153
169
  # @param definitions [Pin::Namespace, Pin::Method]
154
170
  # @param context_type [ComplexType]
155
171
  # @return [ComplexType]
156
172
  def resolve_generics definitions, context_type
157
173
  result = @items.map { |i| i.resolve_generics(definitions, context_type) }
158
- ComplexType.parse(*result.map(&:tag))
174
+ ComplexType.try_parse(*result.map(&:tag))
159
175
  end
160
176
 
161
177
  # @param dst [String]
@@ -164,7 +180,7 @@ module Solargraph
164
180
  return self unless selfy?
165
181
  red = reduce_class(dst)
166
182
  result = @items.map { |i| i.self_to red }
167
- ComplexType.parse(*result.map(&:tag))
183
+ ComplexType.try_parse(*result.map(&:tag))
168
184
  end
169
185
 
170
186
  def nullable?
@@ -190,21 +206,6 @@ module Solargraph
190
206
  @items.all?(&:bot?)
191
207
  end
192
208
 
193
- private
194
-
195
- # @todo This is a quick and dirty hack that forces `self` keywords
196
- # to reference an instance of their class and never the class itself.
197
- # This behavior may change depending on which result is expected
198
- # from YARD conventions. See https://github.com/lsegal/yard/issues/1257
199
- # @param dst [String]
200
- # @return [String]
201
- def reduce_class dst
202
- while dst =~ /^(Class|Module)\<(.*?)\>$/
203
- dst = dst.sub(/^(Class|Module)\</, '').sub(/\>$/, '')
204
- end
205
- dst
206
- end
207
-
208
209
  class << self
209
210
  # Parse type strings into a ComplexType.
210
211
  #
@@ -250,7 +251,7 @@ module Solargraph
250
251
  elsif base.end_with?('=')
251
252
  raise ComplexTypeError, "Invalid hash thing" unless key_types.nil?
252
253
  # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)])
253
- types.push UniqueType.new(base[0..-2].strip)
254
+ types.push UniqueType.parse(base[0..-2].strip, subtype_string)
254
255
  # @todo this should either expand key_type's type
255
256
  # automatically or complain about not being
256
257
  # compatible with key_type's type in type checking
@@ -281,7 +282,7 @@ module Solargraph
281
282
  next
282
283
  elsif char == ',' && point_stack == 0 && curly_stack == 0 && paren_stack == 0
283
284
  # types.push ComplexType.new([UniqueType.new(base.strip, subtype_string.strip)])
284
- types.push UniqueType.new(base.strip, subtype_string.strip)
285
+ types.push UniqueType.parse(base.strip, subtype_string.strip)
285
286
  base.clear
286
287
  subtype_string.clear
287
288
  next
@@ -294,7 +295,7 @@ module Solargraph
294
295
  end
295
296
  raise ComplexTypeError, "Unclosed subtype in #{type_string}" if point_stack != 0 || curly_stack != 0 || paren_stack != 0
296
297
  # types.push ComplexType.new([UniqueType.new(base, subtype_string)])
297
- types.push UniqueType.new(base.strip, subtype_string.strip)
298
+ types.push UniqueType.parse(base.strip, subtype_string.strip)
298
299
  end
299
300
  unless key_types.nil?
300
301
  raise ComplexTypeError, "Invalid use of key/value parameters" unless partial
@@ -311,18 +312,33 @@ module Solargraph
311
312
  def try_parse *strings
312
313
  parse *strings
313
314
  rescue ComplexTypeError => e
314
- Solargraph.logger.info "Error parsing complex type: #{e.message}"
315
+ Solargraph.logger.info "Error parsing complex type `#{strings.join(', ')}`: #{e.message}"
315
316
  ComplexType::UNDEFINED
316
317
  end
317
318
  end
318
319
 
319
320
  VOID = ComplexType.parse('void')
320
321
  UNDEFINED = ComplexType.parse('undefined')
321
- SYMBOL = ComplexType.parse('Symbol')
322
- ROOT = ComplexType.parse('Class<>')
322
+ SYMBOL = ComplexType.parse('::Symbol')
323
+ ROOT = ComplexType.parse('::Class<>')
323
324
  NIL = ComplexType.parse('nil')
324
325
  SELF = ComplexType.parse('self')
325
- BOOLEAN = ComplexType.parse('Boolean')
326
+ BOOLEAN = ComplexType.parse('::Boolean')
326
327
  BOT = ComplexType.parse('bot')
328
+
329
+ private
330
+
331
+ # @todo This is a quick and dirty hack that forces `self` keywords
332
+ # to reference an instance of their class and never the class itself.
333
+ # This behavior may change depending on which result is expected
334
+ # from YARD conventions. See https://github.com/lsegal/yard/issues/1257
335
+ # @param dst [String]
336
+ # @return [String]
337
+ def reduce_class dst
338
+ while dst =~ /^(Class|Module)\<(.*?)\>$/
339
+ dst = dst.sub(/^(Class|Module)\</, '').sub(/\>$/, '')
340
+ end
341
+ dst
342
+ end
327
343
  end
328
344
  end
@@ -8,7 +8,7 @@ module Solargraph
8
8
  attr_reader :requires
9
9
 
10
10
  # @return [Array<Gem::Specification>]
11
- attr_reader :dependencies
11
+ attr_reader :preferences
12
12
 
13
13
  # @return [Array<Pin::Base>]
14
14
  attr_reader :pins
@@ -17,10 +17,12 @@ module Solargraph
17
17
  attr_reader :uncached_gemspecs
18
18
 
19
19
  # @param requires [Array<String>]
20
- # @param dependencies [Array<Gem::Specification>]
21
- def initialize(requires, dependencies)
22
- @requires = requires
23
- @dependencies = dependencies
20
+ # @param preferences [Array<Gem::Specification>]
21
+ # @param rbs_path [String, Pathname, nil]
22
+ def initialize(requires, preferences, rbs_path = nil)
23
+ @requires = requires.compact
24
+ @preferences = preferences.compact
25
+ @rbs_path = rbs_path
24
26
  generate
25
27
  end
26
28
 
@@ -39,17 +41,12 @@ module Solargraph
39
41
  @gems_in_memory ||= {}
40
42
  end
41
43
 
42
- private
43
-
44
- # @return [Hash{String => Gem::Specification, nil}]
45
- def required_gem_map
46
- @required_gem_map ||= requires.to_h { |path| [path, resolve_path_to_gemspec(path)] }
44
+ # @return [Set<Gem::Specification>]
45
+ def dependencies
46
+ @dependencies ||= (gemspecs.flat_map { |spec| fetch_dependencies(spec) } - gemspecs).to_set
47
47
  end
48
48
 
49
- # @return [Hash{String => Gem::Specification}]
50
- def dependency_map
51
- @dependency_map ||= dependencies.to_h { |gemspec| [gemspec.name, gemspec] }
52
- end
49
+ private
53
50
 
54
51
  # @return [void]
55
52
  def generate
@@ -62,6 +59,18 @@ module Solargraph
62
59
  try_stdlib_map path
63
60
  end
64
61
  end
62
+ dependencies.each { |dep| try_cache dep }
63
+ @uncached_gemspecs.uniq!
64
+ end
65
+
66
+ # @return [Hash{String => Gem::Specification, nil}]
67
+ def required_gem_map
68
+ @required_gem_map ||= requires.to_h { |path| [path, resolve_path_to_gemspec(path)] }
69
+ end
70
+
71
+ # @return [Hash{String => Gem::Specification}]
72
+ def preference_map
73
+ @preference_map ||= preferences.to_h { |gemspec| [gemspec.name, gemspec] }
65
74
  end
66
75
 
67
76
  # @param gemspec [Gem::Specification]
@@ -70,7 +79,8 @@ module Solargraph
70
79
  return if try_gem_in_memory(gemspec)
71
80
  cache_file = File.join('gems', "#{gemspec.name}-#{gemspec.version}.ser")
72
81
  if Cache.exist?(cache_file)
73
- gempins = Cache.load(cache_file)
82
+ cached = Cache.load(cache_file)
83
+ gempins = update_from_collection(gemspec, cached)
74
84
  self.class.gems_in_memory[gemspec] = gempins
75
85
  @pins.concat gempins
76
86
  else
@@ -88,7 +98,7 @@ module Solargraph
88
98
  @pins.concat map.pins
89
99
  else
90
100
  # @todo Temporarily ignoring unresolved `require 'set'`
91
- Solargraph.logger.warn "Require path #{path} could not be resolved" unless path == 'set'
101
+ Solargraph.logger.debug "Require path #{path} could not be resolved" unless path == 'set'
92
102
  end
93
103
  end
94
104
 
@@ -102,9 +112,22 @@ module Solargraph
102
112
  true
103
113
  end
104
114
 
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
+
105
126
  # @param path [String]
106
127
  # @return [Gem::Specification, nil]
107
128
  def resolve_path_to_gemspec path
129
+ return nil if path.empty?
130
+
108
131
  gemspec = Gem::Specification.find_by_path(path)
109
132
  if gemspec.nil?
110
133
  gem_name_guess = path.split('/').first
@@ -121,16 +144,16 @@ module Solargraph
121
144
  nil
122
145
  end
123
146
  end
124
- return gemspec if dependencies.empty? || gemspec.nil?
147
+ gemspec_or_preference gemspec
148
+ end
125
149
 
126
- if dependency_map.key?(gemspec.name)
127
- return gemspec if gemspec.version == dependency_map[gemspec.name].version
150
+ # @param gemspec [Gem::Specification, nil]
151
+ # @return [Gem::Specification, nil]
152
+ def gemspec_or_preference gemspec
153
+ return gemspec unless gemspec && preference_map.key?(gemspec.name)
154
+ return gemspec if gemspec.version == preference_map[gemspec.name].version
128
155
 
129
- change_gemspec_version gemspec, dependency_map[by_path.name].version
130
- else
131
- Solargraph.logger.warn "Gem #{gemspec.name} is not an expected dependency"
132
- gemspec
133
- end
156
+ change_gemspec_version gemspec, preference_map[by_path.name].version
134
157
  end
135
158
 
136
159
  # @param gemspec [Gem::Specification]
@@ -142,5 +165,23 @@ module Solargraph
142
165
  Solargraph.logger.info "Gem #{gemspec.name} version #{version} not found. Using #{gemspec.version} instead"
143
166
  gemspec
144
167
  end
168
+
169
+ # @param gemspec [Gem::Specification]
170
+ # @return [Array<Gem::Specification>]
171
+ def fetch_dependencies gemspec
172
+ only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
173
+ Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
174
+ dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
175
+ deps.merge fetch_dependencies(dep) if deps.add?(dep)
176
+ rescue Gem::MissingSpecError
177
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
178
+ end.to_a
179
+ end
180
+
181
+ # @param gemspec [Gem::Specification]
182
+ # @return [Array<Gem::Dependency>]
183
+ def only_runtime_dependencies gemspec
184
+ gemspec.dependencies - gemspec.development_dependencies
185
+ end
145
186
  end
146
187
  end
@@ -16,13 +16,21 @@ module Solargraph
16
16
  def self.build(gemspec)
17
17
  yard_pins = build_yard_pins(gemspec)
18
18
  rbs_map = RbsMap.from_gemspec(gemspec)
19
+ combine yard_pins, rbs_map
20
+ end
21
+
22
+ # @param yard_pins [Array<Pin::Base>]
23
+ # @param rbs_map [RbsMap]
24
+ # @return [Array<Pin::Base>]
25
+ def self.combine(yard_pins, rbs_map)
19
26
  in_yard = Set.new
20
27
  combined = yard_pins.map do |yard|
21
28
  in_yard.add yard.path
22
29
  next yard unless yard.is_a?(Pin::Method)
23
- rbs = rbs_map.path_pin(yard.path)
30
+
31
+ rbs = rbs_map.path_pin(yard.path, Pin::Method)
24
32
  next yard unless rbs
25
- # @todo Could not include: attribute and anon_splat
33
+
26
34
  # @sg-ignore
27
35
  yard.class.new(
28
36
  location: yard.location,
@@ -34,7 +34,7 @@ module Solargraph
34
34
  def update_libraries uri
35
35
  src = sources.find(uri)
36
36
  using = libraries.select { |lib| lib.contain?(src.filename) }
37
- using.push generic_library_for(uri) if using.empty?
37
+ using.push library_for(uri) if using.empty?
38
38
  using.each { |lib| lib.merge src }
39
39
  diagnoser.schedule uri
40
40
  end
@@ -95,6 +95,10 @@ module Solargraph
95
95
  nil
96
96
  end
97
97
 
98
+ def options
99
+ @options ||= {}.freeze
100
+ end
101
+
98
102
  # Get a generic library for the given URI and attach the corresponding
99
103
  # source.
100
104
  #
@@ -109,7 +113,14 @@ module Solargraph
109
113
 
110
114
  # @return [Library]
111
115
  def generic_library
112
- @generic_library ||= Solargraph::Library.new
116
+ @generic_library ||= Solargraph::Library.new(Solargraph::Workspace.new('', nil, options), nil)
117
+ .tap { |lib| lib.add_observer self }
118
+ end
119
+
120
+ # @param library [Solargraph::Library]
121
+ # @return [void]
122
+ def update progress
123
+ progress&.send(self)
113
124
  end
114
125
  end
115
126
  end