solargraph 0.57.0 → 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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +4 -2
  3. data/.github/workflows/plugins.yml +61 -27
  4. data/.github/workflows/rspec.yml +19 -4
  5. data/.github/workflows/typecheck.yml +2 -2
  6. data/.rubocop.yml +1 -1
  7. data/.rubocop_todo.yml +90 -1438
  8. data/CHANGELOG.md +27 -0
  9. data/Rakefile +1 -1
  10. data/bin/solargraph +8 -5
  11. data/lib/solargraph/api_map/constants.rb +107 -46
  12. data/lib/solargraph/api_map/index.rb +32 -8
  13. data/lib/solargraph/api_map/source_to_yard.rb +5 -2
  14. data/lib/solargraph/api_map/store.rb +22 -12
  15. data/lib/solargraph/api_map.rb +27 -33
  16. data/lib/solargraph/complex_type/type_methods.rb +5 -0
  17. data/lib/solargraph/complex_type/unique_type.rb +12 -5
  18. data/lib/solargraph/complex_type.rb +19 -2
  19. data/lib/solargraph/convention/active_support_concern.rb +1 -1
  20. data/lib/solargraph/convention/data_definition/data_definition_node.rb +1 -1
  21. data/lib/solargraph/diagnostics/rubocop_helpers.rb +4 -2
  22. data/lib/solargraph/doc_map.rb +9 -6
  23. data/lib/solargraph/environ.rb +1 -1
  24. data/lib/solargraph/equality.rb +1 -0
  25. data/lib/solargraph/gem_pins.rb +4 -0
  26. data/lib/solargraph/language_server/host.rb +10 -4
  27. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -2
  28. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -1
  29. data/lib/solargraph/language_server/message/text_document/type_definition.rb +1 -1
  30. data/lib/solargraph/language_server/progress.rb +1 -1
  31. data/lib/solargraph/language_server/request.rb +3 -1
  32. data/lib/solargraph/library.rb +3 -3
  33. data/lib/solargraph/location.rb +1 -0
  34. data/lib/solargraph/page.rb +0 -1
  35. data/lib/solargraph/parser/flow_sensitive_typing.rb +1 -1
  36. data/lib/solargraph/parser/parser_gem/class_methods.rb +2 -12
  37. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -14
  38. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +1 -0
  39. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +64 -8
  40. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +12 -3
  41. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +4 -5
  42. data/lib/solargraph/pin/base.rb +29 -8
  43. data/lib/solargraph/pin/base_variable.rb +5 -3
  44. data/lib/solargraph/pin/block.rb +3 -2
  45. data/lib/solargraph/pin/callable.rb +6 -2
  46. data/lib/solargraph/pin/closure.rb +3 -7
  47. data/lib/solargraph/pin/delegated_method.rb +0 -1
  48. data/lib/solargraph/pin/local_variable.rb +0 -4
  49. data/lib/solargraph/pin/method.rb +20 -4
  50. data/lib/solargraph/pin/parameter.rb +6 -2
  51. data/lib/solargraph/pin/proxy_type.rb +4 -1
  52. data/lib/solargraph/pin/reference.rb +2 -11
  53. data/lib/solargraph/pin/search.rb +3 -0
  54. data/lib/solargraph/pin_cache.rb +5 -5
  55. data/lib/solargraph/position.rb +1 -0
  56. data/lib/solargraph/range.rb +4 -0
  57. data/lib/solargraph/rbs_map/conversions.rb +22 -1
  58. data/lib/solargraph/rbs_map/core_fills.rb +18 -0
  59. data/lib/solargraph/rbs_map/core_map.rb +11 -7
  60. data/lib/solargraph/rbs_map.rb +2 -2
  61. data/lib/solargraph/shell.rb +82 -1
  62. data/lib/solargraph/source/chain/call.rb +7 -3
  63. data/lib/solargraph/source/chain/constant.rb +3 -66
  64. data/lib/solargraph/source/chain/if.rb +1 -1
  65. data/lib/solargraph/source/chain/link.rb +1 -1
  66. data/lib/solargraph/source/chain/or.rb +1 -1
  67. data/lib/solargraph/source/chain.rb +2 -0
  68. data/lib/solargraph/source.rb +1 -1
  69. data/lib/solargraph/source_map/clip.rb +17 -25
  70. data/lib/solargraph/source_map/mapper.rb +0 -2
  71. data/lib/solargraph/source_map.rb +8 -3
  72. data/lib/solargraph/type_checker/rules.rb +23 -9
  73. data/lib/solargraph/type_checker.rb +133 -71
  74. data/lib/solargraph/version.rb +1 -1
  75. data/lib/solargraph/workspace/config.rb +21 -3
  76. data/lib/solargraph/workspace/require_paths.rb +0 -1
  77. data/lib/solargraph/workspace.rb +14 -19
  78. data/lib/solargraph/yard_map/mapper/to_method.rb +2 -1
  79. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  80. data/lib/solargraph/yard_map/to_method.rb +2 -1
  81. data/lib/solargraph/yardoc.rb +27 -4
  82. data/rbs/fills/bundler/0/bundler.rbs +4271 -0
  83. data/rbs/fills/open3/0/open3.rbs +172 -0
  84. data/rbs/fills/rubygems/0/basic_specification.rbs +326 -0
  85. data/rbs/fills/rubygems/0/errors.rbs +364 -0
  86. data/rbs/fills/rubygems/0/spec_fetcher.rbs +107 -0
  87. data/rbs/fills/rubygems/0/specification.rbs +1753 -0
  88. data/rbs_collection.yaml +4 -4
  89. data/sig/shims/ast/0/node.rbs +5 -0
  90. data/sig/shims/ast/2.4/.rbs_meta.yaml +9 -0
  91. data/sig/shims/ast/2.4/ast.rbs +73 -0
  92. data/sig/shims/parser/3.2.0.1/manifest.yaml +7 -0
  93. data/sig/shims/parser/3.2.0.1/parser.rbs +201 -0
  94. data/sig/shims/parser/3.2.0.1/polyfill.rbs +4 -0
  95. data/solargraph.gemspec +15 -4
  96. metadata +66 -12
  97. data/lib/solargraph/parser/node_methods.rb +0 -97
  98. /data/rbs/fills/{tuple.rbs → tuple/tuple.rbs} +0 -0
