solargraph 0.56.2 → 0.58.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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +127 -0
  3. data/.github/workflows/plugins.yml +183 -7
  4. data/.github/workflows/rspec.yml +55 -5
  5. data/.github/workflows/typecheck.yml +6 -3
  6. data/.gitignore +5 -0
  7. data/.overcommit.yml +72 -0
  8. data/.rspec +1 -0
  9. data/.rubocop.yml +66 -0
  10. data/.rubocop_todo.yml +1279 -0
  11. data/.yardopts +1 -0
  12. data/CHANGELOG.md +69 -0
  13. data/README.md +8 -4
  14. data/Rakefile +125 -13
  15. data/bin/solargraph +8 -5
  16. data/lib/solargraph/api_map/cache.rb +3 -2
  17. data/lib/solargraph/api_map/constants.rb +279 -0
  18. data/lib/solargraph/api_map/index.rb +49 -31
  19. data/lib/solargraph/api_map/source_to_yard.rb +13 -4
  20. data/lib/solargraph/api_map/store.rb +144 -26
  21. data/lib/solargraph/api_map.rb +217 -245
  22. data/lib/solargraph/bench.rb +1 -0
  23. data/lib/solargraph/complex_type/type_methods.rb +6 -0
  24. data/lib/solargraph/complex_type/unique_type.rb +19 -12
  25. data/lib/solargraph/complex_type.rb +24 -3
  26. data/lib/solargraph/convention/active_support_concern.rb +111 -0
  27. data/lib/solargraph/convention/base.rb +17 -0
  28. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +1 -0
  29. data/lib/solargraph/convention/data_definition/data_definition_node.rb +4 -2
  30. data/lib/solargraph/convention/data_definition.rb +2 -1
  31. data/lib/solargraph/convention/gemspec.rb +1 -1
  32. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +1 -0
  33. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +3 -1
  34. data/lib/solargraph/convention/struct_definition.rb +36 -13
  35. data/lib/solargraph/convention.rb +31 -2
  36. data/lib/solargraph/diagnostics/rubocop.rb +6 -1
  37. data/lib/solargraph/diagnostics/rubocop_helpers.rb +5 -3
  38. data/lib/solargraph/doc_map.rb +44 -13
  39. data/lib/solargraph/environ.rb +9 -2
  40. data/lib/solargraph/equality.rb +1 -0
  41. data/lib/solargraph/gem_pins.rb +21 -11
  42. data/lib/solargraph/language_server/host/dispatch.rb +2 -0
  43. data/lib/solargraph/language_server/host/message_worker.rb +3 -0
  44. data/lib/solargraph/language_server/host.rb +12 -5
  45. data/lib/solargraph/language_server/message/base.rb +2 -1
  46. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  47. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
  48. data/lib/solargraph/language_server/message/text_document/formatting.rb +19 -2
  49. data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -0
  50. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
  51. data/lib/solargraph/language_server/progress.rb +8 -0
  52. data/lib/solargraph/language_server/request.rb +4 -1
  53. data/lib/solargraph/library.rb +11 -18
  54. data/lib/solargraph/location.rb +3 -0
  55. data/lib/solargraph/logging.rb +11 -2
  56. data/lib/solargraph/page.rb +3 -0
  57. data/lib/solargraph/parser/comment_ripper.rb +8 -1
  58. data/lib/solargraph/parser/flow_sensitive_typing.rb +33 -5
  59. data/lib/solargraph/parser/node_processor/base.rb +1 -1
  60. data/lib/solargraph/parser/node_processor.rb +6 -2
  61. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -13
  62. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
  63. data/lib/solargraph/parser/parser_gem/node_chainer.rb +3 -1
  64. data/lib/solargraph/parser/parser_gem/node_methods.rb +5 -16
  65. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +1 -0
  66. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +3 -2
  67. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +2 -0
  68. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +3 -0
  69. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +64 -8
  70. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +12 -3
  71. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +36 -16
  72. data/lib/solargraph/parser/region.rb +3 -0
  73. data/lib/solargraph/parser/snippet.rb +2 -0
  74. data/lib/solargraph/pin/base.rb +77 -14
  75. data/lib/solargraph/pin/base_variable.rb +6 -5
  76. data/lib/solargraph/pin/block.rb +3 -2
  77. data/lib/solargraph/pin/callable.rb +14 -1
  78. data/lib/solargraph/pin/closure.rb +5 -7
  79. data/lib/solargraph/pin/common.rb +6 -2
  80. data/lib/solargraph/pin/constant.rb +2 -0
  81. data/lib/solargraph/pin/local_variable.rb +1 -2
  82. data/lib/solargraph/pin/method.rb +28 -9
  83. data/lib/solargraph/pin/method_alias.rb +3 -0
  84. data/lib/solargraph/pin/parameter.rb +24 -10
  85. data/lib/solargraph/pin/proxy_type.rb +5 -1
  86. data/lib/solargraph/pin/reference/override.rb +15 -1
  87. data/lib/solargraph/pin/reference/superclass.rb +5 -0
  88. data/lib/solargraph/pin/reference.rb +17 -0
  89. data/lib/solargraph/pin/search.rb +6 -1
  90. data/lib/solargraph/pin/signature.rb +2 -0
  91. data/lib/solargraph/pin/symbol.rb +5 -0
  92. data/lib/solargraph/pin_cache.rb +64 -4
  93. data/lib/solargraph/position.rb +3 -0
  94. data/lib/solargraph/range.rb +5 -0
  95. data/lib/solargraph/rbs_map/conversions.rb +29 -6
  96. data/lib/solargraph/rbs_map/core_fills.rb +18 -0
  97. data/lib/solargraph/rbs_map/core_map.rb +14 -7
  98. data/lib/solargraph/rbs_map.rb +14 -1
  99. data/lib/solargraph/shell.rb +85 -1
  100. data/lib/solargraph/source/chain/call.rb +7 -3
  101. data/lib/solargraph/source/chain/constant.rb +3 -66
  102. data/lib/solargraph/source/chain/if.rb +1 -1
  103. data/lib/solargraph/source/chain/link.rb +11 -2
  104. data/lib/solargraph/source/chain/or.rb +1 -1
  105. data/lib/solargraph/source/chain.rb +11 -2
  106. data/lib/solargraph/source/change.rb +2 -2
  107. data/lib/solargraph/source/cursor.rb +2 -3
  108. data/lib/solargraph/source/source_chainer.rb +1 -1
  109. data/lib/solargraph/source.rb +6 -3
  110. data/lib/solargraph/source_map/clip.rb +18 -26
  111. data/lib/solargraph/source_map/data.rb +4 -0
  112. data/lib/solargraph/source_map/mapper.rb +2 -2
  113. data/lib/solargraph/source_map.rb +28 -16
  114. data/lib/solargraph/type_checker/param_def.rb +2 -0
  115. data/lib/solargraph/type_checker/rules.rb +30 -8
  116. data/lib/solargraph/type_checker.rb +301 -186
  117. data/lib/solargraph/version.rb +1 -1
  118. data/lib/solargraph/workspace/config.rb +21 -5
  119. data/lib/solargraph/workspace/require_paths.rb +97 -0
  120. data/lib/solargraph/workspace.rb +30 -67
  121. data/lib/solargraph/yard_map/mapper/to_method.rb +4 -3
  122. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  123. data/lib/solargraph/yard_map/to_method.rb +2 -1
  124. data/lib/solargraph/yardoc.rb +39 -3
  125. data/lib/solargraph.rb +2 -0
  126. data/rbs/fills/bundler/0/bundler.rbs +4271 -0
  127. data/rbs/fills/open3/0/open3.rbs +172 -0
  128. data/rbs/fills/rubygems/0/basic_specification.rbs +326 -0
  129. data/rbs/fills/rubygems/0/errors.rbs +364 -0
  130. data/rbs/fills/rubygems/0/spec_fetcher.rbs +107 -0
  131. data/rbs/fills/rubygems/0/specification.rbs +1753 -0
  132. data/rbs/fills/{tuple.rbs → tuple/tuple.rbs} +2 -3
  133. data/rbs_collection.yaml +4 -4
  134. data/sig/shims/ast/0/node.rbs +5 -0
  135. data/sig/shims/ast/2.4/.rbs_meta.yaml +9 -0
  136. data/sig/shims/ast/2.4/ast.rbs +73 -0
  137. data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  138. data/sig/shims/parser/3.2.0.1/manifest.yaml +7 -0
  139. data/sig/shims/parser/3.2.0.1/parser.rbs +201 -0
  140. data/sig/shims/parser/3.2.0.1/polyfill.rbs +4 -0
  141. data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  142. data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
  143. data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
  144. data/solargraph.gemspec +26 -5
  145. metadata +181 -13
  146. data/lib/.rubocop.yml +0 -22
  147. data/lib/solargraph/parser/node_methods.rb +0 -97
