solargraph 0.17.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +16 -12
  3. data/lib/solargraph/api_map.rb +516 -588
  4. data/lib/solargraph/api_map/completion.rb +16 -0
  5. data/lib/solargraph/api_map/source_to_yard.rb +2 -2
  6. data/lib/solargraph/language_server.rb +12 -0
  7. data/lib/solargraph/language_server/completion_item_kinds.rb +31 -0
  8. data/lib/solargraph/language_server/error_codes.rb +16 -0
  9. data/lib/solargraph/language_server/host.rb +305 -0
  10. data/lib/solargraph/language_server/message.rb +70 -0
  11. data/lib/solargraph/language_server/message/base.rb +64 -0
  12. data/lib/solargraph/language_server/message/cancel_request.rb +11 -0
  13. data/lib/solargraph/language_server/message/client.rb +5 -0
  14. data/lib/solargraph/language_server/message/client/register_capability.rb +13 -0
  15. data/lib/solargraph/language_server/message/completion_item.rb +9 -0
  16. data/lib/solargraph/language_server/message/completion_item/resolve.rb +23 -0
  17. data/lib/solargraph/language_server/message/exit_notification.rb +12 -0
  18. data/lib/solargraph/language_server/message/extended.rb +15 -0
  19. data/lib/solargraph/language_server/message/extended/document.rb +18 -0
  20. data/lib/solargraph/language_server/message/extended/search.rb +18 -0
  21. data/lib/solargraph/language_server/message/initialize.rb +39 -0
  22. data/lib/solargraph/language_server/message/initialized.rb +10 -0
  23. data/lib/solargraph/language_server/message/method_not_found.rb +14 -0
  24. data/lib/solargraph/language_server/message/method_not_implemented.rb +12 -0
  25. data/lib/solargraph/language_server/message/shutdown.rb +11 -0
  26. data/lib/solargraph/language_server/message/text_document.rb +21 -0
  27. data/lib/solargraph/language_server/message/text_document/base.rb +17 -0
  28. data/lib/solargraph/language_server/message/text_document/completion.rb +69 -0
  29. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
  30. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
  31. data/lib/solargraph/language_server/message/text_document/did_close.rb +12 -0
  32. data/lib/solargraph/language_server/message/text_document/did_open.rb +13 -0
  33. data/lib/solargraph/language_server/message/text_document/did_save.rb +15 -0
  34. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +31 -0
  35. data/lib/solargraph/language_server/message/text_document/formatting.rb +36 -0
  36. data/lib/solargraph/language_server/message/text_document/hover.rb +19 -0
  37. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +29 -0
  38. data/lib/solargraph/language_server/message/text_document/signature_help.rb +23 -0
  39. data/lib/solargraph/language_server/message/workspace.rb +11 -0
  40. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +9 -0
  41. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +30 -0
  42. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +31 -0
  43. data/lib/solargraph/language_server/symbol_kinds.rb +32 -0
  44. data/lib/solargraph/language_server/transport.rb +7 -0
  45. data/lib/solargraph/language_server/transport/socket.rb +66 -0
  46. data/lib/solargraph/language_server/uri_helpers.rb +21 -0
  47. data/lib/solargraph/library.rb +225 -0
  48. data/lib/solargraph/live_map.rb +1 -1
  49. data/lib/solargraph/page.rb +61 -0
  50. data/lib/solargraph/pin.rb +7 -0
  51. data/lib/solargraph/pin/attribute.rb +9 -0
  52. data/lib/solargraph/pin/base.rb +76 -6
  53. data/lib/solargraph/pin/base_variable.rb +29 -7
  54. data/lib/solargraph/pin/block_parameter.rb +53 -0
  55. data/lib/solargraph/pin/constant.rb +6 -2
  56. data/lib/solargraph/pin/conversions.rb +65 -0
  57. data/lib/solargraph/pin/directed/attribute.rb +4 -0
  58. data/lib/solargraph/pin/directed/method.rb +6 -1
  59. data/lib/solargraph/pin/helper.rb +35 -0
  60. data/lib/solargraph/pin/keyword.rb +22 -0
  61. data/lib/solargraph/pin/local_variable.rb +0 -1
  62. data/lib/solargraph/pin/method.rb +55 -2
  63. data/lib/solargraph/pin/method_parameter.rb +19 -0
  64. data/lib/solargraph/pin/namespace.rb +7 -2
  65. data/lib/solargraph/pin/parameter.rb +23 -0
  66. data/lib/solargraph/pin/plugin/method.rb +3 -2
  67. data/lib/solargraph/pin/yard_object.rb +101 -0
  68. data/lib/solargraph/server.rb +82 -135
  69. data/lib/solargraph/shell.rb +20 -1
  70. data/lib/solargraph/source.rb +709 -0
  71. data/lib/solargraph/source/flawed_builder.rb +10 -0
  72. data/lib/solargraph/source/fragment.rb +319 -0
  73. data/lib/solargraph/source/position.rb +26 -0
  74. data/lib/solargraph/source/range.rb +39 -0
  75. data/lib/solargraph/suggestion.rb +29 -4
  76. data/lib/solargraph/version.rb +1 -1
  77. data/lib/solargraph/workspace.rb +105 -0
  78. data/lib/solargraph/{api_map → workspace}/config.rb +1 -1
  79. data/lib/solargraph/yard_map.rb +59 -37
  80. metadata +168 -5
  81. data/lib/solargraph/api_map/source.rb +0 -470
  82. data/lib/solargraph/code_map.rb +0 -868
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 633e0f95c131f673e3c6372233ab7cfa49fe972a
4
- data.tar.gz: d934af34ec402bac6d4604dff276e4070e56b3d9
3
+ metadata.gz: 2ccb588fb1b726e174fc3e8d569a92f1e8ae0c9d
4
+ data.tar.gz: 0ec8f37bcd32ecde24d4f56a80bd6db81d559c13
5
5
  SHA512:
6
- metadata.gz: e70e12fa5c2a7daef95b96c7c31dd0b52d2dd7bf4c2ccf7cbafb0f0d86b6fecfc4165c11225f1fa7a6173e5bb09c99a509d669a1726c9f1d4b8db9e13f45fc19
7
- data.tar.gz: 3c923301b70d4091fd4c508921ef1a577968183fe117d5a0fb730cddc3c7ee2705779cdc5bbba388abf85b82016d56521079624c39c32a7c8c77fbf72e0fe2b3
6
+ metadata.gz: 71df1903bd21f4a7e762068d23ff09c5d5c1a078e63eec46ce04b5500d9e27fcda2ff8a921c16ce3a4b7d6379cc8ac582ea48a9d36fc6e4f1e8ed8282d9e77fa
7
+ data.tar.gz: 4e1fe48e17648c06a5ca4bf513fc46856ae848746844a1db6f2d21ee281f8c664a44ddbf7b1dd01e47ec80abbb8eba77c742853086a1d43d66e7a9b218c21a12
data/lib/solargraph.rb CHANGED
@@ -3,18 +3,22 @@ require 'rubygems/package'
3
3
  require 'yard-solargraph'
4
4
 
5
5
  module Solargraph
6
- autoload :Shell, 'solargraph/shell'
7
- autoload :ApiMap, 'solargraph/api_map'
8
- autoload :CodeMap, 'solargraph/code_map'
9
- autoload :NodeMethods, 'solargraph/node_methods'
10
- autoload :Suggestion, 'solargraph/suggestion'
11
- autoload :Server, 'solargraph/server'
12
- autoload :YardMap, 'solargraph/yard_map'
13
- autoload :Pin, 'solargraph/pin'
14
- autoload :LiveMap, 'solargraph/live_map'
15
- autoload :ServerMethods, 'solargraph/server_methods'
16
- autoload :Plugin, 'solargraph/plugin'
17
- autoload :CoreFills, 'solargraph/core_fills'
6
+ autoload :Shell, 'solargraph/shell'
7
+ autoload :Source, 'solargraph/source'
8
+ autoload :ApiMap, 'solargraph/api_map'
9
+ autoload :NodeMethods, 'solargraph/node_methods'
10
+ autoload :Suggestion, 'solargraph/suggestion'
11
+ autoload :Server, 'solargraph/server'
12
+ autoload :YardMap, 'solargraph/yard_map'
13
+ autoload :Pin, 'solargraph/pin'
14
+ autoload :LiveMap, 'solargraph/live_map'
15
+ autoload :ServerMethods, 'solargraph/server_methods'
16
+ autoload :Plugin, 'solargraph/plugin'
17
+ autoload :CoreFills, 'solargraph/core_fills'
18
+ autoload :LanguageServer, 'solargraph/language_server'
19
+ autoload :Workspace, 'solargraph/workspace'
20
+ autoload :Page, 'solargraph/page'
21
+ autoload :Library, 'solargraph/library'
18
22
 