@@ -41,11 +41,13 @@ module Solargraph
41
41
  # solargraph-rails is known to use this method to get the document symbols. It should probably be removed.
42
42
  @document_symbols = nil
43
43
  self.convention_pins = conventions_environ.pins
44
+ # @type [Hash{Class<Pin::Base> => Array<Pin::Base>}]
44
45
  @pin_select_cache = {}
45
46
  end
46
47
 
47
48
  # @generic T
48
49
  # @param klass [Class<generic<T>>]
50
+ #
49
51
  # @return [Array<generic<T>>]
50
52
  def pins_by_class klass
51
53
  @pin_select_cache[klass] ||= pin_class_hash.select { |key, _| key <= klass }.values.flatten
@@ -124,10 +126,13 @@ module Solargraph
124
126
  # @param line [Integer]
125
127
  # @param character [Integer]
126
128
  # @return [Pin::Namespace,Pin::Method,Pin::Block]
127
- def locate_block_pin line, character
128
- _locate_pin line, character, Pin::Namespace, Pin::Method, Pin::Block
129
+ def locate_closure_pin line, character
130
+ _locate_pin line, character, Pin::Closure
129
131
  end
130
132
 
133
+ # @deprecated Please use locate_closure_pin instead
134
+ alias locate_block_pin locate_closure_pin
135
+
131
136
  # @param name [String]
132
137
  # @return [Array<Location>]
133
138
  def references name
@@ -168,10 +173,10 @@ module Solargraph
168
173
 
169
174
  private
170
175
 