@@ -21,14 +21,23 @@ module Solargraph
21
21
  # @return [ApiMap]
22
22
  attr_reader :api_map
23
23
 
24
- # @param filename [String]
24
+ # @param filename [String, nil]
25
25
  # @param api_map [ApiMap, nil]
26
- # @param level [Symbol]
27
- def initialize filename, api_map: nil, level: :normal
26
+ # @param level [Symbol] Don't complain about anything above this level
27
+ # @param workspace [Workspace, nil] Workspace to use for loading
28
+ # type checker rules modified by user config
29
+ # @param type_checker_rules [Hash{Symbol => Symbol}] Overrides for
30
+ # type checker rules - e.g., :report_undefined => :strong
31
+ # @param rules [Rules] Type checker rules object
32
+ def initialize filename,
33
+ api_map: nil,
34
+ level: :normal,
35
+ workspace: filename ? Workspace.new(File.dirname(filename)) : nil,
36
+ rules: workspace ? workspace.rules(level) : Rules.new(level, {})
28
37
  @filename = filename
29
38
  # @todo Smarter directory resolution
30
39
  @api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename))
31
- @rules = Rules.new(level)
40
+ @rules = rules
32
41
  # @type [Array<Range>]
33
42
  @marked_ranges = []
34
43
  end
