solargraph 0.56.2 → 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 (122) 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 +42 -0
  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 +1 -0
  28. data/lib/solargraph/convention/data_definition/data_definition_node.rb +3 -1
  29. data/lib/solargraph/convention/data_definition.rb +2 -1
  30. data/lib/solargraph/convention/gemspec.rb +1 -1
  31. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +1 -0
  32. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +3 -1
  33. data/lib/solargraph/convention/struct_definition.rb +36 -13
  34. data/lib/solargraph/convention.rb +31 -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 +40 -12
  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 +8 -15
  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 +1 -1
  59. data/lib/solargraph/parser/node_processor.rb +6 -2
  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/if_node.rb +2 -0
  66. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +3 -0
  67. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +35 -14
  68. data/lib/solargraph/parser/region.rb +3 -0
  69. data/lib/solargraph/parser/snippet.rb +2 -0
  70. data/lib/solargraph/pin/base.rb +50 -8
  71. data/lib/solargraph/pin/base_variable.rb +1 -2
  72. data/lib/solargraph/pin/callable.rb +9 -0
  73. data/lib/solargraph/pin/closure.rb +2 -0
  74. data/lib/solargraph/pin/common.rb +6 -2
  75. data/lib/solargraph/pin/constant.rb +2 -0
  76. data/lib/solargraph/pin/delegated_method.rb +1 -0
  77. data/lib/solargraph/pin/local_variable.rb +4 -1
  78. data/lib/solargraph/pin/method.rb +8 -5
  79. data/lib/solargraph/pin/method_alias.rb +3 -0
  80. data/lib/solargraph/pin/parameter.rb +18 -8
  81. data/lib/solargraph/pin/proxy_type.rb +1 -0
  82. data/lib/solargraph/pin/reference/override.rb +15 -1
  83. data/lib/solargraph/pin/reference/superclass.rb +5 -0
  84. data/lib/solargraph/pin/reference.rb +26 -0
  85. data/lib/solargraph/pin/search.rb +3 -1
  86. data/lib/solargraph/pin/signature.rb +2 -0
  87. data/lib/solargraph/pin/symbol.rb +5 -0
  88. data/lib/solargraph/pin_cache.rb +64 -4
  89. data/lib/solargraph/position.rb +2 -0
  90. data/lib/solargraph/range.rb +1 -0
  91. data/lib/solargraph/rbs_map/conversions.rb +7 -5
  92. data/lib/solargraph/rbs_map/core_map.rb +3 -0
  93. data/lib/solargraph/rbs_map.rb +15 -2
  94. data/lib/solargraph/shell.rb +3 -0
  95. data/lib/solargraph/source/chain/link.rb +10 -1
  96. data/lib/solargraph/source/chain.rb +9 -2
  97. data/lib/solargraph/source/change.rb +2 -2
  98. data/lib/solargraph/source/cursor.rb +2 -3
  99. data/lib/solargraph/source/source_chainer.rb +1 -1
  100. data/lib/solargraph/source.rb +5 -2
  101. data/lib/solargraph/source_map/clip.rb +1 -1
  102. data/lib/solargraph/source_map/data.rb +4 -0
  103. data/lib/solargraph/source_map/mapper.rb +4 -2
  104. data/lib/solargraph/source_map.rb +21 -14
  105. data/lib/solargraph/type_checker/param_def.rb +2 -0
  106. data/lib/solargraph/type_checker/rules.rb +8 -0
  107. data/lib/solargraph/type_checker.rb +173 -120
  108. data/lib/solargraph/version.rb +1 -1
  109. data/lib/solargraph/workspace/config.rb +0 -2
  110. data/lib/solargraph/workspace/require_paths.rb +98 -0
  111. data/lib/solargraph/workspace.rb +16 -48
  112. data/lib/solargraph/yard_map/mapper/to_method.rb +2 -2
  113. data/lib/solargraph/yardoc.rb +16 -3
  114. data/lib/solargraph.rb +2 -0
  115. data/rbs/fills/tuple.rbs +2 -3
  116. data/sig/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  117. data/sig/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  118. data/sig/shims/thor/1.2.0.1/manifest.yaml +7 -0
  119. data/sig/shims/thor/1.2.0.1/thor.rbs +17 -0
  120. data/solargraph.gemspec +14 -4
  121. metadata +123 -9
  122. data/lib/.rubocop.yml +0 -22
