solargraph 0.55.4 → 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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/plugins.yml +2 -0
  3. data/.github/workflows/typecheck.yml +2 -0
  4. data/.gitignore +2 -0
  5. data/CHANGELOG.md +17 -0
  6. data/README.md +13 -3
  7. data/lib/solargraph/api_map/index.rb +23 -15
  8. data/lib/solargraph/api_map/store.rb +2 -1
  9. data/lib/solargraph/api_map.rb +53 -27
  10. data/lib/solargraph/complex_type/type_methods.rb +5 -1
  11. data/lib/solargraph/complex_type/unique_type.rb +7 -0
  12. data/lib/solargraph/convention/base.rb +3 -3
  13. data/lib/solargraph/convention.rb +3 -3
  14. data/lib/solargraph/doc_map.rb +200 -43
  15. data/lib/solargraph/gem_pins.rb +53 -38
  16. data/lib/solargraph/language_server/host.rb +9 -1
  17. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -0
  18. data/lib/solargraph/language_server/message/extended/document.rb +5 -2
  19. data/lib/solargraph/language_server/message/extended/document_gems.rb +3 -3
  20. data/lib/solargraph/library.rb +6 -3
  21. data/lib/solargraph/location.rb +13 -0
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +5 -8
  23. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +2 -2
  24. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +2 -2
  25. data/lib/solargraph/pin/base.rb +268 -24
  26. data/lib/solargraph/pin/base_variable.rb +9 -8
  27. data/lib/solargraph/pin/callable.rb +69 -0
  28. data/lib/solargraph/pin/closure.rb +12 -0
  29. data/lib/solargraph/pin/local_variable.rb +8 -5
  30. data/lib/solargraph/pin/method.rb +134 -17
  31. data/lib/solargraph/pin/parameter.rb +43 -6
  32. data/lib/solargraph/pin/signature.rb +38 -0
  33. data/lib/solargraph/pin_cache.rb +185 -0
  34. data/lib/solargraph/position.rb +9 -0
  35. data/lib/solargraph/range.rb +9 -0
  36. data/lib/solargraph/rbs_map/conversions.rb +19 -8
  37. data/lib/solargraph/rbs_map/core_map.rb +31 -9
  38. data/lib/solargraph/rbs_map/stdlib_map.rb +15 -5
  39. data/lib/solargraph/rbs_map.rb +74 -17
  40. data/lib/solargraph/shell.rb +16 -18
  41. data/lib/solargraph/source_map.rb +0 -17
  42. data/lib/solargraph/version.rb +1 -1
  43. data/lib/solargraph/views/_method.erb +10 -10
  44. data/lib/solargraph/views/_namespace.erb +3 -3
  45. data/lib/solargraph/views/document.erb +10 -10
  46. data/lib/solargraph/workspace.rb +15 -5
  47. data/lib/solargraph/yardoc.rb +6 -9
  48. data/lib/solargraph.rb +10 -12
  49. data/rbs_collection.yaml +19 -0
  50. data/solargraph.gemspec +1 -0
  51. metadata +19 -7
  52. data/lib/solargraph/cache.rb +0 -77
@@ -8,6 +8,7 @@ module Solargraph
8
8
  include Common
9
9
  include Conversions
10
10
  include Documenting
11
+ include Logging
11
12
 
12
13
  # @return [YARD::CodeObjects::Base]
13
14
  attr_reader :code_object
@@ -36,16 +37,273 @@ module Solargraph
36
37
  # @param closure [Solargraph::Pin::Closure, nil]
37
38
  # @param name [String]
38
39
  # @param comments [String]
39
- def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: ''
40
+ # @param source [Symbol, nil]
41
+ # @param docstring [YARD::Docstring, nil]
42
+ # @param directives [::Array<YARD::Tags::Directive>, nil]
43
+ def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', docstring: nil, directives: nil
40
44
  @location = location
41
45
  @type_location = type_location
42
46
  @closure = closure
43
47
  @name = name
44
48
  @comments = comments
45
49
  @source = source
50
+ @identity = nil
51
+ @docstring = docstring
52
+ @directives = directives
46
53
  assert_source_provided
47
54
  end
48
55
 