19
23
  YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
20
24
  YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
@@ -1,67 +1,37 @@
1
1
  require 'rubygems'
2
- require 'parser/current'
3
- require 'thread'
4
2
  require 'set'
3
+ require 'time'
5
4
 
6
5
  module Solargraph
7
6
  class ApiMap
8
- autoload :Config, 'solargraph/api_map/config'
9
- autoload :Source, 'solargraph/api_map/source'
10
7
  autoload :Cache, 'solargraph/api_map/cache'
11
8
  autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
12
- @@source_cache = {}
9
+ autoload :Completion, 'solargraph/api_map/completion'
13
10
 
14
11
  include NodeMethods
15
12
  include Solargraph::ApiMap::SourceToYard
16
13
  include CoreFills
17
14
 
18
- # The root directory of the project. The ApiMap will search here for
19
- # additional files to parse and analyze.
15
+ # The workspace to analyze and process.
20
16
  #
21
- # @return [String]
17
+ # @return [Solargraph::Workspace]
22
18
  attr_reader :workspace
23
19
 
24
- # @param workspace [String]
20
+ # @param workspace [Solargraph::Workspace]
25
21
  def initialize workspace = nil
26
- @@source_cache.clear
27
- @workspace = workspace.gsub(/\\/, '/') unless workspace.nil?
28
- clear
22
+ # @todo Deprecate strings for the workspace parameter
23
+ workspace = Solargraph::Workspace.new(workspace) if workspace.kind_of?(String)
24
+ workspace = Solargraph::Workspace.new(nil) if workspace.nil?
25
+ @workspace = workspace
29
26
  require_extensions
30
- unless @workspace.nil?
31
- workspace_files.concat config.calculated
32
- workspace_files.each do |wf|
33
- begin
34
- @@source_cache[wf] ||= Source.load(wf)
35
- rescue Parser::SyntaxError, EncodingError => e
36
- STDERR.puts "Failed to load #{wf}: #{e.message}"
37
- @@source_cache[wf] = Source.virtual('', wf)
38
- end
39
- end
40
- end
41
- @sources = {}
42
27
  @virtual_source = nil
43
- @virtual_filename = nil
44
- @stale = true
45
28
  @yard_stale = true
46
- refresh
29
+ process_maps
47
30
  yard_map
48
31
  end
49
32
 
50
- # Get the configuration for the ApiMap's workspace. This method will
51
- # initialize the settings from the workspace's root .solargraph.yml file
52
- # if it exists.
53
- #
54
- # @return [Solargraph::ApiMap::Config]
55
- def config reload = false
56
- @config = ApiMap::Config.new(@workspace) if @config.nil? or reload
57
- @config
58
- end
59
-
60
- # An array of all workspace files included in the map.
61
- #
62
- # @return[Array<String>]
63
- def workspace_files
64
- @workspace_files ||= []
33
+ def self.load directory
34
+ self.new(Solargraph::Workspace.new(directory))
65
35
  end
66
36
 
67
37
  # An array of required paths in the workspace.
@@ -71,67 +41,81 @@ module Solargraph
71
41
  @required ||= []
72
42
  end
73
43
 
74
- # Get a YardMap associated with the current namespace.
44
+ # Get a YardMap associated with the current workspace.
75
45
  #
76
46
  # @return [Solargraph::YardMap]
77
47
  def yard_map
78
- refresh
48
+ # refresh
79
49
  if @yard_map.nil? || @yard_map.required.to_set != required.to_set
80
50
  @yard_map = Solargraph::YardMap.new(required: required, workspace: workspace)
81
51
  end
82
52
  @yard_map
83
53
  end
84
54
 
85
- # Get a LiveMap associated with the current namespace.
55
+ # Get a LiveMap associated with the current workspace.
86
56
  #
87
57
  # @return [Solargraph::LiveMap]
88
58
  def live_map
89
59
  @live_map ||= Solargraph::LiveMap.new(self)
90
60
  end
91
61
 
92
- # @todo Get rid of the cursor parameter. Tracking stubbed lines is the
93
- # better option.
62
+ # Declare a virtual source that will be included in the map regardless of
63
+ # whether it's in the workspace.
94
64
  #
95
- # @param code [String]
96
- # @param filename [String]
97
- # @return [Solargraph::ApiMap::Source]
98
- def virtualize code, filename = nil, cursor = nil
99
- workspace_files.delete_if do |f|
100
- if File.exist?(f)
101
- false
102
- else
103
- eliminate f
104
- true
105
- end
106
- end
107
- if filename.nil? or filename.end_with?('.rb') or filename.end_with?('.erb')
108
- eliminate @virtual_filename unless @virtual_source.nil? or @virtual_filename == filename or workspace_files.include?(@virtual_filename)
109
- @virtual_filename = filename
110
- @virtual_source = Source.fix(code, filename, cursor)
111
- unless filename.nil? or workspace_files.include?(filename)
112
- current_files = @workspace_files
113
- @workspace_files = config(true).calculated
114
- (current_files - @workspace_files).each { |f| eliminate f }
115
- end
116
- process_virtual
65
+ # If the source is in the workspace, virtualizing it has no effect. Only
66
+ # one source can be virtualized at a time.
67
+ #
68
+ # @param [Solargraph::Source]
69
+ def virtualize source
70
+ eliminate @virtual_source unless @virtual_source.nil?
71
+ if workspace.has_source?(source)
72
+ @sources = workspace.sources
73
+ @virtual_source = nil
117
74
  else
118
- unless filename.nil?
119
- # @todo Handle special files like .solargraph.yml
75
+ @virtual_source = source
76
+ @sources = workspace.sources
77
+ unless @virtual_source.nil?
78
+ @sources.push @virtual_source
79
+ process_virtual
120
80
  end
121
81
  end
122
- @virtual_source
123
82
  end
124
83
 
125
- # @return [Solargraph::ApiMap::Source]
126
- def append_source code, filename
127
- virtualize code, filename
84
+ # @todo Candidate for deprecation
85
+ def append_source code, filename = nil
86
+ source = Source.load_string(code, filename)
87
+ virtualize source
128
88
  end
129
89
 
130
90
  # Refresh the ApiMap.
131
91
  #
132
92
  # @param force [Boolean] Perform a refresh even if the map is not "stale."
133
93
  def refresh force = false
134
- process_maps if @stale or force
94
+ return unless @force or changed?
95
+ if force
96
+ process_maps
97
+ else
98
+ current_workspace_sources.reject{|s| workspace.sources.include?(s)}.each do |source|
99
+ eliminate source
100
+ end
101
+ @sources = workspace.sources
102
+ @sources.push @virtual_source unless @virtual_source.nil?
103
+ cache.clear
104
+ namespace_map.clear
105
+ @sources.each do |s|
106
+ s.namespace_nodes.each_pair do |k, v|
107
+ namespace_map[k] ||= []
108
+ namespace_map[k].concat v
109
+ end
110
+ end
111
+ @sources.each do |source|
112
+ if @stime.nil? or source.stime > @stime
113
+ eliminate source
114
+ map_source source
115
+ end
116
+ end
117
+ end
118
+ @stime = Time.new
135
119
  end
136
120
 
137
121
  # True if a workspace file has been created, modified, or deleted since
@@ -139,15 +123,10 @@ module Solargraph
139
123
  #
140
124
  # @return [Boolean]
141
125
  def changed?
142
- current = config.calculated
143
- unless (Set.new(current) ^ workspace_files).empty?
144
- return true
145
- end
146
- current.each do |f|
147
- if !File.exist?(f) or File.mtime(f) != source_file_mtime(f)
148
- return true
149
- end
150
- end
126
+ return true if current_workspace_sources.length != workspace.sources.length
127
+ return true if @stime.nil?
128
+ return true if workspace.stime > @stime
129
+ return true if !@virtual_source.nil? and @virtual_source.stime > @stime
151
130
  false
152
131
  end
153
132
 
@@ -156,17 +135,17 @@ module Solargraph
156
135
  # @param node [AST::Node]
157
136
  # @return [YARD::Docstring]
158
137
  def get_docstring_for node
159
- filename = get_filename_for(node)
160
- return nil if @sources[filename].nil?
161
- @sources[filename].docstring_for(node)
138
+ source = get_source_for(node)
139
+ return nil if source.nil?
140
+ source.docstring_for(node)
162
141
  end
163
142
 
164
143
  # An array of suggestions based on Ruby keywords (`if`, `end`, etc.).
165
144
  #
166
- # @return [Array<Solargraph::Suggestion>]
145
+ # @return [Array<Solargraph::Pin::Keyword>]
167
146
  def self.keywords
168
147
  @keyword_suggestions ||= KEYWORDS.map{ |s|
169
- Suggestion.new(s.to_s, kind: Suggestion::KEYWORD, detail: 'Keyword')
148
+ Pin::Keyword.new(s)
170
149
  }.freeze