@@ -38,15 +47,20 @@ module Solargraph
38
47
  @source_map ||= api_map.source_map(filename)
39
48
  end
40
49
 
50
+ # @return [Source]
51
+ def source
52
+ @source_map.source
53
+ end
54
+
41
55
  # @return [Array<Problem>]
42
56
  def problems
43
57
  @problems ||= begin
44
- without_ignored(
45
- method_tag_problems
46
- .concat variable_type_tag_problems
47
- .concat const_problems
48
- .concat call_problems
49
- )
58
+ all = method_tag_problems
59
+ .concat(variable_type_tag_problems)
60
+ .concat(const_problems)
61
+ .concat(call_problems)
62
+ unignored = without_ignored(all)
63
+ unignored.concat(unneeded_sgignore_problems)
50
64
  end
51
65
  end
52
66
 
@@ -91,7 +105,7 @@ module Solargraph
91
105
  def method_return_type_problems_for pin
92
106
  return [] if pin.is_a?(Pin::MethodAlias)
93
107
  result = []
94
- declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, pin.full_context.tag)
108
+ declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, *pin.gates)
95
109
  if declared.undefined?
96
110
  if pin.return_type.undefined? && rules.require_type_tags?
97
111
  if pin.attribute?
@@ -140,40 +154,39 @@ module Solargraph
140
154
 
141
155
  # @param pin [Pin::Base]
142
156
  def virtual_pin? pin
143
- pin.location && source_map.source.comment_at?(pin.location.range.ending)
157
+ pin.location && source.comment_at?(pin.location.range.ending)
144
158
  end
145
159
 
146
160
  # @param pin [Pin::Method]
147
161
  # @return [Array<Problem>]
148
162
  def method_param_type_problems_for pin
149
163
  stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
150
- params = first_param_hash(stack)
151
164
  result = []
152
- if rules.require_type_tags?
153
- pin.signatures.each do |sig|
154
- sig.parameters.each do |par|
155
- break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
156
- unless params[par.name]
157
- if pin.attribute?
158
- inferred = pin.probe(api_map).self_to_type(pin.full_context)
159
- if inferred.undefined?
165
+ pin.signatures.each do |sig|
166
+ params = param_details_from_stack(sig, stack)
167
+ if rules.require_type_tags?
168
+ sig.parameters.each do |par|
169
+ break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
170
+ unless params[par.name]
171
+ if pin.attribute?
172
+ inferred = pin.probe(api_map).self_to_type(pin.full_context)
173
+ if inferred.undefined?
174
+ result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
175
+ end
176
+ else
160
177
  result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
161
178
  end
162
- else
163
- result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
164
179
  end
165
180
  end
166
- end
167
181
  end
168
- end
169
- # @todo Should be able to probe type of name and data here
170
- # @param name [String]
171
- # @param data [Hash{Symbol => BasicObject}]
172
- params.each_pair do |name, data|
173
- # @type [ComplexType]
174
- type = data[:qualified]
175
- if type.undefined?
176
- result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
182
+ # @param name [String]
183
+ # @param data [Hash{Symbol => BasicObject}]
184
+ params.each_pair do |name, data|
185
+ # @type [ComplexType]
186
+ type = data[:qualified]
187
+ if type.undefined?
188
+ result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin)
189
+ end
177
190
  end
