solargraph 0.17.4 → 0.18.0

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