solargraph 0.56.0 → 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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -0
  3. data/.github/workflows/linting.yml +127 -0
  4. data/.github/workflows/plugins.yml +183 -7
  5. data/.github/workflows/rspec.yml +55 -5
  6. data/.github/workflows/typecheck.yml +6 -3
  7. data/.gitignore +6 -0
  8. data/.overcommit.yml +72 -0
  9. data/.rspec +1 -0
  10. data/.rubocop.yml +66 -0
  11. data/.rubocop_todo.yml +1279 -0
  12. data/.yardopts +1 -0
  13. data/CHANGELOG.md +92 -1
  14. data/README.md +8 -4
  15. data/Rakefile +125 -13
  16. data/bin/solargraph +3 -0
  17. data/lib/solargraph/api_map/cache.rb +110 -109
  18. data/lib/solargraph/api_map/constants.rb +279 -0
  19. data/lib/solargraph/api_map/index.rb +193 -175
  20. data/lib/solargraph/api_map/source_to_yard.rb +97 -88
  21. data/lib/solargraph/api_map/store.rb +384 -266
  22. data/lib/solargraph/api_map.rb +945 -973
  23. data/lib/solargraph/bench.rb +1 -0
  24. data/lib/solargraph/complex_type/type_methods.rb +228 -222
  25. data/lib/solargraph/complex_type/unique_type.rb +482 -475
  26. data/lib/solargraph/complex_type.rb +444 -423
  27. data/lib/solargraph/convention/active_support_concern.rb +111 -0
  28. data/lib/solargraph/convention/base.rb +17 -0
  29. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +61 -0
  30. data/lib/solargraph/convention/data_definition/data_definition_node.rb +91 -0
  31. data/lib/solargraph/convention/data_definition.rb +105 -0
  32. data/lib/solargraph/convention/gemspec.rb +3 -2
  33. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +61 -60
  34. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +102 -100
  35. data/lib/solargraph/convention/struct_definition.rb +164 -101
  36. data/lib/solargraph/convention.rb +32 -2
  37. data/lib/solargraph/diagnostics/require_not_found.rb +53 -53
  38. data/lib/solargraph/diagnostics/rubocop.rb +118 -113
  39. data/lib/solargraph/diagnostics/rubocop_helpers.rb +68 -66
  40. data/lib/solargraph/diagnostics/type_check.rb +55 -55
  41. data/lib/solargraph/doc_map.rb +439 -405
  42. data/lib/solargraph/environ.rb +9 -2
  43. data/lib/solargraph/equality.rb +34 -33
  44. data/lib/solargraph/gem_pins.rb +98 -88
  45. data/lib/solargraph/language_server/host/diagnoser.rb +89 -89
  46. data/lib/solargraph/language_server/host/dispatch.rb +130 -128
  47. data/lib/solargraph/language_server/host/message_worker.rb +112 -109
  48. data/lib/solargraph/language_server/host/sources.rb +99 -99
  49. data/lib/solargraph/language_server/host.rb +878 -871
  50. data/lib/solargraph/language_server/message/base.rb +2 -1
  51. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +114 -114
  52. data/lib/solargraph/language_server/message/extended/document.rb +23 -23
  53. data/lib/solargraph/language_server/message/text_document/completion.rb +56 -56
  54. data/lib/solargraph/language_server/message/text_document/definition.rb +40 -38
  55. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +26 -26
  56. data/lib/solargraph/language_server/message/text_document/formatting.rb +148 -131
  57. data/lib/solargraph/language_server/message/text_document/hover.rb +58 -58
  58. data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -24
  59. data/lib/solargraph/language_server/message/text_document/type_definition.rb +25 -24
  60. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +2 -0
  61. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -23
  62. data/lib/solargraph/language_server/progress.rb +8 -0
  63. data/lib/solargraph/language_server/request.rb +4 -1
  64. data/lib/solargraph/library.rb +683 -666
  65. data/lib/solargraph/location.rb +82 -79
  66. data/lib/solargraph/logging.rb +37 -28
  67. data/lib/solargraph/page.rb +3 -0
  68. data/lib/solargraph/parser/comment_ripper.rb +69 -62
  69. data/lib/solargraph/parser/flow_sensitive_typing.rb +255 -227
  70. data/lib/solargraph/parser/node_processor/base.rb +92 -87
  71. data/lib/solargraph/parser/node_processor.rb +62 -46
  72. data/lib/solargraph/parser/parser_gem/class_methods.rb +149 -159
  73. data/lib/solargraph/parser/parser_gem/flawed_builder.rb +1 -0
  74. data/lib/solargraph/parser/parser_gem/node_chainer.rb +166 -164
  75. data/lib/solargraph/parser/parser_gem/node_methods.rb +486 -497
  76. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +22 -21
  77. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +59 -59
  78. data/lib/solargraph/parser/parser_gem/node_processors/begin_node.rb +15 -15
  79. data/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +46 -45
  80. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +1 -21
  81. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +53 -53
  82. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +23 -21
  83. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +40 -40
  84. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +29 -29
  85. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +59 -53
  86. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +0 -22
  87. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +98 -41
  88. data/lib/solargraph/parser/parser_gem/node_processors/orasgn_node.rb +17 -16
  89. data/lib/solargraph/parser/parser_gem/node_processors/resbody_node.rb +38 -37
  90. data/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +52 -43
  91. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +291 -271
  92. data/lib/solargraph/parser/parser_gem/node_processors/sym_node.rb +1 -0
  93. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +29 -29
  94. data/lib/solargraph/parser/parser_gem/node_processors.rb +70 -66
  95. data/lib/solargraph/parser/region.rb +69 -66
  96. data/lib/solargraph/parser/snippet.rb +17 -15
  97. data/lib/solargraph/pin/base.rb +729 -651
  98. data/lib/solargraph/pin/base_variable.rb +126 -125
  99. data/lib/solargraph/pin/block.rb +104 -103
  100. data/lib/solargraph/pin/breakable.rb +9 -9
  101. data/lib/solargraph/pin/callable.rb +231 -218
  102. data/lib/solargraph/pin/closure.rb +72 -74
  103. data/lib/solargraph/pin/common.rb +79 -75
  104. data/lib/solargraph/pin/constant.rb +2 -0
  105. data/lib/solargraph/pin/conversions.rb +123 -123
  106. data/lib/solargraph/pin/delegated_method.rb +120 -120
  107. data/lib/solargraph/pin/documenting.rb +114 -114
  108. data/lib/solargraph/pin/instance_variable.rb +34 -34
  109. data/lib/solargraph/pin/keyword.rb +20 -20
  110. data/lib/solargraph/pin/local_variable.rb +75 -76
  111. data/lib/solargraph/pin/method.rb +672 -651
  112. data/lib/solargraph/pin/method_alias.rb +34 -31
  113. data/lib/solargraph/pin/namespace.rb +115 -115
  114. data/lib/solargraph/pin/parameter.rb +275 -261
  115. data/lib/solargraph/pin/proxy_type.rb +39 -35
  116. data/lib/solargraph/pin/reference/override.rb +47 -33
  117. data/lib/solargraph/pin/reference/superclass.rb +15 -10
  118. data/lib/solargraph/pin/reference.rb +39 -22
  119. data/lib/solargraph/pin/search.rb +61 -56
  120. data/lib/solargraph/pin/signature.rb +61 -59
  121. data/lib/solargraph/pin/symbol.rb +53 -48
  122. data/lib/solargraph/pin/until.rb +18 -18
  123. data/lib/solargraph/pin/while.rb +18 -18
  124. data/lib/solargraph/pin.rb +44 -44
  125. data/lib/solargraph/pin_cache.rb +245 -185
  126. data/lib/solargraph/position.rb +132 -116
  127. data/lib/solargraph/range.rb +112 -107
  128. data/lib/solargraph/rbs_map/conversions.rb +823 -773
  129. data/lib/solargraph/rbs_map/core_fills.rb +18 -0
  130. data/lib/solargraph/rbs_map/core_map.rb +58 -51
  131. data/lib/solargraph/rbs_map/stdlib_map.rb +43 -43
  132. data/lib/solargraph/rbs_map.rb +163 -150
  133. data/lib/solargraph/shell.rb +352 -268
  134. data/lib/solargraph/source/chain/call.rb +337 -333
  135. data/lib/solargraph/source/chain/constant.rb +26 -89
  136. data/lib/solargraph/source/chain/hash.rb +34 -34
  137. data/lib/solargraph/source/chain/if.rb +28 -28
  138. data/lib/solargraph/source/chain/instance_variable.rb +13 -13
  139. data/lib/solargraph/source/chain/link.rb +11 -2
  140. data/lib/solargraph/source/chain/literal.rb +48 -48
  141. data/lib/solargraph/source/chain/or.rb +23 -23
  142. data/lib/solargraph/source/chain.rb +291 -282
  143. data/lib/solargraph/source/change.rb +82 -82
  144. data/lib/solargraph/source/cursor.rb +166 -167
  145. data/lib/solargraph/source/encoding_fixes.rb +23 -23
  146. data/lib/solargraph/source/source_chainer.rb +194 -194
  147. data/lib/solargraph/source/updater.rb +55 -55
  148. data/lib/solargraph/source.rb +498 -495
  149. data/lib/solargraph/source_map/clip.rb +226 -234
  150. data/lib/solargraph/source_map/data.rb +34 -30
  151. data/lib/solargraph/source_map/mapper.rb +259 -259
  152. data/lib/solargraph/source_map.rb +212 -200
  153. data/lib/solargraph/type_checker/checks.rb +124 -124
  154. data/lib/solargraph/type_checker/param_def.rb +37 -35
  155. data/lib/solargraph/type_checker/problem.rb +32 -32
  156. data/lib/solargraph/type_checker/rules.rb +84 -62
  157. data/lib/solargraph/type_checker.rb +814 -699
  158. data/lib/solargraph/version.rb +5 -5
  159. data/lib/solargraph/workspace/config.rb +255 -239
  160. data/lib/solargraph/workspace/require_paths.rb +97 -0
  161. data/lib/solargraph/workspace.rb +220 -249
  162. data/lib/solargraph/yard_map/helpers.rb +44 -16
  163. data/lib/solargraph/yard_map/mapper/to_constant.rb +5 -5
  164. data/lib/solargraph/yard_map/mapper/to_method.rb +130 -134
  165. data/lib/solargraph/yard_map/mapper/to_namespace.rb +31 -30
  166. data/lib/solargraph/yard_map/mapper.rb +79 -79
  167. data/lib/solargraph/yard_map/to_method.rb +89 -88
  168. data/lib/solargraph/yardoc.rb +87 -49
  169. data/lib/solargraph.rb +105 -90
  170. data/rbs/fills/bundler/0/bundler.rbs +4271 -0
  171. data/rbs/fills/open3/0/open3.rbs +172 -0
  172. data/rbs/fills/rubygems/0/basic_specification.rbs +326 -0
  173. data/rbs/fills/rubygems/0/errors.rbs +364 -0
  174. data/rbs/fills/rubygems/0/spec_fetcher.rbs +107 -0
  175. data/rbs/fills/rubygems/0/specification.rbs +1753 -0
  176. data/rbs/fills/{tuple.rbs → tuple/tuple.rbs} +2 -3
  177. data/rbs/shims/ast/0/node.rbs +5 -0
  178. data/rbs/shims/ast/2.4/.rbs_meta.yaml +9 -0
  179. data/rbs/shims/ast/2.4/ast.rbs +73 -0
  180. data/rbs/shims/parser/3.2.0.1/builders/default.rbs +195 -0
  181. data/rbs/shims/parser/3.2.0.1/manifest.yaml +7 -0
  182. data/rbs/shims/parser/3.2.0.1/parser.rbs +201 -0
  183. data/rbs/shims/parser/3.2.0.1/polyfill.rbs +4 -0
  184. data/rbs/shims/thor/1.2.0.1/.rbs_meta.yaml +9 -0
  185. data/rbs/shims/thor/1.2.0.1/manifest.yaml +7 -0
  186. data/rbs/shims/thor/1.2.0.1/thor.rbs +17 -0
  187. data/rbs_collection.yaml +4 -4
  188. data/solargraph.gemspec +26 -5
  189. metadata +187 -15
  190. data/lib/.rubocop.yml +0 -22
  191. data/lib/solargraph/parser/node_methods.rb +0 -97