@@ -13,6 +13,7 @@ module Solargraph
13
13
  autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
14
14
  autoload :Store, 'solargraph/api_map/store'
15
15
  autoload :Index, 'solargraph/api_map/index'
16
+ autoload :Constants, 'solargraph/api_map/constants'
16
17
 
17
18
  # @return [Array<String>]
18
19
  attr_reader :unresolved_requires
@@ -26,7 +27,6 @@ module Solargraph
26
27
  def initialize pins: []
27
28
  @source_map_hash = {}
28
29
  @cache = Cache.new
29
- @method_alias_stack = []
30
30
  index pins
31
31
  end
32
32
 
@@ -36,11 +36,13 @@ module Solargraph
36
36
  # just caches), please also change `equality_fields` below.
37
37
  #
38
38
 
39
+ # @param other [Object]
39
40
  def eql?(other)
40
41
  self.class == other.class &&
41
42
  equality_fields == other.equality_fields
42
43
  end
43
44
 
45
+ # @param other [Object]
44
46
  def ==(other)
45
47
  self.eql?(other)
46
48
  end
@@ -64,7 +66,7 @@ module Solargraph
64
66
  # @todo This implementation is incomplete. It should probably create a
65
67
  # Bench.
66
68
  @source_map_hash = {}
67
- implicit.clear
69
+ conventions_environ.clear
68
70
  cache.clear
69
71
  store.update @@core_map.pins, pins
70
72
  self
@@ -73,10 +75,11 @@ module Solargraph
73
75
  # Map a single source.
74
76
  #
75
77
  # @param source [Source]
78
+ # @param live [Boolean] True for live source map (active editor file)
76
79
  # @return [self]
77
- def map source
80
+ def map source, live: false
78
81
  map = Solargraph::SourceMap.map(source)
79
- catalog Bench.new(source_maps: [map])
82
+ catalog Bench.new(source_maps: [map], live_map: live ? map : nil)
80
83
  self
81
84
  end
82
85
 
@@ -87,12 +90,12 @@ module Solargraph
87
90
  def catalog bench
88
91
  @source_map_hash = bench.source_map_hash
89
92
  iced_pins = bench.icebox.flat_map(&:pins)
90
- live_pins = bench.live_map&.pins || []
91
- implicit.clear
93
+ live_pins = bench.live_map&.all_pins || []
94
+ conventions_environ.clear
92
95
  source_map_hash.each_value do |map|
93
- implicit.merge map.environ
96
+ conventions_environ.merge map.conventions_environ
94
97
  end
95
- unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq
98
+ unresolved_requires = (bench.external_requires + conventions_environ.requires + bench.workspace.config.required).to_a.compact.uniq
96
99
  recreate_docmap = @unresolved_requires != unresolved_requires ||
97
100
  @doc_map&.uncached_yard_gemspecs&.any? ||
98
101
  @doc_map&.uncached_rbs_collection_gemspecs&.any? ||
@@ -101,7 +104,7 @@ module Solargraph
101
104
  @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences
102
105
  @unresolved_requires = @doc_map.unresolved_requires
103
106
  end
104
- @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
107
+ @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins)
105
108
  @missing_docs = [] # @todo Implement missing docs
106
109
  self
107
110
  end
@@ -110,9 +113,10 @@ module Solargraph
110
113
  # that this overload of 'protected' will typecheck @sg-ignore
111
114
  # @sg-ignore
112
115
  protected def equality_fields
