solargraph 0.39.17 → 0.40.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -8
  3. data/CHANGELOG.md +1003 -0
  4. data/SPONSORS.md +1 -0
  5. data/lib/solargraph.rb +2 -4
  6. data/lib/solargraph/api_map.rb +61 -64
  7. data/lib/solargraph/api_map/cache.rb +2 -2
  8. data/lib/solargraph/api_map/store.rb +8 -7
  9. data/lib/solargraph/{bundle.rb → bench.rb} +6 -2
  10. data/lib/solargraph/compat.rb +14 -0
  11. data/lib/solargraph/convention.rb +13 -4
  12. data/lib/solargraph/convention/base.rb +16 -8
  13. data/lib/solargraph/convention/gemfile.rb +2 -5
  14. data/lib/solargraph/convention/gemspec.rb +3 -6
  15. data/lib/solargraph/convention/rspec.rb +3 -6
  16. data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -20
  17. data/lib/solargraph/environ.rb +11 -6
  18. data/lib/solargraph/language_server/host.rb +5 -0
  19. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +6 -1
  20. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  21. data/lib/solargraph/language_server/message/text_document/formatting.rb +47 -20
  22. data/lib/solargraph/library.rb +6 -8
  23. data/lib/solargraph/parser/legacy/node_methods.rb +9 -0
  24. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +1 -1
  25. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +34 -22
  26. data/lib/solargraph/parser/node_processor/base.rb +3 -0
  27. data/lib/solargraph/parser/rubyvm/node_methods.rb +18 -1
  28. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +12 -12
  29. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +1 -1
  30. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +1 -6
  31. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +38 -28
  32. data/lib/solargraph/pin.rb +0 -3
  33. data/lib/solargraph/pin/common.rb +1 -1
  34. data/lib/solargraph/pin/conversions.rb +1 -1
  35. data/lib/solargraph/pin/documenting.rb +3 -9
  36. data/lib/solargraph/pin/method.rb +141 -7
  37. data/lib/solargraph/pin/method_alias.rb +1 -1
  38. data/lib/solargraph/position.rb +2 -14
  39. data/lib/solargraph/source.rb +11 -7
  40. data/lib/solargraph/source/chain.rb +3 -3
  41. data/lib/solargraph/source/source_chainer.rb +1 -1
  42. data/lib/solargraph/source_map.rb +4 -1
  43. data/lib/solargraph/source_map/clip.rb +3 -2
  44. data/lib/solargraph/source_map/mapper.rb +10 -6
  45. data/lib/solargraph/type_checker.rb +52 -46
  46. data/lib/solargraph/type_checker/checks.rb +5 -1
  47. data/lib/solargraph/type_checker/param_def.rb +1 -1
  48. data/lib/solargraph/type_checker/rules.rb +5 -1
  49. data/lib/solargraph/version.rb +1 -1
  50. data/lib/solargraph/workspace/config.rb +15 -0
  51. data/lib/solargraph/yard_map.rb +38 -47
  52. data/lib/solargraph/yard_map/core_fills.rb +185 -0
  53. data/lib/solargraph/yard_map/helpers.rb +16 -0
  54. data/lib/solargraph/yard_map/mapper.rb +11 -5
  55. data/lib/solargraph/{pin/yard_pin/constant.rb → yard_map/mapper/to_constant.rb} +6 -6
  56. data/lib/solargraph/yard_map/mapper/to_method.rb +78 -0
  57. data/lib/solargraph/{pin/yard_pin/namespace.rb → yard_map/mapper/to_namespace.rb} +6 -6
  58. data/lib/solargraph/yard_map/rdoc_to_yard.rb +1 -1
  59. data/lib/solargraph/yard_map/stdlib_fills.rb +43 -0
  60. data/lib/solargraph/{pin/yard_pin/method.rb → yard_map/to_method.rb} +29 -30
  61. data/solargraph.gemspec +5 -5
  62. metadata +22 -36
  63. data/lib/solargraph/core_fills.rb +0 -164
  64. data/lib/solargraph/pin/attribute.rb +0 -49
  65. data/lib/solargraph/pin/base_method.rb +0 -149
  66. data/lib/solargraph/pin/yard_pin.rb +0 -12
  67. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +0 -20
  68. data/lib/solargraph/stdlib_fills.rb +0 -40
  69. data/travis-bundler.rb +0 -11
