solargraph 0.32.1 → 0.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +25 -0
  6. data/EXAMPLES.md +76 -0
  7. data/Gemfile +3 -0
  8. data/LANGUAGE_SERVER.md +51 -0
  9. data/LICENSE +21 -0
  10. data/OVERVIEW.md +37 -0
  11. data/README.md +106 -0
  12. data/Rakefile +14 -0
  13. data/SERVER.md +95 -0
  14. data/bin/solargraph +0 -0
  15. data/bin/solargraph-runtime +5 -5
  16. data/lib/solargraph.rb +54 -54
  17. data/lib/solargraph/api_map.rb +659 -659
  18. data/lib/solargraph/api_map/cache.rb +49 -49
  19. data/lib/solargraph/api_map/source_to_yard.rb +67 -67
  20. data/lib/solargraph/api_map/store.rb +201 -201
  21. data/lib/solargraph/bundle.rb +24 -24
  22. data/lib/solargraph/complex_type.rb +150 -150
  23. data/lib/solargraph/complex_type/type_methods.rb +124 -124
  24. data/lib/solargraph/complex_type/unique_type.rb +44 -44
  25. data/lib/solargraph/core_fills.rb +37 -37
  26. data/lib/solargraph/diagnostics.rb +52 -52
  27. data/lib/solargraph/diagnostics/base.rb +20 -20
  28. data/lib/solargraph/diagnostics/require_not_found.rb +28 -28
  29. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  30. data/lib/solargraph/diagnostics/rubocop_helpers.rb +46 -46
  31. data/lib/solargraph/diagnostics/type_not_defined.rb +108 -108
  32. data/lib/solargraph/diagnostics/update_errors.rb +38 -38
  33. data/lib/solargraph/language_server/completion_item_kinds.rb +33 -33
  34. data/lib/solargraph/language_server/error_codes.rb +18 -18
  35. data/lib/solargraph/language_server/host.rb +684 -681
  36. data/lib/solargraph/language_server/host/cataloger.rb +54 -79
  37. data/lib/solargraph/language_server/host/diagnoser.rb +80 -80
  38. data/lib/solargraph/language_server/host/dispatch.rb +112 -113
  39. data/lib/solargraph/language_server/host/sources.rb +138 -138
  40. data/lib/solargraph/language_server/message.rb +90 -90
  41. data/lib/solargraph/language_server/message/base.rb +83 -83
  42. data/lib/solargraph/language_server/message/completion_item/resolve.rb +40 -40
  43. data/lib/solargraph/language_server/message/exit_notification.rb +11 -11
  44. data/lib/solargraph/language_server/message/extended.rb +19 -19
  45. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +86 -86
  46. data/lib/solargraph/language_server/message/extended/document.rb +18 -18
  47. data/lib/solargraph/language_server/message/extended/document_gems.rb +30 -30
  48. data/lib/solargraph/language_server/message/extended/environment.rb +20 -20
  49. data/lib/solargraph/language_server/message/extended/search.rb +18 -18
  50. data/lib/solargraph/language_server/message/initialize.rb +141 -141
  51. data/lib/solargraph/language_server/message/initialized.rb +23 -23
  52. data/lib/solargraph/language_server/message/shutdown.rb +11 -11
  53. data/lib/solargraph/language_server/message/text_document.rb +25 -25
  54. data/lib/solargraph/language_server/message/text_document/completion.rb +51 -51
  55. data/lib/solargraph/language_server/message/text_document/definition.rb +18 -18
  56. data/lib/solargraph/language_server/message/text_document/did_change.rb +13 -13
  57. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +21 -21
  58. data/lib/solargraph/language_server/message/text_document/folding_range.rb +24 -24
  59. data/lib/solargraph/language_server/message/text_document/formatting.rb +50 -50
  60. data/lib/solargraph/language_server/message/text_document/hover.rb +31 -31
  61. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +32 -32
  62. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +9 -9
  63. data/lib/solargraph/language_server/message/text_document/references.rb +14 -14
  64. data/lib/solargraph/language_server/message/text_document/rename.rb +17 -17
  65. data/lib/solargraph/language_server/message/text_document/signature_help.rb +19 -19
  66. data/lib/solargraph/language_server/message/workspace.rb +12 -12
  67. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -29
  68. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +29 -27
  69. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  70. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +21 -21
  71. data/lib/solargraph/language_server/request.rb +22 -22
  72. data/lib/solargraph/language_server/symbol_kinds.rb +34 -34
  73. data/lib/solargraph/language_server/transport.rb +11 -11
  74. data/lib/solargraph/language_server/transport/adapter.rb +60 -60
  75. data/lib/solargraph/language_server/transport/data_reader.rb +66 -66
  76. data/lib/solargraph/language_server/uri_helpers.rb +25 -25
  77. data/lib/solargraph/library.rb +421 -419
  78. data/lib/solargraph/live_map.rb +126 -126
  79. data/lib/solargraph/live_map/cache.rb +38 -38
  80. data/lib/solargraph/location.rb +31 -31
  81. data/lib/solargraph/logging.rb +25 -25
  82. data/lib/solargraph/page.rb +68 -68
  83. data/lib/solargraph/pin.rb +50 -50
  84. data/lib/solargraph/pin/attribute.rb +41 -41
  85. data/lib/solargraph/pin/base.rb +280 -280
  86. data/lib/solargraph/pin/base_method.rb +76 -76
  87. data/lib/solargraph/pin/base_variable.rb +72 -72
  88. data/lib/solargraph/pin/block.rb +32 -32
  89. data/lib/solargraph/pin/block_parameter.rb +103 -103
  90. data/lib/solargraph/pin/class_variable.rb +9 -9
  91. data/lib/solargraph/pin/constant.rb +30 -30
  92. data/lib/solargraph/pin/conversions.rb +79 -79
  93. data/lib/solargraph/pin/documenting.rb +41 -41
  94. data/lib/solargraph/pin/duck_method.rb +14 -14
  95. data/lib/solargraph/pin/global_variable.rb +9 -9
  96. data/lib/solargraph/pin/instance_variable.rb +9 -9
  97. data/lib/solargraph/pin/keyword.rb +17 -17
  98. data/lib/solargraph/pin/local_variable.rb +23 -23
  99. data/lib/solargraph/pin/localized.rb +22 -22
  100. data/lib/solargraph/pin/method.rb +126 -126
  101. data/lib/solargraph/pin/method_alias.rb +30 -30
  102. data/lib/solargraph/pin/method_parameter.rb +40 -40
  103. data/lib/solargraph/pin/namespace.rb +54 -54
  104. data/lib/solargraph/pin/plugin/method.rb +25 -25
  105. data/lib/solargraph/pin/proxy_type.rb +35 -35
  106. data/lib/solargraph/pin/reference.rb +22 -22
  107. data/lib/solargraph/pin/reference/extend.rb +11 -11
  108. data/lib/solargraph/pin/reference/include.rb +11 -11
  109. data/lib/solargraph/pin/reference/require.rb +15 -15
  110. data/lib/solargraph/pin/reference/superclass.rb +11 -11
  111. data/lib/solargraph/pin/symbol.rb +44 -44
  112. data/lib/solargraph/pin/yard_pin.rb +10 -10
  113. data/lib/solargraph/pin/yard_pin/constant.rb +14 -14
  114. data/lib/solargraph/pin/yard_pin/method.rb +35 -35
  115. data/lib/solargraph/pin/yard_pin/namespace.rb +19 -19
  116. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +14 -14
  117. data/lib/solargraph/plugin.rb +8 -8
  118. data/lib/solargraph/plugin/base.rb +41 -41
  119. data/lib/solargraph/plugin/canceler.rb +11 -11
  120. data/lib/solargraph/plugin/process.rb +172 -172
  121. data/lib/solargraph/plugin/runtime.rb +134 -134
  122. data/lib/solargraph/position.rb +110 -110
  123. data/lib/solargraph/range.rb +83 -83
  124. data/lib/solargraph/server_methods.rb +14 -14
  125. data/lib/solargraph/shell.rb +102 -102
  126. data/lib/solargraph/source.rb +521 -521
  127. data/lib/solargraph/source/chain.rb +120 -120
  128. data/lib/solargraph/source/chain/call.rb +107 -107
  129. data/lib/solargraph/source/chain/class_variable.rb +11 -11
  130. data/lib/solargraph/source/chain/constant.rb +30 -30
  131. data/lib/solargraph/source/chain/global_variable.rb +11 -11
  132. data/lib/solargraph/source/chain/head.rb +33 -33
  133. data/lib/solargraph/source/chain/instance_variable.rb +11 -11
  134. data/lib/solargraph/source/chain/link.rb +33 -33
  135. data/lib/solargraph/source/chain/literal.rb +21 -21
  136. data/lib/solargraph/source/chain/variable.rb +11 -11
  137. data/lib/solargraph/source/change.rb +77 -77
  138. data/lib/solargraph/source/cursor.rb +157 -157
  139. data/lib/solargraph/source/node_chainer.rb +96 -96
  140. data/lib/solargraph/source/node_methods.rb +225 -225
  141. data/lib/solargraph/source/source_chainer.rb +183 -183
  142. data/lib/solargraph/source_map.rb +169 -169
  143. data/lib/solargraph/source_map/clip.rb +145 -145
  144. data/lib/solargraph/source_map/completion.rb +21 -21
  145. data/lib/solargraph/source_map/mapper.rb +149 -149
  146. data/lib/solargraph/source_map/node_processor.rb +78 -78
  147. data/lib/solargraph/source_map/node_processor/alias_node.rb +19 -19
  148. data/lib/solargraph/source_map/node_processor/args_node.rb +28 -28
  149. data/lib/solargraph/source_map/node_processor/base.rb +68 -68
  150. data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -11
  151. data/lib/solargraph/source_map/node_processor/block_node.rb +14 -14
  152. data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -14
  153. data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -14
  154. data/lib/solargraph/source_map/node_processor/def_node.rb +54 -54
  155. data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -21
  156. data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -12
  157. data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -18
  158. data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -16
  159. data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -26
  160. data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -12
  161. data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -11
  162. data/lib/solargraph/source_map/node_processor/send_node.rb +162 -162
  163. data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -11
  164. data/lib/solargraph/source_map/region.rb +58 -58
  165. data/lib/solargraph/version.rb +3 -3
  166. data/lib/solargraph/views/environment.erb +53 -53
  167. data/lib/solargraph/workspace.rb +183 -183
  168. data/lib/solargraph/workspace/config.rb +170 -170
  169. data/lib/solargraph/yard_map.rb +298 -298
  170. data/lib/solargraph/yard_map/cache.rb +17 -17
  171. data/lib/solargraph/yard_map/core_docs.rb +163 -163
  172. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  173. data/lib/yard-coregen.rb +16 -16
  174. data/lib/yard-solargraph.rb +18 -18
  175. data/solargraph.gemspec +37 -0
  176. data/travis-bundler.rb +10 -0
  177. metadata +19 -6