113
- [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
116
+ [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires]
114
117
  end
115
118
 
119
+ # @return [DocMap]
116
120
  def doc_map
117
121
  @doc_map ||= DocMap.new([], [])
118
122
  end
@@ -132,7 +136,7 @@ module Solargraph
132
136
  @doc_map.uncached_yard_gemspecs
133
137
  end
134
138
 
135
- # @return [Array<Pin::Base>]
139
+ # @return [Enumerable<Pin::Base>]
136
140
  def core_pins
137
141
  @@core_map.pins
138
142
  end
@@ -149,8 +153,8 @@ module Solargraph
149
153
  end
150
154
 
151
155
  # @return [Environ]
152
- def implicit
153
- @implicit ||= Environ.new
156
+ def conventions_environ
157
+ @conventions_environ ||= Environ.new
154
158
  end
155
159
 
156
160
  # @param filename [String]
@@ -186,10 +190,16 @@ module Solargraph
186
190
  api_map
187
191
  end
188
192
 
193
+ # @param out [IO, nil]
194
+ # @return [void]
189
195
  def cache_all!(out)
190
196
  @doc_map.cache_all!(out)
191
197
  end
192
198
 
199
+ # @param gemspec [Gem::Specification]
200
+ # @param rebuild [Boolean]
201
+ # @param out [IO, nil]
202
+ # @return [void]
193
203
  def cache_gem(gemspec, rebuild: false, out: nil)
194
204
  @doc_map.cache(gemspec, rebuild: rebuild, out: out)
195
205
  end
@@ -202,9 +212,6 @@ module Solargraph
202
212
  # any missing gems.
203
213
  #
204
214
  #
205
- # @todo IO::NULL is incorrectly inferred to be a String.
206
- # @sg-ignore
207
- #
208
215
  # @param directory [String]
209
216
  # @param out [IO] The output stream for messages
210
217
  # @return [ApiMap]
@@ -255,19 +262,13 @@ module Solargraph
255
262
  # @return [Array<Solargraph::Pin::Base>]
256
263
  def get_constants namespace, *contexts
257
264
  namespace ||= ''
258
- contexts.push '' if contexts.empty?
259
- cached = cache.get_constants(namespace, contexts)
260
- return cached.clone unless cached.nil?
261
- skip = Set.new
262
- result = []
263
- contexts.each do |context|
264
- fqns = qualify(namespace, context)
265
- visibility = [:public]
266
- visibility.push :private if fqns == context
267
- result.concat inner_get_constants(fqns, visibility, skip)
268
- end
269
- cache.set_constants(namespace, contexts, result)
270
- result
265
+ gates = contexts.clone
266
+ gates.push '' if contexts.empty? && namespace.empty?
267
+ gates.push namespace unless namespace.empty?
268
+ store.constants
269
+ .collect(gates)
270
+ .select { |pin| namespace.empty? || contexts.empty? || pin.namespace == namespace }
271
+ .select { |pin| pin.visibility == :public || pin.namespace == namespace }
271
272
  end
272
273
 
273
274
  # @param namespace [String]
@@ -293,44 +294,27 @@ module Solargraph
293
294
  # Should not be prefixed with '::'.
294
295
  # @return [String, nil] fully qualified tag
295
296
  def qualify tag, context_tag = ''
296
- return tag if ['Boolean', 'self', nil].include?(tag)
297
-
298
- context_type = ComplexType.try_parse(context_tag).force_rooted
299
- return unless context_type
300
-
301
- type = ComplexType.try_parse(tag)
302
- return unless type
303
- return tag if type.literal?
304
-
305
- context_type = ComplexType.try_parse(context_tag)
306
- return unless context_type
297
+ store.constants.qualify(tag, context_tag)
298
+ end
307
299
 
308
- fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
309
- return unless fqns
300
+ # Get a fully qualified namespace from a reference pin.
301
+ #
302
+ # @param pin [Pin::Reference]
303
+ # @return [String, nil]
304
+ def dereference(pin)
305
+ store.constants.dereference(pin)
306
+ end
310
307
 
311
- fqns + type.substring
308
+ # @param fqns [String]
309
+ # @return [Array<String>]
310
+ def get_extends(fqns)
311
+ store.get_extends(fqns)
312
312
  end
313
313
 
314
- # Determine fully qualified namespace for a given namespace used
315
- # inside the definition of another tag ("context"). This method
316
- # will start the search in the specified context until it finds a
317
- # match for the namespace.
318
- #
319
- # @param namespace [String, nil] The namespace to
320
- # match
321
- # @param context_namespace [String] The context namespace in which the
322
- # tag was referenced; start from here to resolve the name
323
- # @return [String, nil] fully qualified namespace
324
- def qualify_namespace(namespace, context_namespace = '')
325
- cached = cache.get_qualified_namespace(namespace, context_namespace)
326
- return cached.clone unless cached.nil?
327
- result = if namespace.start_with?('::')
328
- inner_qualify(namespace[2..-1], '', Set.new)
329
- else
330
- inner_qualify(namespace, context_namespace, Set.new)
331
- end
332
- cache.set_qualified_namespace(namespace, context_namespace, result)
333
- result
314
+ # @param fqns [String]
315
+ # @return [Array<String>]
316
+ def get_includes(fqns)
317
+ store.get_includes(fqns)
334
318
  end
335
319
 
336
320
  # Get an array of instance variable pins defined in specified namespace
@@ -343,15 +327,15 @@ module Solargraph
343
327
  result = []
344
328
  used = [namespace]
345
329
  result.concat store.get_instance_variables(namespace, scope)
346
- sc = qualify_lower(store.get_superclass(namespace), namespace)
347
- until sc.nil? || used.include?(sc)
348
- used.push sc
349
- result.concat store.get_instance_variables(sc, scope)
350
- sc = qualify_lower(store.get_superclass(sc), sc)
330
+ sc_fqns = namespace
331
+ while (sc = store.get_superclass(sc_fqns))
332
+ sc_fqns = store.constants.dereference(sc)
333
+ result.concat store.get_instance_variables(sc_fqns, scope)
351
334
  end
352
335
  result
353
336
  end
354
337
 
338
+ # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins
355
339
  # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
356
340
  def visible_pins(*args, **kwargs, &blk)
357
341
  Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
@@ -403,7 +387,7 @@ module Solargraph
403
387
  skip = Set.new
404
388
  if rooted_tag == ''
405
389
  # @todo Implement domains
406
- implicit.domains.each do |domain|
390
+ conventions_environ.domains.each do |domain|
407
391
  type = ComplexType.try_parse(domain)
408
392
  next if type.undefined?
409
393
  result.concat inner_get_methods(type.name, type.scope, visibility, deep, skip)
@@ -514,12 +498,24 @@ module Solargraph
514
498
  # @param rooted_tag [String] Parameterized namespace, fully qualified
515
499
  # @param name [String] Method name to look up
516
500
  # @param scope [Symbol] :instance or :class
501
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
502
+ # @param preserve_generics [Boolean]
517
503
  # @return [Array<Solargraph::Pin::Method>]
518
504
  def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false
519
505
  rooted_type = ComplexType.parse(rooted_tag)
520
506
  fqns = rooted_type.namespace
521
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
522
- methods = get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name }
507
+ namespace_pin = store.get_path_pins(fqns).first
508
+ methods = if namespace_pin.is_a?(Pin::Constant)
509
+ type = namespace_pin.infer(self)
510
+ if type.defined?
511
+ namespace_pin = store.get_path_pins(type.namespace).first
512
+ get_methods(type.namespace, scope: scope, visibility: visibility).select { |p| p.name == name }
513
+ else
514
+ []
515
+ end
516
+ else
517
+ get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name }
518
+ end
523
519
  methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics
524
520
  methods
525
521
  end
@@ -629,13 +625,22 @@ module Solargraph
629
625
  # @param sub [String] The subclass
630
626
  # @return [Boolean]
631
627
  def super_and_sub?(sup, sub)
632
- fqsup = qualify(sup)
633
- cls = qualify(sub)
634
- tested = []
635
- until fqsup.nil? || cls.nil? || tested.include?(cls)
636
- return true if cls == fqsup
637
- tested.push cls
638
- cls = qualify_superclass(cls)
628
+ sup = ComplexType.try_parse(sup)
629
+ sub = ComplexType.try_parse(sub)
630
+ # @todo If two literals are different values of the same type, it would
631
+ # make more sense for super_and_sub? to return true, but there are a
632
+ # few callers that currently expect this to be false.
633
+ return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s
634
+ sup = sup.simplify_literals.to_s
635
+ sub = sub.simplify_literals.to_s
636
+ return true if sup == sub
637
+ sc_fqns = sub
638
+ while (sc = store.get_superclass(sc_fqns))
639
+ sc_new = store.constants.dereference(sc)
640
+ # Cyclical inheritance is invalid
641
+ return false if sc_new == sc_fqns
642
+ sc_fqns = sc_new
643
+ return true if sc_fqns == sup
639
644
  end