171
- # @return [Hash{Class => Array<Pin::Base>}]
172
176
  # @return [Array<Pin::Base>]
173
177
  attr_writer :convention_pins
174
178
 
179
+ # @return [Hash{Class<Pin::Base> => Array<Pin::Base>}]
175
180
  def pin_class_hash
176
181
  @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a)
177
182
  end
@@ -20,7 +20,8 @@ module Solargraph
20
20
  attr_reader :rank
21
21
 
22
22
  # @param level [Symbol]
23
- def initialize level
23
+ # @param overrides [Hash{Symbol => Symbol}]
24
+ def initialize level, overrides
24
25
  @rank = if LEVELS.key?(level)
25
26
  LEVELS[level]
26
27
  else
@@ -28,34 +29,39 @@ module Solargraph
28
29
  0
29
30
  end
30
31
  @level = LEVELS[LEVELS.values.index(@rank)]
32
+ @overrides = overrides
31
33
  end
32
34
 
33
35
  def ignore_all_undefined?
34
- rank < LEVELS[:strict]
36
+ !report_undefined?
37
+ end
38
+
39
+ def report_undefined?
40
+ report?(:report_undefined, :strict)
35
41
  end
36
42
 
37
43
  def validate_consts?
38
- rank >= LEVELS[:strict]
44
+ report?(:validate_consts, :strict)
39
45
  end
40
46
 
41
47
  def validate_calls?
42
- rank >= LEVELS[:strict]
48
+ report?(:validate_calls, :strict)
43
49
  end
44
50
 
45
51
  def require_type_tags?
46
- rank >= LEVELS[:strong]
52
+ report?(:validate_type_tags, :strong)
47
53
  end
48
54
 
49
55
  def must_tag_or_infer?
50
- rank > LEVELS[:typed]
56
+ report?(:must_tag_or_infer, :strict)
51
57
  end
52
58
 
53
59
  def validate_tags?
54
- rank > LEVELS[:normal]
60
+ report?(:validate_tags, :typed)
55
61
  end
56
62
 
57
63
  def require_all_return_types_match_inferred?
58
- rank >= LEVELS[:alpha]
64
+ report?(:require_all_return_types_match_inferred, :alpha)
59
65
  end
60
66
 
61
67
  # We keep this at strong because if you added an @ sg-ignore to
@@ -63,7 +69,15 @@ module Solargraph
63
69
  # get a false positive - we don't run stronger level checks than
64
70
  # requested for performance reasons
65
71
  def validate_sg_ignores?
66
- rank >= LEVELS[:strong]
72
+ report?(:validate_sg_ignores, :strong)
73
+ end
74
+
75
+ private
76
+
77
+ # @param type [Symbol]
78
+ # @param level [Symbol]
79
+ def report?(type, level)
80
+ rank >= LEVELS[@overrides.fetch(type, level)]
67
81
  end
68
82
  end
69
83
  end
@@ -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
@@ -96,7 +105,7 @@ module Solargraph
96
105
  def method_return_type_problems_for pin
97
106
  return [] if pin.is_a?(Pin::MethodAlias)
98
107
  result = []
99
- 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)
100
109
  if declared.undefined?
101
110
  if pin.return_type.undefined? && rules.require_type_tags?
102
111
  if pin.attribute?
@@ -152,33 +161,32 @@ module Solargraph
152
161
  # @return [Array<Problem>]
153
162
  def method_param_type_problems_for pin
154
163
  stack = api_map.get_method_stack(pin.namespace, pin.name, scope: pin.scope)
155
- params = first_param_hash(stack)
156
164
  result = []
157
- if rules.require_type_tags?
158
- pin.signatures.each do |sig|
159
- sig.parameters.each do |par|
160
- break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg
161
- unless params[par.name]
162
- if pin.attribute?
163
- inferred = pin.probe(api_map).self_to_type(pin.full_context)
164
- 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
165
177
  result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
166
178
  end
167
- else
168
- result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin)
169
179
  end
170
180
  end