178
191
  end
179
192
  result
@@ -191,7 +204,7 @@ module Solargraph
191
204
  if pin.return_type.defined?
192
205
  declared = pin.typify(api_map)
193
206
  next if declared.duck_type?
194
- if declared.defined?
207
+ if declared.defined? && pin.assignment
195
208
  if rules.validate_tags?
196
209
  inferred = pin.probe(api_map)
197
210
  if inferred.undefined?
@@ -212,7 +225,7 @@ module Solargraph
212
225
  elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin)
213
226
  result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
214
227
  end
215
- else
228
+ elsif pin.assignment
216
229
  inferred = pin.probe(api_map)
217
230
  if inferred.undefined? && declared_externally?(pin)
218
231
  ignored_pins.push pin
@@ -231,13 +244,14 @@ module Solargraph
231
244
  def const_problems
232
245
  return [] unless rules.validate_consts?
233
246
  result = []
234
- Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
247
+ Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const|
235
248
  rng = Solargraph::Range.from_node(const)
236
249
  chain = Solargraph::Parser.chain(const, filename)
237
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
250
+ closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
251
+ closure_pin.rebind(api_map)
238
252
  location = Location.new(filename, rng)
239
253
  locals = source_map.locals_at(location)
240
- pins = chain.define(api_map, block_pin, locals)
254
+ pins = chain.define(api_map, closure_pin, locals)
241
255
  if pins.empty?
242
256
  result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
243
257
  @marked_ranges.push location.range
@@ -249,21 +263,29 @@ module Solargraph
249
263
  # @return [Array<Problem>]
250
264
  def call_problems
251
265
  result = []
252
- Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
266
+ Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call|
253
267
  rng = Solargraph::Range.from_node(call)
254
268
  next if @marked_ranges.any? { |d| d.contain?(rng.start) }
255
269
  chain = Solargraph::Parser.chain(call, filename)
256
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
270
+ closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
271
+ namespace_pin = closure_pin
272
+ if call.type == :block
273
+ # blocks in the AST include the method call as well, so the
274
+ # node returned by #call_nodes_from needs to be backed out
275
+ # one closure
276
+ closure_pin = closure_pin.closure
277
+ end
278
+ closure_pin.rebind(api_map)
257
279
  location = Location.new(filename, rng)
258
280
  locals = source_map.locals_at(location)
259
- type = chain.infer(api_map, block_pin, locals)
281
+ type = chain.infer(api_map, closure_pin, locals)
260
282
  if type.undefined? && !rules.ignore_all_undefined?
261
283
  base = chain
262
284
  missing = chain
263
285
  found = nil
264
286
  closest = ComplexType::UNDEFINED
265
287
  until base.links.first.undefined?
266
- found = base.define(api_map, block_pin, locals).first
288
+ found = base.define(api_map, closure_pin, locals).first
267
289
  break if found
268
290
  missing = base
269
291
  base = base.base
@@ -272,145 +294,160 @@ module Solargraph
272
294
  # @todo remove the internal_or_core? check at a higher-than-strict level
273
295
  if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
274
296
  unless closest.generic? || ignored_pins.include?(found)
275
- result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
297
+ if closest.defined?
298
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}")
299
+ else
300
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
301
+ end
276
302
  @marked_ranges.push rng
277
303
  end
278
304
  end
279
305
  end
280
- result.concat argument_problems_for(chain, api_map, block_pin, locals, location)
306
+ result.concat argument_problems_for(chain, api_map, closure_pin, locals, location)
281
307
  end
282
308
  result
283
309
  end
284
310
 
285
311
  # @param chain [Solargraph::Source::Chain]
286
312
  # @param api_map [Solargraph::ApiMap]
287
- # @param block_pin [Solargraph::Pin::Base]
313
+ # @param closure_pin [Solargraph::Pin::Closure]
288
314
  # @param locals [Array<Solargraph::Pin::Base>]
289
315
  # @param location [Solargraph::Location]
290
316
  # @return [Array<Problem>]
291
- def argument_problems_for chain, api_map, block_pin, locals, location
317
+ def argument_problems_for chain, api_map, closure_pin, locals, location
292
318
  result = []
293
319
  base = chain