56
+ # @param other [self]
57
+ # @param attrs [Hash{Symbol => Object}]
58
+ #
59
+ # @return [self]
60
+ def combine_with(other, attrs={})
61
+ raise "tried to combine #{other.class} with #{self.class}" unless other.class == self.class
62
+ type_location = choose(other, :type_location)
63
+ location = choose(other, :location)
64
+ combined_name = combine_name(other)
65
+ new_attrs = {
66
+ location: location,
67
+ type_location: type_location,
68
+ name: combined_name,
69
+ closure: choose_pin_attr_with_same_name(other, :closure),
70
+ comments: choose_longer(other, :comments),
71
+ source: :combined,
72
+ docstring: choose(other, :docstring),
73
+ directives: combine_directives(other),
74
+ }.merge(attrs)
75
+ assert_same_macros(other)
76
+ logger.debug { "Base#combine_with(path=#{path}) - other.comments=#{other.comments.inspect}, self.comments = #{self.comments}" }
77
+ out = self.class.new(**new_attrs)
78
+ out.reset_generated!
79
+ out
80
+ end
81
+
82
+ # @param other [self]
83
+ # @param attr [::Symbol]
84
+ # @sg-ignore
85
+ # @return [undefined]
86
+ def choose_longer(other, attr)
87
+ # @type [undefined]
88
+ val1 = send(attr)
89
+ # @type [undefined]
90
+ val2 = other.send(attr)
91
+ return val1 if val1 == val2
92
+ return val2 if val1.nil?
93
+ # @sg-ignore
94
+ val1.length > val2.length ? val1 : val2
95
+ end
96
+
97
+ # @param other [self]
98
+ # @return [::Array<YARD::Tags::Directive>, nil]
99
+ def combine_directives(other)
100
+ return self.directives if other.directives.empty?
101
+ return other.directives if directives.empty?
102
+ [directives + other.directives].uniq
103
+ end
104
+
105
+ # @param other [self]
106
+ # @return [String]
107
+ def combine_name(other)
108
+ if needs_consistent_name? || other.needs_consistent_name?
109
+ assert_same(other, :name)
110
+ else
111
+ choose(other, :name)
112
+ end
113
+ end
114
+
115
+ # @return [void]
116
+ def reset_generated!
117
+ # @return_type doesn't go here as subclasses tend to assign it
118
+ # themselves in constructors, and they will deal with setting
119
+ # it in any methods that call this
120
+ #
121
+ # @docstring also doesn't go here, as there is code which
122
+ # directly manipulates docstring without editing comments
123
+ # (e.g., Api::Map::Store#index processes overrides that way
124
+ #
125
+ # Same with @directives, @macros, @maybe_directives, which
126
+ # regenerate docstring
127
+ @deprecated = nil
128
+ reset_conversions
129
+ end
130
+
131
+ def needs_consistent_name?
132
+ true
133
+ end
134
+
135
+ # @sg-ignore def should infer as symbol - "Not enough arguments to Module#protected"
136
+ protected def equality_fields
137
+ [name, location, type_location, closure, source]
138
+ end
139
+
140
+ # @param other [self]
141
+ # @return [ComplexType]
142
+ def combine_return_type(other)
143
+ if return_type.undefined?
144
+ other.return_type
145
+ elsif other.return_type.undefined?
146
+ return_type
147
+ elsif dodgy_return_type_source? && !other.dodgy_return_type_source?
148
+ other.return_type
149
+ elsif other.dodgy_return_type_source? && !dodgy_return_type_source?
150
+ return_type
151
+ else
152
+ all_items = return_type.items + other.return_type.items
153
+ if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_tag == context.rooted_tag }
154
+ # assume this was a declaration that should have said 'self'
155
+ all_items.delete_if { |item| item.rooted_tag == context.rooted_tag }
156
+ end
157
+ ComplexType.new(all_items)
158
+ end
159
+ end
160
+
161
+ def dodgy_return_type_source?
162
+ # uses a lot of 'Object' instead of 'self'
163
+ location&.filename&.include?('core_ext/object/')
164
+ end
165
+
166
+ # when choices are arbitrary, make sure the choice is consistent
167
+ #
168
+ # @param other [Pin::Base]
169
+ # @param attr [::Symbol]
170
+ #
171
+ # @return [Object, nil]
172
+ def choose(other, attr)
173
+ results = [self, other].map(&attr).compact
174
+ # true and false are different classes and can't be sorted
175
+ return true if results.any? { |r| r == true || r == false }
176
+ results.min
177
+ rescue
178
+ STDERR.puts("Problem handling #{attr} for \n#{self.inspect}\n and \n#{other.inspect}\n\n#{self.send(attr).inspect} vs #{other.send(attr).inspect}")
179
+ raise
180
+ end
181
+
182
+ # @param other [self]
183
+ # @param attr [Symbol]
184
+ # @sg-ignore
185
+ # @return [undefined]
186
+ def choose_node(other, attr)
187
+ if other.object_id < attr.object_id
188
+ other.send(attr)
189
+ else
190
+ send(attr)
191
+ end
192
+ end
193
+
194
+ # @param other [self]
195
+ # @param attr [::Symbol]
196
+ # @sg-ignore
197
+ # @return [undefined]
198
+ def prefer_rbs_location(other, attr)
199
+ if rbs_location? && !other.rbs_location?
200
+ self.send(attr)
201
+ elsif !rbs_location? && other.rbs_location?
202
+ other.send(attr)
203
+ else
204
+ choose(other, attr)
205
+ end
206
+ end
207
+
208
+ def rbs_location?
209
+ type_location&.rbs?
210
+ end
211
+
212
+ # @param other [self]
213
+ # @return [void]
214
+ def assert_same_macros(other)
215
+ return unless self.source == :yardoc && other.source == :yardoc
216
+ assert_same_count(other, :macros)
217
+ assert_same_array_content(other, :macros) { |macro| macro.tag.name }
218
+ end
219
+
220
+ # @param other [self]
221
+ # @param attr [::Symbol]
222
+ # @return [void]
223
+ # @todo strong typechecking should complain when there are no block-related tags
224
+ def assert_same_array_content(other, attr, &block)
225
+ arr1 = send(attr)
226
+ raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable)
227
+ # @type arr1 [::Enumerable]
228
+ arr2 = other.send(attr)
229
+ raise "Expected #{attr} on #{other} to be an Enumerable, got #{arr2.class}" unless arr2.is_a?(::Enumerable)
230
+ # @type arr2 [::Enumerable]
231
+
232
+ # @sg-ignore
233
+ # @type [undefined]
234
+ values1 = arr1.map(&block)
235
+ # @type [undefined]
236
+ values2 = arr2.map(&block)
237
+ # @sg-ignore
238
+ return arr1 if values1 == values2
239
+ Solargraph.assert_or_log("combine_with_#{attr}".to_sym,
240
+ "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self values = #{values1}\nother values =#{attr} = #{values2}")
241
+ arr1
242
+ end
243
+
244
+ # @param other [self]
245
+ # @param attr [::Symbol]
246
+ #
247
+ # @return [::Enumerable]
248
+ def assert_same_count(other, attr)
249
+ # @type [::Enumerable]
250
+ arr1 = self.send(attr)
251
+ raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable)
252
+ # @type [::Enumerable]
253
+ arr2 = other.send(attr)
254
+ raise "Expected #{attr} on #{other} to be an Enumerable, got #{arr2.class}" unless arr2.is_a?(::Enumerable)
255
+ return arr1 if arr1.count == arr2.count
256
+ Solargraph.assert_or_log("combine_with_#{attr}".to_sym,
257
+ "Inconsistent #{attr.inspect} count value between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{arr1.inspect}\nother.#{attr} = #{arr2.inspect}")
258
+ arr1
259
+ end
260
+
261
+ # @param other [self]
262
+ # @param attr [::Symbol]
263
+ #
264
+ # @return [Object, nil]
265
+ def assert_same(other, attr)
266
+ return false if other.nil?
267
+ val1 = send(attr)
268
+ val2 = other.send(attr)
269
+ return val1 if val1 == val2
270
+ Solargraph.assert_or_log("combine_with_#{attr}".to_sym,
271
+ "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}")
272
+ val1
273
+ end
274
+
275
+ # @param other [self]
276
+ # @param attr [::Symbol]
277
+ # @sg-ignore
278
+ # @return [undefined]
279
+ def choose_pin_attr_with_same_name(other, attr)
280
+ # @type [Pin::Base, nil]
281
+ val1 = send(attr)
282
+ # @type [Pin::Base, nil]
283
+ val2 = other.send(attr)
284
+ raise "Expected pin for #{attr} on\n#{self.inspect},\ngot #{val1.inspect}" unless val1.nil? || val1.is_a?(Pin::Base)
285
+ raise "Expected pin for #{attr} on\n#{other.inspect},\ngot #{val2.inspect}" unless val2.nil? || val2.is_a?(Pin::Base)
286
+ if val1&.name != val2&.name
287
+ Solargraph.assert_or_log("combine_with_#{attr}_name".to_sym,
288
+ "Inconsistent #{attr.inspect} name values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}")
289
+ end
290
+ choose_pin_attr(other, attr)
291
+ end
292
+
293
+ def choose_pin_attr(other, attr)
294
+ # @type [Pin::Base, nil]
295
+ val1 = send(attr)
296
+ # @type [Pin::Base, nil]
297
+ val2 = other.send(attr)
298
+ if val1.class != val2.class
299
+ Solargraph.assert_or_log("combine_with_#{attr}_class".to_sym,
300
+ "Inconsistent #{attr.inspect} class values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}")
301
+ return val1
302
+ end
303
+ # arbitrary way of choosing a pin
304
+ [val1, val2].compact.min_by { _1.best_location.to_s }
305
+ end
306
+
49
307
  def assert_source_provided