640
645
  false
641
646
  end
@@ -648,7 +653,7 @@ module Solargraph
648
653
  #
649
654
  # @return [Boolean]
650
655
  def type_include?(host_ns, module_ns)
651
- store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns)
656
+ store.get_includes(host_ns).map { |inc_tag| inc_tag.parametrized_tag.name }.include?(module_ns)
652
657
  end
653
658
 
654
659
  # @param pins [Enumerable<Pin::Base>]
@@ -656,6 +661,7 @@ module Solargraph
656
661
  # @return [Array<Pin::Base>]
657
662
  def resolve_method_aliases pins, visibility = [:public, :private, :protected]
658
663
  with_resolved_aliases = pins.map do |pin|
664
+ next pin unless pin.is_a?(Pin::MethodAlias)
659
665
  resolved = resolve_method_alias(pin)
660
666
  next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
661
667
  resolved
@@ -664,6 +670,41 @@ module Solargraph
664
670
  GemPins.combine_method_pins_by_path(with_resolved_aliases)
665
671
  end
666
672
 
673
+ # @param fq_reference_tag [String] A fully qualified whose method should be pulled in
674
+ # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type
675
+ # parameter - used to pull generics information
676
+ # @param type [ComplexType] The type which is having its
677
+ # methods supplemented from fq_reference_tag
678
+ # @param scope [Symbol] :class or :instance
679
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
680
+ # @param deep [Boolean]
681
+ # @param skip [Set<String>]
682
+ # @param no_core [Boolean] Skip core classes if true
683
+ # @return [Array<Pin::Base>]
684
+ def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core)
685
+ logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" }
686
+
687
+ # Ensure the types returned by the methods in the referenced
688
+ # type are relative to the generic values passed in the
689
+ # reference. e.g., Foo<String> might include Enumerable<String>
690
+ #
691
+ # @todo perform the same translation in the other areas
692
+ # here after adding a spec and handling things correctly
693
+ # in ApiMap::Store and RbsMap::Conversions for each
694
+ resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type)
695
+ # @todo Can inner_get_methods be cached? Lots of lookups of base types going on.
696
+ methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core)
697
+ if namespace_pin && !resolved_reference_type.all_params.empty?
698
+ reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first
699
+ # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" }
700
+ methods = methods.map do |method_pin|
701
+ method_pin.resolve_generics(reference_pin, resolved_reference_type)
702
+ end
703
+ end
704
+ # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" }
705
+ methods
706
+ end
707
+
667
708
  private
668
709
 
669
710
  # A hash of source maps with filename keys.
@@ -687,6 +728,7 @@ module Solargraph
687
728
  # @param skip [Set<String>]
688
729
  # @param no_core [Boolean] Skip core classes if true
689
730
  # @return [Array<Pin::Base>]
731
+ # rubocop:disable Metrics/CyclomaticComplexity
690
732
  def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
691
733
  rooted_type = ComplexType.parse(rooted_tag).force_rooted
692
734
  fqns = rooted_type.namespace
@@ -697,9 +739,16 @@ module Solargraph
697
739
  return [] if skip.include?(reqstr)
698
740
  skip.add reqstr
699
741
  result = []
742
+ environ = Convention.for_object(self, rooted_tag, scope, visibility, deep, skip, no_core)
743
+ # ensure we start out with any immediate methods in this
744
+ # namespace so we roughly match the same ordering of get_methods
745
+ # and obey the 'deep' instruction
746
+ direct_convention_methods, convention_methods_by_reference = environ.pins.partition { |p| p.namespace == rooted_tag }
747
+ result.concat direct_convention_methods
748
+
700
749
  if deep && scope == :instance