294
- until base.links.length == 1 && base.undefined?
295
- last_base_link = base.links.last
296
- break unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
297
-
298
- arguments = last_base_link.arguments
299
-
300
- pins = base.define(api_map, block_pin, locals)
301
-
302
- first_pin = pins.first
303
- if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
304
- # Do nothing, as we can't find the actual method implementation
305
- elsif first_pin.is_a?(Pin::Method)
306
- # @type [Pin::Method]
307
- pin = first_pin
308
- ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
309
- arity_problems_for(pin, fake_args_for(block_pin), location)
310
- elsif pin.path == 'Class#new'
311
- fqns = if base.links.one?
312
- block_pin.namespace
320
+ # @type last_base_link [Solargraph::Source::Chain::Call]
321
+ last_base_link = base.links.last
322
+ return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
323
+
324
+ arguments = last_base_link.arguments
325
+
326
+ pins = base.define(api_map, closure_pin, locals)
327
+
328
+ first_pin = pins.first
329
+ if first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
330
+ # Do nothing, as we can't find the actual method implementation
331
+ elsif first_pin.is_a?(Pin::Method)
332
+ # @type [Pin::Method]
333
+ pin = first_pin
334
+ ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
335
+ arity_problems_for(pin, fake_args_for(closure_pin), location)
336
+ elsif pin.path == 'Class#new'
337
+ fqns = if base.links.one?
338
+ closure_pin.namespace
339
+ else
340
+ base.base.infer(api_map, closure_pin, locals).namespace
341
+ end
342
+ init = api_map.get_method_stack(fqns, 'initialize').first
343
+ init ? arity_problems_for(init, arguments, location) : []
344
+ else
345
+ arity_problems_for(pin, arguments, location)
346
+ end
347
+ return ap unless ap.empty?
348
+ return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
349
+
350
+ all_errors = []
351
+ pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
352
+ params = param_details_from_stack(sig, pins)
353
+
354
+ signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
355
+
356
+ if signature_errors.empty?
357
+ # we found a signature that works - meaning errors from
358
+ # other signatures don't matter.
359
+ return []
360
+ end
361
+ all_errors.concat signature_errors
362
+ end
363
+ result.concat all_errors
364
+ end
365
+ result
366
+ end
367
+
368
+ # @param location [Location]
369
+ # @param locals [Array<Pin::LocalVariable>]
370
+ # @param closure_pin [Pin::Closure]
371
+ # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}]
372
+ # @param arguments [Array<Source::Chain>]
373
+ # @param sig [Pin::Signature]
374
+ # @param pin [Pin::Method]
375
+ # @param pins [Array<Pin::Method>]
376
+ #
377
+ # @return [Array<Problem>]
378
+ def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
379
+ errors = []
380
+ # @todo add logic mapping up restarg parameters with
381
+ # arguments (including restarg arguments). Use tuples
382
+ # when possible, and when not, ensure provably
383
+ # incorrect situations are detected.
384
+ sig.parameters.each_with_index do |par, idx|
385
+ return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing
386
+ argchain = arguments[idx]
387
+ if argchain.nil?
388
+ if par.decl == :arg
389
+ final_arg = arguments.last
390
+ if final_arg && final_arg.node.type == :splat
391
+ argchain = final_arg
392
+ return errors
313
393
  else
314
- base.base.infer(api_map, block_pin, locals).namespace
394
+ errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
315
395
  end
316
- init = api_map.get_method_stack(fqns, 'initialize').first
317
- init ? arity_problems_for(init, arguments, location) : []
318
396
  else
319
- arity_problems_for(pin, arguments, location)
397
+ final_arg = arguments.last
398
+ argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
320
399
  end
