solargraph 0.58.1 → 0.58.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +7 -1
  4. data/lib/solargraph/api_map/cache.rb +110 -110
  5. data/lib/solargraph/api_map/constants.rb +279 -279
  6. data/lib/solargraph/api_map/index.rb +193 -193
  7. data/lib/solargraph/api_map/source_to_yard.rb +97 -97
  8. data/lib/solargraph/api_map/store.rb +384 -384
  9. data/lib/solargraph/api_map.rb +945 -945
  10. data/lib/solargraph/complex_type/type_methods.rb +228 -228
  11. data/lib/solargraph/complex_type/unique_type.rb +482 -482
  12. data/lib/solargraph/complex_type.rb +444 -444
  13. data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -91
  14. data/lib/solargraph/convention/data_definition.rb +105 -105
  15. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +61 -61
  16. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +102 -102
  17. data/lib/solargraph/convention/struct_definition.rb +164 -164
  18. data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
  19. data/lib/solargraph/diagnostics/rubocop.rb +118 -118
  20. data/lib/solargraph/diagnostics/rubocop_helpers.rb +68 -68
  21. data/lib/solargraph/diagnostics/type_check.rb +55 -55
  22. data/lib/solargraph/doc_map.rb +439 -439
  23. data/lib/solargraph/equality.rb +34 -34
  24. data/lib/solargraph/gem_pins.rb +98 -98
  25. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  26. data/lib/solargraph/language_server/host/dispatch.rb +130 -130
  27. data/lib/solargraph/language_server/host/message_worker.rb +112 -112
  28. data/lib/solargraph/language_server/host/sources.rb +99 -99
  29. data/lib/solargraph/language_server/host.rb +878 -878
  30. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +114 -114
  31. data/lib/solargraph/language_server/message/extended/document.rb +23 -23
  32. data/lib/solargraph/language_server/message/text_document/completion.rb +56 -56
  33. data/lib/solargraph/language_server/message/text_document/definition.rb +40 -40
  34. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +26 -26
  35. data/lib/solargraph/language_server/message/text_document/formatting.rb +148 -148
  36. data/lib/solargraph/language_server/message/text_document/hover.rb +58 -58
  37. data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -24
  38. data/lib/solargraph/language_server/message/text_document/type_definition.rb +25 -25
  39. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
  40. data/lib/solargraph/library.rb +683 -683
  41. data/lib/solargraph/location.rb +82 -82
  42. data/lib/solargraph/logging.rb +37 -37
  43. data/lib/solargraph/parser/comment_ripper.rb +69 -69
  44. data/lib/solargraph/parser/flow_sensitive_typing.rb +255 -255
  45. data/lib/solargraph/parser/node_processor/base.rb +92 -92
  46. data/lib/solargraph/parser/node_processor.rb +62 -62
  47. data/lib/solargraph/parser/parser_gem/class_methods.rb +149 -149
  48. data/lib/solargraph/parser/parser_gem/node_chainer.rb +166 -166
  49. data/lib/solargraph/parser/parser_gem/node_methods.rb +486 -486
  50. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -22
  51. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +59 -59
  52. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +15 -15
  53. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -46
  54. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +53 -53
  55. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +23 -23
  56. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +40 -40
  57. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +29 -29
  58. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +59 -59
  59. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -98
  60. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -17
  61. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +38 -38
  62. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +52 -52
  63. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +291 -291
  64. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -29
  65. data/lib/solargraph/parser/parser_gem/node_processors.rb +70 -70
  66. data/lib/solargraph/parser/region.rb +69 -69
  67. data/lib/solargraph/parser/snippet.rb +17 -17
  68. data/lib/solargraph/pin/base.rb +729 -729
  69. data/lib/solargraph/pin/base_variable.rb +126 -126
  70. data/lib/solargraph/pin/block.rb +104 -104
  71. data/lib/solargraph/pin/breakable.rb +9 -9
  72. data/lib/solargraph/pin/callable.rb +231 -231
  73. data/lib/solargraph/pin/closure.rb +72 -72
  74. data/lib/solargraph/pin/common.rb +79 -79
  75. data/lib/solargraph/pin/conversions.rb +123 -123
  76. data/lib/solargraph/pin/delegated_method.rb +120 -120
  77. data/lib/solargraph/pin/documenting.rb +114 -114
  78. data/lib/solargraph/pin/instance_variable.rb +34 -34
  79. data/lib/solargraph/pin/keyword.rb +20 -20
  80. data/lib/solargraph/pin/local_variable.rb +75 -75
  81. data/lib/solargraph/pin/method.rb +672 -672
  82. data/lib/solargraph/pin/method_alias.rb +34 -34
  83. data/lib/solargraph/pin/namespace.rb +115 -115
  84. data/lib/solargraph/pin/parameter.rb +275 -275
  85. data/lib/solargraph/pin/proxy_type.rb +39 -39
  86. data/lib/solargraph/pin/reference/override.rb +47 -47
  87. data/lib/solargraph/pin/reference/superclass.rb +15 -15
  88. data/lib/solargraph/pin/reference.rb +39 -39
  89. data/lib/solargraph/pin/search.rb +61 -61
  90. data/lib/solargraph/pin/signature.rb +61 -61
  91. data/lib/solargraph/pin/symbol.rb +53 -53
  92. data/lib/solargraph/pin/until.rb +18 -18
  93. data/lib/solargraph/pin/while.rb +18 -18
  94. data/lib/solargraph/pin.rb +44 -44
  95. data/lib/solargraph/pin_cache.rb +245 -245
  96. data/lib/solargraph/position.rb +132 -119
  97. data/lib/solargraph/range.rb +112 -112
  98. data/lib/solargraph/rbs_map/conversions.rb +823 -823
  99. data/lib/solargraph/rbs_map/core_map.rb +58 -58
  100. data/lib/solargraph/rbs_map/stdlib_map.rb +43 -43
  101. data/lib/solargraph/rbs_map.rb +163 -163
  102. data/lib/solargraph/shell.rb +352 -352
  103. data/lib/solargraph/source/chain/call.rb +337 -337
  104. data/lib/solargraph/source/chain/constant.rb +26 -26
  105. data/lib/solargraph/source/chain/hash.rb +34 -34
  106. data/lib/solargraph/source/chain/if.rb +28 -28
  107. data/lib/solargraph/source/chain/instance_variable.rb +13 -13
  108. data/lib/solargraph/source/chain/literal.rb +48 -48
  109. data/lib/solargraph/source/chain/or.rb +23 -23
  110. data/lib/solargraph/source/chain.rb +291 -291
  111. data/lib/solargraph/source/change.rb +82 -82
  112. data/lib/solargraph/source/cursor.rb +166 -166
  113. data/lib/solargraph/source/source_chainer.rb +194 -194
  114. data/lib/solargraph/source/updater.rb +55 -55
  115. data/lib/solargraph/source.rb +498 -498
  116. data/lib/solargraph/source_map/clip.rb +226 -226
  117. data/lib/solargraph/source_map/data.rb +34 -34
  118. data/lib/solargraph/source_map/mapper.rb +259 -259
  119. data/lib/solargraph/source_map.rb +212 -212
  120. data/lib/solargraph/type_checker/checks.rb +124 -124
  121. data/lib/solargraph/type_checker/param_def.rb +37 -37
  122. data/lib/solargraph/type_checker/problem.rb +32 -32
  123. data/lib/solargraph/type_checker/rules.rb +84 -84
  124. data/lib/solargraph/type_checker.rb +814 -814
  125. data/lib/solargraph/version.rb +1 -1
  126. data/lib/solargraph/workspace/config.rb +255 -255
  127. data/lib/solargraph/workspace/require_paths.rb +97 -97
  128. data/lib/solargraph/workspace.rb +220 -220
  129. data/lib/solargraph/yard_map/helpers.rb +44 -44
  130. data/lib/solargraph/yard_map/mapper/to_method.rb +130 -130
  131. data/lib/solargraph/yard_map/mapper/to_namespace.rb +31 -31
  132. data/lib/solargraph/yard_map/mapper.rb +79 -79
  133. data/lib/solargraph/yard_map/to_method.rb +89 -89
  134. data/lib/solargraph/yardoc.rb +87 -87
  135. data/lib/solargraph.rb +105 -105
  136. data/rbs_collection.yaml +1 -1
  137. metadata +12 -12
  138. /data/{sig → rbs}/shims/ast/0/node.rbs +0 -0
  139. /data/{sig → rbs}/shims/ast/2.4/.rbs_meta.yaml +0 -0
  140. /data/{sig → rbs}/shims/ast/2.4/ast.rbs +0 -0
  141. /data/{sig → rbs}/shims/parser/3.2.0.1/builders/default.rbs +0 -0
  142. /data/{sig → rbs}/shims/parser/3.2.0.1/manifest.yaml +0 -0
  143. /data/{sig → rbs}/shims/parser/3.2.0.1/parser.rbs +0 -0
  144. /data/{sig → rbs}/shims/parser/3.2.0.1/polyfill.rbs +0 -0
  145. /data/{sig → rbs}/shims/thor/1.2.0.1/.rbs_meta.yaml +0 -0
  146. /data/{sig → rbs}/shims/thor/1.2.0.1/manifest.yaml +0 -0
  147. /data/{sig → rbs}/shims/thor/1.2.0.1/thor.rbs +0 -0