701
750
  store.get_prepends(fqns).reverse.each do |im|
702
- fqim = qualify(im, fqns)
751
+ fqim = store.constants.dereference(im)
703
752
  result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
704
753
  end
705
754
  end
@@ -707,20 +756,33 @@ module Solargraph
707
756
  # namespaces; resolving the generics in the method pins is this
708
757
  # class' responsibility
709
758
  methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
759
+ logger.info { "ApiMap#inner_get_methods(rooted_tag=#{rooted_tag.inspect}, scope=#{scope.inspect}, visibility=#{visibility.inspect}, deep=#{deep.inspect}, skip=#{skip.inspect}, fqns=#{fqns}) - added from store: #{methods}" }
710
760
  result.concat methods
711
761
  if deep
762
+ result.concat convention_methods_by_reference
763
+
712
764
  if scope == :instance
713
- store.get_includes(fqns).reverse.each do |include_tag|
714
- rooted_include_tag = qualify(include_tag, rooted_tag)
715
- result.concat inner_get_methods_from_reference(rooted_include_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true)
765
+ store.get_includes(fqns).reverse.each do |ref|
766
+ const = get_constants('', *ref.closure.gates).find { |pin| pin.path.end_with? ref.name }
767
+ if const.is_a?(Pin::Namespace)
768
+ result.concat inner_get_methods(const.path, scope, visibility, deep, skip, true)
769
+ elsif const.is_a?(Pin::Constant)
770
+ type = const.infer(self)
771
+ result.concat inner_get_methods(type.namespace, scope, visibility, deep, skip, true) if type.defined?
772
+ else
773
+ referenced_tag = ref.parametrized_tag
774
+ next unless referenced_tag.defined?
775
+ result.concat inner_get_methods_from_reference(referenced_tag.to_s, namespace_pin, rooted_type, scope, visibility, deep, skip, true)
776
+ end
716
777
  end
717
778
  rooted_sc_tag = qualify_superclass(rooted_tag)
718
779
  unless rooted_sc_tag.nil?
719
780
  result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core)
720
781
  end
721
782
  else
783
+ logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" }
722
784
  store.get_extends(fqns).reverse.each do |em|
723
- fqem = qualify(em, fqns)
785
+ fqem = store.constants.dereference(em)
724
786
  result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
725
787
  end
726
788
  rooted_sc_tag = qualify_superclass(rooted_tag)
@@ -740,131 +802,17 @@ module Solargraph
740
802
  end
741
803
  result
742
804
  end
743
-
744
- # @param fq_reference_tag [String] A fully qualified whose method should be pulled in
745
- # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type
746
- # parameter - used to pull generics information
747
- # @param type [ComplexType] The type which is having its
748
- # methods supplemented from fq_reference_tag
749
- # @param scope [Symbol] :class or :instance
750
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
751
- # @param deep [Boolean]
752
- # @param skip [Set<String>]
753
- # @param no_core [Boolean] Skip core classes if true
754
- # @return [Array<Pin::Base>]
755
- def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core)
756
- # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" }
757
-
758
- # Ensure the types returned by the methods in the referenced
759
- # type are relative to the generic values passed in the
760
- # reference. e.g., Foo<String> might include Enumerable<String>
761
- #
762
- # @todo perform the same translation in the other areas
763
- # here after adding a spec and handling things correctly
764
- # in ApiMap::Store and RbsMap::Conversions for each
765
- resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type)
766
- # @todo Can inner_get_methods be cached? Lots of lookups of base types going on.
767
- methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core)
768
- if namespace_pin && !resolved_reference_type.all_params.empty?
769
- reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first
770
- # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" }
771
- methods = methods.map do |method_pin|
772
- method_pin.resolve_generics(reference_pin, resolved_reference_type)
773
- end
774
- end
775
- # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" }
776
- methods
777
- end
778
-
779
- # @param fqns [String]
780
- # @param visibility [Array<Symbol>]
781
- # @param skip [Set<String>]
782
- # @return [Array<Pin::Base>]
783
- def inner_get_constants fqns, visibility, skip
784
- return [] if fqns.nil? || skip.include?(fqns)
785
- skip.add fqns
786
- result = []
787
- store.get_prepends(fqns).each do |is|
788
- result.concat inner_get_constants(qualify(is, fqns), [:public], skip)
789
- end
790
- result.concat store.get_constants(fqns, visibility)
791
- .sort { |a, b| a.name <=> b.name }
792
- store.get_includes(fqns).each do |is|
793
- result.concat inner_get_constants(qualify(is, fqns), [:public], skip)
794
- end
795
- fqsc = qualify_superclass(fqns)
796
- unless %w[Object BasicObject].include?(fqsc)
797
- result.concat inner_get_constants(fqsc, [:public], skip)
798
- end
799
- result
800
- end
805
+ # rubocop:enable Metrics/CyclomaticComplexity
801
806
 
