solargraph 0.58.0 → 0.59.0

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