171
150
  end
172
151
 
@@ -174,7 +153,7 @@ module Solargraph
174
153
  #
175
154
  # @return [Array<String>]
176
155
  def namespaces
177
- refresh
156
+ # refresh
178
157
  namespace_map.keys
179
158
  end
180
159
 
@@ -203,8 +182,9 @@ module Solargraph
203
182
  #
204
183
  # @param namespace [String] The namespace to match
205
184
  # @param context [String] The context to search
206
- # @return [Array<Solargraph::Suggestion>]
185
+ # @return [Array<Solargraph::Pin::Base>]
207
186
  def get_constants namespace, context = ''
187
+ namespace ||= ''
208
188
  skip = []
209
189
  result = []
210
190
  if context.empty?
@@ -222,7 +202,19 @@ module Solargraph
222
202
  parts.pop
223
203
  end
224
204
  end
225
- result.map{|pin| Suggestion.pull(pin)}
205
+ # result.map{|pin| Suggestion.pull(pin)}
206
+ result
207
+ end
208
+
209
+ def find_fully_qualified_type namespace_type, context_type = ''
210
+ namespace, scope = extract_namespace_and_scope(namespace_type)
211
+ context = extract_namespace(context_type)
212
+ fqns = find_fully_qualified_namespace(namespace, context)
213
+ subtypes = get_subtypes(namespace_type)
214
+ fqns = "#{fqns}<#{subtypes.join(', ')}>" unless subtypes.empty?
215
+ return fqns if scope == :instance
216
+ type = get_namespace_type(fqns)
217
+ "#{type == :class ? 'Class<' : 'Module<'}#{fqns}>"
226
218
  end
227
219
 
228
220
  # Get a fully qualified namespace name. This method will start the search
@@ -232,7 +224,8 @@ module Solargraph
232
224
  # @param root [String] The context to search
233
225
  # @return [String]
234
226
  def find_fully_qualified_namespace name, root = '', skip = []
235
- refresh
227
+ # refresh
228
+ return nil if name.nil?
236
229
  return nil if skip.include?(root)
237
230
  skip.push root
238
231
  if name == ''
@@ -244,11 +237,14 @@ module Solargraph
244
237
  else
245
238
  if (root == '')
246
239
  return name unless namespace_map[name].nil?
247
- get_include_strings_from(*file_nodes).each { |i|
248
- reroot = "#{root == '' ? '' : root + '::'}#{i}"
249
- recname = find_fully_qualified_namespace name.to_s, reroot, skip
250
- return recname unless recname.nil?
251
- }
240
+ im = @namespace_includes['']
241
+ unless im.nil?
242
+ im.each do |i|
243
+ reroot = "#{root == '' ? '' : root + '::'}#{i}"
244
+ recname = find_fully_qualified_namespace name.to_s, reroot, skip
245
+ return recname unless recname.nil?
246
+ end
247
+ end
252
248
  else
253
249
  roots = root.to_s.split('::')
254
250
  while roots.length > 0
@@ -257,10 +253,13 @@ module Solargraph
257
253
  roots.pop
258
254
  end
259
255
  return name unless namespace_map[name].nil?
260
- get_include_strings_from(*file_nodes).each { |i|
261
- recname = find_fully_qualified_namespace name, i, skip
262
- return recname unless recname.nil?
263
- }
256
+ im = @namespace_includes['']
257
+ unless im.nil?
258
+ im.each do |i|
259
+ recname = find_fully_qualified_namespace name, i, skip
260
+ return recname unless recname.nil?
261
+ end
262
+ end
264
263
  end
265
264
  end
266
265
  result = yard_map.find_fully_qualified_namespace(name, root)
@@ -277,134 +276,71 @@ module Solargraph
277
276
  # @param scope [Symbol] :instance or :class
278
277
  # @return [Array<Solargraph::Pin::InstanceVariable>]
279
278
  def get_instance_variable_pins(namespace, scope = :instance)
280
- refresh
281
- (@ivar_pins[namespace] || []).select{ |pin| pin.scope == scope }
279
+ suggest_unique_variables (@ivar_pins[namespace] || []).select{ |pin| pin.scope == scope }
282
280
  end
283
281
 
284
- # Get an array of instance variable suggestions defined in specified
285
- # namespace and scope.
282
+ # Get an array of class variable pins for a namespace.
286
283
  #
287
284
  # @param namespace [String] A fully qualified namespace
288
- # @param scope [Symbol] :instance or :class
289
- # @return [Array<Solargraph::Suggestion>]
290
- def get_instance_variables(namespace, scope = :instance)
291
- refresh
292
- result = []
293
- ip = @ivar_pins[namespace]
294
- unless ip.nil?
295
- result.concat suggest_unique_variables(ip.select{ |pin| pin.scope == scope })
296
- end
297
- result
298
- end
299
-
300
285
  # @return [Array<Solargraph::Pin::ClassVariable>]
301
286
  def get_class_variable_pins(namespace)
302
- refresh
303
- @cvar_pins[namespace] || []
304
- end
305
-
306
- # @return [Array<Solargraph::Suggestion>]
307
- def get_class_variables(namespace)
308
- refresh
309
- result = []
310
- cp = @cvar_pins[namespace]
311
- unless cp.nil?
312
- result.concat suggest_unique_variables(cp)
313
- end
314
- result
287
+ suggest_unique_variables(@cvar_pins[namespace] || [])
315
288
  end
316
289
 
317
- # @return [Array<Solargraph::Pin::Symbol>]
290
+ # @return [Array<Solargraph::Pin::Base>]
318
291
  def get_symbols
319
- refresh
320
- @symbol_pins.uniq(&:label)
292
+ # refresh
293
+ @symbol_pins
321
294
  end
322
295
 
323
296
  # @return [String]
324
297
  def get_filename_for(node)
325
- @sources.each do |filename, source|
298
+ @sources.each do |source|
326
299
  return source.filename if source.include?(node)
327
300
  end
328
301
  nil
329
302
  end
330
303
 
331
- # @return [Solargraph::ApiMap::Source]
304
+ # @return [Solargraph::Source]
332
305
  def get_source_for(node)
333
- @sources.each do |filename, source|
334
- return source if source.include?(node)
335
- end
336
- nil
337
- end
338
-
339
- # @return [String]
340
- def infer_instance_variable(var, namespace, scope)
341
- refresh
342
- pins = @ivar_pins[namespace]
343
- return nil if pins.nil?
344
- pin = pins.select{|p| p.name == var and p.scope == scope}.first
345
- return nil if pin.nil?
346
- type = nil
347
- type = find_fully_qualified_namespace(pin.return_type, pin.namespace) unless pin.return_type.nil?
348
- if type.nil?
349
- zparts = resolve_node_signature(pin.assignment_node).split('.')
350
- ztype = infer_signature_type(zparts[0..-2].join('.'), namespace, scope: :instance, call_node: pin.assignment_node)
351
- type = get_return_type_from_macro(ztype, zparts[-1], pin.assignment_node, :instance, [:public, :private, :protected])
306
+ matches = []
307
+ @sources.each do |source|
308
+ matches.push source if source.include?(node)
352
309
  end
353
- type
354
- end
355
-
356
- # @return [String]
357
- def infer_class_variable(var, namespace)
358
- refresh
359
- fqns = find_fully_qualified_namespace(namespace)
360
- pins = @cvar_pins[fqns]
361
- return nil if pins.nil?
362
- pin = pins.select{|p| p.name == var}.first
363
- return nil if pin.nil? or pin.return_type.nil?
364
- find_fully_qualified_namespace(pin.return_type, pin.namespace)
365
- end
366
-
367
- # @return [Array<Solargraph::Suggestion>]
368
- def get_global_variables
369
- globals = []
370
- @sources.values.each do |s|
371
- globals.concat s.global_variable_pins
372
- end
373
- suggest_unique_variables globals
310
+ matches.first
374
311
  end
375
312
 
376
313
  # @return [Array<Solargraph::Pin::GlobalVariable>]
377
314
  def get_global_variable_pins
378
315
  globals = []
379
- @sources.values.each do |s|
316
+ @sources.each do |s|
380
317
  globals.concat s.global_variable_pins
381
318
  end
382
319
  globals
383
320
  end
384
321
 