802
807
  # @return [Hash]
803
808
  def path_macros
804
809
  @path_macros ||= {}
805
810
  end
806
811
 
807
- # @param namespace [String]
808
- # @param context [String]
809
- # @return [String, nil]
810
- def qualify_lower namespace, context
811
- qualify namespace, context.split('::')[0..-2].join('::')
812
- end
813
-
814
- # @param fq_tag [String]
812
+ # @param fq_sub_tag [String]
815
813
  # @return [String, nil]
816
814
  def qualify_superclass fq_sub_tag
817
- fq_sub_type = ComplexType.try_parse(fq_sub_tag)
818
- fq_sub_ns = fq_sub_type.name
819
- sup_tag = store.get_superclass(fq_sub_tag)
820
- sup_type = ComplexType.try_parse(sup_tag)
821
- sup_ns = sup_type.name
822
- return nil if sup_tag.nil?
823
- parts = fq_sub_ns.split('::')
824
- last = parts.pop
825
- parts.pop if last == sup_ns
826
- qualify(sup_tag, parts.join('::'))
827
- end
828
-
829
- # @param name [String] Namespace to fully qualify
830
- # @param root [String] The context to search
831
- # @param skip [Set<String>] Contexts already searched
832
- # @return [String, nil] Fully qualified ("rooted") namespace
833
- def inner_qualify name, root, skip
834
- return name if name == ComplexType::GENERIC_TAG_NAME
835
- return nil if name.nil?
836
- return nil if skip.include?(root)
837
- skip.add root
838
- possibles = []
839
- if name == ''
840
- if root == ''
841
- return ''
842
- else
843
- return inner_qualify(root, '', skip)
844
- end
845
- else
846
- return name if root == '' && store.namespace_exists?(name)
847
- roots = root.to_s.split('::')
848
- while roots.length > 0
849
- fqns = roots.join('::') + '::' + name
850
- return fqns if store.namespace_exists?(fqns)
851
- incs = store.get_includes(roots.join('::'))
852
- incs.each do |inc|
853
- foundinc = inner_qualify(name, inc, skip)
854
- possibles.push foundinc unless foundinc.nil?
855
- end
856
- roots.pop
857
- end
858
- if possibles.empty?
859
- incs = store.get_includes('')
860
- incs.each do |inc|
861
- foundinc = inner_qualify(name, inc, skip)
862
- possibles.push foundinc unless foundinc.nil?
863
- end
864
- end
865
- return name if store.namespace_exists?(name)
866
- return possibles.last
867
- end
815
+ store.qualify_superclass fq_sub_tag
868
816
  end
869
817
 
870
818
  # Get the namespace's type (Class or Module).
@@ -896,49 +844,79 @@ module Solargraph
896
844
  result + nil_pins
897
845
  end
898
846
 