50
308
  Solargraph.assert_or_log(:source, "source not provided - #{@path} #{@source} #{self.class}") if source.nil?
51
309
  end
@@ -144,6 +402,7 @@ module Solargraph
144
402
  # Pin equality is determined using the #nearly? method and also
145
403
  # requiring both pins to have the same location.
146
404
  #
405
+ # @param other [self]
147
406
  def == other
148
407
  return false unless nearly? other
149
408
  comments == other.comments && location == other.location
@@ -158,13 +417,13 @@ module Solargraph
158
417
 
159
418
  # @return [YARD::Docstring]
160
419
  def docstring
161
- parse_comments unless defined?(@docstring)
420
+ parse_comments unless @docstring
162
421
  @docstring ||= Solargraph::Source.parse_docstring('').to_docstring
163
422
  end
164
423
 
165
424
  # @return [::Array<YARD::Tags::Directive>]
166
425
  def directives
167
- parse_comments unless defined?(@directives)
426
+ parse_comments unless @directives
168
427
  @directives
169
428
  end
170
429
 
@@ -182,7 +441,7 @@ module Solargraph
182
441
  #
183
442
  # @return [Boolean]
184
443
  def maybe_directives?
185
- return !@directives.empty? if defined?(@directives)
444
+ return !@directives.empty? if defined?(@directives) && @directives
186
445
  @maybe_directives ||= comments.include?('@!')
