solargraph 0.32.1 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
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