solargraph 0.56.0 → 0.57.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +125 -0
  3. data/.github/workflows/plugins.yml +148 -6
  4. data/.github/workflows/rspec.yml +39 -4
  5. data/.github/workflows/typecheck.yml +5 -2
  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 +2627 -0
  11. data/.yardopts +1 -0
  12. data/CHANGELOG.md +56 -1
  13. data/README.md +8 -4
  14. data/Rakefile +125 -13
  15. data/lib/solargraph/api_map/cache.rb +3 -2
  16. data/lib/solargraph/api_map/constants.rb +218 -0
  17. data/lib/solargraph/api_map/index.rb +20 -26
  18. data/lib/solargraph/api_map/source_to_yard.rb +10 -4
  19. data/lib/solargraph/api_map/store.rb +126 -18
  20. data/lib/solargraph/api_map.rb +212 -234
  21. data/lib/solargraph/bench.rb +1 -0
  22. data/lib/solargraph/complex_type/type_methods.rb +1 -0
  23. data/lib/solargraph/complex_type/unique_type.rb +7 -7
  24. data/lib/solargraph/complex_type.rb +5 -1
  25. data/lib/solargraph/convention/active_support_concern.rb +111 -0
  26. data/lib/solargraph/convention/base.rb +17 -0
  27. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +61 -0
  28. data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -0
  29. data/lib/solargraph/convention/data_definition.rb +105 -0
  30. data/lib/solargraph/convention/gemspec.rb +3 -2
  31. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +2 -1
  32. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +4 -2
  33. data/lib/solargraph/convention/struct_definition.rb +87 -24
  34. data/lib/solargraph/convention.rb +32 -2
  35. data/lib/solargraph/diagnostics/rubocop.rb +6 -1
  36. data/lib/solargraph/diagnostics/rubocop_helpers.rb +1 -1
  37. data/lib/solargraph/doc_map.rb +48 -17
  38. data/lib/solargraph/environ.rb +9 -2
  39. data/lib/solargraph/gem_pins.rb +17 -11
  40. data/lib/solargraph/language_server/host/dispatch.rb +2 -0
  41. data/lib/solargraph/language_server/host/message_worker.rb +3 -0
  42. data/lib/solargraph/language_server/host.rb +2 -1
  43. data/lib/solargraph/language_server/message/base.rb +2 -1
  44. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  45. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -0
  46. data/lib/solargraph/language_server/message/text_document/formatting.rb +16 -2
  47. data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -0
  48. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
  49. data/lib/solargraph/language_server/progress.rb +8 -0
  50. data/lib/solargraph/language_server/request.rb +1 -0
  51. data/lib/solargraph/library.rb +47 -30
  52. data/lib/solargraph/location.rb +2 -0
  53. data/lib/solargraph/logging.rb +11 -2
  54. data/lib/solargraph/page.rb +4 -0
  55. data/lib/solargraph/parser/comment_ripper.rb +8 -1
  56. data/lib/solargraph/parser/flow_sensitive_typing.rb +32 -4
  57. data/lib/solargraph/parser/node_methods.rb +2 -2
  58. data/lib/solargraph/parser/node_processor/base.rb +10 -5
  59. data/lib/solargraph/parser/node_processor.rb +24 -8
  60. data/lib/solargraph/parser/parser_gem/class_methods.rb +1 -1
  61. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
  62. data/lib/solargraph/parser/parser_gem/node_chainer.rb +3 -1
  63. data/lib/solargraph/parser/parser_gem/node_methods.rb +4 -2
  64. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +3 -2
  65. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +1 -21
  66. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +2 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +7 -1
  68. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +0 -22
  69. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +1 -0
  70. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +1 -0
  71. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +1 -0
  72. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +35 -14
  73. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +1 -0
  74. data/lib/solargraph/parser/parser_gem/node_processors.rb +4 -0
  75. data/lib/solargraph/parser/region.rb +3 -0
  76. data/lib/solargraph/parser/snippet.rb +2 -0
  77. data/lib/solargraph/pin/base.rb +65 -8
  78. data/lib/solargraph/pin/base_variable.rb +1 -2
  79. data/lib/solargraph/pin/callable.rb +9 -0
  80. data/lib/solargraph/pin/closure.rb +2 -0
  81. data/lib/solargraph/pin/common.rb +6 -2
  82. data/lib/solargraph/pin/constant.rb +2 -0
  83. data/lib/solargraph/pin/delegated_method.rb +1 -0
  84. data/lib/solargraph/pin/local_variable.rb +4 -1
  85. data/lib/solargraph/pin/method.rb +12 -7
  86. data/lib/solargraph/pin/method_alias.rb +3 -0
  87. data/lib/solargraph/pin/parameter.rb +18 -8
  88. data/lib/solargraph/pin/proxy_type.rb +1 -0
  89. data/lib/solargraph/pin/reference/override.rb +15 -1
  90. data/lib/solargraph/pin/reference/superclass.rb +5 -0
  91. data/lib/solargraph/pin/reference.rb +26 -0
  92. data/lib/solargraph/pin/search.rb +3 -1
  93. data/lib/solargraph/pin/signature.rb +2 -0
  94. data/lib/solargraph/pin/symbol.rb +5 -0
  95. data/lib/solargraph/pin_cache.rb +64 -4
  96. data/lib/solargraph/position.rb +2 -0
  97. data/lib/solargraph/range.rb +1 -0
  98. data/lib/solargraph/rbs_map/conversions.rb +47 -18
  99. data/lib/solargraph/rbs_map/core_map.rb +3 -0
  100. data/lib/solargraph/rbs_map.rb +15 -2
  101. data/lib/solargraph/shell.rb +3 -0
  102. data/lib/solargraph/source/chain/link.rb +10 -1
  103. data/lib/solargraph/source/chain.rb +9 -2
  104. data/lib/solargraph/source/change.rb +2 -2
  105. data/lib/solargraph/source/cursor.rb +2 -3
  106. data/lib/solargraph/source/source_chainer.rb +1 -1
  107. data/lib/solargraph/source.rb +5 -2
  108. data/lib/solargraph/source_map/clip.rb +1 -1
  109. data/lib/solargraph/source_map/data.rb +4 -0
  110. data/lib/solargraph/source_map/mapper.rb +4 -2
  111. data/lib/solargraph/source_map.rb +21 -14
  112. data/lib/solargraph/type_checker/param_def.rb +2 -0
  113. data/lib/solargraph/type_checker/rules.rb +8 -0
  114. data/lib/solargraph/type_checker.rb +173 -120
  115. data/lib/solargraph/version.rb +1 -1
  116. data/lib/solargraph/workspace/config.rb +1 -3
  117. data/lib/solargraph/workspace/require_paths.rb +98 -0
  118. data/lib/solargraph/workspace.rb +24 -48
  119. data/lib/solargraph/yard_map/helpers.rb +29 -1
  120. data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -5
  121. data/lib/solargraph/yard_map/mapper/to_method.rb +3 -8
  122. data/lib/solargraph/yard_map/mapper/to_namespace.rb +7 -7
  123. data/lib/solargraph/yardoc.rb +18 -3
  124. data/lib/solargraph.rb +15 -0
  125. data/rbs/fills/tuple.rbs +2 -3
  126. data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  127. data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  128. data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
  129. data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
  130. data/solargraph.gemspec +14 -4
  131. metadata +128 -11
  132. data/lib/.rubocop.yml +0 -22