@@ -6,7 +6,7 @@ module Solargraph
6
6
  # examples that defer mapping are aliases for superclass methods or
7
7
  # methods from included modules.
8
8
  #
9
- class MethodAlias < BaseMethod
9
+ class MethodAlias < Method
10
10
  # @return [::Symbol]
11
11
  attr_reader :scope
12
12
 
@@ -40,20 +40,8 @@ module Solargraph
40
40
  # @param position [Position]
41
41
  # @return [Integer]
42
42
  def self.to_offset text, position
43
- result = 0
44
- feed = 0
45
- line = position.line
46
- column = position.character
47
- text.lines.each do |l|
48
- line_length = l.length
49
- if feed == line
50
- result += column
51
- break
52
- end
53
- result += line_length
54
- feed += 1
55
- end
56
- result
43
+ return 0 if text.empty?
44
+ text.lines[0...position.line].sum(&:length) + position.character
57
45
  end
58
46
 
59
47
  # Get a numeric offset for the specified text and a position identified
@@ -175,7 +175,7 @@ module Solargraph
175
175
  synced
176
176
  end
177
177
 
178
- # @param position [Position]
178
+ # @param position [Position, Array(Integer, Integer)]
179
179
  # @return [Source::Cursor]
180
180
  def cursor_at position
181
181
  Cursor.new(self, position)
@@ -284,7 +284,7 @@ module Solargraph
284
284
 
285
285
  FOLDING_NODE_TYPES = if Parser.rubyvm?
286
286
  %i[
287
- CLASS SCLASS MODULE DEFN DEFS IF WHILE UNLESS ITER STR
287
+ CLASS SCLASS MODULE DEFN DEFS IF WHILE UNLESS ITER STR HASH ARRAY LIST
288
288
  ].freeze
289
289
  else
290
290
  %i[
@@ -345,18 +345,22 @@ module Solargraph
345
345
 
346
346
  # @param top [Parser::AST::Node]
347
347
  # @param result [Array<Range>]
348
+ # @param parent [Symbol]
348
349
  # @return [void]
349
- def inner_folding_ranges top, result = []
350
+ def inner_folding_ranges top, result = [], parent = nil
350
351
  # return unless top.is_a?(::Parser::AST::Node)
351
352
  return unless Parser.is_ast_node?(top)
352
353
  if FOLDING_NODE_TYPES.include?(top.type)
353
- range = Range.from_node(top)
354
- if result.empty? || range.start.line > result.last.start.line
355
- result.push range unless range.ending.line - range.start.line < 2
354
+ # @todo Smelly exception for hash's first-level array in RubyVM
355
+ unless [:ARRAY, :LIST].include?(top.type) && parent == :HASH
356
+ range = Range.from_node(top)
357
+ if result.empty? || range.start.line > result.last.start.line
358
+ result.push range unless range.ending.line - range.start.line < 2
359
+ end
356
360
  end
357
361
  end
358
362
  top.children.each do |child|
359
- inner_folding_ranges(child, result)
363
+ inner_folding_ranges(child, result, top.type)
360
364
  end
361
365
  end
362
366
 
@@ -119,12 +119,12 @@ module Solargraph
119
119
  @@inference_stack.pop
120
120
  if type.defined?
121
121
  possibles.push type
122
- break if pin.is_a?(Pin::BaseMethod)
122
+ break if pin.is_a?(Pin::Method)
123
123
  end
124
124
  end
125
125
  if possibles.empty?
126
126
  # Limit method inference recursion
127
- return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::BaseMethod)
127
+ return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method)
128
128
  @@inference_depth += 1