899
- # @param pin [Pin::MethodAlias, Pin::Base]
900
- # @return [Pin::Method]
901
- def resolve_method_alias pin
902
- return pin unless pin.is_a?(Pin::MethodAlias)
903
- return nil if @method_alias_stack.include?(pin.path)
904
- @method_alias_stack.push pin.path
905
- origin = get_method_stack(pin.full_context.tag, pin.original, scope: pin.scope, preserve_generics: true).first
906
- @method_alias_stack.pop
907
- return nil if origin.nil?
847
+ include Logging
848
+
849
+ private
850
+
851
+ # @param alias_pin [Pin::MethodAlias]
852
+ # @return [Pin::Method, nil]
853
+ def resolve_method_alias(alias_pin)
854
+ ancestors = store.get_ancestors(alias_pin.full_context.tag)
855
+ original = nil
856
+
857
+ # Search each ancestor for the original method
858
+ ancestors.each do |ancestor_fqns|
859
+ ancestor_fqns = ComplexType.try_parse(ancestor_fqns).force_rooted.namespace
860
+ next if ancestor_fqns.nil?
861
+ ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}"
862
+
863
+ # Search for the original method in the ancestor
864
+ original = store.get_path_pins(ancestor_method_path).find do |candidate_pin|
865
+ next if candidate_pin == alias_pin
866
+
867
+ if candidate_pin.is_a?(Pin::MethodAlias)
868
+ # recursively resolve method aliases
869
+ resolved = resolve_method_alias(candidate_pin)
870
+ break resolved if resolved
871
+ end
872
+
873
+ candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope
874
+ end
875
+
876
+ break if original
877
+ end
878
+
879
+ # @sg-ignore ignore `received nil` for original
880
+ create_resolved_alias_pin(alias_pin, original) if original
881
+ end
882
+
883
+ # Fast path for creating resolved alias pins without individual method stack lookups
884
+ # @param alias_pin [Pin::MethodAlias] The alias pin to resolve
885
+ # @param original [Pin::Method] The original method pin that was already found
886
+ # @return [Pin::Method] The resolved method pin
887
+ def create_resolved_alias_pin(alias_pin, original)
888
+ # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup)
908
889
  args = {
909
- location: pin.location,
910
- type_location: origin.type_location,
911
- closure: pin.closure,
912
- name: pin.name,
913
- comments: origin.comments,
914
- scope: origin.scope,
915
- # context: pin.context,
916
- visibility: origin.visibility,
917
- signatures: origin.signatures.map(&:clone).freeze,
918
- attribute: origin.attribute?,
919
- generics: origin.generics.clone,
920
- return_type: origin.return_type,
890
+ location: alias_pin.location,
891
+ type_location: original.type_location,
892
+ closure: alias_pin.closure,
893
+ name: alias_pin.name,
894
+ comments: original.comments,
895
+ scope: original.scope,
896
+ visibility: original.visibility,
897
+ signatures: original.signatures.map(&:clone).freeze,
898
+ attribute: original.attribute?,
899
+ generics: original.generics.clone,
900
+ return_type: original.return_type,
921
901
  source: :resolve_method_alias
922
902
  }
923
- out = Pin::Method.new **args
924
- out.signatures.each do |sig|
903
+ resolved_pin = Pin::Method.new **args
904
+
905
+ # Clone signatures and parameters
906
+ resolved_pin.signatures.each do |sig|
925
907
  sig.parameters = sig.parameters.map(&:clone).freeze
926
908
  sig.source = :resolve_method_alias
927
909
  sig.parameters.each do |param|
928
- param.closure = out
910
+ param.closure = resolved_pin
929
911
  param.source = :resolve_method_alias
930
912
  param.reset_generated!
931
913
  end
932
- sig.closure = out
914
+ sig.closure = resolved_pin
933
915
  sig.reset_generated!
934
916
  end
935
- logger.debug { "ApiMap#resolve_method_alias(pin=#{pin}) - returning #{out} from #{origin}" }
936
- out
937
- end
938
-
939
- include Logging
940
917
 
941
- private
918
+ resolved_pin
919
+ end
942
920
 
943
921
  # @param namespace_pin [Pin::Namespace]
944
922
  # @param rooted_type [ComplexType]
@@ -959,9 +937,9 @@ module Solargraph
959
937
  has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
960
938
  end
961
939
 
962
- # @param namespace_pin [Pin::Namespace]
940
+ # @param namespace_pin [Pin::Namespace, Pin::Constant]
963
941
  def has_generics?(namespace_pin)
964
- namespace_pin && !namespace_pin.generics.empty?
942
+ namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty?
965
943
  end
966
944
 
967
945
  # @param namespace_pin [Pin::Namespace]