@@ -21,6 +21,11 @@ module Solargraph
21
21
  data.pins
22
22
  end
23
23
 
24
+ # @return [Array<Pin::Base>]
25
+ def all_pins
26
+ pins + convention_pins
27
+ end
28
+
24
29
  # @return [Array<Pin::LocalVariable>]
25
30
  def locals
26
31
  data.locals
@@ -30,13 +35,18 @@ module Solargraph
30
35
  def initialize source
31
36
  @source = source
32
37
 
33
- environ.merge Convention.for_local(self) unless filename.nil?
34
- self.convention_pins = environ.pins
38
+ conventions_environ.merge Convention.for_local(self) unless filename.nil?
39
+ # FIXME: unmemoizing the document_symbols in case it was called and memoized from any of conventions above
40
+ # this is to ensure that the convention_pins from all conventions are used in the document_symbols.
41
+ # solargraph-rails is known to use this method to get the document symbols. It should probably be removed.
42
+ @document_symbols = nil
43
+ self.convention_pins = conventions_environ.pins
35
44
  @pin_select_cache = {}
36
45
  end
37
46
 
38
- # @param klass [Class]
39
- # @return [Array<Pin::Base>]
47
+ # @generic T
48
+ # @param klass [Class<generic<T>>]
49
+ # @return [Array<generic<T>>]
40
50
  def pins_by_class klass