129
129
  pins.each do |pin|
130
130
  # Avoid infinite recursion
@@ -134,7 +134,7 @@ module Solargraph
134
134
  @@inference_stack.pop
135
135
  if type.defined?
136
136
  possibles.push type
137
- break if pin.is_a?(Pin::BaseMethod)
137
+ break if pin.is_a?(Pin::Method)
138
138
  end
139
139
  end
140
140
  @@inference_depth -= 1
@@ -64,7 +64,7 @@ module Solargraph
64
64
  elsif end_of_phrase.strip == '::'
65
65
  chain.links.push Chain::UNDEFINED_CONSTANT
66
66
  end
67
- elsif chain.links.last.is_a?(Source::Chain::Constant) && end_of_phrase.strip == '::'
67
+ elsif chain.links.last.is_a?(Source::Chain::Constant) && end_of_phrase.strip == '::' && !source.code[Position.to_offset(source.code, position)].match?(/[a-z]/i)
68
68
  chain.links.push Source::Chain::UNDEFINED_CONSTANT
69
69
  end
70
70
  chain
@@ -30,7 +30,7 @@ module Solargraph
30
30
  @source = source.dup
31
31
  @pins = pins
32
32
  @locals = locals
33
- environ.merge Convention.for(source)
33
+ environ.merge Convention.for_local(self) unless filename.nil?
34
34
  @pin_class_hash = pins.to_set.classify(&:class).transform_values(&:to_a)
35
35
  @pin_select_cache = {}
36
36
  end
@@ -168,6 +168,9 @@ module Solargraph
168
168
  position = Position.new(line, character)
169
169
  found = nil
170
170
  pins.each do |pin|
171
+ # @todo Attribute pins should not be treated like closures, but
172
+ # there's probably a better way to handle it
173
+ next if pin.is_a?(Pin::Method) && pin.attribute?
171
174
  found = pin if (klasses.empty? || klasses.any? { |kls| pin.is_a?(kls) } ) && pin.location.range.contain?(position)
172
175
  break if pin.location.range.start.line > line
173
176
  end
@@ -149,7 +149,7 @@ module Solargraph
149
149
  frag_start = cursor.start_of_word.to_s.downcase
150
150
  filtered = result.uniq(&:name).select { |s|
151
151
  s.name.downcase.start_with?(frag_start) &&
152
- (!s.is_a?(Pin::BaseMethod) || s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i))
152
+ (!s.is_a?(Pin::Method) || s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i))
153
153
  }
154
154
  Completion.new(filtered, cursor.range)
155
155
  end
@@ -212,7 +212,8 @@ module Solargraph
212
212
  result.concat api_map.get_constants(context_pin.context.namespace, *gates)
213
213
  result.concat api_map.get_methods(block.binder.namespace, scope: block.binder.scope, visibility: [:public, :private, :protected])
214
214
  result.concat api_map.get_methods('Kernel')
215
- result.concat ApiMap.keywords
215
+ # result.concat ApiMap.keywords
216
+ result.concat api_map.keyword_pins
216
217
  result.concat yielded_self_pins
217
218
  end
218
219
  end
@@ -126,27 +126,31 @@ module Solargraph
126
126
  namespace = closure_at(source_position)
127
127
  t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('')
128
128
  if t.nil? || t.include?('r')