385
- # @return [String]
386
- def infer_assignment_node_type node, namespace
387
- cached = cache.get_assignment_node_type(node, namespace)
388
- return cached unless cached.nil?
389
- name_i = (node.type == :casgn ? 1 : 0)
390
- sig_i = (node.type == :casgn ? 2 : 1)
391
- type = infer_literal_node_type(node.children[sig_i])
392
- if type.nil?
393
- sig = resolve_node_signature(node.children[sig_i])
394
- # Avoid infinite loops from variable assignments that reference themselves
395
- return nil if node.children[name_i].to_s == sig.split('.').first
396
- type = infer_signature_type(sig, namespace, call_node: node.children[sig_i])
397
- end
398
- cache.set_assignment_node_type(node, namespace, type)
399
- type
322
+ def get_type_methods type, context = ''
323
+ return [] if type.nil?
324
+ namespace, scope = extract_namespace_and_scope(type)
325
+ base = extract_namespace(context)
326
+ fqns = find_fully_qualified_namespace(namespace, base)
327
+ return [] if fqns.nil?
328
+ visibility = [:public]
329
+ visibility.push :private, :protected if fqns == base
330
+ get_methods fqns, scope: scope, visibility: visibility
400
331
  end
401
332
 
402
- def get_call_arguments node
403
- return [] unless node.type == :send
333
+ def get_methods fqns, scope: :instance, visibility: [:public], deep: true
404
334
  result = []
405
- node.children[2..-1].each do |c|
406
- result.push unpack_name(c)
335
+ if fqns == ''
336
+ skip = []
337
+ result.concat inner_get_methods(fqns, :class, visibility, deep, skip)
338
+ result.concat inner_get_methods(fqns, :instance, visibility, deep, skip)
339
+ result.concat inner_get_methods('Kernel', :instance, visibility, deep, skip)
340
+ else
341
+ result.concat inner_get_methods(fqns, scope, visibility, deep, [])
407
342
  end
343
+ # result.each{|pin| pin.resolve(self)}
408
344
  result
409
345
  end
410
346
 
@@ -419,222 +355,199 @@ module Solargraph
419
355
  # @param scope [Symbol] :class or :instance
420
356
  # @return [String]
421
357
  def infer_signature_type signature, namespace, scope: :class, call_node: nil
422
- namespace ||= ''
423
- if cache.has_signature_type?(signature, namespace, scope)
424
- return cache.get_signature_type(signature, namespace, scope)
425
- end
426
- return nil if signature.nil?
427
- return namespace if signature.empty? and scope == :instance
428
- return nil if signature.empty? # @todo This might need to return Class<namespace>
429
- if !signature.include?('.')
430
- fqns = find_fully_qualified_namespace(signature, namespace)
431
- unless fqns.nil? or fqns.empty?
432
- type = (get_namespace_type(fqns) == :class ? 'Class' : 'Module')
433
- return "#{type}<#{fqns}>"
358
+ return nil if signature.start_with?('.')
359
+ # inner_infer_signature_type signature, namespace, scope, call_node, true
360
+ base, rest = signature.split('.', 2)
361
+ if base == 'self'
362
+ if rest.nil?
363
+ combine_type(namespace, scope)
364
+ else
365
+ inner_infer_signature_type(rest, namespace, scope, call_node, false)
434
366
  end
435
- end
436
- result = nil
437
- if namespace.end_with?('#class')
438
- result = infer_signature_type signature, namespace[0..-7], scope: (scope == :class ? :instance : :class), call_node: call_node
439
367
  else
440
- parts = signature.split('.', 2)
441
- if parts[0].start_with?('@@')
442
- type = infer_class_variable(parts[0], namespace)
443
- if type.nil? or parts.empty?
444
- result = inner_infer_signature_type(parts[1], type, scope: :instance, call_node: call_node)
445
- else
446
- result = type
447
- end
448
- elsif parts[0].start_with?('@')
449
- type = infer_instance_variable(parts[0], namespace, scope)
450
- if type.nil? or parts.empty?
451
- result = inner_infer_signature_type(parts[1], type, scope: :instance, call_node: call_node)
368
+ pins = infer_signature_pins(base, namespace, scope, call_node)
369
+ return nil if pins.empty?
370
+ pin = pins.first
371
+ if rest.nil?
372
+ pin.resolve self
373
+ pin.return_type
374
+ elsif pin.signature.nil? or pin.signature.empty?
375
+ if pin.path.nil?
376
+ pin.resolve self
377
+ fqtype = find_fully_qualified_type(pin.return_type, namespace)
378
+ return nil if fqtype.nil?
379
+ subns, subsc = extract_namespace_and_scope(fqtype)
380
+ inner_infer_signature_type(rest, subns, subsc, call_node, true)
452
381
  else
453
- result = type
382
+ inner_infer_signature_type(rest, pin.path, scope, call_node, true)
454
383
  end
455
384
  else
456
- type = find_fully_qualified_namespace(parts[0], namespace)
457
- if type.nil?
458
- # It's a method call
459
- type = inner_infer_signature_type(parts[0], namespace, scope: scope, call_node: call_node)
460
- if parts.length < 2
461
- if type.nil? and !parts.length.nil?
462
- path = "#{clean_namespace_string(namespace)}#{scope == :class ? '.' : '#'}#{parts[0]}"
463
- subtypes = get_subtypes(namespace)
464
- type = subtypes[0] if METHODS_RETURNING_SUBTYPES.include?(path)
465
- end
466
- result = type
467
- else
468
- result = inner_infer_signature_type(parts[1], type, scope: :instance, call_node: call_node)
469
- end
470
- else
471
- result = inner_infer_signature_type(parts[1], type, scope: :class, call_node: call_node)
472
- end
473
- result = type if result == 'self'
385
+ subtype = inner_infer_signature_type(pin.signature, namespace, scope, call_node, true)
386
+ subns, subsc = extract_namespace_and_scope(subtype)
387
+ inner_infer_signature_type(rest, subns, subsc, call_node, false)
474
388
  end
475
389
  end
476
- cache.set_signature_type signature, namespace, scope, result
477
- result
478
390
  end
479
391
 
480
- # Get the namespace's type (Class or Module).
481
- #
482
- # @param [String] A fully qualified namespace
483
- # @return [Symbol] :class, :module, or nil
484
- def get_namespace_type fqns
485
- return nil if fqns.nil?
486
- type = nil
487
- nodes = get_namespace_nodes(fqns)
488
- unless nodes.nil? or nodes.empty? or !nodes[0].kind_of?(AST::Node)
489
- type = nodes[0].type if [:class, :module].include?(nodes[0].type)
490
- end
491
- if type.nil?
492
- type = yard_map.get_namespace_type(fqns)
493
- end
494
- type
495
- end
496
-
497
- # Get an array of singleton methods that are available in the specified
498
- # namespace.
499
- #
500
- # @return [Array<Solargraph::Suggestion>]
501
- def get_methods(namespace, root = '', visibility: [:public])
502
- refresh
503
- namespace = clean_namespace_string(namespace)
504
- fqns = find_fully_qualified_namespace(namespace, root)
505
- meths = []
506
- skip = []
507
- meths.concat inner_get_methods(namespace, root, skip, visibility)
508
- yard_meths = yard_map.get_methods(fqns, '', visibility: visibility)
509
- if yard_meths.any?
510
- meths.concat yard_meths
511
- else
512
- type = get_namespace_type(fqns)
513
- if type == :class
514
- meths.concat yard_map.get_instance_methods('Class')
392
+ # @return [ApiMap::Completion]
393
+ def complete fragment
394
+ return Completion.new([], fragment.whole_word_range) if fragment.string? or fragment.comment?
395
+ result = []
396
+ if fragment.base.empty?
397
+ if fragment.signature.start_with?('@@')
398
+ result.concat get_class_variable_pins(fragment.namespace)
399
+ elsif fragment.signature.start_with?('@')
400
+ result.concat get_instance_variable_pins(fragment.namespace, fragment.scope)
401
+ elsif fragment.signature.start_with?('$')
402
+ result.concat get_global_variable_pins
403
+ elsif fragment.signature.start_with?(':') and !fragment.signature.start_with?('::')
404
+ result.concat get_symbols
515
405
  else
516
- meths.concat yard_map.get_instance_methods('Module')
517
- end
518
- end
519
- news = meths.select{|s| s.label == 'new'}
520
- unless news.empty?
521
- if @method_pins[fqns]
522
- inits = @method_pins[fqns].select{|p| p.name == 'initialize'}
523
- meths -= news unless inits.empty?
524
- inits.each do |pin|
525
- meths.push Suggestion.new('new', kind: pin.kind, docstring: pin.docstring, detail: pin.namespace, arguments: pin.parameters, path: pin.path)
406
+ unless fragment.signature.include?('::')
407
+ result.concat fragment.local_variable_pins
408
+ result.concat get_type_methods(fragment.namespace, fragment.namespace)
409
+ result.concat ApiMap.keywords
526
410
  end
411
+ result.concat get_constants(fragment.base, fragment.namespace)
527
412
  end
