solargraph 0.57.0 → 0.58.3

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