187
446
  end
188
447
 
@@ -221,26 +480,6 @@ module Solargraph
221
480
  probe api_map
222
481
  end
223
482
 
224
- # Try to merge data from another pin. Merges are only possible if the
225
- # pins are near matches (see the #nearly? method). The changes should
226
- # not have any side effects on the API surface.
227
- #
228
- # @param pin [Pin::Base] The pin to merge into this one
229
- # @return [Boolean] True if the pins were merged
230
- def try_merge! pin
231
- return false unless nearly?(pin)
232
- @location = pin.location
233
- @closure = pin.closure
234
- return true if comments == pin.comments
235
- @comments = pin.comments
236
- @docstring = pin.docstring
237
- @return_type = pin.return_type
238
- @documentation = nil
239
- @deprecated = nil
240
- reset_conversions
241
- true
242
- end
243
-
244
483
  def proxied?
245
484
  @proxied ||= false
246
485
  end
@@ -313,6 +552,7 @@ module Solargraph
313
552
  "[#{inner_desc}]"
314
553
  end
315
554
 
555
+ # @return [String]
316
556
  def inspect
317
557
  "#<#{self.class} `#{self.inner_desc}`#{all_location_text} via #{source.inspect}>"
318
558
  end
@@ -343,6 +583,10 @@ module Solargraph
343
583
  # @return [ComplexType]
344
584
  attr_writer :return_type
345
585
 
586
+ attr_writer :docstring
587
+
588
+ attr_writer :directives
589
+
346
590
  private
347
591
 
348
592
  # @return [void]
@@ -21,6 +21,15 @@ module Solargraph
21
21
  @return_type = return_type
22
22
  end
23
23
 
24
+ def combine_with(other, attrs={})
25
+ attrs.merge({
26
+ assignment: assert_same(other, :assignment),
27
+ mass_assignment: assert_same(other, :mass_assignment),
28
+ return_type: combine_return_type(other),
29
+ })
30
+ super(other, attrs)
31
+ end
32
+
24
33
  def completion_item_kind
25
34
  Solargraph::LanguageServer::CompletionItemKinds::VARIABLE
26
35
  end
@@ -99,14 +108,6 @@ module Solargraph
99
108
  assignment == other.assignment
100
109
  end
101
110
 
102
- # @param pin [self]
103
- def try_merge! pin
104
- return false unless super
105
- @assignment = pin.assignment
106
- @return_type = pin.return_type
107
- true
108
- end
109
-
110
111
  def type_desc