41
51
  @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten
42
52
  end
@@ -67,8 +77,8 @@ module Solargraph
67
77
  end
68
78
 
69
79
  # @return [Environ]
70
- def environ
71
- @environ ||= Environ.new
80
+ def conventions_environ
81
+ @conventions_environ ||= Environ.new
72
82
  end
73
83
 
74
84
  # all pins except Solargraph::Pin::Reference::Reference
@@ -158,10 +168,15 @@ module Solargraph
158
168
 
159
169
  private
160
170
 
171
+ # @return [Hash{Class => Array<Pin::Base>}]
172
+ # @return [Array<Pin::Base>]
173
+ attr_writer :convention_pins
174
+
161
175
  def pin_class_hash
162
176
  @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a)
163
177
  end
164
178
 
179
+ # @return [Data]
165
180
  def data
166
181
  @data ||= Data.new(source)
167
182
  end
@@ -171,14 +186,6 @@ module Solargraph
171
186
  @convention_pins || []
172
187
  end
173
188
 
174
- # @param pins [Array<Pin::Base>]
175
- # @return [Array<Pin::Base>]
176
- def convention_pins=(pins)
177
- # unmemoizing the document_symbols in case it was called from any of conventions
178
- @document_symbols = nil
179
- @convention_pins = pins
180
- end
181
-
182
189
  # @param line [Integer]
183
190
  # @param character [Integer]
184
191
  # @param klasses [Array<Class>]
@@ -12,6 +12,8 @@ module Solargraph
12
12
  # @return [Symbol]
13
13
  attr_reader :type
14
14
 
15
+ # @param name [String]
16
+ # @param type [Symbol] The type of parameter, such as :req, :opt, :rest, etc.
15
17
  def initialize name, type
16
18
  @name = name
17
19
  @type = type
@@ -57,6 +57,14 @@ module Solargraph
57
57
  def require_all_return_types_match_inferred?
58
58
  rank >= LEVELS[:alpha]
59
59
  end
60
+
61
+ # We keep this at strong because if you added an @ sg-ignore to
62
+ # address a strong-level issue, then ran at a lower level, you'd
63
+ # get a false positive - we don't run stronger level checks than
64
+ # requested for performance reasons
65
+ def validate_sg_ignores?
66
+ rank >= LEVELS[:strong]
67
+ end
60
68
  end
61
69
  end
62
70
  end
@@ -38,15 +38,20 @@ module Solargraph
38
38
  @source_map ||= api_map.source_map(filename)
39
39
  end
40
40
 
41
+ # @return [Source]
42
+ def source
43
+ @source_map.source
44
+ end
45
+
41
46
  # @return [Array<Problem>]
42
47
  def problems
43
48
  @problems ||= begin
44
- without_ignored(
45
- method_tag_problems
46
- .concat variable_type_tag_problems
47
- .concat const_problems
48
- .concat call_problems
49
- )
49
+ all = method_tag_problems
50
+ .concat(variable_type_tag_problems)
51
+ .concat(const_problems)
52
+ .concat(call_problems)
53
+ unignored = without_ignored(all)
54
+ unignored.concat(unneeded_sgignore_problems)
50
55
  end
51
56
  end
52
57
 
@@ -140,7 +145,7 @@ module Solargraph
140
145
 
141
146
  # @param pin [Pin::Base]
142
147
  def virtual_pin? pin
143
- pin.location && source_map.source.comment_at?(pin.location.range.ending)
148
+ pin.location && source.comment_at?(pin.location.range.ending)
144
149
  end
145
150
 
146
151
  # @param pin [Pin::Method]
@@ -231,7 +236,7 @@ module Solargraph
231
236
  def const_problems
232
237
  return [] unless rules.validate_consts?
233
238
  result = []
234
- Solargraph::Parser::NodeMethods.const_nodes_from(source_map.source.node).each do |const|
239
+ Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const|
235
240
  rng = Solargraph::Range.from_node(const)
236
241
  chain = Solargraph::Parser.chain(const, filename)
237
242
  block_pin = source_map.locate_block_pin(rng.start.line, rng.start.column)
@@ -249,7 +254,7 @@ module Solargraph
249
254
  # @return [Array<Problem>]
250
255
  def call_problems
251
256
  result = []
252
- Solargraph::Parser::NodeMethods.call_nodes_from(source_map.source.node).each do |call|
257
+ Solargraph::Parser::NodeMethods.call_nodes_from(source.node).each do |call|
253
258
  rng = Solargraph::Range.from_node(call)