129
- pins.push Solargraph::Pin::Attribute.new(
129
+ pins.push Solargraph::Pin::Method.new(
130
130
  location: location,
131
131
  closure: namespace,
132
132
  name: directive.tag.name,
133
133
  comments: docstring.all.to_s,
134
- access: :reader,
135
134
  scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
136
135
  visibility: :public,
137
- explicit: false
136
+ explicit: false,
137
+ attribute: true
138
138
  )
139
139
  end
140
140
  if t.nil? || t.include?('w')
141
- pins.push Solargraph::Pin::Attribute.new(
141
+ pins.push Solargraph::Pin::Method.new(
142
142
  location: location,
143
143
  closure: namespace,
144
144
  name: "#{directive.tag.name}=",
145
145
  comments: docstring.all.to_s,
146
- access: :writer,
147
146
  scope: namespace.is_a?(Pin::Singleton) ? :class : :instance,
148
- visibility: :public
147
+ visibility: :public,
148
+ attribute: true
149
149
  )
150
+ pins.last.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
151
+ if pins.last.return_type.defined?
152
+ pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
153
+ end
150
154
  end
151
155
  when 'parse'
152
156
  begin
@@ -73,15 +73,15 @@ module Solargraph
73
73
  # @return [Array<Problem>]
74
74
  def method_tag_problems
75
75
  result = []
76
- # @param pin [Pin::BaseMethod]
77
- source_map.pins_by_class(Pin::BaseMethod).each do |pin|
76
+ # @param pin [Pin::Method]
77
+ source_map.pins_by_class(Pin::Method).each do |pin|
78
78
  result.concat method_return_type_problems_for(pin)
79
79
  result.concat method_param_type_problems_for(pin)
80
80
  end
81
81
  result
82
82
  end
83
83
 
84
- # @param pin [Pin::BaseMethod]
84
+ # @param pin [Pin::Method]
85
85
  # @return [Array<Problem>]
86
86
  def method_return_type_problems_for pin
87
87
  result = []
@@ -95,7 +95,7 @@ module Solargraph
95
95
  result.push Problem.new(pin.location, "Untyped method #{pin.path} could not be inferred")
96
96
  end
97
97
  elsif rules.validate_tags?
98
- unless pin.node.nil? || declared.void? || macro_pin?(pin) || abstract?(pin)
98
+ unless pin.node.nil? || declared.void? || virtual_pin?(pin) || abstract?(pin)
99
99
  inferred = pin.probe(api_map).self_to(pin.full_context.namespace)
100
100
  if inferred.undefined?
101
101
  unless rules.ignore_all_undefined? || external?(pin)
@@ -111,11 +111,11 @@ module Solargraph
111
111
  result
112
112
  end
113
113
 
114
- def macro_pin? pin
114
+ def virtual_pin? pin
115
115
  pin.location && source_map.source.comment_at?(pin.location.range.ending)
116
116
  end
117
117
 
118
- # @param pin [Pin::BaseMethod]
118
+ # @param pin [Pin::Method]
119
119
  # @return [Array<Problem>]
120
120
  def method_param_type_problems_for pin
121
121
  stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
@@ -129,10 +129,10 @@ module Solargraph
129
129
  end
130
130
  end
131
131
  end
132
- params.each_pair do |name, tag|
133
- type = tag.qualify(api_map, pin.full_context.namespace)
132
+ params.each_pair do |name, data|
133
+ type = data[:qualified]
134
134
  if type.undefined?
135
- result.push Problem.new(pin.location, "Unresolved type #{tag} for #{name} param on #{pin.path}", pin: pin)
135
+ result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
136
136
  end
137
137
  end
138
138
  result
@@ -147,14 +147,12 @@ module Solargraph
147
147
  result = []
148
148
  all_variables.each do |pin|
149
149
  if pin.return_type.defined?
150
- # @todo Somwhere in here we still need to determine if the variable is defined by an external call
151
150
  declared = pin.typify(api_map)
152
151
  if declared.defined?
153
152
  if rules.validate_tags?
154
153
  inferred = pin.probe(api_map)
155
154
  if inferred.undefined?
156
155
  next if rules.ignore_all_undefined?
157
- # next unless internal?(pin) # @todo This might be redundant for variables
158
156
  if declared_externally?(pin)
159
157
  ignored_pins.push pin
160
158
  else
@@ -172,7 +170,6 @@ module Solargraph
172
170
  result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
173
171
  end
174
172
  else
175
- # @todo Check if the variable is defined by an external call
176
173
  inferred = pin.probe(api_map)
177
174
  if inferred.undefined? && declared_externally?(pin)
178
175
  ignored_pins.push pin
@@ -227,7 +224,7 @@ module Solargraph
227
224
  base = base.base
228
225
  end
229
226
  closest = found.typify(api_map) if found
230
- if !found || closest.defined? || internal?(found)
227
+ if !found || (closest.defined? && internal_or_core?(found))
231
228
  unless ignored_pins.include?(found)
232
229
  result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
233
230
  @marked_ranges.push rng
@@ -244,8 +241,8 @@ module Solargraph
244
241
  base = chain
245
242
  until base.links.length == 1 && base.undefined?
246
243
  pins = base.define(api_map, block_pin, locals)
247
- if pins.first.is_a?(Pin::BaseMethod)
248
- # @type [Pin::BaseMethod]
244
+ if pins.first.is_a?(Pin::Method)
245
+ # @type [Pin::Method]
249
246
  pin = pins.first
250
247
  ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
251
248
  arity_problems_for(pin, fake_args_for(block_pin), location)
@@ -269,12 +266,12 @@ module Solargraph
269
266
  result.concat kwarg_problems_for argchain, api_map, block_pin, locals, location, pin, params, idx
270
267
  break
271
268
  else
272
- ptype = params[par.name]
269
+ ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
273
270
  if ptype.nil?
274
271
  # @todo Some level (strong, I guess) should require the param here
275
272
  else
276
273
  argtype = argchain.infer(api_map, block_pin, locals)
277
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
274
+ if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
278
275
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
279
276
  end
280
277
  end
@@ -302,20 +299,19 @@ module Solargraph
302
299
  result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
303
300
  else
304
301
  if argchain
305
- ptype = params[par.name]
306
- if ptype.nil?
302
+ data = params[par.name]
303
+ if data.nil?
307
304
  # @todo Some level (strong, I guess) should require the param here
308
305
  else
306
+ ptype = data[:qualified]
307
+ next if ptype.undefined?
309
308
  argtype = argchain.infer(api_map, block_pin, locals)
310
309
  if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
311
310
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
312
311
  end
313
312
  end
314
- else
315
- if par.decl == :kwarg
316
- # @todo Problem: missing required keyword argument
317
- result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
318
- end
313
+ elsif par.decl == :kwarg
314
+ result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
319
315
  end
320
316
  end
321
317
  end
@@ -325,31 +321,34 @@ module Solargraph
325
321
  def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
326
322
  result = []
327
323
  kwargs.each_pair do |pname, argchain|
328
- ptype = params[pname.to_s]
329
- if ptype.nil?
330
- # Probably nothing to do here. All of these args should be optional.
331
- else
332
- argtype = argchain.infer(api_map, block_pin, locals)
333
- if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
334
- result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
335
- end
324
+ next unless params.key?(pname.to_s)
325
+ ptype = params[pname.to_s][:qualified]
326
+ argtype = argchain.infer(api_map, block_pin, locals)
327
+ if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
328
+ result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
336
329
  end
337
330
  end
338
331
  result
339
332
  end
340
333
 
334
+ # @param [Pin::Method]
335
+ # @return [Hash]
341
336
  def param_hash(pin)
342
337
  tags = pin.docstring.tags(:param)
343
338
  return {} if tags.empty?
344
339
  result = {}
345
340
  tags.each do |tag|
346
341
  next if tag.types.nil? || tag.types.empty?
347
- result[tag.name.to_s] = Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
342
+ result[tag.name.to_s] = {
343
+ tagged: tag.types.join(', '),
344
+ qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
345
+ }
348
346
  end
349
347
  result
350
348
  end
351
349
 
352
350
  # @param [Array<Pin::Method>]
351
+ # @return [Hash]
353
352
  def first_param_hash(pins)
354
353
  pins.each do |pin|
355
354
  result = param_hash(pin)
@@ -360,9 +359,14 @@ module Solargraph
360
359
 
361
360
  # @param pin [Pin::Base]
362
361
  def internal? pin
362
+ return false if pin.nil?
363
363
  pin.location && api_map.bundled?(pin.location.filename)
364
364
  end
365
365
 
366
+ def internal_or_core? pin
367
+ internal?(pin) || api_map.yard_map.core_pins.include?(pin) || api_map.yard_map.stdlib_pins.include?(pin)
368
+ end
369
+
366
370
  # @param pin [Pin::Base]
367
371
  def external? pin
368
372
  !internal? pin
@@ -395,7 +399,7 @@ module Solargraph
395
399
  true
396
400
  end
397
401
 
398
- # @param pin [Pin::BaseMethod]
402
+ # @param pin [Pin::Method]
399
403
  def arity_problems_for(pin, arguments, location)
400
404
  return [] unless pin.explicit?
401
405
  return [] if pin.parameters.empty? && arguments.empty?
@@ -410,9 +414,12 @@ module Solargraph
410
414
  if unchecked.empty? && pin.parameters.any? { |param| param.decl == :kwarg }
411
415
  return [Problem.new(location, "Missing keyword arguments to #{pin.path}")]
412
416
  end
417
+ settled_kwargs = 0
413
418
  unless unchecked.empty?
414
- kwargs = convert_hash(unchecked.last.node)
415
- # unless kwargs.empty?
419
+ if Parser.is_ast_node?(unchecked.last.node) && splatted_call?(unchecked.last.node)
420
+ settled_kwargs = pin.parameters.count(&:keyword?)
421
+ else
422
+ kwargs = convert_hash(unchecked.last.node)
416
423
  if pin.parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? }
417
424
  if kwargs.empty?
418
425
  add_params += 1
@@ -422,6 +429,7 @@ module Solargraph
422
429
  next unless param.keyword?
423
430
  if kwargs.key?(param.name.to_sym)
424
431
  kwargs.delete param.name.to_sym
432
+ settled_kwargs += 1
425
433
  elsif param.decl == :kwarg
426
434
  return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
427
435
  end
@@ -432,7 +440,7 @@ module Solargraph
432
440
  end
433
441
  end
434
442
  end
435
- # end
443
+ end
436
444
  end
437
445
  req = required_param_count(pin)
438
446
  if req + add_params < unchecked.length
@@ -442,24 +450,22 @@ module Solargraph
442
450
  if unchecked.length == req + opt + 1 && unchecked.last.links.last.is_a?(Source::Chain::BlockVariable)
443
451
  return []
444
452
  end
453
+ if req + add_params + 1 == unchecked.length && splatted_call?(unchecked.last.node) && (pin.parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any?
454
+ return []
455
+ end
445
456
  return [Problem.new(location, "Too many arguments to #{pin.path}")]
446
- elsif unchecked.length < req && (arguments.empty? || !arguments.last.splat?)
457
+ elsif unchecked.length < req - settled_kwargs && (arguments.empty? || !arguments.last.splat?)
447
458
  return [Problem.new(location, "Not enough arguments to #{pin.path}")]
448
459
  end
449
460
  []
450
461
  end
451
462
 
452
- # @param pin [Pin::BaseMethod]
463
+ # @param pin [Pin::Method]
453
464
  def required_param_count(pin)
454
- count = 0
455
- pin.parameters.each do |param|
456
- break unless param.decl == :arg
457
- count += 1
458
- end
459
- count
465
+ pin.parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
460
466
  end
461
467
 
462
- # @param pin [Pin::BaseMethod]
468
+ # @param pin [Pin::Method]
463
469
  def optional_param_count(pin)
464
470
  count = 0
465
471
  pin.parameters.each do |param|