111
112
  "#{super} = #{assignment&.type.inspect}"
112
113
  end
@@ -25,11 +25,63 @@ module Solargraph
25
25
  closure.namespace
26
26
  end
27
27
 
28
+ def combine_blocks(other)
29
+ if block.nil?
30
+ other.block
31
+ elsif other.block.nil?
32
+ block
33
+ else
34
+ choose_pin_attr(other, :block)
35
+ end
36
+ end
37
+
38
+ # @param other [self]
39
+ # @param attrs [Hash{Symbol => Object}]
40
+ #
41
+ # @return [self]
42
+ def combine_with(other, attrs={})
43
+ new_attrs = {
44
+ block: combine_blocks(other),
45
+ return_type: combine_return_type(other),
46
+ }.merge(attrs)
47
+ new_attrs[:parameters] = choose_parameters(other).clone.freeze unless new_attrs.key?(:parameters)
48
+ super(other, new_attrs)
49
+ end
50
+
28
51
  # @return [::Array<String>]
29
52
  def parameter_names
30
53
  @parameter_names ||= parameters.map(&:name)
31
54
  end
32
55
 
56
+ def generics
57
+ []
58
+ end
59
+
60
+ def choose_parameters(other)
61
+ raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{self.arity}, \nother.arity=#{other.arity}" if other.arity != arity
62
+ parameters.zip(other.parameters).map do |param, other_param|
63
+ if param.nil? && other_param.block?
64
+ other_param
65
+ elsif other_param.nil? && param.block?
66
+ param
67
+ else
68
+ param.combine_with(other_param)
69
+ end
70
+ end
71
+ end
72
+
73
+ def blockless_parameters
74
+ if parameters.last&.block?
75
+ parameters[0..-2]
76
+ else
77
+ parameters
78
+ end
79
+ end
80
+
81
+ def arity
82
+ [generics, blockless_parameters.map(&:arity_decl), block&.arity]
83
+ end
84
+
33
85
  # @param generics_to_resolve [Enumerable<String>]
34
86
  # @param arg_types [Array<ComplexType>, nil]
35
87
  # @param return_type_context [ComplexType, nil]
@@ -61,6 +113,23 @@ module Solargraph
61
113
  callable
62
114
  end
63
115
 
116
+ def typify api_map
117
+ type = super
118
+ return type if type.defined?
119
+ if method_name.end_with?('?')
120
+ logger.debug { "Callable#typify(self=#{self}) => Boolean (? suffix)" }
121
+ ComplexType::BOOLEAN
122
+ else
123
+ logger.debug { "Callable#typify(self=#{self}) => undefined" }
124
+ ComplexType::UNDEFINED
125
+ end
126
+ end
127
+
128
+ def method_name
129
+ raise "closure was nil in #{self.inspect}" if closure.nil?
130
+ @method_name ||= closure.name
131
+ end
132
+
64
133
  # @param generics_to_resolve [::Array<String>]
65
134
  # @param arg_types [Array<ComplexType>, nil]
66
135
  # @param return_type_context [ComplexType, nil]
@@ -19,6 +19,18 @@ module Solargraph
19
19
  @generic_defaults ||= {}
20
20
  end
21
21
 
22
+ # @param other [self]
23
+ # @param attrs [Hash{Symbol => Object}]
24
+ #
25
+ # @return [self]
26
+ def combine_with(other, attrs={})
27
+ new_attrs = {
28
+ scope: assert_same(other, :scope),
29
+ generics: generics.empty? ? other.generics : generics,
30
+ }.merge(attrs)
31
+ super(other, new_attrs)
32
+ end
33
+
22
34
  def context
23
35
  @context ||= begin
24
36
  result = super
@@ -21,11 +21,14 @@ module Solargraph
21
21
  @presence_certain = presence_certain
22
22
  end
23
23
 
24
- # @param pin [self]
25
- def try_merge! pin
26
- return false unless super
27
- @presence = pin.presence
28
- true
24
+ def combine_with(other, attrs={})
25
+ new_attrs = {
26
+ assignment: assert_same(other, :assignment),
27
+ presence_certain: assert_same(other, :presence_certain?),
28
+ }.merge(attrs)
29
+ new_attrs[:presence] = assert_same(other, :presence) unless attrs.key?(:presence)
30
+
31
+ super(other, new_attrs)
29
32
  end
30
33
 
31
34
  # @param other_closure [Pin::Closure]