321
- unless ap.empty?
322
- result.concat ap
323
- break
324
- end
325
- break if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
326
-
327
- params = first_param_hash(pins)
328
-
329
- all_errors = []
330
- pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
331
- errors = []
332
- sig.parameters.each_with_index do |par, idx|
333
- # @todo add logic mapping up restarg parameters with
334
- # arguments (including restarg arguments). Use tuples
335
- # when possible, and when not, ensure provably
336
- # incorrect situations are detected.
337
- break if par.decl == :restarg # bail out pending better arg processing
338
- argchain = arguments[idx]
339
- if argchain.nil?
340
- if par.decl == :arg
341
- final_arg = arguments.last
342
- if final_arg && final_arg.node.type == :splat
343
- argchain = final_arg
344
- next # don't try to apply the type of the splat - unlikely to be specific enough
345
- else
346
- errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
347
- next
348
- end
349
- else
350
- final_arg = arguments.last
351
- argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
352
- end
353
- end
354
- if argchain
355
- if par.decl != :arg
356
- errors.concat kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx
357
- next
358
- else
359
- if argchain.node.type == :splat && argchain == arguments.last
360
- final_arg = argchain
361
- end
362
- if (final_arg && final_arg.node.type == :splat)
363
- # The final argument given has been seen and was a
364
- # splat, which doesn't give us useful types or
365
- # arities against positional parameters, so let's
366
- # continue on in case there are any required
367
- # kwargs we should warn about
368
- next
369
- end
370
-
371
- if argchain.node.type == :splat && par != sig.parameters.last
372
- # we have been given a splat and there are more
373
- # arguments to come.
374
-
375
- # @todo Improve this so that we can skip past the
376
- # rest of the positional parameters here but still
377
- # process the kwargs
378
- break
379
- end
380
- ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
381
- ptype = ptype.self_to_type(par.context)
382
- if ptype.nil?
383
- # @todo Some level (strong, I guess) should require the param here
384
- else
385
- argtype = argchain.infer(api_map, block_pin, locals)
386
- if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
387
- errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
388
- next
389
- end
390
- end
391
- end
392
- elsif par.decl == :kwarg
393
- errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
394
- next
395
- end
400
+ end
401
+ if argchain
402
+ if par.decl != :arg
403
+ errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
404
+ next
405
+ else
406
+ if argchain.node.type == :splat && argchain == arguments.last
407
+ final_arg = argchain
408
+ end
409
+ if (final_arg && final_arg.node.type == :splat)
410
+ # The final argument given has been seen and was a
411
+ # splat, which doesn't give us useful types or
412
+ # arities against positional parameters, so let's
413
+ # continue on in case there are any required
414
+ # kwargs we should warn about
415
+ next
416
+ end
417
+ if argchain.node.type == :splat && par != sig.parameters.last
418
+ # we have been given a splat and there are more
419
+ # arguments to come.
420
+
421
+ # @todo Improve this so that we can skip past the
422
+ # rest of the positional parameters here but still
423
+ # process the kwargs
424
+ return errors
396
425
  end
397
- if errors.empty?
398
- all_errors.clear
399
- break
426
+ ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
427
+ ptype = ptype.self_to_type(par.context)
428
+ if ptype.nil?
429
+ # @todo Some level (strong, I guess) should require the param here
430
+ else
431
+ argtype = argchain.infer(api_map, closure_pin, locals)
432
+ argtype = argtype.self_to_type(closure_pin.context)
433
+ if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
434
+ errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
435
+ return errors
436
+ end
400
437
  end
401
- all_errors.concat errors
402
438
  end
403
- result.concat all_errors
439
+ elsif par.decl == :kwarg
440
+ errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
441
+ next
404
442
  end
405
- base = base.base
406
443
  end
407
- result
444
+ errors
408
445
  end
409
446
 
410
447
  # @param sig [Pin::Signature]
411
448
  # @param argchain [Source::Chain]
412
449
  # @param api_map [ApiMap]
413
- # @param block_pin [Pin::Block]
450
+ # @param closure_pin [Pin::Closure]
414
451
  # @param locals [Array<Pin::LocalVariable>]
415
452
  # @param location [Location]
416
453
  # @param pin [Pin::Method]
@@ -418,13 +455,13 @@ module Solargraph
418
455
  # @param idx [Integer]
419
456
  #
420
457
  # @return [Array<Problem>]
421
- def kwarg_problems_for sig, argchain, api_map, block_pin, locals, location, pin, params, idx
458
+ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
422
459
  result = []
423
460
  kwargs = convert_hash(argchain.node)
424
461
  par = sig.parameters[idx]
425
462
  argchain = kwargs[par.name.to_sym]
426
463
  if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
427
- result.concat kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
464
+ result.concat kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs)
428
465
  else
429
466
  if argchain
430
467
  data = params[par.name]
@@ -432,8 +469,11 @@ module Solargraph
432
469
  # @todo Some level (strong, I guess) should require the param here
433
470
  else
434
471
  ptype = data[:qualified]
472
+ ptype = ptype.self_to_type(pin.context)
435
473
  unless ptype.undefined?
436
- argtype = argchain.infer(api_map, block_pin, locals)
474
+ # @sg-ignore https://github.com/castwide/solargraph/pull/1127
475
+ argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context)
476
+ # @sg-ignore Unresolved call to defined?
437
477
  if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