171
- end
172
181
  end
173
- end
174
- # @todo Should be able to probe type of name and data here
175
- # @param name [String]
176
- # @param data [Hash{Symbol => BasicObject}]
177
- params.each_pair do |name, data|
178
- # @type [ComplexType]
179
- type = data[:qualified]
180
- if type.undefined?
181
- 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
182
190
  end
183
191
  end
184
192
  result
@@ -196,7 +204,7 @@ module Solargraph
196
204
  if pin.return_type.defined?
197
205
  declared = pin.typify(api_map)
198
206
  next if declared.duck_type?
199
- if declared.defined?
207
+ if declared.defined? && pin.assignment
200
208
  if rules.validate_tags?
201
209
  inferred = pin.probe(api_map)
202
210
  if inferred.undefined?
@@ -217,7 +225,7 @@ module Solargraph
217
225
  elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin)
218
226
  result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin)
219
227
  end
220
- else
228
+ elsif pin.assignment
221
229
  inferred = pin.probe(api_map)
222
230
  if inferred.undefined? && declared_externally?(pin)
223
231
  ignored_pins.push pin
@@ -239,10 +247,11 @@ module Solargraph
239
247
  Solargraph::Parser::NodeMethods.const_nodes_from(source.node).each do |const|
240
248
  rng = Solargraph::Range.from_node(const)
241
249
  chain = Solargraph::Parser.chain(const, filename)
242
- 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)
243
252
  location = Location.new(filename, rng)
244
253
  locals = source_map.locals_at(location)
245
- pins = chain.define(api_map, block_pin, locals)
254
+ pins = chain.define(api_map, closure_pin, locals)
246
255
  if pins.empty?
247
256
  result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
248
257
  @marked_ranges.push location.range
@@ -258,17 +267,25 @@ module Solargraph
258
267
  rng = Solargraph::Range.from_node(call)
259
268
  next if @marked_ranges.any? { |d| d.contain?(rng.start) }
260
269
  chain = Solargraph::Parser.chain(call, filename)
261
- 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)
262
279
  location = Location.new(filename, rng)
263
280
  locals = source_map.locals_at(location)
264
- type = chain.infer(api_map, block_pin, locals)
281
+ type = chain.infer(api_map, closure_pin, locals)
265
282
  if type.undefined? && !rules.ignore_all_undefined?
266
283
  base = chain
267
284
  missing = chain
268
285
  found = nil
269
286
  closest = ComplexType::UNDEFINED
270
287
  until base.links.first.undefined?
271
- found = base.define(api_map, block_pin, locals).first
288
+ found = base.define(api_map, closure_pin, locals).first
272
289
  break if found
273
290
  missing = base
274
291
  base = base.base
@@ -286,7 +303,7 @@ module Solargraph
286
303
  end
287
304
  end
288
305
  end
289
- 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)
290
307
  end
291
308
  result
292
309
  end
@@ -309,8 +326,9 @@ module Solargraph
309
326
  pins = base.define(api_map, closure_pin, locals)
310
327
 
311
328
  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)
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)
314
332
  # @type [Pin::Method]
315
333
  pin = first_pin
316
334
  ap = if base.links.last.is_a?(Solargraph::Source::Chain::ZSuper)
@@ -322,7 +340,6 @@ module Solargraph
322
340
  base.base.infer(api_map, closure_pin, locals).namespace
323
341
  end
324
342
  init = api_map.get_method_stack(fqns, 'initialize').first
325
-
326
343
  init ? arity_problems_for(init, arguments, location) : []
327
344
  else
328
345
  arity_problems_for(pin, arguments, location)
@@ -330,11 +347,12 @@ module Solargraph
330
347
  return ap unless ap.empty?
331
348
  return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper)
332
349
 
333
- params = first_param_hash(pins)
334
-
335
350
  all_errors = []
336
351
  pin.signatures.sort { |sig| sig.parameters.length }.each do |sig|
352
+ params = param_details_from_stack(sig, pins)
353
+
337
354
  signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin
355
+
338
356
  if signature_errors.empty?
339
357
  # we found a signature that works - meaning errors from
340
358
  # other signatures don't matter.
@@ -411,6 +429,7 @@ module Solargraph
411
429
  # @todo Some level (strong, I guess) should require the param here
412
430
  else
413
431
  argtype = argchain.infer(api_map, closure_pin, locals)
432
+ argtype = argtype.self_to_type(closure_pin.context)
414
433
  if argtype.defined? && ptype.defined? && !any_types_match?(api_map, ptype, argtype)
415
434
  errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
416
435
  return errors
@@ -428,7 +447,7 @@ module Solargraph
428
447
  # @param sig [Pin::Signature]
429
448
  # @param argchain [Source::Chain]
430
449
  # @param api_map [ApiMap]
431
- # @param block_pin [Pin::Block]
450
+ # @param closure_pin [Pin::Closure]
432
451
  # @param locals [Array<Pin::LocalVariable>]
433
452
  # @param location [Location]
434
453
  # @param pin [Pin::Method]
@@ -436,13 +455,13 @@ module Solargraph
436
455
  # @param idx [Integer]
437
456
  #
438
457
  # @return [Array<Problem>]
439
- 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
440
459
  result = []
441
460
  kwargs = convert_hash(argchain.node)
442
461
  par = sig.parameters[idx]
443
462
  argchain = kwargs[par.name.to_sym]
444
463
  if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
445
- 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)
446
465
  else
447
466
  if argchain
448
467
  data = params[par.name]
@@ -450,8 +469,11 @@ module Solargraph
450
469
  # @todo Some level (strong, I guess) should require the param here
451
470
  else
452
471
  ptype = data[:qualified]
472
+ ptype = ptype.self_to_type(pin.context)
453
473
  unless ptype.undefined?
454
- 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?
455
477
  if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
456
478
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}")
457
479
  end
@@ -465,19 +487,21 @@ module Solargraph
465
487
  end
466
488
 
467
489
  # @param api_map [ApiMap]
468
- # @param block_pin [Pin::Block]
490
+ # @param closure_pin [Pin::Closure]
469
491
  # @param locals [Array<Pin::LocalVariable>]
470
492
  # @param location [Location]
471
493
  # @param pin [Pin::Method]
472
494
  # @param params [Hash{String => [nil, Hash]}]
473
495
  # @param kwargs [Hash{Symbol => Source::Chain}]
474
496
  # @return [Array<Problem>]
475
- 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)
476
498
  result = []
477
499
  kwargs.each_pair do |pname, argchain|
478
500
  next unless params.key?(pname.to_s)
479
501
  ptype = params[pname.to_s][:qualified]
480
- 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)
481
505
  if argtype.defined? && ptype && !any_types_match?(api_map, ptype, argtype)
482
506
  result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}")
483
507
  end
@@ -485,9 +509,32 @@ module Solargraph
485
509
  result
486
510
  end
487
511
 
488
- # @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]
489
536
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
490
- def param_hash(pin)
537
+ def signature_param_details(pin)
491
538
  # @type [Hash{String => Hash{Symbol => String, ComplexType}}]
492
539
  result = {}
493
540
  pin.parameters.each do |param|
@@ -506,36 +553,50 @@ module Solargraph
506
553
  next if tag.types.nil?
507
554
  result[tag.name.to_s] = {
508
555
  tagged: tag.types.join(', '),
509
- 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)
510
557
  }
511
558
  end
512
559
  result
513
560
  end
514
561
 
515
- # @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>]
516
582
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
517
- def first_param_hash(pins)
518
- return {} if pins.empty?
519
- first_pin_type = pins.first.typify(api_map)
520
- first_pin = pins.first.proxy first_pin_type
521
- param_names = first_pin.parameter_names
522
- results = param_hash(first_pin)
523
- pins[1..].each do |pin|
524
- # @todo this assignment from parametric use of Hash should not lose its generic
525
- # @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)
526
591
 