@@ -1,973 +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
-
17
- # @return [Array<String>]
18
- attr_reader :unresolved_requires
19
-
20
- @@core_map = RbsMap::CoreMap.new
21
-
22
- # @return [Array<String>]
23
- attr_reader :missing_docs
24
-
25
- # @param pins [Array<Solargraph::Pin::Base>]
26
- def initialize pins: []
27
- @source_map_hash = {}
28
- @cache = Cache.new
29
- @method_alias_stack = []
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
- def eql?(other)
40
- self.class == other.class &&
41
- equality_fields == other.equality_fields
42
- end
43
-
44
- def ==(other)
45
- self.eql?(other)
46
- end
47
-
48
- def hash
49
- equality_fields.hash
50
- end
51
-
52
- def to_s
53
- self.class.to_s
54
- end
55
-
56
- # avoid enormous dump
57
- def inspect
58
- to_s
59
- end
60
-
61
- # @param pins [Array<Pin::Base>]
62
- # @return [self]
63
- def index pins
64
- # @todo This implementation is incomplete. It should probably create a
65
- # Bench.
66
- @source_map_hash = {}
67
- implicit.clear
68
- cache.clear
69
- store.update @@core_map.pins, pins
70
- self
71
- end
72
-
73
- # Map a single source.
74
- #
75
- # @param source [Source]
76
- # @return [self]
77
- def map source
78
- map = Solargraph::SourceMap.map(source)
79
- catalog Bench.new(source_maps: [map])
80
- self
81
- end
82
-
83
- # Catalog a bench.
84
- #
85
- # @param bench [Bench]
86
- # @return [self]
87
- def catalog bench
88
- @source_map_hash = bench.source_map_hash
89
- iced_pins = bench.icebox.flat_map(&:pins)
90
- live_pins = bench.live_map&.pins || []
91
- implicit.clear
92
- source_map_hash.each_value do |map|
93
- implicit.merge map.environ
94
- end
95
- unresolved_requires = (bench.external_requires + implicit.requires + bench.workspace.config.required).to_a.compact.uniq
96
- recreate_docmap = @unresolved_requires != unresolved_requires ||
97
- @doc_map&.uncached_yard_gemspecs&.any? ||
98
- @doc_map&.uncached_rbs_collection_gemspecs&.any? ||
99
- @doc_map&.rbs_collection_path != bench.workspace.rbs_collection_path
100
- if recreate_docmap
101
- @doc_map = DocMap.new(unresolved_requires, [], bench.workspace) # @todo Implement gem preferences
102
- @unresolved_requires = @doc_map.unresolved_requires
103
- end
104
- @cache.clear if store.update(@@core_map.pins, @doc_map.pins, implicit.pins, iced_pins, live_pins)
105
- @missing_docs = [] # @todo Implement missing docs
106
- self
107
- end
108
-
109
- # @todo need to model type def statement in chains as a symbol so
110
- # that this overload of 'protected' will typecheck @sg-ignore
111
- # @sg-ignore
112
- protected def equality_fields
113
- [self.class, @source_map_hash, implicit, @doc_map, @unresolved_requires]
114
- end
115
-
116
- def doc_map
117
- @doc_map ||= DocMap.new([], [])
118
- end
119
-
120
- # @return [::Array<Gem::Specification>]
121
- def uncached_gemspecs
122
- @doc_map&.uncached_gemspecs || []
123
- end
124
-
125
- # @return [::Array<Gem::Specification>]
126
- def uncached_rbs_collection_gemspecs
127
- @doc_map.uncached_rbs_collection_gemspecs
128
- end
129
-
130
- # @return [::Array<Gem::Specification>]
131
- def uncached_yard_gemspecs
132
- @doc_map.uncached_yard_gemspecs
133
- end
134
-
135
- # @return [Array<Pin::Base>]
136
- def core_pins
137
- @@core_map.pins
138
- end
139
-
140
- # @param name [String]
141
- # @return [YARD::Tags::MacroDirective, nil]
142
- def named_macro name
143
- store.named_macros[name]
144
- end
145
-
146
- # @return [Set<String>]
147
- def required
148
- @required ||= Set.new
149
- end
150
-
151
- # @return [Environ]
152
- def implicit
153
- @implicit ||= Environ.new
154
- end
155
-
156
- # @param filename [String]
157
- # @param position [Position, Array(Integer, Integer)]
158
- # @return [Source::Cursor]
159
- def cursor_at filename, position
160
- position = Position.normalize(position)
161
- raise FileNotFoundError, "File not found: #{filename}" unless source_map_hash.key?(filename)
162
- source_map_hash[filename].cursor_at(position)
163
- end
164
-
165
- # Get a clip by filename and position.
166
- #
167
- # @param filename [String]
168
- # @param position [Position, Array(Integer, Integer)]
169
- # @return [SourceMap::Clip]
170
- def clip_at filename, position
171
- position = Position.normalize(position)
172
- clip(cursor_at(filename, position))
173
- end
174
-
175
- # Create an ApiMap with a workspace in the specified directory.
176
- #
177
- # @param directory [String]
178
- # @return [ApiMap]
179
- def self.load directory
180
- api_map = new
181
- workspace = Solargraph::Workspace.new(directory)
182
- # api_map.catalog Bench.new(workspace: workspace)
183
- library = Library.new(workspace)
184
- library.map!
185
- api_map.catalog library.bench
186
- api_map
187
- end
188
-
189
- def cache_all!(out)
190
- @doc_map.cache_all!(out)
191
- end
192
-
193
- def cache_gem(gemspec, rebuild: false, out: nil)
194
- @doc_map.cache(gemspec, rebuild: rebuild, out: out)
195
- end
196
-
197
- class << self
198
- include Logging
199
- end
200
-
201
- # Create an ApiMap with a workspace in the specified directory and cache
202
- # any missing gems.
203
- #
204
- #
205
- # @todo IO::NULL is incorrectly inferred to be a String.
206
- # @sg-ignore
207
- #
208
- # @param directory [String]
209
- # @param out [IO] The output stream for messages
210
- # @return [ApiMap]
211
- def self.load_with_cache directory, out
212
- api_map = load(directory)
213
- if api_map.uncached_gemspecs.empty?
214
- logger.info { "All gems cached for #{directory}" }
215
- return api_map
216
- end
217
-
218
- api_map.cache_all!(out)
219
- load(directory)
220
- end
221
-
222
- # @return [Array<Solargraph::Pin::Base>]
223
- def pins
224
- store.pins.clone.freeze
225
- end
226
-
227
- # An array of pins based on Ruby keywords (`if`, `end`, etc.).
228
- #
229
- # @return [Enumerable<Solargraph::Pin::Keyword>]
230
- def keyword_pins
231
- store.pins_by_class(Pin::Keyword)
232
- end
233
-
234
- # An array of namespace names defined in the ApiMap.
235
- #
236
- # @return [Set<String>]
237
- def namespaces
238
- store.namespaces
239
- end
240
-
241
- # True if the namespace exists.
242
- #
243
- # @param name [String] The namespace to match
244
- # @param context [String] The context to search
245
- # @return [Boolean]
246
- def namespace_exists? name, context = ''
247
- !qualify(name, context).nil?
248
- end
249
-
250
- # Get suggestions for constants in the specified namespace. The result
251
- # may contain both constant and namespace pins.
252
- #
253
- # @param namespace [String] The namespace
254
- # @param contexts [Array<String>] The contexts
255
- # @return [Array<Solargraph::Pin::Base>]
256
- def get_constants namespace, *contexts
257
- 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
271
- end
272
-
273
- # @param namespace [String]
274
- # @param context [String]
275
- # @return [Array<Pin::Namespace>]
276
- def get_namespace_pins namespace, context
277
- store.fqns_pins(qualify(namespace, context))
278
- end
279
-
280
- # Determine fully qualified tag for a given tag used inside the
281
- # definition of another tag ("context"). This method will start
282
- # the search in the specified context until it finds a match for
283
- # the tag.
284
- #
285
- # Does not recurse into qualifying the type parameters, but
286
- # returns any which were passed in unchanged.
287
- #
288
- # @param tag [String, nil] The namespace to
289
- # match, complete with generic parameters set to appropriate
290
- # values if available
291
- # @param context_tag [String] The fully qualified context in which
292
- # the tag was referenced; start from here to resolve the name.
293
- # Should not be prefixed with '::'.
294
- # @return [String, nil] fully qualified tag
295
- 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
307
-
308
- fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
309
- return unless fqns
310
-
311
- fqns + type.substring
312
- end
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
334
- end
335
-
336
- # Get an array of instance variable pins defined in specified namespace
337
- # and scope.
338
- #
339
- # @param namespace [String] A fully qualified namespace
340
- # @param scope [Symbol] :instance or :class
341
- # @return [Array<Solargraph::Pin::InstanceVariable>]
342
- def get_instance_variable_pins(namespace, scope = :instance)
343
- result = []
344
- used = [namespace]
345
- 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)
351
- end
352
- result
353
- end
354
-
355
- # @see Solargraph::Parser::FlowSensitiveTyping#visible_pins
356
- def visible_pins(*args, **kwargs, &blk)
357
- Solargraph::Parser::FlowSensitiveTyping.visible_pins(*args, **kwargs, &blk)
358
- end
359
-
360
- # Get an array of class variable pins for a namespace.
361
- #
362
- # @param namespace [String] A fully qualified namespace
363
- # @return [Enumerable<Solargraph::Pin::ClassVariable>]
364
- def get_class_variable_pins(namespace)
365
- prefer_non_nil_variables(store.get_class_variables(namespace))
366
- end
367
-
368
- # @return [Enumerable<Solargraph::Pin::Base>]
369
- def get_symbols
370
- store.get_symbols
371
- end
372
-
373
- # @return [Enumerable<Solargraph::Pin::GlobalVariable>]
374
- def get_global_variable_pins
375
- store.pins_by_class(Pin::GlobalVariable)
376
- end
377
-
378
- # @return [Enumerable<Solargraph::Pin::Block>]
379
- def get_block_pins
380
- store.pins_by_class(Pin::Block)
381
- end
382
-
383
- # Get an array of methods available in a particular context.
384
- #
385
- # @param rooted_tag [String] The fully qualified namespace to search for methods
386
- # @param scope [Symbol] :class or :instance
387
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
388
- # @param deep [Boolean] True to include superclasses, mixins, etc.
389
- # @return [Array<Solargraph::Pin::Method>]
390
- def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true
391
- if rooted_tag.start_with? 'Array('
392
- # Array() are really tuples - use our fill, as the RBS repo
393
- # does not give us definitions for it
394
- rooted_tag = "Solargraph::Fills::Tuple(#{rooted_tag[6..-2]})"
395
- end
396
- rooted_type = ComplexType.try_parse(rooted_tag)
397
- fqns = rooted_type.namespace
398
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
399
- cached = cache.get_methods(rooted_tag, scope, visibility, deep)
400
- return cached.clone unless cached.nil?
401
- # @type [Array<Solargraph::Pin::Method>]
402
- result = []
403
- skip = Set.new
404
- if rooted_tag == ''
405
- # @todo Implement domains
406
- implicit.domains.each do |domain|
407
- type = ComplexType.try_parse(domain)
408
- next if type.undefined?
409
- result.concat inner_get_methods(type.name, type.scope, visibility, deep, skip)
410
- end
411
- result.concat inner_get_methods(rooted_tag, :class, visibility, deep, skip)
412
- result.concat inner_get_methods(rooted_tag, :instance, visibility, deep, skip)
413
- result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
414
- else
415
- result.concat inner_get_methods(rooted_tag, scope, visibility, deep, skip)
416
- unless %w[Class Class<Class>].include?(rooted_tag)
417
- result.map! do |pin|
418
- next pin unless pin.path == 'Class#new'
419
- init_pin = get_method_stack(rooted_tag, 'initialize').first
420
- next pin unless init_pin
421
-
422
- type = ComplexType::SELF
423
- new_pin = Pin::Method.new(
424
- name: 'new',
425
- scope: :class,
426
- location: init_pin.location,
427
- return_type: type,
428
- comments: init_pin.comments,
429
- closure: init_pin.closure,
430
- source: init_pin.source,
431
- type_location: init_pin.type_location,
432
- )
433
- new_pin.parameters = init_pin.parameters.map do |init_param|
434
- param = init_param.clone
435
- param.closure = new_pin
436
- param.reset_generated!
437
- param
438
- end.freeze
439
- new_pin.signatures = init_pin.signatures.map do |init_sig|
440
- sig = init_sig.proxy(type)
441
- sig.parameters = init_sig.parameters.map do |param|
442
- param = param.clone
443
- param.closure = new_pin
444
- param.reset_generated!
445
- param
446
- end.freeze
447
- sig.closure = new_pin
448
- sig.reset_generated!
449
- sig
450
- end.freeze
451
- new_pin
452
- end
453
- end
454
- result.concat inner_get_methods('Kernel', :instance, [:public], deep, skip) if visibility.include?(:private)
455
- result.concat inner_get_methods('Module', scope, visibility, deep, skip) if scope == :module
456
- end
457
- result = resolve_method_aliases(result, visibility)
458
- if namespace_pin && rooted_tag != rooted_type.name
459
- result = result.map { |method_pin| method_pin.resolve_generics(namespace_pin, rooted_type) }
460
- end
461
- cache.set_methods(rooted_tag, scope, visibility, deep, result)
462
- result
463
- end
464
-
465
- # Get an array of method pins for a complex type.
466
- #
467
- # The type's namespace and the context should be fully qualified. If the
468
- # context matches the namespace type or is a subclass of the type,
469
- # protected methods are included in the results. If protected methods are
470
- # included and internal is true, private methods are also included.
471
- #
472
- # @example
473
- # api_map = Solargraph::ApiMap.new
474
- # type = Solargraph::ComplexType.parse('String')
475
- # api_map.get_complex_type_methods(type)
476
- #
477
- # @param complex_type [Solargraph::ComplexType] The complex type of the namespace
478
- # @param context [String] The context from which the type is referenced
479
- # @param internal [Boolean] True to include private methods
480
- # @return [Array<Solargraph::Pin::Base>]
481
- def get_complex_type_methods complex_type, context = '', internal = false
482
- # This method does not qualify the complex type's namespace because
483
- # it can cause conflicts between similar names, e.g., `Foo` vs.
484
- # `Other::Foo`. It still takes a context argument to determine whether
485
- # protected and private methods are visible.
486
- return [] if complex_type.undefined? || complex_type.void?
487
- result = Set.new
488
- complex_type.each do |type|
489
- if type.duck_type?
490
- result.add Pin::DuckMethod.new(name: type.to_s[1..-1], source: :api_map)
491
- result.merge get_methods('Object')
492
- else
493
- unless type.nil? || type.name == 'void'
494
- visibility = [:public]
495
- if type.namespace == context || super_and_sub?(type.namespace, context)
496
- visibility.push :protected
497
- visibility.push :private if internal
498
- end
499
- result.merge get_methods(type.tag, scope: type.scope, visibility: visibility)
500
- end
501
- end
502
- end
503
- result.to_a
504
- end
505
-
506
- # Get a stack of method pins for a method name in a potentially
507
- # parameterized namespace. The order of the pins corresponds to
508
- # the ancestry chain, with highest precedence first.
509
- #
510
- # @example
511
- # api_map.get_method_stack('Subclass', 'method_name')
512
- # #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]
513
- #
514
- # @param rooted_tag [String] Parameterized namespace, fully qualified
515
- # @param name [String] Method name to look up
516
- # @param scope [Symbol] :instance or :class
517
- # @return [Array<Solargraph::Pin::Method>]
518
- def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false
519
- rooted_type = ComplexType.parse(rooted_tag)
520
- 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 }
523
- methods = erase_generics(namespace_pin, rooted_type, methods) unless preserve_generics
524
- methods
525
- end
526
-
527
- # Get an array of all suggestions that match the specified path.
528
- #
529
- # @deprecated Use #get_path_pins instead.
530
- #
531
- # @param path [String] The path to find
532
- # @return [Enumerable<Solargraph::Pin::Base>]
533
- def get_path_suggestions path
534
- return [] if path.nil?
535
- resolve_method_aliases store.get_path_pins(path)
536
- end
537
-
538
- # Get an array of pins that match the specified path.
539
- #
540
- # @param path [String]
541
- # @return [Enumerable<Pin::Base>]
542
- def get_path_pins path
543
- get_path_suggestions(path)
544
- end
545
-
546
- # Get a list of documented paths that match the query.
547
- #
548
- # @example
549
- # api_map.query('str') # Results will include `String` and `Struct`
550
- #
551
- # @param query [String] The text to match
552
- # @return [Array<String>]
553
- def search query
554
- pins.map(&:path)
555
- .compact
556
- .select { |path| path.downcase.include?(query.downcase) }
557
- end
558
-
559
- # @deprecated This method is likely superfluous. Calling #get_path_pins
560
- # directly should be sufficient.
561
- #
562
- # @param path [String] The path to find
563
- # @return [Enumerable<Pin::Base>]
564
- def document path
565
- get_path_pins(path)
566
- end
567
-
568
- # Get an array of all symbols in the workspace that match the query.
569
- #
570
- # @param query [String]
571
- # @return [Array<Pin::Base>]
572
- def query_symbols query
573
- Pin::Search.new(
574
- source_map_hash.values.flat_map(&:document_symbols),
575
- query
576
- ).results
577
- end
578
-
579
- # @param location [Solargraph::Location]
580
- # @return [Array<Solargraph::Pin::Base>]
581
- def locate_pins location
582
- return [] if location.nil? || !source_map_hash.key?(location.filename)
583
- resolve_method_aliases source_map_hash[location.filename].locate_pins(location)
584
- end
585
-
586
- # @raise [FileNotFoundError] if the cursor's file is not in the ApiMap
587
- # @param cursor [Source::Cursor]
588
- # @return [SourceMap::Clip]
589
- def clip cursor
590
- raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.key?(cursor.filename)
591
-
592
- SourceMap::Clip.new(self, cursor)
593
- end
594
-
595
- # Get an array of document symbols from a file.
596
- #
597
- # @param filename [String]
598
- # @return [Array<Pin::Symbol>]
599
- def document_symbols filename
600
- return [] unless source_map_hash.key?(filename) # @todo Raise error?
601
- resolve_method_aliases source_map_hash[filename].document_symbols
602
- end
603
-
604
- # @return [Array<SourceMap>]
605
- def source_maps
606
- source_map_hash.values
607
- end
608
-
609
- # Get a source map by filename.
610
- #
611
- # @param filename [String]
612
- # @return [SourceMap]
613
- def source_map filename
614
- raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.key?(filename)
615
- source_map_hash[filename]
616
- end
617
-
618
- # True if the specified file was included in a bundle, i.e., it's either
619
- # included in a workspace or open in a library.
620
- #
621
- # @param filename [String]
622
- def bundled? filename
623
- source_map_hash.keys.include?(filename)
624
- end
625
-
626
- # Check if a class is a superclass of another class.
627
- #
628
- # @param sup [String] The superclass
629
- # @param sub [String] The subclass
630
- # @return [Boolean]
631
- 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)
639
- end
640
- false
641
- end
642
-
643
- # Check if the host class includes the specified module, ignoring
644
- # type parameters used.
645
- #
646
- # @param host_ns [String] The class namesapce (no type parameters)
647
- # @param module_ns [String] The module namespace (no type parameters)
648
- #
649
- # @return [Boolean]
650
- def type_include?(host_ns, module_ns)
651
- store.get_includes(host_ns).map { |inc_tag| ComplexType.parse(inc_tag).name }.include?(module_ns)
652
- end
653
-
654
- # @param pins [Enumerable<Pin::Base>]
655
- # @param visibility [Enumerable<Symbol>]
656
- # @return [Array<Pin::Base>]
657
- def resolve_method_aliases pins, visibility = [:public, :private, :protected]
658
- with_resolved_aliases = pins.map do |pin|
659
- resolved = resolve_method_alias(pin)
660
- next nil if resolved.respond_to?(:visibility) && !visibility.include?(resolved.visibility)
661
- resolved
662
- end.compact
663
- logger.debug { "ApiMap#resolve_method_aliases(pins=#{pins.map(&:name)}, visibility=#{visibility}) => #{with_resolved_aliases.map(&:name)}" }
664
- GemPins.combine_method_pins_by_path(with_resolved_aliases)
665
- end
666
-
667
- private
668
-
669
- # A hash of source maps with filename keys.
670
- #
671
- # @return [Hash{String => SourceMap}]
672
- attr_reader :source_map_hash
673
-
674
- # @return [ApiMap::Store]
675
- def store
676
- @store ||= Store.new
677
- end
678
-
679
- # @return [Solargraph::ApiMap::Cache]
680
- attr_reader :cache
681
-
682
- # @param rooted_tag [String] A fully qualified namespace, with
683
- # generic parameter values if applicable
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 rooted_tag, scope, visibility, deep, skip, no_core = false
691
- rooted_type = ComplexType.parse(rooted_tag).force_rooted
692
- fqns = rooted_type.namespace
693
- fqns_generic_params = rooted_type.all_params
694
- namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first
695
- return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
696
- reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
697
- return [] if skip.include?(reqstr)
698
- skip.add reqstr
699
- result = []
700
- if deep && scope == :instance
701
- store.get_prepends(fqns).reverse.each do |im|
702
- fqim = qualify(im, fqns)
703
- result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
704
- end
705
- end
706
- # Store#get_methods doesn't know about full tags, just
707
- # namespaces; resolving the generics in the method pins is this
708
- # class' responsibility
709
- methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
710
- result.concat methods
711
- if deep
712
- 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)
716
- end
717
- rooted_sc_tag = qualify_superclass(rooted_tag)
718
- unless rooted_sc_tag.nil?
719
- result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, no_core)
720
- end
721
- else
722
- store.get_extends(fqns).reverse.each do |em|
723
- fqem = qualify(em, fqns)
724
- result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
725
- end
726
- rooted_sc_tag = qualify_superclass(rooted_tag)
727
- unless rooted_sc_tag.nil?
728
- result.concat inner_get_methods_from_reference(rooted_sc_tag, namespace_pin, rooted_type, scope, visibility, true, skip, true)
729
- end
730
- unless no_core || fqns.empty?
731
- type = get_namespace_type(fqns)
732
- result.concat inner_get_methods('Class', :instance, visibility, deep, skip, no_core) if type == :class
733
- result.concat inner_get_methods('Module', :instance, visibility, deep, skip, no_core)
734
- end
735
- end
736
- store.domains(fqns).each do |d|
737
- dt = ComplexType.try_parse(d)
738
- result.concat inner_get_methods(dt.namespace, dt.scope, visibility, deep, skip)
739
- end
740
- end
741
- result
742
- 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
801
-
802
- # @return [Hash]
803
- def path_macros
804
- @path_macros ||= {}
805
- end
806
-
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]
815
- # @return [String, nil]
816
- 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
868
- end
869
-
870
- # Get the namespace's type (Class or Module).
871
- #
872
- # @param fqns [String] A fully qualified namespace
873
- # @return [Symbol, nil] :class, :module, or nil
874
- def get_namespace_type fqns
875
- return nil if fqns.nil?
876
- # @type [Pin::Namespace, nil]
877
- pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first
878
- return nil if pin.nil?
879
- pin.type
880
- end
881
-
882
- # Sort an array of pins to put nil or undefined variables last.
883
- #
884
- # @param pins [Enumerable<Pin::BaseVariable>]
885
- # @return [Enumerable<Pin::BaseVariable>]
886
- def prefer_non_nil_variables pins
887
- result = []
888
- nil_pins = []
889
- pins.each do |pin|
890
- if pin.variable? && pin.nil_assignment?
891
- nil_pins.push pin
892
- else
893
- result.push pin
894
- end
895
- end
896
- result + nil_pins
897
- end
898
-
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?
908
- 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,
921
- source: :resolve_method_alias
922
- }
923
- out = Pin::Method.new **args
924
- out.signatures.each do |sig|
925
- sig.parameters = sig.parameters.map(&:clone).freeze
926
- sig.source = :resolve_method_alias
927
- sig.parameters.each do |param|
928
- param.closure = out
929
- param.source = :resolve_method_alias
930
- param.reset_generated!
931
- end
932
- sig.closure = out
933
- sig.reset_generated!
934
- end
935
- logger.debug { "ApiMap#resolve_method_alias(pin=#{pin}) - returning #{out} from #{origin}" }
936
- out
937
- end
938
-
939
- include Logging
940
-
941
- private
942
-
943
- # @param namespace_pin [Pin::Namespace]
944
- # @param rooted_type [ComplexType]
945
- # @param pins [Enumerable<Pin::Base>]
946
- # @return [Array<Pin::Base>]
947
- def erase_generics(namespace_pin, rooted_type, pins)
948
- return pins unless should_erase_generics_when_done?(namespace_pin, rooted_type)
949
-
950
- logger.debug("Erasing generics on namespace_pin=#{namespace_pin} / rooted_type=#{rooted_type}")
951
- pins.map do |method_pin|
952
- method_pin.erase_generics(namespace_pin.generics)
953
- end
954
- end
955
-
956
- # @param namespace_pin [Pin::Namespace]
957
- # @param rooted_type [ComplexType]
958
- def should_erase_generics_when_done?(namespace_pin, rooted_type)
959
- has_generics?(namespace_pin) && !can_resolve_generics?(namespace_pin, rooted_type)
960
- end
961
-
962
- # @param namespace_pin [Pin::Namespace]
963
- def has_generics?(namespace_pin)
964
- namespace_pin && !namespace_pin.generics.empty?
965
- end
966
-
967
- # @param namespace_pin [Pin::Namespace]
968
- # @param rooted_type [ComplexType]
969
- def can_resolve_generics?(namespace_pin, rooted_type)
970
- has_generics?(namespace_pin) && !rooted_type.all_params.empty?
971
- end
972
- end
973
- 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