data/bin/solargraph CHANGED
File without changes
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'solargraph/plugin/process'
4
-
5
- Solargraph::Plugin::Process.new.run
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'solargraph/plugin/process'
4
+
5
+ Solargraph::Plugin::Process.new.run
data/lib/solargraph.rb CHANGED
@@ -1,54 +1,54 @@
1
- require 'yard'
2
- require 'solargraph/version'
3
- require 'rubygems/package'
4
- require 'yard-solargraph'
5
-
6
- # The top-level namespace for the Solargraph code mapping, documentation,
7
- # static analysis, and language server libraries.
8
- #
9
- module Solargraph
10
- class InvalidOffsetError < RangeError; end
11
- class DiagnosticsError < RuntimeError; end
12
- class FileNotFoundError < RuntimeError; end
13
- class SourceNotAvailableError < StandardError; end
14
- class ComplexTypeError < StandardError; end
15
- class WorkspaceTooLargeError < RuntimeError; end
16
-
17
- autoload :Position, 'solargraph/position'
18
- autoload :Range, 'solargraph/range'
19
- autoload :Location, 'solargraph/location'
20
- autoload :Shell, 'solargraph/shell'
21
- autoload :Source, 'solargraph/source'
22
- autoload :SourceMap, 'solargraph/source_map'
23
- autoload :ApiMap, 'solargraph/api_map'
24
- autoload :YardMap, 'solargraph/yard_map'
25
- autoload :Pin, 'solargraph/pin'
26
- autoload :LiveMap, 'solargraph/live_map'
27
- autoload :ServerMethods, 'solargraph/server_methods'
28
- autoload :Plugin, 'solargraph/plugin'
29
- autoload :CoreFills, 'solargraph/core_fills'
30
- autoload :LanguageServer, 'solargraph/language_server'
31
- autoload :Workspace, 'solargraph/workspace'
32
- autoload :Page, 'solargraph/page'
33
- autoload :Library, 'solargraph/library'
34
- autoload :Diagnostics, 'solargraph/diagnostics'
35
- autoload :ComplexType, 'solargraph/complex_type'
36
- autoload :Bundle, 'solargraph/bundle'
37
- autoload :Logging, 'solargraph/logging'
38
-
39
- dir = File.dirname(__FILE__)
40
- YARDOC_PATH = File.realpath(File.join(dir, '..', 'yardoc'))
41
- YARD_EXTENSION_FILE = File.join(dir, 'yard-solargraph.rb')
42
- VIEWS_PATH = File.join(dir, 'solargraph', 'views')
43
-
44
- # A convenience method for Solargraph::Logging.logger.
45
- #
46
- # @return [Logger]
47
- def self.logger
48
- Solargraph::Logging.logger
49
- end
50
- end
51
-
52
- Solargraph::YardMap::CoreDocs.require_minimum
53
- # Change YARD log IO to avoid sending unexpected messages to STDOUT
54
- YARD::Logger.instance.io = File.new(File::NULL, 'w')
1
+ require 'yard'
2
+ require 'solargraph/version'
3
+ require 'rubygems/package'
4
+ require 'yard-solargraph'
5
+
6
+ # The top-level namespace for the Solargraph code mapping, documentation,
7
+ # static analysis, and language server libraries.
8
+ #
9
+ module Solargraph
10
+ class InvalidOffsetError < RangeError; end
11
+ class DiagnosticsError < RuntimeError; end
12
+ class FileNotFoundError < RuntimeError; end
13
+ class SourceNotAvailableError < StandardError; end
14
+ class ComplexTypeError < StandardError; end
15
+ class WorkspaceTooLargeError < RuntimeError; end
16
+
17
+ autoload :Position, 'solargraph/position'
18
+ autoload :Range, 'solargraph/range'
19
+ autoload :Location, 'solargraph/location'
20
+ autoload :Shell, 'solargraph/shell'
21
+ autoload :Source, 'solargraph/source'
22
+ autoload :SourceMap, 'solargraph/source_map'
23
+ autoload :ApiMap, 'solargraph/api_map'
24
+ autoload :YardMap, 'solargraph/yard_map'
25
+ autoload :Pin, 'solargraph/pin'
26
+ autoload :LiveMap, 'solargraph/live_map'
27
+ autoload :ServerMethods, 'solargraph/server_methods'
28
+ autoload :Plugin, 'solargraph/plugin'
29
+ autoload :CoreFills, 'solargraph/core_fills'
30
+ autoload :LanguageServer, 'solargraph/language_server'
31
+ autoload :Workspace, 'solargraph/workspace'
32
+ autoload :Page, 'solargraph/page'
33
+ autoload :Library, 'solargraph/library'
34
+ autoload :Diagnostics, 'solargraph/diagnostics'
35
+ autoload :ComplexType, 'solargraph/complex_type'
36
+ autoload :Bundle, 'solargraph/bundle'
37
+ autoload :Logging, 'solargraph/logging'
38
+
39
+ dir = File.dirname(__FILE__)
40
+ YARDOC_PATH = File.realpath(File.join(dir, '..', 'yardoc'))
41
+ YARD_EXTENSION_FILE = File.join(dir, 'yard-solargraph.rb')
42
+ VIEWS_PATH = File.join(dir, 'solargraph', 'views')
43
+
44
+ # A convenience method for Solargraph::Logging.logger.
45
+ #
46
+ # @return [Logger]
47
+ def self.logger
48
+ Solargraph::Logging.logger
49
+ end
50
+ end
51
+
52
+ Solargraph::YardMap::CoreDocs.require_minimum
53
+ # Change YARD log IO to avoid sending unexpected messages to STDOUT
54
+ YARD::Logger.instance.io = File.new(File::NULL, 'w')
@@ -1,659 +1,659 @@
1
- require 'rubygems'
2
- require 'set'
3
- require 'pathname'
4
-
5
- module Solargraph
6
- # An aggregate provider for information about workspaces, sources, gems, and
7
- # the Ruby core.
8
- #
9
- class ApiMap
10
- autoload :Cache, 'solargraph/api_map/cache'
11
- autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
12
- autoload :Store, 'solargraph/api_map/store'
13
-
14
- include SourceToYard
15
-
16
- # Get a LiveMap associated with the current workspace.
17
- #
18
- # @return [Solargraph::LiveMap]
19
- attr_reader :live_map
20
-
21
- # @return [Array<String>]
22
- attr_reader :unresolved_requires
23
-
24
- # @param pins [Array<Solargraph::Pin::Base>]
25
- def initialize pins: []
26
- # @todo Extensions don't work yet
27
- # require_extensions
28
- @source_map_hash = {}
29
- @cache = Cache.new
30
- @mutex = Mutex.new
31
- index pins
32
- end
33
-
34
- # @param pins [Array<Pin::Base>]
35
- # @return [self]
36
- def index pins
37
- @mutex.synchronize {
38
- @source_map_hash.clear
39
- @cache.clear
40
- @store = Store.new(pins + YardMap.new.pins)
41
- @unresolved_requires = []
42
- }
43
- # resolve_method_aliases
44
- self
45
- end
46
-
47
- # Map a single source.
48
- #
49
- # @param source [Source]
50
- # @return [self]
51
- def map source
52
- catalog Bundle.new(opened: [source])
53
- self
54
- end
55
-
56
- def named_macro name
57
- store.named_macros[name]
58
- end
59
-
60
- # Catalog a bundle.
61
- #
62
- # @param bundle [Bundle]
63
- # @return [self]
64
- def catalog bundle
65
- new_map_hash = {}
66
- # Bundle always needs to be merged if it adds or removes sources
67
- merged = (bundle.sources.length == source_map_hash.values.length)
68
- bundle.sources.each do |source|
69
- if source_map_hash.key?(source.filename)
70
- if source_map_hash[source.filename].code == source.code && source_map_hash[source.filename].source.synchronized? && source.synchronized?
71
- new_map_hash[source.filename] = source_map_hash[source.filename]
72
- elsif !source.synchronized?
73
- new_map_hash[source.filename] = source_map_hash[source.filename]
74
- # @todo Smelly instance variable access
75
- new_map_hash[source.filename].instance_variable_set(:@source, source)
76
- else
77
- map = Solargraph::SourceMap.map(source)
78
- if source_map_hash[source.filename].try_merge!(map)
79
- new_map_hash[source.filename] = source_map_hash[source.filename]
80
- else
81
- new_map_hash[source.filename] = map
82
- merged = false
83
- end
84
- end
85
- else
86
- map = Solargraph::SourceMap.map(source)
87
- new_map_hash[source.filename] = map
88
- merged = false
89
- end
90
- end
91
- return self if merged
92
- pins = []
93
- reqs = []
94
- # @param map [SourceMap]
95
- new_map_hash.values.each do |map|
96
- pins.concat map.pins
97
- reqs.concat map.requires.map(&:name)
98
- end
99
- reqs.concat bundle.workspace.config.required
100
- unless bundle.workspace.require_paths.empty?
101
- reqs.delete_if do |r|
102
- result = false
103
- bundle.workspace.require_paths.each do |l|
104
- pn = Pathname.new(bundle.workspace.directory).join(l, "#{r}.rb")
105
- if new_map_hash.keys.include?(pn.to_s)
106
- result = true
107
- break
108
- end
109
- end
110
- result
111
- end
112
- end
113
- yard_map.change(reqs)
114
- new_store = Store.new(pins + yard_map.pins)
115
- @mutex.synchronize {
116
- @cache.clear
117
- @source_map_hash = new_map_hash
118
- @store = new_store
119
- @unresolved_requires = yard_map.unresolved_requires
120
- }
121
- # resolve_method_aliases
122
- self
123
- end
124
-
125
- # @param filename [String]
126
- # @param position [Position, Array(Integer, Integer)]
127
- # @return [Source::Cursor]
128
- def cursor_at filename, position
129
- position = Position.normalize(position)
130
- raise "File not found: #{filename}" unless source_map_hash.has_key?(filename)
131
- source_map_hash[filename].cursor_at(position)
132
- end
133
-
134
- # Get a clip by filename and position.
135
- #
136
- # @param filename [String]
137
- # @param position [Position, Array(Integer, Integer)]
138
- # @return [SourceMap::Clip]
139
- def clip_at filename, position
140
- position = Position.normalize(position)
141
- SourceMap::Clip.new(self, cursor_at(filename, position))
142
- end
143
-
144
- # Create an ApiMap with a workspace in the specified directory.
145
- #
146
- # @param directory [String]
147
- # @return [ApiMap]
148
- def self.load directory
149
- api_map = self.new
150
- workspace = Solargraph::Workspace.new(directory)
151
- api_map.catalog Bundle.new(workspace: workspace)
152
- api_map
153
- end
154
-
155
- # @return [Array<Solargraph::Pin::Base>]
156
- def pins
157
- store.pins
158
- end
159
-
160
- # An array of pins based on Ruby keywords (`if`, `end`, etc.).
161
- #
162
- # @return [Array<Solargraph::Pin::Keyword>]
163
- def self.keywords
164
- @keywords ||= CoreFills::KEYWORDS.map{ |s|
165
- Pin::Keyword.new(s)
166
- }.freeze
167
- end
168
-
169
- # An array of namespace names defined in the ApiMap.
170
- #
171
- # @return [Array<String>]
172
- def namespaces
173
- store.namespaces
174
- end
175
-
176
- # True if the namespace exists.
177
- #
178
- # @param name [String] The namespace to match
179
- # @param context [String] The context to search
180
- # @return [Boolean]
181
- def namespace_exists? name, context = ''
182
- !qualify(name, context).nil?
183
- end
184
-
185
- # Get suggestions for constants in the specified namespace. The result
186
- # may contain both constant and namespace pins.
187
- #
188
- # @param namespace [String] The namespace
189
- # @param context [String] The context
190
- # @return [Array<Solargraph::Pin::Base>]
191
- def get_constants namespace, context = ''
192
- namespace ||= ''
193
- cached = cache.get_constants(namespace, context)
194
- return cached.clone unless cached.nil?
195
- skip = []
196
- result = []
197
- bases = context.split('::')
198
- while bases.length > 0
199
- built = bases.join('::')
200
- fqns = qualify(namespace, built)
201
- visibility = [:public]
202
- visibility.push :private if fqns == context
203
- result.concat inner_get_constants(fqns, visibility, skip)
204
- bases.pop
205
- end
206
- fqns = qualify(namespace, '')
207
- visibility = [:public]
208
- visibility.push :private if fqns == context
209
- result.concat inner_get_constants(fqns, visibility, skip)
210
- cache.set_constants(namespace, context, result)
211
- result
212
- end
213
-
214
- # Get a fully qualified namespace name. This method will start the search
215
- # in the specified context until it finds a match for the name.
216
- #
217
- # @param namespace [String, nil] The namespace to match
218
- # @param context [String] The context to search
219
- # @return [String]
220
- def qualify namespace, context = ''
221
- # @todo The return for self might work better elsewhere
222
- return nil if namespace.nil?
223
- return qualify(context) if namespace == 'self'
224
- cached = cache.get_qualified_namespace(namespace, context)
225
- return cached.clone unless cached.nil?
226
- result = if namespace.start_with?('::')
227
- inner_qualify(namespace[2..-1], '', [])
228
- else
229
- inner_qualify(namespace, context, [])
230
- end
231
- cache.set_qualified_namespace(namespace, context, result)
232
- result
233
- end
234
-
235
- # Get an array of instance variable pins defined in specified namespace
236
- # and scope.
237
- #
238
- # @param namespace [String] A fully qualified namespace
239
- # @param scope [Symbol] :instance or :class
240
- # @return [Array<Solargraph::Pin::InstanceVariable>]
241
- def get_instance_variable_pins(namespace, scope = :instance)
242
- result = []
243
- result.concat store.get_instance_variables(namespace, scope)
244
- sc = qualify(store.get_superclass(namespace), namespace)
245
- until sc.nil?
246
- result.concat store.get_instance_variables(sc, scope)
247
- sc = qualify(store.get_superclass(sc), sc)
248
- end
249
- result
250
- end
251
-
252
- # Get an array of class variable pins for a namespace.
253
- #
254
- # @param namespace [String] A fully qualified namespace
255
- # @return [Array<Solargraph::Pin::ClassVariable>]
256
- def get_class_variable_pins(namespace)
257
- prefer_non_nil_variables(store.get_class_variables(namespace))
258
- end
259
-
260
- # @return [Array<Solargraph::Pin::Base>]
261
- def get_symbols
262
- store.get_symbols
263
- end
264
-
265
- # @return [Array<Solargraph::Pin::GlobalVariable>]
266
- def get_global_variable_pins
267
- # @todo Slow version
268
- pins.select{|p| p.kind == Pin::GLOBAL_VARIABLE}
269
- end
270
-
271
- # Get an array of methods available in a particular context.
272
- #
273
- # @param fqns [String] The fully qualified namespace to search for methods
274
- # @param scope [Symbol] :class or :instance
275
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
276
- # @param deep [Boolean] True to include superclasses, mixins, etc.
277
- # @return [Array<Solargraph::Pin::Base>]
278
- def get_methods fqns, scope: :instance, visibility: [:public], deep: true
279
- cached = cache.get_methods(fqns, scope, visibility, deep)
280
- return cached.clone unless cached.nil?
281
- result = []
282
- skip = []
283
- if fqns == ''
284
- # @todo Implement domains
285
- # domains.each do |domain|
286
- # type = ComplexType.parse(domain).first
287
- # result.concat inner_get_methods(type.name, type.scope, [:public], deep, skip)
288
- # end
289
- result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
290
- result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
291
- result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
292
- else
293
- result.concat inner_get_methods(fqns, scope, visibility, deep, skip)
294
- end
295
- # live = live_map.get_methods(fqns, '', scope.to_s, visibility.include?(:private))
296
- # unless live.empty?
297
- # exist = result.map(&:name)
298
- # result.concat live.reject{|p| exist.include?(p.name)}
299
- # end
300
- resolved = resolve_method_aliases(result)
301
- cache.set_methods(fqns, scope, visibility, deep, resolved)
302
- resolved
303
- end
304
-
305
- # Get an array of method pins for a complex type.
306
- #
307
- # The type's namespace and the context should be fully qualified. If the
308
- # context matches the namespace type or is a subclass of the type,
309
- # protected methods are included in the results. If protected methods are
310
- # included and internal is true, private methods are also included.
311
- #
312
- # @example
313
- # api_map = Solargraph::ApiMap.new
314
- # type = Solargraph::ComplexType.parse('String')
315
- # api_map.get_complex_type_methods(type)
316
- #
317
- # @param type [Solargraph::ComplexType] The complex type of the namespace
318
- # @param context [String] The context from which the type is referenced
319
- # @param internal [Boolean] True to include private methods
320
- # @return [Array<Solargraph::Pin::Base>]
321
- def get_complex_type_methods type, context = '', internal = false
322
- # This method does not qualify the complex type's namespace because
323
- # it can cause conflicts between similar names, e.g., `Foo` vs.
324
- # `Other::Foo`. It still takes a context argument to determine whether
325
- # protected and private methods are visible.
326
- return [] if type.undefined? || type.void?
327
- result = []
328
- if type.duck_type?
329
- type.select(&:duck_type?).each do |t|
330
- result.push Pin::DuckMethod.new(nil, t.tag[1..-1])
331
- end
332
- result.concat get_methods('Object')
333
- else
334
- unless type.nil? || type.name == 'void'
335
- visibility = [:public]
336
- if type.namespace == context || super_and_sub?(type.namespace, context)
337
- visibility.push :protected
338
- visibility.push :private if internal
339
- end
340
- result.concat get_methods(type.namespace, scope: type.scope, visibility: visibility)
341
- end
342
- end
343
- result
344
- end
345
-
346
- # Get a stack of method pins for a method name in a namespace. The order
347
- # of the pins corresponds to the ancestry chain, with highest precedence
348
- # first.
349
- #
350
- # @example
351
- # api_map.get_method_stack('Subclass', 'method_name')
352
- # #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]
353
- #
354
- # @param fqns [String]
355
- # @param name [String]
356
- # @param scope [Symbol] :instance or :class
357
- # @return [Array<Solargraph::Pin::Base>]
358
- def get_method_stack fqns, name, scope: :instance
359
- get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
360
- end
361
-
362
- # Get an array of all suggestions that match the specified path.
363
- #
364
- # @deprecated Use #get_path_pins instead.
365
- #
366
- # @param path [String] The path to find
367
- # @return [Array<Solargraph::Pin::Base>]
368
- def get_path_suggestions path
369
- return [] if path.nil?
370
- result = []
371
- result.concat store.get_path_pins(path)
372
- # if result.empty?
373
- # lp = live_map.get_path_pin(path)
374
- # result.push lp unless lp.nil?
375
- # end
376
- resolve_method_aliases(result)
377
- end
378
-
379
- # Get an array of pins that match the specified path.
380
- #
381
- # @param path [String]
382
- # @return [Array<Pin::Base>]
383
- def get_path_pins path
384
- get_path_suggestions(path)
385
- end
386
-
387
- # Get a list of documented paths that match the query.
388
- #
389
- # @example
390
- # api_map.query('str') # Results will include `String` and `Struct`
391
- #
392
- # @param query [String] The text to match
393
- # @return [Array<String>]
394
- def search query
395
- rake_yard(store)
396
- found = []
397
- code_object_paths.each do |k|
398
- if found.empty? || (query.include?('.') || query.include?('#')) || !(k.include?('.') || k.include?('#'))
399
- found.push k if k.downcase.include?(query.downcase)
400
- end
401
- end
402
- found
403
- end
404
-
405
- # Get YARD documentation for the specified path.
406
- #
407
- # @example
408
- # api_map.document('String#split')
409
- #
410
- # @param path [String] The path to find
411
- # @return [Array<YARD::CodeObject::Base>]
412
- def document path
413
- rake_yard(store)
414
- docs = []
415
- docs.push code_object_at(path) unless code_object_at(path).nil?
416
- docs
417
- end
418
-
419
- # Get an array of all symbols in the workspace that match the query.
420
- #
421
- # @param query [String]
422
- # @return [Array<Pin::Base>]
423
- def query_symbols query
424
- result = []
425
- source_map_hash.values.each do |s|
426
- result.concat s.query_symbols(query)
427
- end
428
- result
429
- end
430
-
431
- # @param location [Solargraph::Location]
432
- # @return [Array<Solargraph::Pin::Base>]
433
- def locate_pins location
434
- return [] if location.nil? || !source_map_hash.has_key?(location.filename)
435
- source_map_hash[location.filename].locate_pins(location)
436
- end
437
-
438
- # @raise [FileNotFoundError] if the cursor's file is not in the ApiMap
439
- # @param cursor [Source::Cursor]
440
- # @return [SourceMap::Clip]
441
- def clip cursor
442
- raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.has_key?(cursor.filename)
443
- SourceMap::Clip.new(self, cursor)
444
- end
445
-
446
- # Get an array of document symbols from a file.
447
- #
448
- # @param filename [String]
449
- # @return [Array<Pin::Symbol>]
450
- def document_symbols filename
451
- return [] unless source_map_hash.has_key?(filename) # @todo Raise error?
452
- source_map_hash[filename].document_symbols
453
- end
454
-
455
- # Get a source map by filename.
456
- #
457
- # @param filename [String]
458
- # @return [SourceMap]
459
- def source_map filename
460
- raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.has_key?(filename)
461
- source_map_hash[filename]
462
- end
463
-
464
- private
465
-
466
- # @return [YardMap]
467
- def yard_map
468
- @yard_map ||= YardMap.new
469
- end
470
-
471
- # A hash of source maps with filename keys.
472
- #
473
- # @return [Hash{String => SourceMap}]
474
- def source_map_hash
475
- @mutex.synchronize { @source_map_hash }
476
- end
477
-
478
- # @return [ApiMap::Store]
479
- def store
480
- @mutex.synchronize { @store }
481
- end
482
-
483
- # @return [Solargraph::ApiMap::Cache]
484
- def cache
485
- @mutex.synchronize { @cache }
486
- end
487
-
488
- # @param fqns [String] A fully qualified namespace
489
- # @param scope [Symbol] :class or :instance
490
- # @param visibility [Array<Symbol>] :public, :protected, and/or :private
491
- # @param deep [Boolean]
492
- # @param skip [Array<String>]
493
- # @param no_core [Boolean] Skip core classes if true
494
- # @return [Array<Pin::Base>]
495
- def inner_get_methods fqns, scope, visibility, deep, skip, no_core = false
496
- return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module|Kernel)$/
497
- reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
498
- return [] if skip.include?(reqstr)
499
- skip.push reqstr
500
- result = []
501
- result.concat store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
502
- if deep
503
- sc = store.get_superclass(fqns)
504
- unless sc.nil?
505
- fqsc = qualify(sc, fqns.split('::')[0..-2].join('::'))
506
- result.concat inner_get_methods(fqsc, scope, visibility, true, skip, true) unless fqsc.nil?
507
- end
508
- if scope == :instance
509
- store.get_includes(fqns).reverse.each do |im|
510
- fqim = qualify(im, fqns)
511
- result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
512
- end
513
- result.concat inner_get_methods('Object', :instance, [:public], deep, skip, no_core)
514
- result.concat inner_get_methods('BasicObject', :instance, [:public], deep, skip, no_core)
515
- else
516
- store.get_extends(fqns).reverse.each do |em|
517
- fqem = qualify(em, fqns)
518
- result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
519
- end
520
- unless no_core || fqns.empty?
521
- type = get_namespace_type(fqns)
522
- result.concat inner_get_methods('Class', :instance, visibility, deep, skip, no_core) if type == :class
523
- result.concat inner_get_methods('Module', :instance,visibility, deep, skip, no_core)
524
- end
525
- end
526
- store.domains(fqns).each do |d|
527
- dt = ComplexType.try_parse(d)
528
- result.concat inner_get_methods(dt.namespace, dt.scope, [:public], deep, skip)
529
- end
530
- end
531
- result
532
- end
533
-
534
- # @param fqns [String]
535
- # @param visibility [Array<Symbol>]
536
- # @param skip [Array<String>]
537
- # @return [Array<Pin::Base>]
538
- def inner_get_constants fqns, visibility, skip
539
- return [] if skip.include?(fqns)
540
- skip.push fqns
541
- result = []
542
- result.concat store.get_constants(fqns, visibility).sort{ |a, b| a.name <=> b.name }
543
- store.get_includes(fqns).each do |is|
544
- fqis = qualify(is, fqns)
545
- result.concat inner_get_constants(fqis, [:public], skip) unless fqis.nil?
546
- end
547
- # result.concat live_map.get_constants(fqns)
548
- result
549
- end
550
-
551
- # Require extensions for the experimental plugin architecture. Any
552
- # installed gem with a name that starts with "solargraph-" is considered
553
- # an extension.
554
- #
555
- # @return [void]
556
- def require_extensions
557
- Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
558
- Solargraph::Logging.logger.info "Loading extension #{n}"
559
- require n.match(/^(solargraph\-[a-z0-9_\-]*?\-ext)\-[0-9\.]*$/)[1]
560
- end
561
- end
562
-
563
- # @return [Hash]
564
- def path_macros
565
- @path_macros ||= {}
566
- end
567
-
568
- # @param name [String]
569
- # @param root [String]
570
- # @param skip [Array<String>]
571
- # @return [String]
572
- def inner_qualify name, root, skip
573
- return nil if name.nil?
574
- return nil if skip.include?(root)
575
- skip.push root
576
- if name == ''
577
- if root == ''
578
- return ''
579
- else
580
- return inner_qualify(root, '', skip)
581
- end
582
- else
583
- return name if root == '' && store.namespace_exists?(name)
584
- roots = root.to_s.split('::')
585
- while roots.length > 0
586
- fqns = roots.join('::') + '::' + name
587
- return fqns if store.namespace_exists?(fqns)
588
- incs = store.get_includes(roots.join('::'))
589
- incs.each do |inc|
590
- foundinc = inner_qualify(name, inc, skip)
591
- return foundinc unless foundinc.nil?
592
- end
593
- roots.pop
594
- end
595
- incs = store.get_includes('')
596
- incs.each do |inc|
597
- foundinc = inner_qualify(name, inc, skip)
598
- return foundinc unless foundinc.nil?
599
- end
600
- return name if store.namespace_exists?(name)
601
- end
602
- # live_map.get_fqns(name, root)
603
- end
604
-
605
- # Get the namespace's type (Class or Module).
606
- #
607
- # @param fqns [String] A fully qualified namespace
608
- # @return [Symbol] :class, :module, or nil
609
- def get_namespace_type fqns
610
- return nil if fqns.nil?
611
- pin = store.get_path_pins(fqns).first
612
- return nil if pin.nil?
613
- pin.type
614
- end
615
-
616
- # Sort an array of pins to put nil or undefined variables last.
617
- #
618
- # @param pins [Array<Solargraph::Pin::Base>]
619
- # @return [Array<Solargraph::Pin::Base>]
620
- def prefer_non_nil_variables pins
621
- result = []
622
- nil_pins = []
623
- pins.each do |pin|
624
- if pin.variable? && pin.nil_assignment?
625
- nil_pins.push pin
626
- else
627
- result.push pin
628
- end
629
- end
630
- result + nil_pins
631
- end
632
-
633
- # Check if a class is a superclass of another class.
634
- #
635
- # @param sup [String] The superclass
636
- # @param sub [String] The subclass
637
- # @return [Boolean]
638
- def super_and_sub?(sup, sub)
639
- fqsup = qualify(sup)
640
- cls = qualify(store.get_superclass(sub), sub)
641
- until cls.nil?
642
- return true if cls == fqsup
643
- cls = qualify(store.get_superclass(cls), cls)
644
- end
645
- false
646
- end
647
-
648
- # @param pins [Array<Pin::Base>]
649
- # @return [Array<Pin::Base>]
650
- def resolve_method_aliases pins
651
- pins.map do |pin|
652
- next pin unless pin.kind == Pin::METHOD_ALIAS
653
- origin = get_method_stack(pin.namespace, pin.original, scope: pin.scope).select{|pin| pin.is_a?(Pin::BaseMethod)}.first
654
- next pin if origin.nil?
655
- Pin::Method.new(pin.location, pin.namespace, pin.name, origin.comments, origin.scope, origin.visibility, origin.parameters)
656
- end
657
- end
658
- end
659
- end
1
+ require 'rubygems'
2
+ require 'set'
3
+ require 'pathname'
4
+
5
+ module Solargraph
6
+ # An aggregate provider for information about workspaces, sources, gems, and
7
+ # the Ruby core.
8
+ #
9
+ class ApiMap
10
+ autoload :Cache, 'solargraph/api_map/cache'
11
+ autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
12
+ autoload :Store, 'solargraph/api_map/store'
13
+
14
+ include SourceToYard
15
+
16
+ # Get a LiveMap associated with the current workspace.
17
+ #
18
+ # @return [Solargraph::LiveMap]
19
+ attr_reader :live_map
20
+
21
+ # @return [Array<String>]
22
+ attr_reader :unresolved_requires
23
+
24
+ # @param pins [Array<Solargraph::Pin::Base>]
25
+ def initialize pins: []
26
+ # @todo Extensions don't work yet
27
+ # require_extensions
28
+ @source_map_hash = {}
29
+ @cache = Cache.new
30
+ @mutex = Mutex.new
31
+ index pins
32
+ end
33
+
34
+ # @param pins [Array<Pin::Base>]
35
+ # @return [self]
36
+ def index pins
37
+ @mutex.synchronize {
38
+ @source_map_hash.clear
39
+ @cache.clear
40
+ @store = Store.new(pins + YardMap.new.pins)
41
+ @unresolved_requires = []
42
+ }
43
+ # resolve_method_aliases
44
+ self
45
+ end
46
+
47
+ # Map a single source.
48
+ #
49
+ # @param source [Source]
50
+ # @return [self]
51
+ def map source
52
+ catalog Bundle.new(opened: [source])
53
+ self
54
+ end
55
+
56
+ def named_macro name
57
+ store.named_macros[name]
58
+ end
59
+
60
+ # Catalog a bundle.
61
+ #
62
+ # @param bundle [Bundle]
63
+ # @return [self]
64
+ def catalog bundle
65
+ new_map_hash = {}
66
+ # Bundle always needs to be merged if it adds or removes sources
67
+ merged = (bundle.sources.length == source_map_hash.values.length)
68
+ bundle.sources.each do |source|
69
+ if source_map_hash.key?(source.filename)
70
+ if source_map_hash[source.filename].code == source.code && source_map_hash[source.filename].source.synchronized? && source.synchronized?
71
+ new_map_hash[source.filename] = source_map_hash[source.filename]
72
+ elsif !source.synchronized?
73
+ new_map_hash[source.filename] = source_map_hash[source.filename]
74
+ # @todo Smelly instance variable access
75
+ new_map_hash[source.filename].instance_variable_set(:@source, source)
76
+ else
77
+ map = Solargraph::SourceMap.map(source)
78
+ if source_map_hash[source.filename].try_merge!(map)
79
+ new_map_hash[source.filename] = source_map_hash[source.filename]
80
+ else
81
+ new_map_hash[source.filename] = map
82
+ merged = false
83
+ end
84
+ end
85
+ else
86
+ map = Solargraph::SourceMap.map(source)
87
+ new_map_hash[source.filename] = map
88
+ merged = false
89
+ end
90
+ end
91
+ return self if merged
92
+ pins = []
93
+ reqs = []
94
+ # @param map [SourceMap]
95
+ new_map_hash.values.each do |map|
96
+ pins.concat map.pins
97
+ reqs.concat map.requires.map(&:name)
98
+ end
99
+ reqs.concat bundle.workspace.config.required
100
+ unless bundle.workspace.require_paths.empty?
101
+ reqs.delete_if do |r|
102
+ result = false
103
+ bundle.workspace.require_paths.each do |l|
104
+ pn = Pathname.new(bundle.workspace.directory).join(l, "#{r}.rb")
105
+ if new_map_hash.keys.include?(pn.to_s)
106
+ result = true
107
+ break
108
+ end
109
+ end
110
+ result
111
+ end
112
+ end
113
+ yard_map.change(reqs)
114
+ new_store = Store.new(pins + yard_map.pins)
115
+ @mutex.synchronize {
116
+ @cache.clear
117
+ @source_map_hash = new_map_hash
118
+ @store = new_store
119
+ @unresolved_requires = yard_map.unresolved_requires
120
+ }
121
+ # resolve_method_aliases
122
+ self
123
+ end
124
+
125
+ # @param filename [String]
126
+ # @param position [Position, Array(Integer, Integer)]
127
+ # @return [Source::Cursor]
128
+ def cursor_at filename, position
129
+ position = Position.normalize(position)
130
+ raise "File not found: #{filename}" unless source_map_hash.has_key?(filename)
131
+ source_map_hash[filename].cursor_at(position)
132
+ end
133
+
134
+ # Get a clip by filename and position.
135
+ #
136
+ # @param filename [String]
137
+ # @param position [Position, Array(Integer, Integer)]
138
+ # @return [SourceMap::Clip]
139
+ def clip_at filename, position
140
+ position = Position.normalize(position)
141
+ SourceMap::Clip.new(self, cursor_at(filename, position))
142
+ end
143
+
144
+ # Create an ApiMap with a workspace in the specified directory.
145
+ #
146
+ # @param directory [String]
147
+ # @return [ApiMap]
148
+ def self.load directory
149
+ api_map = self.new
150
+ workspace = Solargraph::Workspace.new(directory)
151
+ api_map.catalog Bundle.new(workspace: workspace)
152
+ api_map
153
+ end
154
+
155
+ # @return [Array<Solargraph::Pin::Base>]
156
+ def pins
157
+ store.pins
158
+ end
159
+
160
+ # An array of pins based on Ruby keywords (`if`, `end`, etc.).
161
+ #
162
+ # @return [Array<Solargraph::Pin::Keyword>]
163
+ def self.keywords
164
+ @keywords ||= CoreFills::KEYWORDS.map{ |s|
165
+ Pin::Keyword.new(s)
166
+ }.freeze
167
+ end
168
+
169
+ # An array of namespace names defined in the ApiMap.
170
+ #
171
+ # @return [Array<String>]
172
+ def namespaces
173
+ store.namespaces
174
+ end
175
+
176
+ # True if the namespace exists.
177
+ #
178
+ # @param name [String] The namespace to match
179
+ # @param context [String] The context to search
180
+ # @return [Boolean]
181
+ def namespace_exists? name, context = ''
182
+ !qualify(name, context).nil?
183
+ end
184
+
185
+ # Get suggestions for constants in the specified namespace. The result
186
+ # may contain both constant and namespace pins.
187
+ #
188
+ # @param namespace [String] The namespace
189
+ # @param context [String] The context
190
+ # @return [Array<Solargraph::Pin::Base>]
191
+ def get_constants namespace, context = ''
192
+ namespace ||= ''
193
+ cached = cache.get_constants(namespace, context)
194
+ return cached.clone unless cached.nil?
195
+ skip = []
196
+ result = []
197
+ bases = context.split('::')
198
+ while bases.length > 0
199
+ built = bases.join('::')
200
+ fqns = qualify(namespace, built)
201
+ visibility = [:public]
202
+ visibility.push :private if fqns == context
203
+ result.concat inner_get_constants(fqns, visibility, skip)
204
+ bases.pop
205
+ end
206
+ fqns = qualify(namespace, '')
207
+ visibility = [:public]
208
+ visibility.push :private if fqns == context
209
+ result.concat inner_get_constants(fqns, visibility, skip)
210
+ cache.set_constants(namespace, context, result)
211
+ result
212
+ end
213
+
214
+ # Get a fully qualified namespace name. This method will start the search
215
+ # in the specified context until it finds a match for the name.
216
+ #
217
+ # @param namespace [String, nil] The namespace to match
218
+ # @param context [String] The context to search
219
+ # @return [String]
220
+ def qualify namespace, context = ''
221
+ # @todo The return for self might work better elsewhere
222
+ return nil if namespace.nil?
223
+ return qualify(context) if namespace == 'self'
224
+ cached = cache.get_qualified_namespace(namespace, context)
225
+ return cached.clone unless cached.nil?
226
+ result = if namespace.start_with?('::')
227
+ inner_qualify(namespace[2..-1], '', [])
228
+ else
229
+ inner_qualify(namespace, context, [])
230
+ end
231
+ cache.set_qualified_namespace(namespace, context, result)
232
+ result
233
+ end
234
+
235
+ # Get an array of instance variable pins defined in specified namespace
236
+ # and scope.
237
+ #
238
+ # @param namespace [String] A fully qualified namespace
239
+ # @param scope [Symbol] :instance or :class
240
+ # @return [Array<Solargraph::Pin::InstanceVariable>]
241
+ def get_instance_variable_pins(namespace, scope = :instance)
242
+ result = []
243
+ result.concat store.get_instance_variables(namespace, scope)
244
+ sc = qualify(store.get_superclass(namespace), namespace)
245
+ until sc.nil?
246
+ result.concat store.get_instance_variables(sc, scope)
247
+ sc = qualify(store.get_superclass(sc), sc)
248
+ end
249
+ result
250
+ end
251
+
252
+ # Get an array of class variable pins for a namespace.
253
+ #
254
+ # @param namespace [String] A fully qualified namespace
255
+ # @return [Array<Solargraph::Pin::ClassVariable>]
256
+ def get_class_variable_pins(namespace)
257
+ prefer_non_nil_variables(store.get_class_variables(namespace))
258
+ end
259
+
260
+ # @return [Array<Solargraph::Pin::Base>]
261
+ def get_symbols
262
+ store.get_symbols
263
+ end
264
+
265
+ # @return [Array<Solargraph::Pin::GlobalVariable>]
266
+ def get_global_variable_pins
267
+ # @todo Slow version
268
+ pins.select{|p| p.kind == Pin::GLOBAL_VARIABLE}
269
+ end
270
+
271
+ # Get an array of methods available in a particular context.
272
+ #
273
+ # @param fqns [String] The fully qualified namespace to search for methods
274
+ # @param scope [Symbol] :class or :instance
275
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
276
+ # @param deep [Boolean] True to include superclasses, mixins, etc.
277
+ # @return [Array<Solargraph::Pin::Base>]
278
+ def get_methods fqns, scope: :instance, visibility: [:public], deep: true
279
+ cached = cache.get_methods(fqns, scope, visibility, deep)
280
+ return cached.clone unless cached.nil?
281
+ result = []
282
+ skip = []
283
+ if fqns == ''
284
+ # @todo Implement domains
285
+ # domains.each do |domain|
286
+ # type = ComplexType.parse(domain).first
287
+ # result.concat inner_get_methods(type.name, type.scope, [:public], deep, skip)
288
+ # end
289
+ result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
290
+ result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
291
+ result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
292
+ else
293
+ result.concat inner_get_methods(fqns, scope, visibility, deep, skip)
294
+ end
295
+ # live = live_map.get_methods(fqns, '', scope.to_s, visibility.include?(:private))
296
+ # unless live.empty?
297
+ # exist = result.map(&:name)
298
+ # result.concat live.reject{|p| exist.include?(p.name)}
299
+ # end
300
+ resolved = resolve_method_aliases(result)
301
+ cache.set_methods(fqns, scope, visibility, deep, resolved)
302
+ resolved
303
+ end
304
+
305
+ # Get an array of method pins for a complex type.
306
+ #
307
+ # The type's namespace and the context should be fully qualified. If the
308
+ # context matches the namespace type or is a subclass of the type,
309
+ # protected methods are included in the results. If protected methods are
310
+ # included and internal is true, private methods are also included.
311
+ #
312
+ # @example
313
+ # api_map = Solargraph::ApiMap.new
314
+ # type = Solargraph::ComplexType.parse('String')
315
+ # api_map.get_complex_type_methods(type)
316
+ #
317
+ # @param type [Solargraph::ComplexType] The complex type of the namespace
318
+ # @param context [String] The context from which the type is referenced
319
+ # @param internal [Boolean] True to include private methods
320
+ # @return [Array<Solargraph::Pin::Base>]
321
+ def get_complex_type_methods type, context = '', internal = false
322
+ # This method does not qualify the complex type's namespace because
323
+ # it can cause conflicts between similar names, e.g., `Foo` vs.
324
+ # `Other::Foo`. It still takes a context argument to determine whether
325
+ # protected and private methods are visible.
326
+ return [] if type.undefined? || type.void?
327
+ result = []
328
+ if type.duck_type?
329
+ type.select(&:duck_type?).each do |t|
330
+ result.push Pin::DuckMethod.new(nil, t.tag[1..-1])
331
+ end
332
+ result.concat get_methods('Object')
333
+ else
334
+ unless type.nil? || type.name == 'void'
335
+ visibility = [:public]
336
+ if type.namespace == context || super_and_sub?(type.namespace, context)
337
+ visibility.push :protected
338
+ visibility.push :private if internal
339
+ end
340
+ result.concat get_methods(type.namespace, scope: type.scope, visibility: visibility)
341
+ end
342
+ end
343
+ result
344
+ end
345
+
346
+ # Get a stack of method pins for a method name in a namespace. The order
347
+ # of the pins corresponds to the ancestry chain, with highest precedence
348
+ # first.
349
+ #
350
+ # @example
351
+ # api_map.get_method_stack('Subclass', 'method_name')
352
+ # #=> [ <Subclass#method_name pin>, <Superclass#method_name pin> ]
353
+ #
354
+ # @param fqns [String]
355
+ # @param name [String]
356
+ # @param scope [Symbol] :instance or :class
357
+ # @return [Array<Solargraph::Pin::Base>]
358
+ def get_method_stack fqns, name, scope: :instance
359
+ get_methods(fqns, scope: scope, visibility: [:private, :protected, :public]).select{|p| p.name == name}
360
+ end
361
+
362
+ # Get an array of all suggestions that match the specified path.
363
+ #
364
+ # @deprecated Use #get_path_pins instead.
365
+ #
366
+ # @param path [String] The path to find
367
+ # @return [Array<Solargraph::Pin::Base>]
368
+ def get_path_suggestions path
369
+ return [] if path.nil?
370
+ result = []
371
+ result.concat store.get_path_pins(path)
372
+ # if result.empty?
373
+ # lp = live_map.get_path_pin(path)
374
+ # result.push lp unless lp.nil?
375
+ # end
376
+ resolve_method_aliases(result)
377
+ end
378
+
379
+ # Get an array of pins that match the specified path.
380
+ #
381
+ # @param path [String]
382
+ # @return [Array<Pin::Base>]
383
+ def get_path_pins path
384
+ get_path_suggestions(path)
385
+ end
386
+
387
+ # Get a list of documented paths that match the query.
388
+ #
389
+ # @example
390
+ # api_map.query('str') # Results will include `String` and `Struct`
391
+ #
392
+ # @param query [String] The text to match
393
+ # @return [Array<String>]
394
+ def search query
395
+ rake_yard(store)
396
+ found = []
397
+ code_object_paths.each do |k|
398
+ if found.empty? || (query.include?('.') || query.include?('#')) || !(k.include?('.') || k.include?('#'))
399
+ found.push k if k.downcase.include?(query.downcase)
400
+ end
401
+ end
402
+ found
403
+ end
404
+
405
+ # Get YARD documentation for the specified path.
406
+ #
407
+ # @example
408
+ # api_map.document('String#split')
409
+ #
410
+ # @param path [String] The path to find
411
+ # @return [Array<YARD::CodeObject::Base>]
412
+ def document path
413
+ rake_yard(store)
414
+ docs = []
415
+ docs.push code_object_at(path) unless code_object_at(path).nil?
416
+ docs
417
+ end
418
+
419
+ # Get an array of all symbols in the workspace that match the query.
420
+ #
421
+ # @param query [String]
422
+ # @return [Array<Pin::Base>]
423
+ def query_symbols query
424
+ result = []
425
+ source_map_hash.values.each do |s|
426
+ result.concat s.query_symbols(query)
427
+ end
428
+ result
429
+ end
430
+
431
+ # @param location [Solargraph::Location]
432
+ # @return [Array<Solargraph::Pin::Base>]
433
+ def locate_pins location
434
+ return [] if location.nil? || !source_map_hash.has_key?(location.filename)
435
+ source_map_hash[location.filename].locate_pins(location)
436
+ end
437
+
438
+ # @raise [FileNotFoundError] if the cursor's file is not in the ApiMap
439
+ # @param cursor [Source::Cursor]
440
+ # @return [SourceMap::Clip]
441
+ def clip cursor
442
+ raise FileNotFoundError, "ApiMap did not catalog #{cursor.filename}" unless source_map_hash.has_key?(cursor.filename)
443
+ SourceMap::Clip.new(self, cursor)
444
+ end
445
+
446
+ # Get an array of document symbols from a file.
447
+ #
448
+ # @param filename [String]
449
+ # @return [Array<Pin::Symbol>]
450
+ def document_symbols filename
451
+ return [] unless source_map_hash.has_key?(filename) # @todo Raise error?
452
+ source_map_hash[filename].document_symbols
453
+ end
454
+
455
+ # Get a source map by filename.
456
+ #
457
+ # @param filename [String]
458
+ # @return [SourceMap]
459
+ def source_map filename
460
+ raise FileNotFoundError, "Source map for `#{filename}` not found" unless source_map_hash.has_key?(filename)
461
+ source_map_hash[filename]
462
+ end
463
+
464
+ private
465
+
466
+ # @return [YardMap]
467
+ def yard_map
468
+ @yard_map ||= YardMap.new
469
+ end
470
+
471
+ # A hash of source maps with filename keys.
472
+ #
473
+ # @return [Hash{String => SourceMap}]
474
+ def source_map_hash
475
+ @mutex.synchronize { @source_map_hash }
476
+ end
477
+
478
+ # @return [ApiMap::Store]
479
+ def store
480
+ @mutex.synchronize { @store }
481
+ end
482
+
483
+ # @return [Solargraph::ApiMap::Cache]
484
+ def cache
485
+ @mutex.synchronize { @cache }
486
+ end
487
+
488
+ # @param fqns [String] A fully qualified namespace
489
+ # @param scope [Symbol] :class or :instance
490
+ # @param visibility [Array<Symbol>] :public, :protected, and/or :private
491
+ # @param deep [Boolean]
492
+ # @param skip [Array<String>]
493
+ # @param no_core [Boolean] Skip core classes if true
494
+ # @return [Array<Pin::Base>]
495
+ def inner_get_methods fqns, scope, visibility, deep, skip, no_core = false
496
+ return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module|Kernel)$/
497
+ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
498
+ return [] if skip.include?(reqstr)
499
+ skip.push reqstr
500
+ result = []
501
+ result.concat store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name }
502
+ if deep
503
+ sc = store.get_superclass(fqns)
504
+ unless sc.nil?
505
+ fqsc = qualify(sc, fqns.split('::')[0..-2].join('::'))
506
+ result.concat inner_get_methods(fqsc, scope, visibility, true, skip, true) unless fqsc.nil?
507
+ end
508
+ if scope == :instance
509
+ store.get_includes(fqns).reverse.each do |im|
510
+ fqim = qualify(im, fqns)
511
+ result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
512
+ end
513
+ result.concat inner_get_methods('Object', :instance, [:public], deep, skip, no_core)
514
+ result.concat inner_get_methods('BasicObject', :instance, [:public], deep, skip, no_core)
515
+ else
516
+ store.get_extends(fqns).reverse.each do |em|
517
+ fqem = qualify(em, fqns)
518
+ result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
519
+ end
520
+ unless no_core || fqns.empty?
521
+ type = get_namespace_type(fqns)
522
+ result.concat inner_get_methods('Class', :instance, visibility, deep, skip, no_core) if type == :class
523
+ result.concat inner_get_methods('Module', :instance,visibility, deep, skip, no_core)
524
+ end
525
+ end
526
+ store.domains(fqns).each do |d|
527
+ dt = ComplexType.try_parse(d)
528
+ result.concat inner_get_methods(dt.namespace, dt.scope, [:public], deep, skip)
529
+ end
530
+ end
531
+ result
532
+ end
533
+
534
+ # @param fqns [String]
535
+ # @param visibility [Array<Symbol>]
536
+ # @param skip [Array<String>]
537
+ # @return [Array<Pin::Base>]
538
+ def inner_get_constants fqns, visibility, skip
539
+ return [] if skip.include?(fqns)
540
+ skip.push fqns
541
+ result = []
542
+ result.concat store.get_constants(fqns, visibility).sort{ |a, b| a.name <=> b.name }
543
+ store.get_includes(fqns).each do |is|
544
+ fqis = qualify(is, fqns)
545
+ result.concat inner_get_constants(fqis, [:public], skip) unless fqis.nil?
546
+ end
547
+ # result.concat live_map.get_constants(fqns)
548
+ result
549
+ end
550
+
551
+ # Require extensions for the experimental plugin architecture. Any
552
+ # installed gem with a name that starts with "solargraph-" is considered
553
+ # an extension.
554
+ #
555
+ # @return [void]
556
+ def require_extensions
557
+ Gem::Specification.all_names.select{|n| n.match(/^solargraph\-[a-z0-9_\-]*?\-ext\-[0-9\.]*$/)}.each do |n|
558
+ Solargraph::Logging.logger.info "Loading extension #{n}"
559
+ require n.match(/^(solargraph\-[a-z0-9_\-]*?\-ext)\-[0-9\.]*$/)[1]
560
+ end
561
+ end
562
+
563
+ # @return [Hash]
564
+ def path_macros
565
+ @path_macros ||= {}
566
+ end
567
+
568
+ # @param name [String]
569
+ # @param root [String]
570
+ # @param skip [Array<String>]
571
+ # @return [String]
572
+ def inner_qualify name, root, skip
573
+ return nil if name.nil?
574
+ return nil if skip.include?(root)
575
+ skip.push root
576
+ if name == ''
577
+ if root == ''
578
+ return ''
579
+ else
580
+ return inner_qualify(root, '', skip)
581
+ end
582
+ else
583
+ return name if root == '' && store.namespace_exists?(name)
584
+ roots = root.to_s.split('::')
585
+ while roots.length > 0
586
+ fqns = roots.join('::') + '::' + name
587
+ return fqns if store.namespace_exists?(fqns)
588
+ incs = store.get_includes(roots.join('::'))
589
+ incs.each do |inc|
590
+ foundinc = inner_qualify(name, inc, skip)
591
+ return foundinc unless foundinc.nil?
592
+ end
593
+ roots.pop
594
+ end
595
+ incs = store.get_includes('')
596
+ incs.each do |inc|
597
+ foundinc = inner_qualify(name, inc, skip)
598
+ return foundinc unless foundinc.nil?
599
+ end
600
+ return name if store.namespace_exists?(name)
601
+ end
602
+ # live_map.get_fqns(name, root)
603
+ end
604
+
605
+ # Get the namespace's type (Class or Module).
606
+ #
607
+ # @param fqns [String] A fully qualified namespace
608
+ # @return [Symbol] :class, :module, or nil
609
+ def get_namespace_type fqns
610
+ return nil if fqns.nil?
611
+ pin = store.get_path_pins(fqns).first
612
+ return nil if pin.nil?
613
+ pin.type
614
+ end
615
+
616
+ # Sort an array of pins to put nil or undefined variables last.
617
+ #
618
+ # @param pins [Array<Solargraph::Pin::Base>]
619
+ # @return [Array<Solargraph::Pin::Base>]
620
+ def prefer_non_nil_variables pins
621
+ result = []
622
+ nil_pins = []
623
+ pins.each do |pin|
624
+ if pin.variable? && pin.nil_assignment?
625
+ nil_pins.push pin
626
+ else
627
+ result.push pin
628
+ end
629
+ end
630
+ result + nil_pins
631
+ end
632
+
633
+ # Check if a class is a superclass of another class.
634
+ #
635
+ # @param sup [String] The superclass
636
+ # @param sub [String] The subclass
637
+ # @return [Boolean]
638
+ def super_and_sub?(sup, sub)
639
+ fqsup = qualify(sup)
640
+ cls = qualify(store.get_superclass(sub), sub)
641
+ until cls.nil?
642
+ return true if cls == fqsup
643
+ cls = qualify(store.get_superclass(cls), cls)
644
+ end
645
+ false
646
+ end
647
+
648
+ # @param pins [Array<Pin::Base>]
649
+ # @return [Array<Pin::Base>]
650
+ def resolve_method_aliases pins
651
+ pins.map do |pin|
652
+ next pin unless pin.kind == Pin::METHOD_ALIAS
653
+ origin = get_method_stack(pin.namespace, pin.original, scope: pin.scope).select{|pin| pin.is_a?(Pin::BaseMethod)}.first
654
+ next pin if origin.nil?
655
+ Pin::Method.new(pin.location, pin.namespace, pin.name, origin.comments, origin.scope, origin.visibility, origin.parameters)
656
+ end
657
+ end
658
+ end
659
+ end