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