528
- end
529
- if namespace == '' and root == ''
530
- config.domains.each do |d|
531
- meths.concat get_instance_methods(d)
413
+ else
414
+ if fragment.signature.include?('::') and !fragment.signature.include?('.')
415
+ result.concat get_constants(fragment.base, fragment.namespace)
416
+ else
417
+ type = infer_signature_type(fragment.base, fragment.namespace, scope: fragment.scope, call_node: fragment.node)
418
+ result.concat get_type_methods(type)
532
419
  end
533
420
  end
534
- strings = meths.map(&:to_s)
535
- live_map.get_methods(fqns, '', 'class', visibility.include?(:private)).each do |ls|
536
- next if strings.include?(ls.to_s)
537
- meths.push ls
538
- end
539
- meths
421
+ filtered = result.uniq(&:identifier).select{|s| s.kind != Solargraph::LanguageServer::CompletionItemKinds::METHOD or s.name.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}.sort_by.with_index{ |x, idx| [x.name, idx] }
422
+ Completion.new(filtered, fragment.whole_word_range)
540
423
  end
541
424
 
542
- # Get an array of instance methods that are available in the specified
543
- # namespace.
544
- #
545
- # @return [Array<Solargraph::Suggestion>]
546
- def get_instance_methods(namespace, root = '', visibility: [:public])
547
- refresh
548
- namespace = clean_namespace_string(namespace)
549
- if namespace.end_with?('#class') or namespace.end_with?('#module')
550
- return get_methods(namespace.split('#').first, root, visibility: visibility)
551
- end
552
- meths = []
553
- meths += inner_get_instance_methods(namespace, root, [], visibility) #unless has_yardoc?
554
- fqns = find_fully_qualified_namespace(namespace, root)
555
- yard_meths = yard_map.get_instance_methods(fqns, '', visibility: visibility)
556
- if yard_meths.any?
557
- meths.concat yard_meths
558
- else
559
- type = get_namespace_type(fqns)
560
- if type == :class
561
- meths += yard_map.get_instance_methods('Object')
562
- elsif type == :module
563
- meths += yard_map.get_instance_methods('Module')
564
- end
565
- end
566
- if namespace == '' and root == ''
567
- config.domains.each do |d|
568
- meths.concat get_instance_methods(d)
425
+ def define fragment
426
+ return [] if fragment.string? or fragment.comment?
427
+ pins = infer_signature_pins fragment.whole_signature, fragment.namespace, fragment.scope, fragment.node
428
+ return [] if pins.empty?
429
+ if pins.first.variable?
430
+ result = []
431
+ pins.select{|pin| pin.variable?}.each do |pin|
432
+ pin.resolve self
433
+ result.concat infer_signature_pins(pin.return_type, fragment.namespace, fragment.scope, fragment.node)
569
434
  end
435
+ result
436
+ else
437
+ pins.reject{|pin| pin.path.nil?}
570
438
  end
571
- strings = meths.map(&:to_s)
572
- live_map.get_methods(fqns, '', 'class', visibility.include?(:private)).each do |ls|
573
- next if strings.include?(ls.to_s)
574
- meths.push ls
575
- end
576
- meths
577
439
  end
578
440
 
579
- # Update the ApiMap with the most recent version of the specified file.
441
+ # Identify the variable, constant, or method call at the fragment's location.
580
442
  #
581
- # @param filename [String]
582
- def update filename
583
- filename.gsub!(/\\/, '/')
584
- if filename.end_with?('.rb')
585
- if @virtual_filename == filename
586
- @virtual_filename = nil
587
- @virtual_source = nil
443
+ # @param fragment [Solargraph::Source::Fragment]
444
+ # @return [Array<Solargraph::Pin::Base>]
445
+ def identify fragment
446
+ pins = infer_signature_pins(fragment.whole_signature, fragment.namespace, fragment.scope, fragment.node)
447
+ pins.each { |pin| pin.resolve self }
448
+ pins
449
+ end
450
+
451
+ def infer_signature_pins signature, namespace, scope, call_node
452
+ return [] if signature.nil? or signature.empty?
453
+ base, rest = signature.split('.', 2)
454
+ if base.start_with?('@@')
455
+ pin = get_class_variable_pins(namespace).select{|pin| pin.name == base}.first
456
+ return [] if pin.nil?
457
+ return [pin] if rest.nil?
458
+ fqns = find_fully_qualified_namespace(pin.return_type, namespace)
459
+ return [] if fqns.nil?
460
+ return inner_infer_signature_pins rest, namespace, scope, call_node, false
461
+ elsif base.start_with?('@')
462
+ pin = get_instance_variable_pins(namespace, scope).select{|pin| pin.name == base}.first
463
+ return [] if pin.nil?
464
+ pin.resolve self
465
+ return [pin] if rest.nil?
466
+ fqtype = find_fully_qualified_type(pin.return_type, namespace)
467
+ return [] if fqtype.nil?
468
+ subns, subsc = extract_namespace_and_scope(fqtype)
469
+ return inner_infer_signature_pins rest, subns, subsc, call_node, false
470
+ elsif base.start_with?('$')
471
+ # @todo globals
472
+ else
473
+ type = find_fully_qualified_namespace(base, namespace)
474
+ unless type.nil?
475
+ if rest.nil?
476
+ return get_path_suggestions(type)
477
+ else
478
+ return inner_infer_signature_pins rest, type, :class, call_node, false
479
+ end
588
480
  end
589
- if @workspace_files.include?(filename)
590
- eliminate filename
591
- @@source_cache[filename] = Source.load(filename)
592
- @sources.delete filename
593
- @sources[filename] = @@source_cache[filename]
594
- rebuild_local_yardoc #if @workspace_files.include?(filename)
595
- @stale = true
596
- else
597
- @workspace_files = config(true).calculated
598
- update filename if @workspace_files.include?(filename)
481
+ source = get_source_for(call_node)
482
+ unless source.nil?
483
+ lvpins = source.local_variable_pins.select{|pin| pin.name == base and pin.visible_from?(call_node)}
484
+ unless lvpins.empty?
485
+ if rest.nil?
486
+ return lvpins
487
+ else
488
+ lvp = lvpins.first
489
+ lvp.resolve self
490
+ type = lvp.return_type
491
+ unless type.nil?
492
+ fqtype = find_fully_qualified_type(type, namespace)
493
+ return [] if fqtype.nil?
494
+ subns, subsc = extract_namespace_and_scope(fqtype)
495
+ return inner_infer_signature_pins(rest, subns, subsc, call_node, false)
496
+ end
497
+ end
498
+ end
599
499
  end
600
- elsif File.basename(filename) == '.solargraph.yml'
601
- # @todo Finish refreshing the map
602
- @workspace_files = config(true).calculated
500
+ return inner_infer_signature_pins signature, namespace, scope, call_node, true
603
501
  end
604
502
  end
605
503
 
606
- # All sources generated from workspace files.
504
+ # Get the namespace's type (Class or Module).
607
505
  #
608
- # @return [Array<Solargraph::ApiMap::Source>]
609
- def sources
610
- @sources.values
506
+ # @param [String] A fully qualified namespace
507
+ # @return [Symbol] :class, :module, or nil
508
+ def get_namespace_type fqns
509
+ pin = @namespace_path_pins[fqns]
510
+ return yard_map.get_namespace_type(fqns) if pin.nil?
511
+ pin.first.type
512
+ end
513
+
514
+ def combine_type namespace, scope
515
+ if scope == :instance
516
+ namespace
517
+ else
518
+ type = get_namespace_type(namespace)
519
+ "#{type == :class ? 'Class' : 'Module'}<#{namespace}>"
520
+ end
611
521
  end
612
522
 
613
523
  # Get an array of all suggestions that match the specified path.
614
524
  #
615
525
  # @param path [String] The path to find
616
- # @return [Array<Solargraph::Suggestion>]
526
+ # @return [Array<Solargraph::Pin::Base>]
617
527
  def get_path_suggestions path
618
- refresh
528
+ return [] if path.nil?
529
+ # refresh
619
530
  result = []
620
531
  if path.include?('#')
621
532
  # It's an instance method
622
533
  parts = path.split('#')