@@ -1,945 +1,945 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pathname'
4
- require 'yard'
5
- require 'solargraph/yard_tags'
6
-
7
- module Solargraph
8
- # An aggregate provider for information about Workspaces, Sources, gems, and
9
- # the Ruby core.
10
- #
11
- class ApiMap
12
- autoload :Cache, 'solargraph/api_map/cache'
13
- autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
14
- autoload :Store, 'solargraph/api_map/store'
15
- autoload :Index, 'solargraph/api_map/index'
16
- autoload :Constants, 'solargraph/api_map/constants'
17
-
18
- # @return [Array<String>]
19
- attr_reader :unresolved_requires
20
-
21
- @@core_map = RbsMap::CoreMap.new
22
-
23
- # @return [Array<String>]
24
- attr_reader :missing_docs
25
-
26
- # @param pins [Array<Solargraph::Pin::Base>]
27
- def initialize pins: []
28
- @source_map_hash = {}
29
- @cache = Cache.new
30
- index pins
31
- end
32
-
33
- #
34
- # This is a mutable object, which is cached in the Chain class -
35
- # if you add any fields which change the results of calls (not
36
- # just caches), please also change `equality_fields` below.
37
- #
38
-
39
- # @param other [Object]
40
- def eql?(other)
41
- self.class == other.class &&
42
- # @sg-ignore Flow sensitive typing needs to handle self.class == other.class
43
- equality_fields == other.equality_fields
44
- end
45
-
46
- # @param other [Object]
47
- def ==(other)
48
- self.eql?(other)
49
- end
50
-
51
- def hash
52
- equality_fields.hash
53
- end
54
-
55
- def to_s
56
- self.class.to_s
57
- end
58
-
59
- # avoid enormous dump
60
- def inspect
61
- to_s
62
- end
63
-
64
- # @param pins [Array<Pin::Base>]
65
- # @return [self]
66
- def index pins
67
- # @todo This implementation is incomplete. It should probably create a
68
- # Bench.
69
- @source_map_hash = {}
70
- conventions_environ.clear
71
- cache.clear
72
- store.update @@core_map.pins, pins
73
- self
74
- end
75
-
76
- # Map a single source.
77
- #
78
- # @param source [Source]
79
- # @param live [Boolean] True for live source map (active editor file)
80
- # @return [self]
81
- def map source, live: false
82
- map = Solargraph::SourceMap.map(source)
83
- catalog Bench.new(source_maps: [map], live_map: live ? map : nil)
84
- self
85
- end
86
-
87
- # Catalog a bench.
88
- #
89
- # @param bench [Bench]
90
- # @return [self]
91
- def catalog bench
92
- @source_map_hash = bench.source_map_hash
93
- iced_pins = bench.icebox.flat_map(&:pins)
94
- live_pins = bench.live_map&.all_pins || []
95
- conventions_environ.clear
96
- source_map_hash.each_value do |map|
97
- conventions_environ.merge map.conventions_environ
98
- end
99
- unresolved_requires = (bench.external_requires + conventions_environ.requires + bench.workspace.config.required).to_a.compact.uniq
100
- recreate_docmap = @unresolved_requires != unresolved_requires ||
101
- @doc_map&.uncached_yard_gemspecs&.any? ||
102
- @doc_map&.uncached_rbs_collection_gemspecs&.any? ||
103
- @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path
104
- if recreate_docmap
105
- @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences
106
- @unresolved_requires = @doc_map.unresolved_requires
107
- end
108
- @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins)
109
- @missing_docs = [] # @todo Implement missing docs
110
- self
111
- end
112
-
113
- # @todo need to model type def statement in chains as a symbol so
114
- # that this overload of 'protected' will typecheck @sg-ignore
115
- # @sg-ignore
116
- protected def equality_fields
117
- [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires]
118
- end
119
-
120
- # @return [DocMap]
121
- def doc_map
122
- @doc_map ||= DocMap.new([], [])
123
- end
124
-
125
- # @return [::Array<Gem::Specification>]
126
- def uncached_gemspecs
127
- @doc_map&.uncached_gemspecs || []
128
- end
129
-
130
- # @return [::Array<Gem::Specification>]
131
- def uncached_rbs_collection_gemspecs
132
- @doc_map.uncached_rbs_collection_gemspecs
133
- end
134
-
135
- # @return [::Array<Gem::Specification>]
136
- def uncached_yard_gemspecs
137
- @doc_map.uncached_yard_gemspecs
138
- end
139
-
140
- # @return [Enumerable<Pin::Base>]
141
- def core_pins
142
- @@core_map.pins
143
- end
144
-
145
- # @param name [String]
146
- # @return [YARD::Tags::MacroDirective, nil]
147
- def named_macro name
148
- store.named_macros[name]
149
- end
150
-
151
- # @return [Set<String>]
152
- def required
153
- @required ||= Set.new
154
- end
155
-
156
- # @return [Environ]
157
- def conventions_environ
158
- @conventions_environ ||= Environ.new
159
- end
160
-
161
- # @param filename [String]
162
- # @param position [Position, Array(Integer, Integer)]
163
- # @return [Source::Cursor]
164
- def cursor_at filename, position
165
- position = Position.normalize(position)
166
- raise FileNotFoundError, "File not found: #{filename}" unless source_map_hash.key?(filename)
167
- source_map_hash[filename].cursor_at(position)
168
- end
169
-
170
- # Get a clip by filename and position.
171
- #
172
- # @param filename [String]
173
- # @param position [Position, Array(Integer, Integer)]
174
- # @return [SourceMap::Clip]
175
- def clip_at filename, position
176
- position = Position.normalize(position)
177
- clip(cursor_at(filename, position))
178
- end
179
-
180
- # Create an ApiMap with a workspace in the specified directory.
181
- #
182
- # @param directory [String]
183
- #
184
- # @return [ApiMap]
185
- def self.load directory
186
- api_map = new
187
- workspace = Solargraph::Workspace.new(directory)
188
- # api_map.catalog Bench.new(workspace: workspace)
189
- library = Library.new(workspace)
190
- library.map!
191
- api_map.catalog library.bench
192
- api_map
193
- end
194
-
195
- # @param out [IO, nil]
196
- # @return [void]
197
- def cache_all!(out)
198
- @doc_map.cache_all!(out)
199
- end
200
-
201
- # @param gemspec [Gem::Specification]
202
- # @param rebuild [Boolean]
203
- # @param out [IO, nil]
204
- # @return [void]
205
- def cache_gem(gemspec, rebuild: false, out: nil)
206
- @doc_map.cache(gemspec, rebuild: rebuild, out: out)
207
- end
208
-
209
- class << self
210
- include Logging
211
- end
212
-
213
- # Create an ApiMap with a workspace in the specified directory and cache
214
- # any missing gems.
215
- #
216
- #
217
- # @param directory [String]
218
- # @param out [IO] The output stream for messages
219
- #
220
- # @return [ApiMap]
221
- def self.load_with_cache directory, out
222
- api_map = load(directory)
223
- if api_map.uncached_gemspecs.empty?
224
- logger.info { "All gems cached for #{directory}" }
225
- return api_map
226
- end
227
-
228
- api_map.cache_all!(out)
229
- load(directory)
230
- end
231
-
232
- # @return [Array<Solargraph::Pin::Base>]
233
- def pins
234
- store.pins.clone.freeze
235
- end
236
-
237
- # An array of pins based on Ruby keywords (`if`, `end`, etc.).
238
- #
239
- # @return [Enumerable<Solargraph::Pin::Keyword>]
240
- def keyword_pins
241
- store.pins_by_class(Pin::Keyword)
242
- end
243
-
244
- # True if the namespace exists.
245
- #
246
- # @param name [String] The namespace to match
247
- # @param context [String] The context to search
248
- # @return [Boolean]
249
- def namespace_exists? name, context = ''
250
- !qualify(name, context).nil?
251
- end
252
-
253
- # Get suggestions for constants in the specified namespace. The result
254
- # may contain both constant and namespace pins.
255
- #
256
- # @param namespace [String] The namespace
257
- # @param contexts [Array<String>] The contexts
258
- # @return [Array<Solargraph::Pin::Constant, Solargraph::Pin::Namespace>]
259
- def get_constants namespace, *contexts
260
- namespace ||= ''
261
- gates = contexts.clone
262
- gates.push '' if contexts.empty? && namespace.empty?
263
- gates.push namespace unless namespace.empty?
264
- store.constants
265
- .collect(gates)
266
- .select { |pin| namespace.empty? || contexts.empty? || pin.namespace == namespace }
267
- .select { |pin| pin.visibility == :public || pin.namespace == namespace }
268
- end
269
-
270
- # @param namespace [String]
271
- # @param context [String]
272
- # @return [Array<Pin::Namespace>]
273
- def get_namespace_pins namespace, context
274
- store.fqns_pins(qualify(namespace, context))
275
- end
276
-
277
- # Determine fully qualified tag for a given tag used inside the
278
- # definition of another tag ("context"). This method will start
279
- # the search in the specified context until it finds a match for
280
- # the tag.
281
- #
282
- # Does not recurse into qualifying the type parameters, but
283
- # returns any which were passed in unchanged.
284
- #
285
- # @param tag [String, nil] The namespace to
286
- # match, complete with generic parameters set to appropriate
287
- # values if available
288
- # @param gates [Array<String>] The fully qualified context in which
289
- # the tag was referenced; start from here to resolve the name.
290
- # Should not be prefixed with '::'.
291
- # @return [String, nil] fully qualified tag
292
- def qualify tag, *gates
293
- store.constants.qualify(tag, *gates)
294
- end
295
-
296
- # @see Store::Constants#resolve
297
- #
298
- # @param name [String]
299
- # @param gates [Array<String, Array<String>>]
300
- # @return [String, nil]
301
- def resolve name, *gates
302
- store.constants.resolve(name, *gates)
303
- end
304
-
305
- # Get a fully qualified namespace from a reference pin.
306
- #
307
- # @param pin [Pin::Reference]
308
- # @return [String, nil]
309
- def dereference(pin)
310
- store.constants.dereference(pin)
311
- end
312
-
313
- # @param fqns [String]
314
- # @return [Array<Pin::Reference::Extend>]
315
- def get_extends(fqns)
316
- store.get_extends(fqns)
317
- end
318
-
319
- # @param fqns [String]
320
- # @return [Array<Pin::Reference::Include>]
321
- def get_includes(fqns)
322
- store.get_includes(fqns)
323
- end
324
-
325
- # Get an array of instance variable pins defined in specified namespace
326
- # and scope.
327
- #
328
- # @param namespace [String] A fully qualified namespace
329
- # @param scope [Symbol] :instance or :class
330
- # @return [Array<Solargraph::Pin::InstanceVariable>]
331
- def get_instance_variable_pins(namespace, scope = :instance)
332
- result = []
333
- used = [namespace]
334
- result.concat store.get_instance_variables(namespace, scope)
335
- sc_fqns = namespace
336
- while (sc = store.get_superclass(sc_fqns))
337
- sc_fqns = store.constants.dereference(sc)
338
- result.concat store.get_instance_variables(sc_fqns, scope)
339
- end
340
- result
341
- end
342
-
343
- # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins
344
- # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
345
- def visible_pins(*args, **kwargs, &blk)
346
- Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
347
- end
348
-
349
- # Get an array of class variable pins for a namespace.
350
- #
351
- # @param namespace [String] A fully qualified namespace
352
- # @return [Enumerable<Solargraph::Pin::ClassVariable>]
353
- def get_class_variable_pins(namespace)
354
- prefer_non_nil_variables(store.get_class_variables(namespace))
355
- end
356
-
357
- # @return [Enumerable<Solargraph::Pin::Base>]
358
- def get_symbols
359
- store.get_symbols
360
- end
361
-
362
- # @return [Enumerable<Solargraph::Pin::GlobalVariable>]
363
- def get_global_variable_pins
364
- store.pins_by_class(Pin::GlobalVariable)
365
- end
366
-
367
- # @return [Enumerable<Solargraph::Pin::Block>]
368
- def get_block_pins
369
- store.pins_by_class(Pin::Block)
370
- end
371
-
372
- # Get an array of methods available in a particular context.
373
- #
374
- # @param rooted_tag [String] The fully qualified namespace to search for methods
375
- # @param scope [Symbol] :class or :instance
376
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
377
- # @param deep [Boolean] True to include superclasses, mixins, etc.
378
- # @return [Array<Solargraph::Pin::Method>]
379
- def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true
380
- if rooted_tag.start_with? 'Array('
381
- # Array() are really tuples - use our fill, as the RBS repo
382
- # does not give us definitions for it
383
- rooted_tag = "Solargraph::Fills::Tuple(#{rooted_tag[6..-2]})"
384
- end
385
- rooted_type = ComplexType.try_parse(rooted_tag)
386
- fqns = rooted_type.namespace
387
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
388
- cached = cache.get_methods(rooted_tag, scope, visibility, deep)
389
- return cached.clone unless cached.nil?
390
- # @type [Array<Solargraph::Pin::Method>]
391
- result = []
392
- skip = Set.new
393
- if rooted_tag == ''
394
- # @todo Implement domains
395
- conventions_environ.domains.each do |domain|
396
- type = ComplexType.try_parse(domain)
397
- next if type.undefined?
398
- result.concat inner_get_methods(type.name, type.scope, visibility, deep, skip)
399
- end
400
- result.concat inner_get_methods(rooted_tag, :class, visibility, deep, skip)
401
- result.concat inner_get_methods(rooted_tag, :instance, visibility, deep, skip)
402
- result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
403
- else
404
- result.concat inner_get_methods(rooted_tag, scope, visibility, deep, skip)
405
- unless %w[Class Class<Class>].include?(rooted_tag)
406
- result.map! do |pin|
407
- next pin unless pin.path == 'Class#new'
408
- init_pin = get_method_stack(rooted_tag, 'initialize').first
409
- next pin unless init_pin
410
-
411
- type = ComplexType::SELF
412
- new_pin = Pin::Method.new(
413
- name: 'new',
414
- scope: :class,
415
- location: init_pin.location,
416
- return_type: type,
417
- comments: init_pin.comments,
418
- closure: init_pin.closure,
419
- source: init_pin.source,
420
- type_location: init_pin.type_location,
421
- )
422
- new_pin.parameters = init_pin.parameters.map do |init_param|
423
- param = init_param.clone
424
- param.closure = new_pin
425
- param.reset_generated!
426
- param
427
- end.freeze
428
- new_pin.signatures = init_pin.signatures.map do |init_sig|
429
- sig = init_sig.proxy(type)
430
- sig.parameters = init_sig.parameters.map do |param|
431
- param = param.clone
432
- param.closure = new_pin
433
- param.reset_generated!
434
- param
435
- end.freeze
436
- sig.closure = new_pin
437
- sig.reset_generated!
438
- sig
439
- end.freeze
440
- new_pin
441
- end
442
- end
443
- result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
444
- result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
445
- end
446
- result = resolve_method_aliases(result, visibility)
447
- if namespace_pin && rooted_tag != rooted_type.name
448
- result = result.map { |method_pin| method_pin.resolve_generics(namespace_pin, rooted_type) }
449
- end
450
- cache.set_methods(rooted_tag, scope, visibility, deep, result)
451
- result
452
- end
453
-
454
- # Get an array of method pins for a complex type.
455
- #
456
- # The type's namespace and the context should be fully qualified. If the
457
- # context matches the namespace type or is a subclass of the type,
458
- # protected methods are included in the results. If protected methods are
459
- # included and internal is true, private methods are also included.
460
- #
461
- # @example
462
- # api_map = Solargraph::ApiMap.new
463
- # type = Solargraph::ComplexType.parse('String')
464
- # api_map.get_complex_type_methods(type)
465
- #
466
- # @param complex_type [Solargraph::ComplexType] The complex type of the namespace
467
- # @param context [String] The context from which the type is referenced
468
- # @param internal [Boolean] True to include private methods
469
- # @return [Array<Solargraph::Pin::Base>]
470
- def get_complex_type_methods complex_type, context = '', internal = false
471
- # This method does not qualify the complex type's namespace because
472
- # it can cause conflicts between similar names, e.g., `Foo` vs.
473
- # `Other::Foo`. It still takes a context argument to determine whether
474
- # protected and private methods are visible.
475
- return [] if complex_type.undefined? || complex_type.void?
476
- result = Set.new
477
- complex_type.each do |type|
478
- if type.duck_type?
479
- result.add Pin::DuckMethod.new(name: type.to_s[1..-1], source: :api_map)
480
- result.merge get_methods('Object')
481
- else
482
- unless type.nil? || type.name == 'void'
483
- visibility = [:public]
484
- if type.namespace == context || super_and_sub?(type.namespace, context)
485
- visibility.push :protected
486
- visibility.push :private if internal
487
- end
488
- result.merge get_methods(type.tag, scope: type.scope, visibility: visibility)
489
- end
490
- end
491
- end
492
- result.to_a
493
- end
494
-
495
- # Get a stack of method pins for a method name in a potentially
496
- # parameterized namespace. The order of the pins corresponds to
497
- # the ancestry chain, with highest precedence first.
498
- #
499
- # @example
500
- # api_map.get_method_stack('Subclass', 'method_name')
501
- # #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]
502
- #
503
- # @param rooted_tag [String] Parameterized namespace, fully qualified
504
- # @param name [String] Method name to look up
505
- # @param scope [Symbol] :instance or :class
506
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
507
- # @param preserve_generics [Boolean] True to preserve any
508
- # unresolved generic parameters, false to erase them
509
- # @return [Array<Solargraph::Pin::Method>]
510
- def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false
511
- rooted_type = ComplexType.parse(rooted_tag)
512
- fqns = rooted_type.namespace
513
- namespace_pin = store.get_path_pins(fqns).first
514
- methods = if namespace_pin.is_a?(Pin::Constant)
515
- type = namespace_pin.infer(self)
516
- if type.defined?
517
- namespace_pin = store.get_path_pins(type.namespace).first
518
- get_methods(type.namespace, scope: scope, visibility: visibility).select { |p| p.name == name }
519
- else
520
- []
521
- end
522
- else
523
- get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name }
524
- end
525
- methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics
526
- methods
527
- end
528
-
529
- # Get an array of all suggestions that match the specified path.
530
- #
531
- # @deprecated Use #get_path_pins instead.
532
- #
533
- # @param path [String] The path to find
534
- # @return [Array<Solargraph::Pin::Base>]
535
- def get_path_suggestions path
536
- return [] if path.nil?
537
- resolve_method_aliases store.get_path_pins(path)
538
- end
539
-
540
- # Get an array of pins that match the specified path.
541
- #
542
- # @param path [String]
543
- # @return [Array<Pin::Base>]
544
- def get_path_pins path
545
- get_path_suggestions(path)
546
- end
547
-
548
- # Get a list of documented paths that match the query.
549
- #
550
- # @example
551
- # api_map.query('str') # Results will include `String` and `Struct`
552
- #
553
- # @param query [String] The text to match
554
- # @return [Array<String>]
555
- def search query
556
- pins.map(&:path)
557
- .compact
558
- .select { |path| path.downcase.include?(query.downcase) }
559
- end
560
-
561
- # @deprecated This method is likely superfluous. Calling #get_path_pins
562
- # directly should be sufficient.
563
- #
564
- # @param path [String] The path to find
565
- # @return [Enumerable<Pin::Base>]
566
- def document path
567
- get_path_pins(path)
568
- end
569
-
570
- # Get an array of all symbols in the workspace that match the query.
571
- #
572
- # @param query [String]
573
- # @return [Array<Pin::Base>]
574
- def query_symbols query
575
- Pin::Search.new(
576
- source_map_hash.values.flat_map(&:document_symbols),
577
- query
578
- ).results
579
- end
580
-
581
- # @param location [Solargraph::Location]
582
- # @return [Array<Solargraph::Pin::Base>]
583
- def locate_pins location
584
- return [] if location.nil? || !source_map_hash.key?(location.filename)
585
- resolve_method_aliases source_map_hash[location.filename].locate_pins(location)
586
- end
587
-
588
- # @raise [FileNotFoundError] if the cursor's file is not in the ApiMap
589
- # @param cursor [Source::Cursor]
590
- # @return [SourceMap::Clip]
591
- def clip cursor
592
- raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)
593
-
594
- SourceMap::Clip.new(self, cursor)
595
- end
596
-
597
- # Get an array of document symbols from a file.
598
- #
599
- # @param filename [String]
600
- # @return [Array<Pin::Symbol>]
601
- def document_symbols filename
602
- return [] unless source_map_hash.key?(filename) # @todo Raise error?
603
- resolve_method_aliases source_map_hash[filename].document_symbols
604
- end
605
-
606
- # @return [Array<SourceMap>]
607
- def source_maps
608
- source_map_hash.values
609
- end
610
-
611
- # Get a source map by filename.
612
- #
613
- # @param filename [String]
614
- # @return [SourceMap]
615
- def source_map filename
616
- raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.key?(filename)
617
- source_map_hash[filename]
618
- end
619
-
620
- # True if the specified file was included in a bundle, i.e., it's either
621
- # included in a workspace or open in a library.
622
- #
623
- # @param filename [String]
624
- def bundled? filename
625
- source_map_hash.keys.include?(filename)
626
- end
627
-
628
- # Check if a class is a superclass of another class.
629
- #
630
- # @param sup [String] The superclass
631
- # @param sub [String] The subclass
632
- # @return [Boolean]
633
- def super_and_sub?(sup, sub)
634
- sup = ComplexType.try_parse(sup)
635
- sub = ComplexType.try_parse(sub)
636
- # @todo If two literals are different values of the same type, it would
637
- # make more sense for super_and_sub? to return true, but there are a
638
- # few callers that currently expect this to be false.
639
- return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s
640
- sup = sup.simplify_literals.to_s
641
- sub = sub.simplify_literals.to_s
642
- return true if sup == sub
643
- sc_fqns = sub
644
- while (sc = store.get_superclass(sc_fqns))
645
- sc_new = store.constants.dereference(sc)
646
- # Cyclical inheritance is invalid
647
- return false if sc_new == sc_fqns
648
- sc_fqns = sc_new
649
- return true if sc_fqns == sup
650
- end
651
- false
652
- end
653
-
654
- # Check if the host class includes the specified module, ignoring
655
- # type parameters used.
656
- #
657
- # @param host_ns [String] The class namesapce (no type parameters)
658
- # @param module_ns [String] The module namespace (no type parameters)
659
- #
660
- # @return [Boolean]
661
- def type_include?(host_ns, module_ns)
662
- store.get_includes(host_ns).map { |inc_tag| inc_tag.type.name }.include?(module_ns)
663
- end
664
-
665
- # @param pins [Enumerable<Pin::Base>]
666
- # @param visibility [Enumerable<Symbol>]
667
- # @return [Array<Pin::Base>]
668
- def resolve_method_aliases pins, visibility = [:public, :private, :protected]
669
- with_resolved_aliases = pins.map do |pin|
670
- next pin unless pin.is_a?(Pin::MethodAlias)
671
- resolved = resolve_method_alias(pin)
672
- next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
673
- resolved
674
- end.compact
675
- logger.debug { "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" }
676
- GemPins.combine_method_pins_by_path(with_resolved_aliases)
677
- end
678
-
679
- # @param fq_reference_tag [String] A fully qualified whose method should be pulled in
680
- # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type
681
- # parameter - used to pull generics information
682
- # @param type [ComplexType] The type which is having its
683
- # methods supplemented from fq_reference_tag
684
- # @param scope [Symbol] :class or :instance
685
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
686
- # @param deep [Boolean]
687
- # @param skip [Set<String>]
688
- # @param no_core [Boolean] Skip core classes if true
689
- # @return [Array<Pin::Base>]
690
- def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core)
691
- logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" }
692
-
693
- # Ensure the types returned by the methods in the referenced
694
- # type are relative to the generic values passed in the
695
- # reference. e.g., Foo<String> might include Enumerable<String>
696
- #
697
- # @todo perform the same translation in the other areas
698
- # here after adding a spec and handling things correctly
699
- # in ApiMap::Store and RbsMap::Conversions for each
700
- resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type)
701
- # @todo Can inner_get_methods be cached? Lots of lookups of base types going on.
702
- methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core)
703
- if namespace_pin && !resolved_reference_type.all_params.empty?
704
- reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first
705
- # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" }
706
- methods = methods.map do |method_pin|
707
- method_pin.resolve_generics(reference_pin, resolved_reference_type)
708
- end
709
- end
710
- # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" }
711
- methods
712
- end
713
-
714
- private
715
-
716
- # A hash of source maps with filename keys.
717
- #
718
- # @return [Hash{String => SourceMap}]
719
- attr_reader :source_map_hash
720
-
721
- # @return [ApiMap::Store]
722
- def store
723
- @store ||= Store.new
724
- end
725
-
726
- # @return [Solargraph::ApiMap::Cache]
727
- attr_reader :cache
728
-
729
- # @param rooted_tag [String] A fully qualified namespace, with
730
- # generic parameter values if applicable
731
- # @param scope [Symbol] :class or :instance
732
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
733
- # @param deep [Boolean]
734
- # @param skip [Set<String>]
735
- # @param no_core [Boolean] Skip core classes if true
736
- # @return [Array<Pin::Base>]
737
- def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
738
- rooted_type = ComplexType.parse(rooted_tag).force_rooted
739
- fqns = rooted_type.namespace
740
- fqns_generic_params = rooted_type.all_params
741
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
742
- return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
743
- reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
744
- return [] if skip.include?(reqstr)
745
- skip.add reqstr
746
- result = []
747
- environ = Convention.for_object(self, rooted_tag, scope, visibility, deep, skip, no_core)
748
- # ensure we start out with any immediate methods in this
749
- # namespace so we roughly match the same ordering of get_methods
750
- # and obey the 'deep' instruction
751
- direct_convention_methods, convention_methods_by_reference = environ.pins.partition { |p| p.namespace == rooted_tag }
752
- result.concat direct_convention_methods
753
-
754
- if deep && scope == :instance
755
- store.get_prepends(fqns).reverse.each do |im|
756
- fqim = store.constants.dereference(im)
757
- result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
758
- end
759
- end
760
- # Store#get_methods doesn't know about full tags, just
761
- # namespaces; resolving the generics in the method pins is this
762
- # class' responsibility
763
- methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
764
- 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}" }
765
- result.concat methods
766
- if deep
767
- result.concat convention_methods_by_reference
768
-
769
- if scope == :instance
770
- store.get_includes(fqns).reverse.each do |ref|
771
- in_tag = dereference(ref)
772
- result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true)
773
- end
774
- rooted_sc_tag = qualify_superclass(rooted_tag)
775
- unless rooted_sc_tag.nil?
776
- result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core)
777
- end
778
- else
779
- logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" }
780
- store.get_extends(fqns).reverse.each do |em|
781
- fqem = dereference(em)
782
- result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
783
- end
784
- rooted_sc_tag = qualify_superclass(rooted_tag)
785
- unless rooted_sc_tag.nil?
786
- result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, true)
787
- end
788
- unless no_core || fqns.empty?
789
- type = get_namespace_type(fqns)
790
- result.concat inner_get_methods('Class', :instance, visibility, deep, skip, no_core) if type == :class
791
- result.concat inner_get_methods('Module', :instance, visibility, deep, skip, no_core)
792
- end
793
- end
794
- store.domains(fqns).each do |d|
795
- dt = ComplexType.try_parse(d)
796
- result.concat inner_get_methods(dt.namespace, dt.scope, visibility, deep, skip)
797
- end
798
- end
799
- result
800
- end
801
-
802
- # @return [Hash]
803
- def path_macros
804
- @path_macros ||= {}
805
- end
806
-
807
- # @param fq_sub_tag [String]
808
- # @return [String, nil]
809
- def qualify_superclass fq_sub_tag
810
- store.qualify_superclass fq_sub_tag
811
- end
812
-
813
- # Get the namespace's type (Class or Module).
814
- #
815
- # @param fqns [String] A fully qualified namespace
816
- # @return [Symbol, nil] :class, :module, or nil
817
- def get_namespace_type fqns
818
- return nil if fqns.nil?
819
- # @type [Pin::Namespace, nil]
820
- pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first
821
- return nil if pin.nil?
822
- pin.type
823
- end
824
-
825
- # Sort an array of pins to put nil or undefined variables last.
826
- #
827
- # @param pins [Enumerable<Pin::BaseVariable>]
828
- # @return [Enumerable<Pin::BaseVariable>]
829
- def prefer_non_nil_variables pins
830
- result = []
831
- nil_pins = []
832
- pins.each do |pin|
833
- if pin.variable? && pin.nil_assignment?
834
- nil_pins.push pin
835
- else
836
- result.push pin
837
- end
838
- end
839
- result + nil_pins
840
- end
841
-
842
- include Logging
843
-
844
- private
845
-
846
- # @param alias_pin [Pin::MethodAlias]
847
- # @return [Pin::Method, nil]
848
- def resolve_method_alias(alias_pin)
849
- ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag)
850
- original = nil
851
-
852
- # Search each ancestor for the original method
853
- ancestors.each do |ancestor_fqns|
854
- next if ancestor_fqns.nil?
855
- ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}"
856
-
857
- # Search for the original method in the ancestor
858
- original = store.get_path_pins(ancestor_method_path).find do |candidate_pin|
859
- next if candidate_pin == alias_pin
860
-
861
- if candidate_pin.is_a?(Pin::MethodAlias)
862
- # recursively resolve method aliases
863
- resolved = resolve_method_alias(candidate_pin)
864
- break resolved if resolved
865
- end
866
-
867
- candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope
868
- end
869
-
870
- break if original
871
- end
872
-
873
- # @sg-ignore ignore `received nil` for original
874
- create_resolved_alias_pin(alias_pin, original) if original
875
- end
876
-
877
- # Fast path for creating resolved alias pins without individual method stack lookups
878
- # @param alias_pin [Pin::MethodAlias] The alias pin to resolve
879
- # @param original [Pin::Method] The original method pin that was already found
880
- # @return [Pin::Method] The resolved method pin
881
- def create_resolved_alias_pin(alias_pin, original)
882
- # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup)
883
- args = {
884
- location: alias_pin.location,
885
- type_location: original.type_location,
886
- closure: alias_pin.closure,
887
- name: alias_pin.name,
888
- comments: original.comments,
889
- scope: original.scope,
890
- visibility: original.visibility,
891
- signatures: original.signatures.map(&:clone).freeze,
892
- attribute: original.attribute?,
893
- generics: original.generics.clone,
894
- return_type: original.return_type,
895
- source: :resolve_method_alias
896
- }
897
- resolved_pin = Pin::Method.new **args
898
-
899
- # Clone signatures and parameters
900
- resolved_pin.signatures.each do |sig|
901
- sig.parameters = sig.parameters.map(&:clone).freeze
902
- sig.source = :resolve_method_alias
903
- sig.parameters.each do |param|
904
- param.closure = resolved_pin
905
- param.source = :resolve_method_alias
906
- param.reset_generated!
907
- end
908
- sig.closure = resolved_pin
909
- sig.reset_generated!
910
- end
911
-
912
- resolved_pin
913
- end
914
-
915
- # @param namespace_pin [Pin::Namespace]
916
- # @param rooted_type [ComplexType]
917
- # @param pins [Enumerable<Pin::Base>]
918
- # @return [Array<Pin::Base>]
919
- def erase_generics(namespace_pin, rooted_type, pins)
920
- return pins unless should_erase_generics_when_done?(namespace_pin, rooted_type)
921
-
922
- logger.debug("Erasing generics on namespace_pin=#{namespace_pin} / rooted_type=#{rooted_type}")
923
- pins.map do |method_pin|
924
- method_pin.erase_generics(namespace_pin.generics)
925
- end
926
- end
927
-
928
- # @param namespace_pin [Pin::Namespace]
929
- # @param rooted_type [ComplexType]
930
- def should_erase_generics_when_done?(namespace_pin, rooted_type)
931
- has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
932
- end
933
-
934
- # @param namespace_pin [Pin::Namespace, Pin::Constant]
935
- def has_generics?(namespace_pin)
936
- namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty?
937
- end
938
-
939
- # @param namespace_pin [Pin::Namespace]
940
- # @param rooted_type [ComplexType]
941
- def can_resolve_generics?(namespace_pin, rooted_type)
942
- has_generics?(namespace_pin) && !rooted_type.all_params.empty?
943
- end
944
- end
945
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'yard'
5
+ require 'solargraph/yard_tags'
6
+
7
+ module Solargraph
8
+ # An aggregate provider for information about Workspaces, Sources, gems, and
9
+ # the Ruby core.
10
+ #
11
+ class ApiMap
12
+ autoload :Cache, 'solargraph/api_map/cache'
13
+ autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
14
+ autoload :Store, 'solargraph/api_map/store'
15
+ autoload :Index, 'solargraph/api_map/index'
16
+ autoload :Constants, 'solargraph/api_map/constants'
17
+
18
+ # @return [Array<String>]
19
+ attr_reader :unresolved_requires
20
+
21
+ @@core_map = RbsMap::CoreMap.new
22
+
23
+ # @return [Array<String>]
24
+ attr_reader :missing_docs
25
+
26
+ # @param pins [Array<Solargraph::Pin::Base>]
27
+ def initialize pins: []
28
+ @source_map_hash = {}
29
+ @cache = Cache.new
30
+ index pins
31
+ end
32
+
33
+ #
34
+ # This is a mutable object, which is cached in the Chain class -
35
+ # if you add any fields which change the results of calls (not
36
+ # just caches), please also change `equality_fields` below.
37
+ #
38
+
39
+ # @param other [Object]
40
+ def eql?(other)
41
+ self.class == other.class &&
42
+ # @sg-ignore Flow sensitive typing needs to handle self.class == other.class
43
+ equality_fields == other.equality_fields
44
+ end
45
+
46
+ # @param other [Object]
47
+ def ==(other)
48
+ self.eql?(other)
49
+ end
50
+
51
+ def hash
52
+ equality_fields.hash
53
+ end
54
+
55
+ def to_s
56
+ self.class.to_s
57
+ end
58
+
59
+ # avoid enormous dump
60
+ def inspect
61
+ to_s
62
+ end
63
+
64
+ # @param pins [Array<Pin::Base>]
65
+ # @return [self]
66
+ def index pins
67
+ # @todo This implementation is incomplete. It should probably create a
68
+ # Bench.
69
+ @source_map_hash = {}
70
+ conventions_environ.clear
71
+ cache.clear
72
+ store.update @@core_map.pins, pins
73
+ self
74
+ end
75
+
76
+ # Map a single source.
77
+ #
78
+ # @param source [Source]
79
+ # @param live [Boolean] True for live source map (active editor file)
80
+ # @return [self]
81
+ def map source, live: false
82
+ map = Solargraph::SourceMap.map(source)
83
+ catalog Bench.new(source_maps: [map], live_map: live ? map : nil)
84
+ self
85
+ end
86
+
87
+ # Catalog a bench.
88
+ #
89
+ # @param bench [Bench]
90
+ # @return [self]
91
+ def catalog bench
92
+ @source_map_hash = bench.source_map_hash
93
+ iced_pins = bench.icebox.flat_map(&:pins)
94
+ live_pins = bench.live_map&.all_pins || []
95
+ conventions_environ.clear
96
+ source_map_hash.each_value do |map|
97
+ conventions_environ.merge map.conventions_environ
98
+ end
99
+ unresolved_requires = (bench.external_requires + conventions_environ.requires + bench.workspace.config.required).to_a.compact.uniq
100
+ recreate_docmap = @unresolved_requires != unresolved_requires ||
101
+ @doc_map&.uncached_yard_gemspecs&.any? ||
102
+ @doc_map&.uncached_rbs_collection_gemspecs&.any? ||
103
+ @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path
104
+ if recreate_docmap
105
+ @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences
106
+ @unresolved_requires = @doc_map.unresolved_requires
107
+ end
108
+ @cache.clear if store.update(@@core_map.pins, @doc_map.pins, conventions_environ.pins, iced_pins, live_pins)
109
+ @missing_docs = [] # @todo Implement missing docs
110
+ self
111
+ end
112
+
113
+ # @todo need to model type def statement in chains as a symbol so
114
+ # that this overload of 'protected' will typecheck @sg-ignore
115
+ # @sg-ignore
116
+ protected def equality_fields
117
+ [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires]
118
+ end
119
+
120
+ # @return [DocMap]
121
+ def doc_map
122
+ @doc_map ||= DocMap.new([], [])
123
+ end
124
+
125
+ # @return [::Array<Gem::Specification>]
126
+ def uncached_gemspecs
127
+ @doc_map&.uncached_gemspecs || []
128
+ end
129
+
130
+ # @return [::Array<Gem::Specification>]
131
+ def uncached_rbs_collection_gemspecs
132
+ @doc_map.uncached_rbs_collection_gemspecs
133
+ end
134
+
135
+ # @return [::Array<Gem::Specification>]
136
+ def uncached_yard_gemspecs
137
+ @doc_map.uncached_yard_gemspecs
138
+ end
139
+
140
+ # @return [Enumerable<Pin::Base>]
141
+ def core_pins
142
+ @@core_map.pins
143
+ end
144
+
145
+ # @param name [String]
146
+ # @return [YARD::Tags::MacroDirective, nil]
147
+ def named_macro name
148
+ store.named_macros[name]
149
+ end
150
+
151
+ # @return [Set<String>]
152
+ def required
153
+ @required ||= Set.new
154
+ end
155
+
156
+ # @return [Environ]
157
+ def conventions_environ
158
+ @conventions_environ ||= Environ.new
159
+ end
160
+
161
+ # @param filename [String]
162
+ # @param position [Position, Array(Integer, Integer)]
163
+ # @return [Source::Cursor]
164
+ def cursor_at filename, position
165
+ position = Position.normalize(position)
166
+ raise FileNotFoundError, "File not found: #{filename}" unless source_map_hash.key?(filename)
167
+ source_map_hash[filename].cursor_at(position)
168
+ end
169
+
170
+ # Get a clip by filename and position.
171
+ #
172
+ # @param filename [String]
173
+ # @param position [Position, Array(Integer, Integer)]
174
+ # @return [SourceMap::Clip]
175
+ def clip_at filename, position
176
+ position = Position.normalize(position)
177
+ clip(cursor_at(filename, position))
178
+ end
179
+
180
+ # Create an ApiMap with a workspace in the specified directory.
181
+ #
182
+ # @param directory [String]
183
+ #
184
+ # @return [ApiMap]
185
+ def self.load directory
186
+ api_map = new
187
+ workspace = Solargraph::Workspace.new(directory)
188
+ # api_map.catalog Bench.new(workspace: workspace)
189
+ library = Library.new(workspace)
190
+ library.map!
191
+ api_map.catalog library.bench
192
+ api_map
193
+ end
194
+
195
+ # @param out [IO, nil]
196
+ # @return [void]
197
+ def cache_all!(out)
198
+ @doc_map.cache_all!(out)
199
+ end
200
+
201
+ # @param gemspec [Gem::Specification]
202
+ # @param rebuild [Boolean]
203
+ # @param out [IO, nil]
204
+ # @return [void]
205
+ def cache_gem(gemspec, rebuild: false, out: nil)
206
+ @doc_map.cache(gemspec, rebuild: rebuild, out: out)
207
+ end
208
+
209
+ class << self
210
+ include Logging
211
+ end
212
+
213
+ # Create an ApiMap with a workspace in the specified directory and cache
214
+ # any missing gems.
215
+ #
216
+ #
217
+ # @param directory [String]
218
+ # @param out [IO] The output stream for messages
219
+ #
220
+ # @return [ApiMap]
221
+ def self.load_with_cache directory, out
222
+ api_map = load(directory)
223
+ if api_map.uncached_gemspecs.empty?
224
+ logger.info { "All gems cached for #{directory}" }
225
+ return api_map
226
+ end
227
+
228
+ api_map.cache_all!(out)
229
+ load(directory)
230
+ end
231
+
232
+ # @return [Array<Solargraph::Pin::Base>]
233
+ def pins
234
+ store.pins.clone.freeze
235
+ end
236
+
237
+ # An array of pins based on Ruby keywords (`if`, `end`, etc.).
238
+ #
239
+ # @return [Enumerable<Solargraph::Pin::Keyword>]
240
+ def keyword_pins
241
+ store.pins_by_class(Pin::Keyword)
242
+ end
243
+
244
+ # True if the namespace exists.
245
+ #
246
+ # @param name [String] The namespace to match
247
+ # @param context [String] The context to search
248
+ # @return [Boolean]
249
+ def namespace_exists? name, context = ''
250
+ !qualify(name, context).nil?
251
+ end
252
+
253
+ # Get suggestions for constants in the specified namespace. The result
254
+ # may contain both constant and namespace pins.
255
+ #
256
+ # @param namespace [String] The namespace
257
+ # @param contexts [Array<String>] The contexts
258
+ # @return [Array<Solargraph::Pin::Constant, Solargraph::Pin::Namespace>]
259
+ def get_constants namespace, *contexts
260
+ namespace ||= ''
261
+ gates = contexts.clone
262
+ gates.push '' if contexts.empty? && namespace.empty?
263
+ gates.push namespace unless namespace.empty?
264
+ store.constants
265
+ .collect(gates)
266
+ .select { |pin| namespace.empty? || contexts.empty? || pin.namespace == namespace }
267
+ .select { |pin| pin.visibility == :public || pin.namespace == namespace }
268
+ end
269
+
270
+ # @param namespace [String]
271
+ # @param context [String]
272
+ # @return [Array<Pin::Namespace>]
273
+ def get_namespace_pins namespace, context
274
+ store.fqns_pins(qualify(namespace, context))
275
+ end
276
+
277
+ # Determine fully qualified tag for a given tag used inside the
278
+ # definition of another tag ("context"). This method will start
279
+ # the search in the specified context until it finds a match for
280
+ # the tag.
281
+ #
282
+ # Does not recurse into qualifying the type parameters, but
283
+ # returns any which were passed in unchanged.
284
+ #
285
+ # @param tag [String, nil] The namespace to
286
+ # match, complete with generic parameters set to appropriate
287
+ # values if available
288
+ # @param gates [Array<String>] The fully qualified context in which
289
+ # the tag was referenced; start from here to resolve the name.
290
+ # Should not be prefixed with '::'.
291
+ # @return [String, nil] fully qualified tag
292
+ def qualify tag, *gates
293
+ store.constants.qualify(tag, *gates)
294
+ end
295
+
296
+ # @see Store::Constants#resolve
297
+ #
298
+ # @param name [String]
299
+ # @param gates [Array<String, Array<String>>]
300
+ # @return [String, nil]
301
+ def resolve name, *gates
302
+ store.constants.resolve(name, *gates)
303
+ end
304
+
305
+ # Get a fully qualified namespace from a reference pin.
306
+ #
307
+ # @param pin [Pin::Reference]
308
+ # @return [String, nil]
309
+ def dereference(pin)
310
+ store.constants.dereference(pin)
311
+ end
312
+
313
+ # @param fqns [String]
314
+ # @return [Array<Pin::Reference::Extend>]
315
+ def get_extends(fqns)
316
+ store.get_extends(fqns)
317
+ end
318
+
319
+ # @param fqns [String]
320
+ # @return [Array<Pin::Reference::Include>]
321
+ def get_includes(fqns)
322
+ store.get_includes(fqns)
323
+ end
324
+
325
+ # Get an array of instance variable pins defined in specified namespace
326
+ # and scope.
327
+ #
328
+ # @param namespace [String] A fully qualified namespace
329
+ # @param scope [Symbol] :instance or :class
330
+ # @return [Array<Solargraph::Pin::InstanceVariable>]
331
+ def get_instance_variable_pins(namespace, scope = :instance)
332
+ result = []
333
+ used = [namespace]
334
+ result.concat store.get_instance_variables(namespace, scope)
335
+ sc_fqns = namespace
336
+ while (sc = store.get_superclass(sc_fqns))
337
+ sc_fqns = store.constants.dereference(sc)
338
+ result.concat store.get_instance_variables(sc_fqns, scope)
339
+ end
340
+ result
341
+ end
342
+
343
+ # @sg-ignore Missing @return tag for Solargraph::ApiMap#visible_pins
344
+ # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
345
+ def visible_pins(*args, **kwargs, &blk)
346
+ Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
347
+ end
348
+
349
+ # Get an array of class variable pins for a namespace.
350
+ #
351
+ # @param namespace [String] A fully qualified namespace
352
+ # @return [Enumerable<Solargraph::Pin::ClassVariable>]
353
+ def get_class_variable_pins(namespace)
354
+ prefer_non_nil_variables(store.get_class_variables(namespace))
355
+ end
356
+
357
+ # @return [Enumerable<Solargraph::Pin::Base>]
358
+ def get_symbols
359
+ store.get_symbols
360
+ end
361
+
362
+ # @return [Enumerable<Solargraph::Pin::GlobalVariable>]
363
+ def get_global_variable_pins
364
+ store.pins_by_class(Pin::GlobalVariable)
365
+ end
366
+
367
+ # @return [Enumerable<Solargraph::Pin::Block>]
368
+ def get_block_pins
369
+ store.pins_by_class(Pin::Block)
370
+ end
371
+
372
+ # Get an array of methods available in a particular context.
373
+ #
374
+ # @param rooted_tag [String] The fully qualified namespace to search for methods
375
+ # @param scope [Symbol] :class or :instance
376
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
377
+ # @param deep [Boolean] True to include superclasses, mixins, etc.
378
+ # @return [Array<Solargraph::Pin::Method>]
379
+ def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true
380
+ if rooted_tag.start_with? 'Array('
381
+ # Array() are really tuples - use our fill, as the RBS repo
382
+ # does not give us definitions for it
383
+ rooted_tag = "Solargraph::Fills::Tuple(#{rooted_tag[6..-2]})"
384
+ end
385
+ rooted_type = ComplexType.try_parse(rooted_tag)
386
+ fqns = rooted_type.namespace
387
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
388
+ cached = cache.get_methods(rooted_tag, scope, visibility, deep)
389
+ return cached.clone unless cached.nil?
390
+ # @type [Array<Solargraph::Pin::Method>]
391
+ result = []
392
+ skip = Set.new
393
+ if rooted_tag == ''
394
+ # @todo Implement domains
395
+ conventions_environ.domains.each do |domain|
396
+ type = ComplexType.try_parse(domain)
397
+ next if type.undefined?
398
+ result.concat inner_get_methods(type.name, type.scope, visibility, deep, skip)
399
+ end
400
+ result.concat inner_get_methods(rooted_tag, :class, visibility, deep, skip)
401
+ result.concat inner_get_methods(rooted_tag, :instance, visibility, deep, skip)
402
+ result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
403
+ else
404
+ result.concat inner_get_methods(rooted_tag, scope, visibility, deep, skip)
405
+ unless %w[Class Class<Class>].include?(rooted_tag)
406
+ result.map! do |pin|
407
+ next pin unless pin.path == 'Class#new'
408
+ init_pin = get_method_stack(rooted_tag, 'initialize').first
409
+ next pin unless init_pin
410
+
411
+ type = ComplexType::SELF
412
+ new_pin = Pin::Method.new(
413
+ name: 'new',
414
+ scope: :class,
415
+ location: init_pin.location,
416
+ return_type: type,
417
+ comments: init_pin.comments,
418
+ closure: init_pin.closure,
419
+ source: init_pin.source,
420
+ type_location: init_pin.type_location,
421
+ )
422
+ new_pin.parameters = init_pin.parameters.map do |init_param|
423
+ param = init_param.clone
424
+ param.closure = new_pin
425
+ param.reset_generated!
426
+ param
427
+ end.freeze
428
+ new_pin.signatures = init_pin.signatures.map do |init_sig|
429
+ sig = init_sig.proxy(type)
430
+ sig.parameters = init_sig.parameters.map do |param|
431
+ param = param.clone
432
+ param.closure = new_pin
433
+ param.reset_generated!
434
+ param
435
+ end.freeze
436
+ sig.closure = new_pin
437
+ sig.reset_generated!
438
+ sig
439
+ end.freeze
440
+ new_pin
441
+ end
442
+ end
443
+ result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
444
+ result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
445
+ end
446
+ result = resolve_method_aliases(result, visibility)
447
+ if namespace_pin && rooted_tag != rooted_type.name
448
+ result = result.map { |method_pin| method_pin.resolve_generics(namespace_pin, rooted_type) }
449
+ end
450
+ cache.set_methods(rooted_tag, scope, visibility, deep, result)
451
+ result
452
+ end
453
+
454
+ # Get an array of method pins for a complex type.
455
+ #
456
+ # The type's namespace and the context should be fully qualified. If the
457
+ # context matches the namespace type or is a subclass of the type,
458
+ # protected methods are included in the results. If protected methods are
459
+ # included and internal is true, private methods are also included.
460
+ #
461
+ # @example
462
+ # api_map = Solargraph::ApiMap.new
463
+ # type = Solargraph::ComplexType.parse('String')
464
+ # api_map.get_complex_type_methods(type)
465
+ #
466
+ # @param complex_type [Solargraph::ComplexType] The complex type of the namespace
467
+ # @param context [String] The context from which the type is referenced
468
+ # @param internal [Boolean] True to include private methods
469
+ # @return [Array<Solargraph::Pin::Base>]
470
+ def get_complex_type_methods complex_type, context = '', internal = false
471
+ # This method does not qualify the complex type's namespace because
472
+ # it can cause conflicts between similar names, e.g., `Foo` vs.
473
+ # `Other::Foo`. It still takes a context argument to determine whether
474
+ # protected and private methods are visible.
475
+ return [] if complex_type.undefined? || complex_type.void?
476
+ result = Set.new
477
+ complex_type.each do |type|
478
+ if type.duck_type?
479
+ result.add Pin::DuckMethod.new(name: type.to_s[1..-1], source: :api_map)
480
+ result.merge get_methods('Object')
481
+ else
482
+ unless type.nil? || type.name == 'void'
483
+ visibility = [:public]
484
+ if type.namespace == context || super_and_sub?(type.namespace, context)
485
+ visibility.push :protected
486
+ visibility.push :private if internal
487
+ end
488
+ result.merge get_methods(type.tag, scope: type.scope, visibility: visibility)
489
+ end
490
+ end
491
+ end
492
+ result.to_a
493
+ end
494
+
495
+ # Get a stack of method pins for a method name in a potentially
496
+ # parameterized namespace. The order of the pins corresponds to
497
+ # the ancestry chain, with highest precedence first.
498
+ #
499
+ # @example
500
+ # api_map.get_method_stack('Subclass', 'method_name')
501
+ # #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]
502
+ #
503
+ # @param rooted_tag [String] Parameterized namespace, fully qualified
504
+ # @param name [String] Method name to look up
505
+ # @param scope [Symbol] :instance or :class
506
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
507
+ # @param preserve_generics [Boolean] True to preserve any
508
+ # unresolved generic parameters, false to erase them
509
+ # @return [Array<Solargraph::Pin::Method>]
510
+ def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false
511
+ rooted_type = ComplexType.parse(rooted_tag)
512
+ fqns = rooted_type.namespace
513
+ namespace_pin = store.get_path_pins(fqns).first
514
+ methods = if namespace_pin.is_a?(Pin::Constant)
515
+ type = namespace_pin.infer(self)
516
+ if type.defined?
517
+ namespace_pin = store.get_path_pins(type.namespace).first
518
+ get_methods(type.namespace, scope: scope, visibility: visibility).select { |p| p.name == name }
519
+ else
520
+ []
521
+ end
522
+ else
523
+ get_methods(rooted_tag, scope: scope, visibility: visibility).select { |p| p.name == name }
524
+ end
525
+ methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics
526
+ methods
527
+ end
528
+
529
+ # Get an array of all suggestions that match the specified path.
530
+ #
531
+ # @deprecated Use #get_path_pins instead.
532
+ #
533
+ # @param path [String] The path to find
534
+ # @return [Array<Solargraph::Pin::Base>]
535
+ def get_path_suggestions path
536
+ return [] if path.nil?
537
+ resolve_method_aliases store.get_path_pins(path)
538
+ end
539
+
540
+ # Get an array of pins that match the specified path.
541
+ #
542
+ # @param path [String]
543
+ # @return [Array<Pin::Base>]
544
+ def get_path_pins path
545
+ get_path_suggestions(path)
546
+ end
547
+
548
+ # Get a list of documented paths that match the query.
549
+ #
550
+ # @example
551
+ # api_map.query('str') # Results will include `String` and `Struct`
552
+ #
553
+ # @param query [String] The text to match
554
+ # @return [Array<String>]
555
+ def search query
556
+ pins.map(&:path)
557
+ .compact
558
+ .select { |path| path.downcase.include?(query.downcase) }
559
+ end
560
+
561
+ # @deprecated This method is likely superfluous. Calling #get_path_pins
562
+ # directly should be sufficient.
563
+ #
564
+ # @param path [String] The path to find
565
+ # @return [Enumerable<Pin::Base>]
566
+ def document path
567
+ get_path_pins(path)
568
+ end
569
+
570
+ # Get an array of all symbols in the workspace that match the query.
571
+ #
572
+ # @param query [String]
573
+ # @return [Array<Pin::Base>]
574
+ def query_symbols query
575
+ Pin::Search.new(
576
+ source_map_hash.values.flat_map(&:document_symbols),
577
+ query
578
+ ).results
579
+ end
580
+
581
+ # @param location [Solargraph::Location]
582
+ # @return [Array<Solargraph::Pin::Base>]
583
+ def locate_pins location
584
+ return [] if location.nil? || !source_map_hash.key?(location.filename)
585
+ resolve_method_aliases source_map_hash[location.filename].locate_pins(location)
586
+ end
587
+
588
+ # @raise [FileNotFoundError] if the cursor's file is not in the ApiMap
589
+ # @param cursor [Source::Cursor]
590
+ # @return [SourceMap::Clip]
591
+ def clip cursor
592
+ raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)
593
+
594
+ SourceMap::Clip.new(self, cursor)
595
+ end
596
+
597
+ # Get an array of document symbols from a file.
598
+ #
599
+ # @param filename [String]
600
+ # @return [Array<Pin::Symbol>]
601
+ def document_symbols filename
602
+ return [] unless source_map_hash.key?(filename) # @todo Raise error?
603
+ resolve_method_aliases source_map_hash[filename].document_symbols
604
+ end
605
+
606
+ # @return [Array<SourceMap>]
607
+ def source_maps
608
+ source_map_hash.values
609
+ end
610
+
611
+ # Get a source map by filename.
612
+ #
613
+ # @param filename [String]
614
+ # @return [SourceMap]
615
+ def source_map filename
616
+ raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.key?(filename)
617
+ source_map_hash[filename]
618
+ end
619
+
620
+ # True if the specified file was included in a bundle, i.e., it's either
621
+ # included in a workspace or open in a library.
622
+ #
623
+ # @param filename [String]
624
+ def bundled? filename
625
+ source_map_hash.keys.include?(filename)
626
+ end
627
+
628
+ # Check if a class is a superclass of another class.
629
+ #
630
+ # @param sup [String] The superclass
631
+ # @param sub [String] The subclass
632
+ # @return [Boolean]
633
+ def super_and_sub?(sup, sub)
634
+ sup = ComplexType.try_parse(sup)
635
+ sub = ComplexType.try_parse(sub)
636
+ # @todo If two literals are different values of the same type, it would
637
+ # make more sense for super_and_sub? to return true, but there are a
638
+ # few callers that currently expect this to be false.
639
+ return false if sup.literal? && sub.literal? && sup.to_s != sub.to_s
640
+ sup = sup.simplify_literals.to_s
641
+ sub = sub.simplify_literals.to_s
642
+ return true if sup == sub
643
+ sc_fqns = sub
644
+ while (sc = store.get_superclass(sc_fqns))
645
+ sc_new = store.constants.dereference(sc)
646
+ # Cyclical inheritance is invalid
647
+ return false if sc_new == sc_fqns
648
+ sc_fqns = sc_new
649
+ return true if sc_fqns == sup
650
+ end
651
+ false
652
+ end
653
+
654
+ # Check if the host class includes the specified module, ignoring
655
+ # type parameters used.
656
+ #
657
+ # @param host_ns [String] The class namesapce (no type parameters)
658
+ # @param module_ns [String] The module namespace (no type parameters)
659
+ #
660
+ # @return [Boolean]
661
+ def type_include?(host_ns, module_ns)
662
+ store.get_includes(host_ns).map { |inc_tag| inc_tag.type.name }.include?(module_ns)
663
+ end
664
+
665
+ # @param pins [Enumerable<Pin::Base>]
666
+ # @param visibility [Enumerable<Symbol>]
667
+ # @return [Array<Pin::Base>]
668
+ def resolve_method_aliases pins, visibility = [:public, :private, :protected]
669
+ with_resolved_aliases = pins.map do |pin|
670
+ next pin unless pin.is_a?(Pin::MethodAlias)
671
+ resolved = resolve_method_alias(pin)
672
+ next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
673
+ resolved
674
+ end.compact
675
+ logger.debug { "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" }
676
+ GemPins.combine_method_pins_by_path(with_resolved_aliases)
677
+ end
678
+
679
+ # @param fq_reference_tag [String] A fully qualified whose method should be pulled in
680
+ # @param namespace_pin [Pin::Base] Namespace pin for the rooted_type
681
+ # parameter - used to pull generics information
682
+ # @param type [ComplexType] The type which is having its
683
+ # methods supplemented from fq_reference_tag
684
+ # @param scope [Symbol] :class or :instance
685
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
686
+ # @param deep [Boolean]
687
+ # @param skip [Set<String>]
688
+ # @param no_core [Boolean] Skip core classes if true
689
+ # @return [Array<Pin::Base>]
690
+ def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core)
691
+ logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" }
692
+
693
+ # Ensure the types returned by the methods in the referenced
694
+ # type are relative to the generic values passed in the
695
+ # reference. e.g., Foo<String> might include Enumerable<String>
696
+ #
697
+ # @todo perform the same translation in the other areas
698
+ # here after adding a spec and handling things correctly
699
+ # in ApiMap::Store and RbsMap::Conversions for each
700
+ resolved_reference_type = ComplexType.parse(fq_reference_tag).force_rooted.resolve_generics(namespace_pin, type)
701
+ # @todo Can inner_get_methods be cached? Lots of lookups of base types going on.
702
+ methods = inner_get_methods(resolved_reference_type.tag, scope, visibility, deep, skip, no_core)
703
+ if namespace_pin && !resolved_reference_type.all_params.empty?
704
+ reference_pin = store.get_path_pins(resolved_reference_type.name).select { |p| p.is_a?(Pin::Namespace) }.first
705
+ # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolving generics with #{reference_pin.generics}, #{resolved_reference_type.rooted_tags}" }
706
+ methods = methods.map do |method_pin|
707
+ method_pin.resolve_generics(reference_pin, resolved_reference_type)
708
+ end
709
+ end
710
+ # logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) - resolved_reference_type: #{resolved_reference_type} for type=#{type}: #{methods.map(&:name)}" }
711
+ methods
712
+ end
713
+
714
+ # @param fq_sub_tag [String]
715
+ # @return [String, nil]
716
+ def qualify_superclass fq_sub_tag
717
+ store.qualify_superclass fq_sub_tag
718
+ end
719
+
720
+ private
721
+
722
+ # A hash of source maps with filename keys.
723
+ #
724
+ # @return [Hash{String => SourceMap}]
725
+ attr_reader :source_map_hash
726
+
727
+ # @return [ApiMap::Store]
728
+ def store
729
+ @store ||= Store.new
730
+ end
731
+
732
+ # @return [Solargraph::ApiMap::Cache]
733
+ attr_reader :cache
734
+
735
+ # @param rooted_tag [String] A fully qualified namespace, with
736
+ # generic parameter values if applicable
737
+ # @param scope [Symbol] :class or :instance
738
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
739
+ # @param deep [Boolean]
740
+ # @param skip [Set<String>]
741
+ # @param no_core [Boolean] Skip core classes if true
742
+ # @return [Array<Pin::Base>]
743
+ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
744
+ rooted_type = ComplexType.parse(rooted_tag).force_rooted
745
+ fqns = rooted_type.namespace
746
+ fqns_generic_params = rooted_type.all_params
747
+ namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
748
+ return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
749
+ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
750
+ return [] if skip.include?(reqstr)
751
+ skip.add reqstr
752
+ result = []
753
+ environ = Convention.for_object(self, rooted_tag, scope, visibility, deep, skip, no_core)
754
+ # ensure we start out with any immediate methods in this
755
+ # namespace so we roughly match the same ordering of get_methods
756
+ # and obey the 'deep' instruction
757
+ direct_convention_methods, convention_methods_by_reference = environ.pins.partition { |p| p.namespace == rooted_tag }
758
+ result.concat direct_convention_methods
759
+
760
+ if deep && scope == :instance
761
+ store.get_prepends(fqns).reverse.each do |im|
762
+ fqim = store.constants.dereference(im)
763
+ result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
764
+ end
765
+ end
766
+ # Store#get_methods doesn't know about full tags, just
767
+ # namespaces; resolving the generics in the method pins is this
768
+ # class' responsibility
769
+ methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
770
+ 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}" }
771
+ result.concat methods
772
+ if deep
773
+ result.concat convention_methods_by_reference
774
+
775
+ if scope == :instance
776
+ store.get_includes(fqns).reverse.each do |ref|
777
+ in_tag = dereference(ref)
778
+ result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true)
779
+ end
780
+ rooted_sc_tag = qualify_superclass(rooted_tag)
781
+ unless rooted_sc_tag.nil?
782
+ result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core)
783
+ end
784
+ else
785
+ logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" }
786
+ store.get_extends(fqns).reverse.each do |em|
787
+ fqem = dereference(em)
788
+ result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
789
+ end
790
+ rooted_sc_tag = qualify_superclass(rooted_tag)
791
+ unless rooted_sc_tag.nil?
792
+ result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, true)
793
+ end
794
+ unless no_core || fqns.empty?
795
+ type = get_namespace_type(fqns)
796
+ result.concat inner_get_methods('Class', :instance, visibility, deep, skip, no_core) if type == :class
797
+ result.concat inner_get_methods('Module', :instance, visibility, deep, skip, no_core)
798
+ end
799
+ end
800
+ store.domains(fqns).each do |d|
801
+ dt = ComplexType.try_parse(d)
802
+ result.concat inner_get_methods(dt.namespace, dt.scope, visibility, deep, skip)
803
+ end
804
+ end
805
+ result
806
+ end
807
+
808
+ # @return [Hash]
809
+ def path_macros
810
+ @path_macros ||= {}
811
+ end
812
+
813
+ # Get the namespace's type (Class or Module).
814
+ #
815
+ # @param fqns [String] A fully qualified namespace
816
+ # @return [Symbol, nil] :class, :module, or nil
817
+ def get_namespace_type fqns
818
+ return nil if fqns.nil?
819
+ # @type [Pin::Namespace, nil]
820
+ pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first
821
+ return nil if pin.nil?
822
+ pin.type
823
+ end
824
+
825
+ # Sort an array of pins to put nil or undefined variables last.
826
+ #
827
+ # @param pins [Enumerable<Pin::BaseVariable>]
828
+ # @return [Enumerable<Pin::BaseVariable>]
829
+ def prefer_non_nil_variables pins
830
+ result = []
831
+ nil_pins = []
832
+ pins.each do |pin|
833
+ if pin.variable? && pin.nil_assignment?
834
+ nil_pins.push pin
835
+ else
836
+ result.push pin
837
+ end
838
+ end
839
+ result + nil_pins
840
+ end
841
+
842
+ include Logging
843
+
844
+ private
845
+
846
+ # @param alias_pin [Pin::MethodAlias]
847
+ # @return [Pin::Method, nil]
848
+ def resolve_method_alias(alias_pin)
849
+ ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag)
850
+ original = nil
851
+
852
+ # Search each ancestor for the original method
853
+ ancestors.each do |ancestor_fqns|
854
+ next if ancestor_fqns.nil?
855
+ ancestor_method_path = "#{ancestor_fqns}#{alias_pin.scope == :instance ? '#' : '.'}#{alias_pin.original}"
856
+
857
+ # Search for the original method in the ancestor
858
+ original = store.get_path_pins(ancestor_method_path).find do |candidate_pin|
859
+ next if candidate_pin == alias_pin
860
+
861
+ if candidate_pin.is_a?(Pin::MethodAlias)
862
+ # recursively resolve method aliases
863
+ resolved = resolve_method_alias(candidate_pin)
864
+ break resolved if resolved
865
+ end
866
+
867
+ candidate_pin.is_a?(Pin::Method) && candidate_pin.scope == alias_pin.scope
868
+ end
869
+
870
+ break if original
871
+ end
872
+
873
+ # @sg-ignore ignore `received nil` for original
874
+ create_resolved_alias_pin(alias_pin, original) if original
875
+ end
876
+
877
+ # Fast path for creating resolved alias pins without individual method stack lookups
878
+ # @param alias_pin [Pin::MethodAlias] The alias pin to resolve
879
+ # @param original [Pin::Method] The original method pin that was already found
880
+ # @return [Pin::Method] The resolved method pin
881
+ def create_resolved_alias_pin(alias_pin, original)
882
+ # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup)
883
+ args = {
884
+ location: alias_pin.location,
885
+ type_location: original.type_location,
886
+ closure: alias_pin.closure,
887
+ name: alias_pin.name,
888
+ comments: original.comments,
889
+ scope: original.scope,
890
+ visibility: original.visibility,
891
+ signatures: original.signatures.map(&:clone).freeze,
892
+ attribute: original.attribute?,
893
+ generics: original.generics.clone,
894
+ return_type: original.return_type,
895
+ source: :resolve_method_alias
896
+ }
897
+ resolved_pin = Pin::Method.new **args
898
+
899
+ # Clone signatures and parameters
900
+ resolved_pin.signatures.each do |sig|
901
+ sig.parameters = sig.parameters.map(&:clone).freeze
902
+ sig.source = :resolve_method_alias
903
+ sig.parameters.each do |param|
904
+ param.closure = resolved_pin
905
+ param.source = :resolve_method_alias
906
+ param.reset_generated!
907
+ end
908
+ sig.closure = resolved_pin
909
+ sig.reset_generated!
910
+ end
911
+
912
+ resolved_pin
913
+ end
914
+
915
+ # @param namespace_pin [Pin::Namespace]
916
+ # @param rooted_type [ComplexType]
917
+ # @param pins [Enumerable<Pin::Base>]
918
+ # @return [Array<Pin::Base>]
919
+ def erase_generics(namespace_pin, rooted_type, pins)
920
+ return pins unless should_erase_generics_when_done?(namespace_pin, rooted_type)
921
+
922
+ logger.debug("Erasing generics on namespace_pin=#{namespace_pin} / rooted_type=#{rooted_type}")
923
+ pins.map do |method_pin|
924
+ method_pin.erase_generics(namespace_pin.generics)
925
+ end
926
+ end
927
+
928
+ # @param namespace_pin [Pin::Namespace]
929
+ # @param rooted_type [ComplexType]
930
+ def should_erase_generics_when_done?(namespace_pin, rooted_type)
931
+ has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
932
+ end
933
+
934
+ # @param namespace_pin [Pin::Namespace, Pin::Constant]
935
+ def has_generics?(namespace_pin)
936
+ namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty?
937
+ end
938
+
939
+ # @param namespace_pin [Pin::Namespace]
940
+ # @param rooted_type [ComplexType]
941
+ def can_resolve_generics?(namespace_pin, rooted_type)
942
+ has_generics?(namespace_pin) && !rooted_type.all_params.empty?
943
+ end
944
+ end
945
+ end