438
478
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
439
479
  end
@@ -447,19 +487,21 @@ module Solargraph
447
487
  end
448
488
 
449
489
  # @param api_map [ApiMap]
450
- # @param block_pin [Pin::Block]
490
+ # @param closure_pin [Pin::Closure]
451
491
  # @param locals [Array<Pin::LocalVariable>]
452
492
  # @param location [Location]
453
493
  # @param pin [Pin::Method]
454
494
  # @param params [Hash{String => [nil, Hash]}]
455
495
  # @param kwargs [Hash{Symbol => Source::Chain}]
456
496
  # @return [Array<Problem>]
457
- def kwrestarg_problems_for(api_map, block_pin, locals, location, pin, params, kwargs)
497
+ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs)
458
498
  result = []
459
499
  kwargs.each_pair do |pname, argchain|
460
500
  next unless params.key?(pname.to_s)
461
501
  ptype = params[pname.to_s][:qualified]
462
- argtype = argchain.infer(api_map, block_pin, locals)
502
+ ptype = ptype.self_to_type(pin.context)
503
+ argtype = argchain.infer(api_map, closure_pin, locals)
504
+ argtype = argtype.self_to_type(closure_pin.context)
463
505
  if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
464
506
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
465
507
  end
@@ -467,9 +509,32 @@ module Solargraph
467
509
  result
468
510
  end
469
511
 
470
- # @param pin [Pin::Method]
512
+ # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
513
+ # @param pin [Pin::Method, Pin::Signature]
514
+ # @param relevant_pin [Pin::Method, Pin::Signature] the pin which is under inspection
515
+ # @return [void]
516
+ def add_restkwarg_param_tag_details(param_details, pin, relevant_pin)
517
+ # see if we have additional tags to pay attention to from YARD -
518
+ # e.g., kwargs in a **restkwargs splat
519
+ tags = pin.docstring.tags(:param)
520
+ tags.each do |tag|
521
+ next if param_details.key? tag.name.to_s
522
+ next if tag.types.nil?
523
+ details = {
524
+ tagged: tag.types.join(', '),
525
+ qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
526
+ }
527
+ # don't complain about a param that didn't come from the pin we're looking at anyway
528
+ if details[:qualified].defined? ||
529
+ relevant_pin.parameter_names.include?(tag.name.to_s)
530
+ param_details[tag.name.to_s] = details
531
+ end
532
+ end
533
+ end
534
+
535
+ # @param pin [Pin::Signature]
471
536
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
472
- def param_hash(pin)
537
+ def signature_param_details(pin)
473
538
  # @type [Hash{String => Hash{Symbol => String, ComplexType}}]
474
539
  result = {}
475
540
  pin.parameters.each do |param|
@@ -488,36 +553,50 @@ module Solargraph
488
553
  next if tag.types.nil?
489
554
  result[tag.name.to_s] = {
490
555
  tagged: tag.types.join(', '),
491
- qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
556
+ qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, *pin.closure.gates)
492
557
  }
493
558
  end
494
559
  result
495
560
  end
496
561
 
497
- # @param pins [Array<Pin::Method>]
562
+ # The original signature defines the parameters, but other
563
+ # signatures and method pins can help by adding type information
564
+ #
565
+ # @param param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
566
+ # @param param_names [Array<String>]
567
+ # @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}]
568
+ #
569
+ # @return [void]
570
+ def add_to_param_details(param_details, param_names, new_param_details)
571
+ new_param_details.each do |param_name, details|
572
+ next unless param_names.include?(param_name)
573
+
574
+ param_details[param_name] ||= {}
575
+ param_details[param_name][:tagged] ||= details[:tagged]
576
+ param_details[param_name][:qualified] ||= details[:qualified]
577
+ end
578
+ end
579
+
580
+ # @param signature [Pin::Signature]
581
+ # @param method_pin_stack [Array<Pin::Method>]
498
582
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
499
- def first_param_hash(pins)
500
- return {} if pins.empty?
501
- first_pin_type = pins.first.typify(api_map)
502
- first_pin = pins.first.proxy first_pin_type
503
- param_names = first_pin.parameter_names
504
- results = param_hash(first_pin)
505
- pins[1..].each do |pin|
506
- # @todo this assignment from parametric use of Hash should not lose its generic
507
- # @type [Hash{String => Hash{Symbol => BasicObject}}]
583
+ def param_details_from_stack(signature, method_pin_stack)
584
+ signature_type = signature.typify(api_map)
585
+ signature = signature.proxy signature_type
586
+ param_details = signature_param_details(signature)
587
+ param_names = signature.parameter_names
588
+
589
+ method_pin_stack.each do |method_pin|
590
+ add_restkwarg_param_tag_details(param_details, method_pin, signature)
508
591
 