623
- result = get_instance_methods(parts[0], '', visibility: [:public, :private, :protected]).select{|s| s.label == parts[1]}
534
+ result = get_methods(parts[0], visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
624
535
  elsif path.include?('.')
625
536
  # It's a class method
626
537
  parts = path.split('.')
627
- result = get_methods(parts[0], '', visibility: [:public, :private, :protected]).select{|s| s.label == parts[1]}
538
+ result = get_methods(parts[0], scope: :class, visibility: [:public, :private, :protected]).select{|s| s.name == parts[1]}
628
539
  else
629
540
  # It's a class or module
630
541
  parts = path.split('::')
631
542
  np = @namespace_pins[parts[0..-2].join('::')]
632
543
  unless np.nil?
633
- result.concat np.select{|p| p.name == parts.last}.map{|p| pin_to_suggestion(p)}
544
+ result.concat np.select{|p| p.name == parts.last}
634
545
  end
635
546
  result.concat yard_map.objects(path)
636
547
  end
637
- result
548
+ # @todo Resolve the pins?
549
+ result.map{|pin| pin.resolve(self); pin}
550
+ # result
638
551
  end
639
552
 
640
553
  # Get a list of documented paths that match the query.
@@ -645,8 +558,8 @@ module Solargraph
645
558
  # @param query [String] The text to match
646
559
  # @return [Array<String>]
647
560
  def search query
648
- refresh
649
- rake_yard(@sources.values) if @yard_stale
561
+ # refresh
562
+ rake_yard(@sources) if @yard_stale
650
563
  @yard_stale = false
651
564
  found = []
652
565
  code_object_paths.each do |k|
@@ -665,8 +578,8 @@ module Solargraph
665
578
  # @param path [String] The path to find
666
579
  # @return [Array<YARD::CodeObject::Base>]
667
580
  def document path
668
- refresh
669
- rake_yard(@sources.values) if @yard_stale
581
+ # refresh
582
+ rake_yard(@sources) if @yard_stale
670
583
  @yard_stale = false
671
584
  docs = []
672
585
  docs.push code_object_at(path) unless code_object_at(path).nil?
@@ -674,6 +587,31 @@ module Solargraph
674
587
  docs
675
588
  end
676
589
 
590
+ def query_symbols query
591
+ result = []
592
+ @sources.each do |s|
593
+ result.concat s.query_symbols(query)
594
+ end
595
+ result
596
+ end
597
+
598
+ def superclass_of fqns
599
+ found = @superclasses[fqns]
600
+ return nil if found.nil?
601
+ find_fully_qualified_namespace(found, fqns)
602
+ end
603
+
604
+ def locate_pin location
605
+ @sources.each do |source|
606
+ pin = source.locate_pin(location)
607
+ unless pin.nil?
608
+ pin.resolve self
609
+ return pin
610
+ end
611
+ end
612
+ nil
613
+ end
614
+
677
615
  private
678
616
 
679
617
  # @return [Hash]
@@ -681,15 +619,9 @@ module Solargraph
681
619
  @namespace_map ||= {}
682
620
  end
683
621
 
684
- def clear
685
- @stale = false
686
- namespace_map.clear
687
- path_macros.clear
688
- @required = config.required.clone
689
- end
690
-
691
622
  def process_maps
692
- process_workspace_files
623
+ @sources = workspace.sources
624
+ @sources.push @virtual_source unless @virtual_source.nil?
693
625
  cache.clear
694
626
  @ivar_pins = {}
695
627
  @cvar_pins = {}
@@ -701,25 +633,22 @@ module Solargraph
701
633
  @namespace_extends = {}
702
634
  @superclasses = {}
703
635
  @namespace_pins = {}
636
+ @namespace_path_pins = {}
704
637
  namespace_map.clear
705
- @required = config.required.clone
706
- @pin_suggestions = {}
707
- unless @virtual_source.nil?
708
- @sources[@virtual_filename] = @virtual_source
709
- end
710
- @sources.values.each do |s|
638
+ @required = workspace.config.required.clone
639
+ @sources.each do |s|
711
640
  s.namespace_nodes.each_pair do |k, v|
712
641
  namespace_map[k] ||= []
713
642
  namespace_map[k].concat v
714
643
  end
715
644
  end
716
- @sources.values.each { |s|
645
+ @sources.each do |s|
717
646
  map_source s
718
- }
647
+ end
719
648
  @required.uniq!
720
649
  live_map.refresh
721
- @stale = false
722
650
  @yard_stale = true
651
+ @stime = Time.now
723
652
  end
724
653
 
725
654
  def rebuild_local_yardoc
@@ -728,46 +657,30 @@ module Solargraph
728
657
  Dir.chdir(workspace) { Process.spawn('yardoc') }
729
658
  end
730
659
 
731
- def process_workspace_files
732
- @sources.clear
733
- workspace_files.each do |f|
734
- if File.file?(f)
735
- begin
736
- @@source_cache[f] ||= Source.load(f)
737
- @sources[f] = @@source_cache[f]
738
- rescue Exception => e
739
- STDERR.puts "Failed to load #{f}: #{e.message}"
740
- end
741
- end
742
- end
743
- end
744
-
745
660
  def process_virtual
746
661
  unless @virtual_source.nil?
747
662
  cache.clear
748
663
  namespace_map.clear
749
- @sources[@virtual_filename] = @virtual_source
750
- @sources.values.each do |s|
664
+ @sources.each do |s|
751
665
  s.namespace_nodes.each_pair do |k, v|
752
666
  namespace_map[k] ||= []
753
667
  namespace_map[k].concat v
754
668
  end
755
669
  end
756
- eliminate @virtual_filename
757
670
  map_source @virtual_source
758
671
  end
759
672
  end
760
673
 
761
- def eliminate filename
674
+ def eliminate source
762
675
  [@ivar_pins.values, @cvar_pins.values, @const_pins.values, @method_pins.values, @attr_pins.values, @namespace_pins.values].each do |pinsets|
763
676
  pinsets.each do |pins|
764
- pins.delete_if{|pin| pin.filename == filename}
677
+ pins.delete_if{|pin| pin.filename == source.filename}
765
678
  end
766
679
  end
767
- #@symbol_pins.delete_if{|pin| pin.filename == filename}
680
+ @symbol_pins.delete_if{|pin| pin.filename == source.filename}
768
681
  end
769
682
 
770
- # @param [Solargraph::ApiMap::Source]
683
+ # @param [Solargraph::Source]
771
684
  def map_source source
772
685
  source.method_pins.each do |pin|
773
686
  @method_pins[pin.namespace] ||= []
@@ -790,7 +703,7 @@ module Solargraph
790
703
  @const_pins[pin.namespace].push pin
791
704
  end
792
705
  source.symbol_pins.each do |pin|
793
- @symbol_pins.push Suggestion.new(pin.name, kind: Suggestion::CONSTANT, return_type: 'Symbol')
706
+ @symbol_pins.push pin
794
707
  end
795
708
  source.namespace_includes.each_pair do |ns, i|
796
709
  @namespace_includes[ns || ''] ||= []
@@ -804,6 +717,8 @@ module Solargraph
804
717
  @superclasses[cls] = sup
805
718
  end
806
719
  source.namespace_pins.each do |pin|
720
+ @namespace_path_pins[pin.path] ||= []
721
+ @namespace_path_pins[pin.path].push pin
807
722
  @namespace_pins[pin.namespace] ||= []
808
723
  @namespace_pins[pin.namespace].push pin
809
724
  end
@@ -818,144 +733,60 @@ module Solargraph
818
733
  @cache ||= Cache.new
819
734
  end
820
735
 
821
- def inner_get_methods(namespace, root = '', skip = [], visibility = [:public])
822
- meths = []
823
- return meths if skip.include?(namespace)
824
- skip.push namespace
825
- fqns = find_fully_qualified_namespace(namespace, root)
826
- return meths if fqns.nil?
827
- mn = @method_pins[fqns]
828
- unless mn.nil?
829
- mn.select{ |pin| pin.scope == :class }.each do |pin|
830
- meths.push pin_to_suggestion(pin) if visibility.include?(pin.visibility)
831
- end
832
- end
833
- if visibility.include?(:public) or visibility.include?(:protected)
834
- sc = @superclasses[fqns]
835
- unless sc.nil?
836
- sc_visi = [:public]
837
- sc_visi.push :protected if root == fqns
838
- nfqns = find_fully_qualified_namespace(sc, fqns)
839
- meths.concat inner_get_methods('', nfqns, skip, sc_visi)
840
- meths.concat yard_map.get_methods(nfqns, '', visibility: sc_visi)
841
- end
842
- end
843
- em = @namespace_extends[fqns]
844
- unless em.nil?
845
- em.each do |e|
846
- meths.concat get_instance_methods(e, fqns, visibility: visibility)
847
- end
848
- end
849
- meths.concat get_instance_methods('', '', visibility: [:public])
850
- meths.uniq
851
- end
852
-
853
- def inner_get_instance_methods(namespace, root, skip, visibility = [:public])
854
- fqns = find_fully_qualified_namespace(namespace, root)
855
- meths = []
856
- return meths if skip.include?(fqns)
857
- skip.push fqns
858
- an = @attr_pins[fqns]
859
- unless an.nil?
860
- an.each do |pin|
861
- meths.push pin_to_suggestion(pin)
862
- end
863
- end
864
- mn = @method_pins[fqns]
865
- unless mn.nil?
866
- mn.select{|pin| visibility.include?(pin.visibility) and pin.scope == :instance }.each do |pin|
867
- meths.push pin_to_suggestion(pin)
736
+ def inner_get_methods fqns, scope, visibility, deep, skip
737
+ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}"
738
+ return [] if skip.include?(reqstr)
739
+ skip.push reqstr
740
+ result = []
741
+ if scope == :instance
742
+ aps = @attr_pins[fqns]
743
+ result.concat aps unless aps.nil?
744
+ end
745
+ mps = @method_pins[fqns]
746
+ result.concat mps.select{|pin| (pin.scope == scope or fqns == '') and visibility.include?(pin.visibility)} unless mps.nil?
747
+ if fqns != '' and scope == :class and !result.map(&:path).include?("#{fqns}.new")
748
+ # Create a [Class].new method pin from [Class]#initialize
749
+ init = inner_get_methods(fqns, :instance, [:private], deep, skip - [fqns]).select{|pin| pin.name == 'initialize'}.first
750
+ unless init.nil?
751
+ result.unshift Solargraph::Pin::Directed::Method.new(init.source, init.node, init.namespace, :class, :public, init.docstring, 'new', init.namespace)
868
752
  end