254
259
  next if @marked_ranges.any? { |d| d.contain?(rng.start) }
255
260
  chain = Solargraph::Parser.chain(call, filename)
@@ -272,7 +277,11 @@ module Solargraph
272
277
  # @todo remove the internal_or_core? check at a higher-than-strict level
273
278
  if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))
274
279
  unless closest.generic? || ignored_pins.include?(found)
275
- result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
280
+ if closest.defined?
281
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}")
282
+ else
283
+ result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
284
+ end
276
285
  @marked_ranges.push rng
277
286
  end
278
287
  end
@@ -284,127 +293,136 @@ module Solargraph
284
293
 
285
294
  # @param chain [Solargraph::Source::Chain]
286
295
  # @param api_map [Solargraph::ApiMap]
287
- # @param block_pin [Solargraph::Pin::Base]
296
+ # @param closure_pin [Solargraph::Pin::Closure]
288
297
  # @param locals [Array<Solargraph::Pin::Base>]
289
298
  # @param location [Solargraph::Location]
290
299
  # @return [Array<Problem>]
291
- def argument_problems_for chain, api_map, block_pin, locals, location
300
+ def argument_problems_for chain, api_map, closure_pin, locals, location
292
301
  result = []
293
302
  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
303
+ # @type last_base_link [Solargraph::Source::Chain::Call]
304
+ last_base_link = base.links.last
305
+ return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
306
+
307
+ arguments = last_base_link.arguments
308
+
309
+ pins = base.define(api_map, closure_pin, locals)
310
+
311
+ first_pin = pins.first
312
+ unresolvable = first_pin.is_a?(Pin::DelegatedMethod) && !first_pin.resolvable?(api_map)
313
+ if !unresolvable && first_pin.is_a?(Pin::Method)
314
+ # @type [Pin::Method]
315
+ pin = first_pin
316
+ ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
317
+ arity_problems_for(pin, fake_args_for(closure_pin), location)
318
+ elsif pin.path == 'Class#new'
319
+ fqns = if base.links.one?
320
+ closure_pin.namespace
321
+ else
322
+ base.base.infer(api_map, closure_pin, locals).namespace
323
+ end
324
+ init = api_map.get_method_stack(fqns, 'initialize').first
325
+
326
+ init ? arity_problems_for(init, arguments, location) : []
327
+ else
328
+ arity_problems_for(pin, arguments, location)
329
+ end
330
+ return ap unless ap.empty?
331
+ return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
332
+
333
+ params = first_param_hash(pins)
334
+
335
+ all_errors = []
336
+ pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
337
+ signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
338
+ if signature_errors.empty?
339
+ # we found a signature that works - meaning errors from
340
+ # other signatures don't matter.
341
+ return []
342
+ end
343
+ all_errors.concat signature_errors
344
+ end
345
+ result.concat all_errors
346
+ end
347
+ result
348
+ end
349
+
350
+ # @param location [Location]
351
+ # @param locals [Array<Pin::LocalVariable>]
352
+ # @param closure_pin [Pin::Closure]
353
+ # @param params [Hash{String => Hash{Symbol => String, Solargraph::ComplexType}}]
354
+ # @param arguments [Array<Source::Chain>]
355
+ # @param sig [Pin::Signature]
356
+ # @param pin [Pin::Method]
357
+ # @param pins [Array<Pin::Method>]
358
+ #
359
+ # @return [Array<Problem>]
360
+ def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
361
+ errors = []
362
+ # @todo add logic mapping up restarg parameters with
363
+ # arguments (including restarg arguments). Use tuples
364
+ # when possible, and when not, ensure provably
365
+ # incorrect situations are detected.
366
+ sig.parameters.each_with_index do |par, idx|
367
+ return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing
368
+ argchain = arguments[idx]
369
+ if argchain.nil?
370
+ if par.decl == :arg
371
+ final_arg = arguments.last
372
+ if final_arg && final_arg.node.type == :splat
373
+ argchain = final_arg
374
+ return errors
313
375
  else
314
- base.base.infer(api_map, block_pin, locals).namespace
376
+ errors.push Problem.new(location, "Not enough arguments to #{pin.path}")
315
377
  end
316
- init = api_map.get_method_stack(fqns, 'initialize').first
317
- init ? arity_problems_for(init, arguments, location) : []
318
378
  else