527
592
  # documentation of types in superclasses should fail back to
528
593
  # subclasses if the subclass hasn't documented something
529
- superclass_results = param_hash(pin)
530
- superclass_results.each do |param_name, details|
531
- next unless param_names.include?(param_name)
532
-
533
- results[param_name] ||= {}
534
- results[param_name][:tagged] ||= details[:tagged]
535
- 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)
536
597
  end
537
598
  end
538
- results
599
+ param_details
539
600
  end
540
601
 
541
602
  # @param pin [Pin::Base]
@@ -558,20 +619,21 @@ module Solargraph
558
619
 
559
620
  # @param pin [Pin::BaseVariable]
560
621
  def declared_externally? pin
561
- return true if pin.assignment.nil?
622
+ raise "No assignment found" if pin.assignment.nil?
623
+
562
624
  chain = Solargraph::Parser.chain(pin.assignment, filename)
563
625
  rng = Solargraph::Range.from_node(pin.assignment)
564
- 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)
565
627
  location = Location.new(filename, Range.from_node(pin.assignment))
566
628
  locals = source_map.locals_at(location)
567
- type = chain.infer(api_map, block_pin, locals)
629
+ type = chain.infer(api_map, closure_pin, locals)
568
630
  if type.undefined? && !rules.ignore_all_undefined?
569
631
  base = chain
570
632
  missing = chain
571
633
  found = nil
572
634
  closest = ComplexType::UNDEFINED
573
635
  until base.links.first.undefined?
574
- found = base.define(api_map, block_pin, locals).first
636
+ found = base.define(api_map, closure_pin, locals).first
575
637
  break if found
576
638
  missing = base
577
639
  base = base.base
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.57.0'
4
+ VERSION = '0.58.0'
5
5
  end
@@ -14,8 +14,8 @@ module Solargraph
14
14
  # @return [String]
15
15
  attr_reader :directory
16
16
 
17
- # @todo To make this strongly typed we'll need a record syntax
18
- # @return [Hash{String => Array, Hash, Integer, nil}]
17
+ # @todo To make JSON strongly typed we'll need a record syntax
18
+ # @return [Hash{String => undefined, nil}]
19
19
  attr_reader :raw_data
20
20
 
21
21
  # @param directory [String]
@@ -63,6 +63,7 @@ module Solargraph
63
63
  # namespace. It's typically used to identify available DSLs.
64
64
  #
65
65
  # @return [Array<String>]
66
+ # @sg-ignore Need to validate config
66
67
  def domains
67
68
  raw_data['domains']
68
69
  end
@@ -70,6 +71,7 @@ module Solargraph
70
71
  # An array of required paths to add to the workspace.
71
72
  #
72
73
  # @return [Array<String>]
74
+ # @sg-ignore Need to validate config
73
75
  def required
74
76
  raw_data['require']
75
77
  end
@@ -83,6 +85,7 @@ module Solargraph
83
85
 
84
86
  # An array of reporters to use for diagnostics.
85
87
  #
88
+ # @sg-ignore Need to validate config
86
89
  # @return [Array<String>]
87
90
  def reporters
88
91
  raw_data['reporters']
@@ -90,6 +93,7 @@ module Solargraph
90
93
 
91
94
  # A hash of options supported by the formatter
92
95
  #
96
+ # @sg-ignore Need to validate config
93
97
  # @return [Hash]
94
98
  def formatter
95
99
  raw_data['formatter']
@@ -97,6 +101,7 @@ module Solargraph
97
101
 
98
102
  # An array of plugins to require.
99
103
  #
104
+ # @sg-ignore Need to validate config
100
105
  # @return [Array<String>]
101
106
  def plugins
102
107
  raw_data['plugins']
@@ -104,11 +109,21 @@ module Solargraph
104
109
 
105
110
  # The maximum number of files to parse from the workspace.
106
111
  #
112
+ # @sg-ignore Need to validate config
107
113
  # @return [Integer]