869
753
  end
870
- if visibility.include?(:public) or visibility.include?(:protected)
754
+ if deep
871
755
  sc = @superclasses[fqns]
872
756
  unless sc.nil?
873
757
  sc_visi = [:public]
874
- sc_visi.push :protected if sc == fqns
875
- nfqns = find_fully_qualified_namespace(sc, fqns)
876
- meths.concat inner_get_instance_methods('', nfqns, skip, sc_visi)
877
- meths.concat yard_map.get_instance_methods(nfqns, '', visibility: sc_visi)
758
+ sc_visi.push :protected if visibility.include?(:protected)
759
+ sc_fqns = find_fully_qualified_namespace(sc, fqns)
760
+ result.concat inner_get_methods(sc_fqns, scope, sc_visi, true, skip)
878
761
  end
879
- end
880
- im = @namespace_includes[fqns]
881
- unless im.nil?
882
- im.each do |i|
883
- nfqns = find_fully_qualified_namespace(i, fqns)
884
- meths.concat inner_get_instance_methods('', nfqns, skip, visibility)
885
- end
886
- end
887
- meths.uniq
888
- end
889
-
890
- # Get a fully qualified namespace for the given signature.
891
- # The signature should be in the form of a method chain, e.g.,
892
- # method1.method2
893
- #
894
- # @return [String] The fully qualified namespace for the signature's type
895
- # or nil if a type could not be determined
896
- def inner_infer_signature_type signature, namespace, scope: :instance, top: true, call_node: nil
897
- return nil if signature.nil?
898
- signature.gsub!(/\.$/, '')
899
- if signature.empty?
900
- if scope == :class
901
- type = get_namespace_type(namespace)
902
- if type == :class
903
- return "Class<#{namespace}>"
904
- else
905
- return "Module<#{namespace}>"
762
+ if scope == :instance
763
+ im = @namespace_includes[fqns]
764
+ unless im.nil?
765
+ im.each do |i|
766
+ ifqns = find_fully_qualified_namespace(i, fqns)
767
+ result.concat inner_get_methods(ifqns, scope, visibility, deep, skip)
768
+ end
906
769
  end
907
- end
908
- end
909
- parts = signature.split('.')
910
- type = namespace || ''
911
- while (parts.length > 0)
912
- part = parts.shift
913
- if top == true and part == 'self'
914
- top = false
915
- next
916
- end
917
- cls_match = type.match(/^Class<([A-Za-z0-9_:]*?)>$/)
918
- if cls_match
919
- type = cls_match[1]
920
- scope = :class
921
- end
922
- if scope == :class and part == 'new'
923
- scope = :instance
770
+ result.concat yard_map.get_instance_methods(fqns, visibility: visibility)
771
+ result.concat inner_get_methods('Object', :instance, [:public], deep, skip) unless fqns == 'Object'
924
772
  else
925
- curtype = type
926
- type = nil
927
- visibility = [:public]
928
- visibility.concat [:private, :protected] if top
929
- if scope == :instance || namespace == ''
930
- tmp = get_instance_methods(namespace, visibility: visibility)
931
- else
932
- tmp = get_methods(namespace, visibility: visibility)
933
- end
934
- tmp.concat get_instance_methods('Kernel', visibility: [:public]) if top
935
- matches = tmp.select{|s| s.label == part}
936
- return nil if matches.empty?
937
- matches.each do |m|
938
- type = get_return_type_from_macro(namespace, signature, call_node, scope, visibility)
939
- if type.nil?
940
- if METHODS_RETURNING_SELF.include?(m.path)
941
- type = curtype
942
- elsif METHODS_RETURNING_SUBTYPES.include?(m.path)
943
- subtypes = get_subtypes(namespace)
944
- type = subtypes[0]
945
- else
946
- type = m.return_type
947
- end
773
+ em = @namespace_extends[fqns]
774
+ unless em.nil?
775
+ em.each do |e|
776
+ efqns = find_fully_qualified_namespace(e, fqns)
777
+ result.concat inner_get_methods(efqns, :instance, visibility, deep, skip)
948
778
  end
949
- break unless type.nil?
950
779
  end
951
- scope = :instance
780
+ result.concat yard_map.get_methods(fqns, '', visibility: visibility)
781
+ type = get_namespace_type(fqns)
782
+ if type == :class
783
+ result.concat inner_get_methods('Class', :instance, [:public], deep, skip)
784
+ else
785
+ result.concat inner_get_methods('Module', :instance, [:public], deep, skip)
786
+ end
952
787
  end
953
- top = false
954
- end
955
- if scope == :class and !type.nil?
956
- type = "Class<#{type}>"
957
788
  end
958
- type
789
+ result
959
790
  end
960
791
 
961
792
  def inner_get_constants fqns, visibility, skip
@@ -976,41 +807,33 @@ module Solargraph
976
807
  result
977
808
  end
978
809
 
979
- # @return [AST::Node]
980
- def file_nodes
981
- @sources.values.map(&:node)
982
- end
983
-
984
- # @param namespace [String]
810
+ # Extract a namespace from a type.
811
+ #
812
+ # @example
813
+ # extract_namespace('String') => 'String'
814
+ # extract_namespace('Class<String>') => 'String'
815
+ #
985
816
  # @return [String]
986
- def clean_namespace_string namespace
987
- result = namespace.to_s.gsub(/<.*$/, '')
988
- if result == 'Class' and namespace.include?('<')
989
- subtype = namespace.match(/<([a-z0-9:_]*)/i)[1]
990
- result = "#{subtype}#class"
991
- elsif result == 'Module' and namespace.include?('<')
992
- subtype = namespace.match(/<([a-z0-9:_]*)/i)[1]
993
- result = "#{subtype}#module"
994
- end
995
- result
817
+ def extract_namespace type
818
+ extract_namespace_and_scope(type)[0]
996
819
  end
997
820
 
998
- # @param pin [Solargraph::Pin::Base]
999
- # @return [Solargraph::Suggestion]
1000
- def pin_to_suggestion pin
1001
- return_type = nil
1002
- return_type = find_fully_qualified_namespace(pin.return_type, pin.namespace) unless pin.return_type.nil?
1003
- if return_type.nil? and pin.is_a?(Solargraph::Pin::Method)
1004
- sc = @superclasses[pin.namespace]
1005
- while return_type.nil? and !sc.nil?
1006
- sc_path = "#{sc}#{pin.scope == :instance ? '#' : '.'}#{pin.name}"
1007
- sugg = get_path_suggestions(sc_path).first
1008
- break if sugg.nil?
1009
- return_type = find_fully_qualified_namespace(sugg.return_type, sugg.namespace) unless sugg.return_type.nil?
1010
- sc = @superclasses[sc]
1011
- end
821
+ # Extract a namespace and a scope (:instance or :class) from a type.
822
+ #
823
+ # @example
824
+ # extract_namespace('String') #=> ['String', :instance]
825
+ # extract_namespace('Class<String>') #=> ['String', :class]
826
+ # extract_namespace('Module<Enumerable') #=> ['Enumberable', :class]
827
+ #
828
+ # @return [Array] The namespace (String) and scope (Symbol).
829
+ def extract_namespace_and_scope type
830
+ scope = :instance
831
+ result = type.to_s.gsub(/<.*$/, '')
832
+ if (result == 'Class' or result == 'Module') and type.include?('<')
833
+ result = type.match(/<([a-z0-9:_]*)/i)[1]
834
+ scope = :class
1012
835
  end
1013
- @pin_suggestions[pin] ||= Suggestion.pull(pin, return_type)
836
+ [result, scope]
1014
837
  end
1015
838
 
1016
839
  def require_extensions
@@ -1020,6 +843,7 @@ module Solargraph
1020
843
  end
1021
844
  end