319
- arity_problems_for(pin, arguments, location)
320
- end
321
- unless ap.empty?
322
- result.concat ap
323
- break
379
+ final_arg = arguments.last
380
+ argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type)
324
381
  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
382
+ end
383
+ if argchain
384
+ if par.decl != :arg
385
+ errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx
386
+ next
387
+ else
388
+ if argchain.node.type == :splat && argchain == arguments.last
389
+ final_arg = argchain
390
+ end
391
+ if (final_arg && final_arg.node.type == :splat)
392
+ # The final argument given has been seen and was a
393
+ # splat, which doesn't give us useful types or
394
+ # arities against positional parameters, so let's
395
+ # continue on in case there are any required
396
+ # kwargs we should warn about
397
+ next
396
398
  end
397
- if errors.empty?
398
- all_errors.clear
399
- break
399
+ if argchain.node.type == :splat && par != sig.parameters.last
400
+ # we have been given a splat and there are more
401
+ # arguments to come.
402
+
403
+ # @todo Improve this so that we can skip past the
404
+ # rest of the positional parameters here but still
405
+ # process the kwargs
406
+ return errors
407
+ end
408
+ ptype = params.key?(par.name) ? params[par.name][:qualified] : ComplexType::UNDEFINED
409
+ ptype = ptype.self_to_type(par.context)
410
+ if ptype.nil?
411
+ # @todo Some level (strong, I guess) should require the param here
412
+ else
413
+ argtype = argchain.infer(api_map, closure_pin, locals)
414
+ if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
415
+ errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
416
+ return errors
417
+ end
400
418
  end
401
- all_errors.concat errors
402
419
  end
403
- result.concat all_errors
420
+ elsif par.decl == :kwarg
421
+ errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
422
+ next
404
423
  end
405
- base = base.base
406
424
  end
407
- result
425
+ errors
408
426
  end
409
427
 
410
428
  # @param sig [Pin::Signature]
@@ -646,7 +664,6 @@ module Solargraph
646
664
  # @param parameters [Enumerable<Pin::Parameter>]
647
665
  # @todo need to use generic types in method to choose correct
648
666
  # signature and generate Integer as return type
649
- # @sg-ignore
650
667
  # @return [Integer]
651
668
  def required_param_count(parameters)
652
669
  parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 }
@@ -687,12 +704,48 @@ module Solargraph
687
704
  args
688
705
  end
689
706
 
707
+ # @return [Set<Integer>]
708
+ def sg_ignore_lines_processed
709
+ @sg_ignore_lines_processed ||= Set.new
710
+ end
711
+
712
+ # @return [Set<Integer>]
713
+ def all_sg_ignore_lines
714
+ source.associated_comments.select do |_line, text|
715
+ text.include?('@sg-ignore')
716
+ end.keys.to_set
717
+ end
718
+
719
+ # @return [Array<Integer>]
720
+ def unprocessed_sg_ignore_lines
721
+ (all_sg_ignore_lines - sg_ignore_lines_processed).to_a.sort
722
+ end
723
+
724
+ # @return [Array<Problem>]
725
+ def unneeded_sgignore_problems
726
+ return [] unless rules.validate_sg_ignores?
727
+
728
+ unprocessed_sg_ignore_lines.map do |line|
729
+ Problem.new(
730
+ Location.new(filename, Range.from_to(line, 0, line, 0)),
731
+ 'Unneeded @sg-ignore comment'
732
+ )
733
+ end
734
+ end
735
+
690
736
  # @param problems [Array<Problem>]
691
737
  # @return [Array<Problem>]
692
738
  def without_ignored problems
693
739
  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')
740
+ node = source.node_at(problem.location.range.start.line, problem.location.range.start.column)
741
+ ignored = node && source.comments_for(node)&.include?('@sg-ignore')
742
+ unless !ignored || all_sg_ignore_lines.include?(problem.location.range.start.line)
743
+ # :nocov:
744
+ Solargraph.assert_or_log(:sg_ignore) { "@sg-ignore accounting issue - node is #{node}" }
745
+ # :nocov:
746
+ end
747
+ sg_ignore_lines_processed.add problem.location.range.start.line if ignored
748
+ ignored
696
749
  end
697
750
  end
698
751
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.56.0'
4
+ VERSION = '0.57.0'
5
5
  end
@@ -90,7 +90,6 @@ module Solargraph
90
90
 
91
91
  # A hash of options supported by the formatter