108
114
  def max_files
109
115
  raw_data['max_files']
110
116
  end
111
117
 
118
+ # @return [Hash{Symbol => Symbol}]
119
+ def type_checker_rules
120
+ # @type [Hash{String => String}]
121
+ raw_rules = raw_data.fetch('type_checker', {}).fetch('rules', {})
122
+ raw_rules.to_h do |k, v|
123
+ [k.to_sym, v.to_sym]
124
+ end
125
+ end
126
+
112
127
  private
113
128
 
114
129
  # @return [String]
@@ -123,7 +138,7 @@ module Solargraph
123
138
  File.join(@directory, '.solargraph.yml')
124
139
  end
125
140
 
126
- # @return [Hash{String => Array<undefined>, Hash{String => undefined}, Integer}]
141
+ # @return [Hash{String => undefined}]
127
142
  def config_data
128
143
  workspace_config = read_config(workspace_config_path)
129
144
  global_config = read_config(global_config_path)
@@ -162,6 +177,9 @@ module Solargraph
162
177
  'extra_args' =>[]
163
178
  }
164
179
  },
180
+ 'type_checker' => {
181
+ 'rules' => { }
182
+ },
165
183
  'require_paths' => [],
166
184
  'plugins' => [],
167
185
  'max_files' => MAX_FILES
@@ -76,7 +76,6 @@ module Solargraph
76
76
  "spec = eval(File.read('#{gemspec_file_path}'), TOPLEVEL_BINDING, '#{gemspec_file_path}'); " \
77
77
  'return unless Gem::Specification === spec; ' \
78
78
  'puts({name: spec.name, paths: spec.require_paths}.to_json)']
79
- # @sg-ignore Unresolved call to capture3 on Module<Open3>
80
79
  o, e, s = Open3.capture3(*cmd)
81
80
  if s.success?
82
81
  begin
@@ -19,11 +19,17 @@ module Solargraph
19
19
  attr_reader :gemnames
20
20
  alias source_gems gemnames
21
21
 
22
- # @param directory [String]
22
+ # @param directory [String] TODO: Remove '' and '*' special cases
23
23
  # @param config [Config, nil]
24
24
  # @param server [Hash]
25
25
  def initialize directory = '', config = nil, server = {}
26
- @directory = directory
26
+ raise ArgumentError, 'directory must be a String' unless directory.is_a?(String)
27
+
28
+ @directory = if ['*', ''].include?(directory)
29
+ directory
30
+ else
31
+ File.absolute_path(directory)
32
+ end
27
33
  @config = config
28
34
  @server = server
29
35
  load_sources
@@ -44,6 +50,12 @@ module Solargraph
44
50
  @config ||= Solargraph::Workspace::Config.new(directory)
45
51
  end
46
52
 
53
+ # @param level [Symbol]
54
+ # @return [TypeChecker::Rules]
55
+ def rules(level)
56
+ @rules ||= TypeChecker::Rules.new(level, config.type_checker_rules)
57
+ end
58
+
47
59
  # Merge the source. A merge will update the existing source for the file
48
60
  # or add it to the sources if the workspace is configured to include it.
49
61
  # The source is ignored if the configuration excludes it.
@@ -114,23 +126,6 @@ module Solargraph
114
126
  false
115
127
  end
116
128
 
117
- # True if the workspace contains at least one gemspec file.
118
- #
119
- # @return [Boolean]
120
- def gemspec?
121
- !gemspecs.empty?
122
- end
123
-
124
- # Get an array of all gemspec files in the workspace.
125
- #
126
- # @return [Array<String>]
127
- def gemspecs
128
- return [] if directory.empty? || directory == '*'
129
- @gemspecs ||= Dir[File.join(directory, '**/*.gemspec')].select do |gs|
130
- config.allow? gs
131
- end
132
- end
133
-
134
129
  # @return [String, nil]
135
130
  def rbs_collection_path
136
131
  @gem_rbs_collection ||= read_rbs_collection_path