1022
845
 
846
+ # @return [Array<Solargraph::Pin::Base>]
1023
847
  def suggest_unique_variables pins
1024
848
  result = []
1025
849
  nil_pins = []
@@ -1029,20 +853,20 @@ module Solargraph
1029
853
  nil_pins.push pin
1030
854
  else
1031
855
  unless val_names.include?(pin.name)
1032
- result.push pin_to_suggestion(pin)
856
+ result.push pin
1033
857
  val_names.push pin.name
1034
858
  end
1035
859
  end
1036
860
  end
1037
861
  nil_pins.reject{|p| val_names.include?(p.name)}.each do |pin|
1038
- result.push pin_to_suggestion(pin)
862
+ result.push pin
1039
863
  end
1040
864
  result
1041
865
  end
1042
866
 
1043
867
  def source_file_mtime(filename)
1044
868
  # @todo This is naively inefficient.
1045
- sources.each do |s|
869
+ @sources.each do |s|
1046
870
  return s.mtime if s.filename == filename
1047
871
  end
1048
872
  nil
@@ -1060,25 +884,6 @@ module Solargraph
1060
884
  set.select{|p| p.path == fqns}
1061
885
  end
1062
886
 
1063
- def get_namespace_nodes(fqns)
1064
- return file_nodes if fqns == '' or fqns.nil?
1065
- refresh
1066
- namespace_map[fqns] || []
1067
- end
1068
-
1069
- # @return [Array<String>]
1070
- def get_include_strings_from *nodes
1071
- arr = []
1072
- nodes.each { |node|
1073
- next unless node.kind_of?(AST::Node)
1074
- arr.push unpack_name(node.children[2]) if (node.type == :send and node.children[1] == :include)
1075
- node.children.each { |n|
1076
- arr += get_include_strings_from(n) if n.kind_of?(AST::Node) and n.type != :class and n.type != :module and n.type != :sclass
1077
- }
1078
- }
1079
- arr
1080
- end
1081
-
1082
887
  # @todo DRY this method. It's duplicated in CodeMap
1083
888
  def get_subtypes type
1084
889
  return nil if type.nil?
@@ -1092,15 +897,28 @@ module Solargraph
1092
897
  @path_macros ||= {}
1093
898
  end
1094
899
 
900
+ def get_call_arguments node
901
+ return get_call_arguments(node.children[1]) if [:ivasgn, :cvasgn, :lvasgn].include?(node.type)
902
+ return [] unless node.type == :send
903
+ result = []
904
+ node.children[2..-1].each do |c|
905
+ result.push unpack_name(c)
906
+ end
907
+ result
908
+ end
909
+
910
+ # @todo This method shouldn't need to calculate the path. In fact, it should work directly off a pin.
1095
911
  def get_return_type_from_macro namespace, signature, call_node, scope, visibility
1096
912
  return nil if signature.empty? or signature.include?('.') or call_node.nil?
1097
913
  path = "#{namespace}#{scope == :class ? '.' : '#'}#{signature}"
1098
914
  macmeth = get_path_suggestions(path).first
1099
915
  type = nil
1100
916
  unless macmeth.nil?
917
+ macmeths = Suggestion.pull(macmeth)
1101
918
  macro = path_macros[macmeth.path]
1102
919
  macro = macro.first unless macro.nil?
1103
- if macro.nil? and !macmeth.code_object.nil? and !macmeth.code_object.base_docstring.nil? and macmeth.code_object.base_docstring.all.include?('@!macro')
920
+ # @todo Smelly respond_to? call
921
+ if macro.nil? and macmeth.respond_to?(:code_object) and !macmeth.code_object.nil? and !macmeth.code_object.base_docstring.nil? and macmeth.code_object.base_docstring.all.include?('@!macro')
1104
922
  all = YARD::Docstring.parser.parse(macmeth.code_object.base_docstring.all).directives
1105
923
  macro = all.select{|m| m.tag.tag_name == 'macro'}.first
1106
924
  end
@@ -1115,5 +933,115 @@ module Solargraph
1115
933
  end
1116
934
  type
1117
935
  end
936
+
937
+ def inner_infer_signature_type signature, namespace, scope, call_node, top
938
+ namespace ||= ''
939
+ if cache.has_signature_type?(signature, namespace, scope)
940
+ return cache.get_signature_type(signature, namespace, scope)
941
+ end
942
+ return nil if signature.nil?
943
+ return namespace if signature.empty? and scope == :instance
944
+ return nil if signature.empty? # @todo This might need to return Class<namespace>
945
+ if !signature.include?('.')
946
+ fqns = find_fully_qualified_namespace(signature, namespace)
947
+ unless fqns.nil? or fqns.empty?
948
+ type = (get_namespace_type(fqns) == :class ? 'Class' : 'Module')
949
+ return "#{type}<#{fqns}>"
950
+ end
951
+ end
952
+ result = nil
953
+ parts = signature.split('.', 2)
954
+ type = find_fully_qualified_namespace(parts[0], namespace)
955
+ if type.nil?
956
+ # It's a variable or method call
957
+ if top and parts[0] == 'self'
958
+ if parts[1].nil?
959
+ result = namespace
960
+ else
961
+ return inner_infer_signature_type(parts[1], namespace, scope, call_node, false)
962
+ end
963
+ elsif parts[0] == 'new' and scope == :class
964
+ scope = :instance
965
+ if parts[1].nil?
966
+ result = namespace
967
+ else
968
+ result = inner_infer_signature_type(parts[1], namespace, :instance, call_node, false)
969
+ end
970
+ else
971
+ visibility = [:public]
972
+ visibility.concat [:private, :protected] if top
973
+ if scope == :instance || namespace == ''
974
+ tmp = get_methods(extract_namespace(namespace), visibility: visibility)
975
+ else
976
+ tmp = get_methods(namespace, visibility: visibility, scope: :class)
977
+ # tmp = get_type_methods(namespace, (top ? namespace : ''))
978
+ end
979
+ tmp.concat get_methods('Kernel', visibility: [:public]) if top
980
+ matches = tmp.select{|s| s.name == parts[0]}
981
+ return nil if matches.empty?
982
+ matches.each do |m|
983
+ type = get_return_type_from_macro(namespace, signature, call_node, scope, visibility)
984
+ if type.nil?
985
+ if METHODS_RETURNING_SELF.include?(m.path)
986
+ type = curtype
987
+ elsif METHODS_RETURNING_SUBTYPES.include?(m.path)
988
+ subtypes = get_subtypes(namespace)
989
+ type = subtypes[0]
990
+ elsif !m.return_type.nil?
991
+ if m.return_type == 'self'
992
+ type = combine_type(namespace, scope)
993
+ else
994
+ type = m.return_type
995
+ end
996
+ end
997
+ end
998
+ break unless type.nil?
999
+ end
1000
+ unless type.nil?
1001
+ scope = :instance
1002
+ if parts[1].nil?
1003
+ result = type
1004
+ else
1005
+ subns, subsc = extract_namespace_and_scope(type)
1006
+ result = inner_infer_signature_type(parts[1], subns, subsc, call_node, false)
1007
+ end
1008
+ end
1009
+ end
1010
+ else
1011
+ return inner_infer_signature_type(parts[1], type, :class, call_node, false)
1012
+ end
1013
+ # @todo Assuming `self` only works at the top level
1014
+ # result = type if result == 'self'
1015
+ unless result.nil?
1016
+ if scope == :class
1017
+ nstype = get_namespace_type(result)
1018
+ result = "#{nstype == :class ? 'Class<' : 'Module<'}#{result}>"
1019
+ end
1020
+ end
1021
+ cache.set_signature_type signature, namespace, scope, result
1022
+ result
1023
+ end
1024
+
1025
+ # @todo call_node might be superfluous here. We're already past looking for local variables.
1026
+ def inner_infer_signature_pins signature, namespace, scope, call_node, top
1027
+ base, rest = signature.split('.', 2)
1028
+ type = nil
1029
+ if rest.nil?
1030
+ visibility = [:public]
1031
+ visibility.push :private, :protected if top
1032
+ methods = []
1033
+ methods.concat get_methods(namespace, visibility: visibility, scope: scope).select{|pin| pin.name == base}
1034
+ methods.concat get_methods('Kernel', scope: :instance).select{|pin| pin.name == base} if top
1035
+ return methods
1036
+ else
1037
+ type = inner_infer_signature_type base, namespace, scope, call_node, top
1038
+ nxt_ns, nxt_scope = extract_namespace_and_scope(type)
1039
+ return inner_infer_signature_pins rest, nxt_ns, nxt_scope, call_node, false
1040
+ end
1041
+ end
1042
+
1043
+ def current_workspace_sources
1044
+ @sources - [@virtual_source]
1045
+ end
1118
1046
  end
1119
1047
  end