92
92
  #
93
- # @sg-ignore pending https://github.com/castwide/solargraph/pull/905
94
93
  # @return [Hash]
95
94
  def formatter
96
95
  raw_data['formatter']
@@ -105,7 +104,6 @@ module Solargraph
105
104
 
106
105
  # The maximum number of files to parse from the workspace.
107
106
  #
108
- # @sg-ignore pending https://github.com/castwide/solargraph/pull/905
109
107
  # @return [Integer]
110
108
  def max_files
111
109
  raw_data['max_files']
@@ -151,7 +149,7 @@ module Solargraph
151
149
  # @return [Hash{String => Array, Hash, Integer}]
152
150
  def default_config
153
151
  {
154
- 'include' => ['**/*.rb'],
152
+ 'include' => ['Rakefile', 'Gemfile', '*.gemspec', '**/*.rb'],
155
153
  'exclude' => ['spec/**/*', 'test/**/*', 'vendor/**/*', '.bundle/**/*'],
156
154
  'require' => [],
157
155
  'domains' => [],
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module Solargraph
6
+ # A workspace consists of the files in a project's directory and the
7
+ # project's configuration. It provides a Source for each file to be used
8
+ # in an associated Library or ApiMap.
9
+ #
10
+ class Workspace
11
+ # Manages determining which gemspecs are available in a workspace
12
+ class RequirePaths
13
+ attr_reader :directory, :config
14
+
15
+ # @param directory [String, nil]
16
+ # @param config [Config, nil]
17
+ def initialize directory, config
18
+ @directory = directory
19
+ @config = config
20
+ end
21
+
22
+ # Generate require paths from gemspecs if they exist or assume the default
23
+ # lib directory.
24
+ #
25
+ # @return [Array<String>]
26
+ def generate
27
+ result = require_paths_from_gemspec_files
28
+ return configured_require_paths if result.empty?
29
+ result.concat(config.require_paths.map { |p| File.join(directory, p) }) if config
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Array<String>]
36
+ def require_paths_from_gemspec_files
37
+ results = []
38
+ gemspec_file_paths.each do |gemspec_file_path|
39
+ results.concat require_path_from_gemspec_file(gemspec_file_path)
40
+ end
41
+ results
42
+ end
43
+
44
+ # Get an array of all gemspec files in the workspace.
45
+ #
46
+ # @return [Array<String>]
47
+ def gemspec_file_paths
48
+ return [] if directory.nil?
49
+ @gemspec_file_paths ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
50
+ config.nil? || config.allow?(gs)
51
+ end
52
+ end
53
+
54
+ # Get additional require paths defined in the configuration.
55
+ #
56
+ # @return [Array<String>]
57
+ def configured_require_paths
58
+ return ['lib'] unless directory
59
+ return [File.join(directory, 'lib')] if !config || config.require_paths.empty?
60
+ config.require_paths.map { |p| File.join(directory, p) }
61
+ end
62
+
63
+ # Generate require paths from gemspecs if they exist or assume the default
64
+ # lib directory.
65
+ #
66
+ # @param gemspec_file_path [String]
67
+ # @return [Array<String>]
68
+ def require_path_from_gemspec_file gemspec_file_path
69
+ base = File.dirname(gemspec_file_path)
70
+ # HACK: Evaluating gemspec files violates the goal of not running
71
+ # workspace code, but this is how Gem::Specification.load does it
72
+ # anyway.
73
+ cmd = ['ruby', '-e',
74
+ "require 'rubygems'; " \
75
+ "require 'json'; " \
76
+ "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
77
+ 'return unless Gem::Specification === spec; ' \
78
+ 'puts({name: spec.name, paths: spec.require_paths}.to_json)']
79
+ # @sg-ignore Unresolved call to capture3 on Module<Open3>
80
+ o, e, s = Open3.capture3(*cmd)
81
+ if s.success?
82
+ begin
83
+ hash = o && !o.empty? ? JSON.parse(o.split("\n").last) : {}
84
+ return [] if hash.empty?
85
+ hash['paths'].map { |path| File.join(base, path) }
86
+ rescue StandardError => e
87
+ Solargraph.logger.warn "Error reading #{gemspec_file_path}: [#{e.class}] #{e.message}"
88
+ []
89
+ end
90
+ else
91
+ Solargraph.logger.warn "Error reading #{gemspec_file_path}"
92
+ Solargraph.logger.warn e
93
+ []
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end