509
592
  # documentation of types in superclasses should fail back to
510
593
  # subclasses if the subclass hasn't documented something
511
- superclass_results = param_hash(pin)
512
- superclass_results.each do |param_name, details|
513
- next unless param_names.include?(param_name)
514
-
515
- results[param_name] ||= {}
516
- results[param_name][:tagged] ||= details[:tagged]
517
- results[param_name][:qualified] ||= details[:qualified]
594
+ method_pin.signatures.each do |sig|
595
+ add_restkwarg_param_tag_details(param_details, sig, signature)
596
+ add_to_param_details param_details, param_names, signature_param_details(sig)
518
597
  end
519
598
  end
520
- results
599
+ param_details
521
600
  end
522
601
 
523
602
  # @param pin [Pin::Base]
@@ -540,20 +619,21 @@ module Solargraph
540
619
 
541
620
  # @param pin [Pin::BaseVariable]
542
621
  def declared_externally? pin
543
- return true if pin.assignment.nil?
622
+ raise "No assignment found" if pin.assignment.nil?
623
+
544
624
  chain = Solargraph::Parser.chain(pin.assignment, filename)
545
625
  rng = Solargraph::Range.from_node(pin.assignment)
546
- block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
626
+ closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
547
627
  location = Location.new(filename, Range.from_node(pin.assignment))
548
628
  locals = source_map.locals_at(location)
549
- type = chain.infer(api_map, block_pin, locals)
629
+ type = chain.infer(api_map, closure_pin, locals)
550
630
  if type.undefined? && !rules.ignore_all_undefined?
551
631
  base = chain
552
632
  missing = chain
553
633
  found = nil
554
634
  closest = ComplexType::UNDEFINED
555
635
  until base.links.first.undefined?
556
- found = base.define(api_map, block_pin, locals).first
636
+ found = base.define(api_map, closure_pin, locals).first
557
637
  break if found
558
638
  missing = base
559
639
  base = base.base
@@ -646,7 +726,6 @@ module Solargraph
646
726
  # @param parameters [Enumerable<Pin::Parameter>]
647
727
  # @todo need to use generic types in method to choose correct
648
728
  # signature and generate Integer as return type
649
- # @sg-ignore
650
729
  # @return [Integer]
651
730
  def required_param_count(parameters)
652
731
  parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
@@ -687,12 +766,48 @@ module Solargraph
687
766
  args
688
767
  end
689
768
 
769
+ # @return [Set<Integer>]
770
+ def sg_ignore_lines_processed
771
+ @sg_ignore_lines_processed ||= Set.new
772
+ end
773
+
774
+ # @return [Set<Integer>]
775
+ def all_sg_ignore_lines
776
+ source.associated_comments.select do |_line, text|
777
+ text.include?('@sg-ignore')
778
+ end.keys.to_set
779
+ end
780
+
781
+ # @return [Array<Integer>]
782
+ def unprocessed_sg_ignore_lines
783
+ (all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort
784
+ end
785
+
786
+ # @return [Array<Problem>]
787
+ def unneeded_sgignore_problems
788
+ return [] unless rules.validate_sg_ignores?
789
+
790
+ unprocessed_sg_ignore_lines.map do |line|
791
+ Problem.new(
792
+ Location.new(filename, Range.from_to(line, 0, line, 0)),
793
+ 'Unneeded @sg-ignore comment'
794
+ )
795
+ end
796
+ end
797
+
690
798
  # @param problems [Array<Problem>]
691
799
  # @return [Array<Problem>]
692
800
  def without_ignored problems
693
801
  problems.reject do |problem|
694
- node = source_map.source.node_at(problem.location.range.start.line, problem.location.range.start.column)
695
- node && source_map.source.comments_for(node)&.include?('@sg-ignore')
802
+ node = source.node_at(problem.location.range.start.line, problem.location.range.start.column)
803
+ ignored = node && source.comments_for(node)&.include?('@sg-ignore')
804
+ unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line)
805
+ # :nocov:
806
+ Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" }
807
+ # :nocov:
808
+ end
809
+ sg_ignore_lines_processed.add problem.location.range.start.line if ignored
810
+ ignored
696
811
  end
697
812
  end
698
813
  end