solargraph 0.17.4 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/solargraph.rb +16 -12
- data/lib/solargraph/api_map.rb +516 -588
- data/lib/solargraph/api_map/completion.rb +16 -0
- data/lib/solargraph/api_map/source_to_yard.rb +2 -2
- data/lib/solargraph/language_server.rb +12 -0
- data/lib/solargraph/language_server/completion_item_kinds.rb +31 -0
- data/lib/solargraph/language_server/error_codes.rb +16 -0
- data/lib/solargraph/language_server/host.rb +305 -0
- data/lib/solargraph/language_server/message.rb +70 -0
- data/lib/solargraph/language_server/message/base.rb +64 -0
- data/lib/solargraph/language_server/message/cancel_request.rb +11 -0
- data/lib/solargraph/language_server/message/client.rb +5 -0
- data/lib/solargraph/language_server/message/client/register_capability.rb +13 -0
- data/lib/solargraph/language_server/message/completion_item.rb +9 -0
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +23 -0
- data/lib/solargraph/language_server/message/exit_notification.rb +12 -0
- data/lib/solargraph/language_server/message/extended.rb +15 -0
- data/lib/solargraph/language_server/message/extended/document.rb +18 -0
- data/lib/solargraph/language_server/message/extended/search.rb +18 -0
- data/lib/solargraph/language_server/message/initialize.rb +39 -0
- data/lib/solargraph/language_server/message/initialized.rb +10 -0
- data/lib/solargraph/language_server/message/method_not_found.rb +14 -0
- data/lib/solargraph/language_server/message/method_not_implemented.rb +12 -0
- data/lib/solargraph/language_server/message/shutdown.rb +11 -0
- data/lib/solargraph/language_server/message/text_document.rb +21 -0
- data/lib/solargraph/language_server/message/text_document/base.rb +17 -0
- data/lib/solargraph/language_server/message/text_document/completion.rb +69 -0
- data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
- data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
- data/lib/solargraph/language_server/message/text_document/did_close.rb +12 -0
- data/lib/solargraph/language_server/message/text_document/did_open.rb +13 -0
- data/lib/solargraph/language_server/message/text_document/did_save.rb +15 -0
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +31 -0
- data/lib/solargraph/language_server/message/text_document/formatting.rb +36 -0
- data/lib/solargraph/language_server/message/text_document/hover.rb +19 -0
- data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +29 -0
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +23 -0
- data/lib/solargraph/language_server/message/workspace.rb +11 -0
- data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +9 -0
- data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +30 -0
- data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +31 -0
- data/lib/solargraph/language_server/symbol_kinds.rb +32 -0
- data/lib/solargraph/language_server/transport.rb +7 -0
- data/lib/solargraph/language_server/transport/socket.rb +66 -0
- data/lib/solargraph/language_server/uri_helpers.rb +21 -0
- data/lib/solargraph/library.rb +225 -0
- data/lib/solargraph/live_map.rb +1 -1
- data/lib/solargraph/page.rb +61 -0
- data/lib/solargraph/pin.rb +7 -0
- data/lib/solargraph/pin/attribute.rb +9 -0
- data/lib/solargraph/pin/base.rb +76 -6
- data/lib/solargraph/pin/base_variable.rb +29 -7
- data/lib/solargraph/pin/block_parameter.rb +53 -0
- data/lib/solargraph/pin/constant.rb +6 -2
- data/lib/solargraph/pin/conversions.rb +65 -0
- data/lib/solargraph/pin/directed/attribute.rb +4 -0
- data/lib/solargraph/pin/directed/method.rb +6 -1
- data/lib/solargraph/pin/helper.rb +35 -0
- data/lib/solargraph/pin/keyword.rb +22 -0
- data/lib/solargraph/pin/local_variable.rb +0 -1
- data/lib/solargraph/pin/method.rb +55 -2
- data/lib/solargraph/pin/method_parameter.rb +19 -0
- data/lib/solargraph/pin/namespace.rb +7 -2
- data/lib/solargraph/pin/parameter.rb +23 -0
- data/lib/solargraph/pin/plugin/method.rb +3 -2
- data/lib/solargraph/pin/yard_object.rb +101 -0
- data/lib/solargraph/server.rb +82 -135
- data/lib/solargraph/shell.rb +20 -1
- data/lib/solargraph/source.rb +709 -0
- data/lib/solargraph/source/flawed_builder.rb +10 -0
- data/lib/solargraph/source/fragment.rb +319 -0
- data/lib/solargraph/source/position.rb +26 -0
- data/lib/solargraph/source/range.rb +39 -0
- data/lib/solargraph/suggestion.rb +29 -4
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +105 -0
- data/lib/solargraph/{api_map → workspace}/config.rb +1 -1
- data/lib/solargraph/yard_map.rb +59 -37
- metadata +168 -5
- data/lib/solargraph/api_map/source.rb +0 -470
- data/lib/solargraph/code_map.rb +0 -868
data/lib/solargraph/code_map.rb
DELETED
@@ -1,868 +0,0 @@
|
|
1
|
-
require 'parser/current'
|
2
|
-
|
3
|
-
module Solargraph
|
4
|
-
class CodeMap
|
5
|
-
# The root node of the parsed code.
|
6
|
-
#
|
7
|
-
# @return [Parser::AST::Node]
|
8
|
-
attr_reader :node
|
9
|
-
|
10
|
-
# The source code being analyzed.
|
11
|
-
#
|
12
|
-
# @return [String]
|
13
|
-
attr_reader :code
|
14
|
-
|
15
|
-
# The source object generated from the code.
|
16
|
-
#
|
17
|
-
# @return [Solargraph::ApiMap::Source]
|
18
|
-
attr_reader :source
|
19
|
-
|
20
|
-
# The filename for the source code.
|
21
|
-
#
|
22
|
-
# @return [String]
|
23
|
-
attr_reader :filename
|
24
|
-
|
25
|
-
include NodeMethods
|
26
|
-
include CoreFills
|
27
|
-
|
28
|
-
def initialize code: '', filename: nil, api_map: nil, cursor: nil
|
29
|
-
# HACK: Adjust incoming filename's path separator for yardoc file comparisons
|
30
|
-
filename = filename.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless filename.nil? or File::ALT_SEPARATOR.nil?
|
31
|
-
@filename = filename
|
32
|
-
@api_map = api_map
|
33
|
-
if !filename.nil? and filename.end_with?('.erb')
|
34
|
-
@source = self.api_map.virtualize(convert_erb(code), filename, cursor)
|
35
|
-
else
|
36
|
-
@source = self.api_map.virtualize(code, filename, cursor)
|
37
|
-
end
|
38
|
-
@node = @source.node
|
39
|
-
@code = @source.code
|
40
|
-
@comments = @source.comments
|
41
|
-
self.api_map.refresh
|
42
|
-
end
|
43
|
-
|
44
|
-
# Get the associated ApiMap.
|
45
|
-
#
|
46
|
-
# @return [Solargraph::ApiMap]
|
47
|
-
def api_map
|
48
|
-
@api_map ||= ApiMap.new(nil)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Get the offset of the specified line and column.
|
52
|
-
# The offset (also called the "index") is typically used to identify the
|
53
|
-
# cursor's location in the code when generating suggestions.
|
54
|
-
# The line and column numbers should start at zero.
|
55
|
-
#
|
56
|
-
# @param line [Integer]
|
57
|
-
# @param col [Integer]
|
58
|
-
# @return [Integer]
|
59
|
-
def get_offset line, col
|
60
|
-
CodeMap.get_offset @code, line, col
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.get_offset text, line, col
|
64
|
-
offset = 0
|
65
|
-
if line > 0
|
66
|
-
text.lines[0..line - 1].each { |l|
|
67
|
-
offset += l.length
|
68
|
-
}
|
69
|
-
end
|
70
|
-
offset + col
|
71
|
-
end
|
72
|
-
|
73
|
-
# Get an array of nodes containing the specified index, starting with the
|
74
|
-
# topmost node and ending with the nearest.
|
75
|
-
#
|
76
|
-
# @param index [Integer]
|
77
|
-
# @return [Array<AST::Node>]
|
78
|
-
def tree_at(index)
|
79
|
-
arr = []
|
80
|
-
arr.push @node
|
81
|
-
inner_node_at(index, @node, arr)
|
82
|
-
arr
|
83
|
-
end
|
84
|
-
|
85
|
-
# Get the nearest node that contains the specified index.
|
86
|
-
#
|
87
|
-
# @param index [Integer]
|
88
|
-
# @return [AST::Node]
|
89
|
-
def node_at(index)
|
90
|
-
tree_at(index).first
|
91
|
-
end
|
92
|
-
|
93
|
-
# Determine if the specified index is inside a string.
|
94
|
-
#
|
95
|
-
# @return [Boolean]
|
96
|
-
def string_at?(index)
|
97
|
-
n = node_at(index)
|
98
|
-
n.kind_of?(AST::Node) and n.type == :str
|
99
|
-
end
|
100
|
-
|
101
|
-
# Determine if the specified index is inside a comment.
|
102
|
-
#
|
103
|
-
# @return [Boolean]
|
104
|
-
def comment_at?(index)
|
105
|
-
return false if string_at?(index)
|
106
|
-
line, col = Solargraph::ApiMap::Source.get_position_at(source.code, index)
|
107
|
-
return false if source.stubbed_lines.include?(line)
|
108
|
-
@comments.each do |c|
|
109
|
-
return true if index > c.location.expression.begin_pos and index <= c.location.expression.end_pos
|
110
|
-
end
|
111
|
-
# Extra test due to some comments not getting tracked
|
112
|
-
while (index > 0 and @code[index] != "\n")
|
113
|
-
return true if @code[index] == '#'
|
114
|
-
index -= 1
|
115
|
-
end
|
116
|
-
false
|
117
|
-
end
|
118
|
-
|
119
|
-
# Find the nearest parent node from the specified index. If one or more
|
120
|
-
# types are provided, find the nearest node whose type is in the list.
|
121
|
-
#
|
122
|
-
# @param index [Integer]
|
123
|
-
# @param types [Array<Symbol>]
|
124
|
-
# @return [AST::Node]
|
125
|
-
def parent_node_from(index, *types)
|
126
|
-
arr = tree_at(index)
|
127
|
-
arr.each { |a|
|
128
|
-
if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
|
129
|
-
return a
|
130
|
-
end
|
131
|
-
}
|
132
|
-
@node
|
133
|
-
end
|
134
|
-
|
135
|
-
# Get the namespace at the specified location. For example, given the code
|
136
|
-
# `class Foo; def bar; end; end`, index 14 (the center) is in the
|
137
|
-
# "Foo" namespace.
|
138
|
-
#
|
139
|
-
# @return [String]
|
140
|
-
def namespace_at(index)
|
141
|
-
tree = tree_at(index)
|
142
|
-
return nil if tree.length == 0
|
143
|
-
slice = tree
|
144
|
-
parts = []
|
145
|
-
slice.reverse.each { |n|
|
146
|
-
if n.type == :class or n.type == :module
|
147
|
-
c = const_from(n.children[0])
|
148
|
-
parts.push c
|
149
|
-
end
|
150
|
-
}
|
151
|
-
parts.join("::")
|
152
|
-
end
|
153
|
-
|
154
|
-
# Get the namespace for the specified node. For example, given the code
|
155
|
-
# `class Foo; def bar; end; end`, the node for `def bar` is in the "Foo"
|
156
|
-
# namespace.
|
157
|
-
#
|
158
|
-
# @return [String]
|
159
|
-
def namespace_from(node)
|
160
|
-
if node.respond_to?(:loc)
|
161
|
-
namespace_at(node.loc.expression.begin_pos)
|
162
|
-
else
|
163
|
-
''
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# Select the word that directly precedes the specified index.
|
168
|
-
# A word can only consist of letters, numbers, and underscores.
|
169
|
-
#
|
170
|
-
# @param index [Integer]
|
171
|
-
# @return [String]
|
172
|
-
def word_at index
|
173
|
-
word = ''
|
174
|
-
cursor = index - 1
|
175
|
-
while cursor > -1
|
176
|
-
char = @code[cursor, 1]
|
177
|
-
break if char.nil? or char == ''
|
178
|
-
word = char + word if char == '$'
|
179
|
-
break unless char.match(/[a-z0-9_]/i)
|
180
|
-
word = char + word
|
181
|
-
cursor -= 1
|
182
|
-
end
|
183
|
-
word
|
184
|
-
end
|
185
|
-
|
186
|
-
# @return [Array<Solargraph::Suggestion>]
|
187
|
-
def get_class_variables_at(index)
|
188
|
-
ns = namespace_at(index) || ''
|
189
|
-
api_map.get_class_variables(ns)
|
190
|
-
end
|
191
|
-
|
192
|
-
def get_instance_variables_at(index)
|
193
|
-
# @todo There are a lot of other cases that need to be handled here
|
194
|
-
node = parent_node_from(index, :def, :defs, :class, :module, :sclass)
|
195
|
-
ns = namespace_at(index) || ''
|
196
|
-
scope = (node.type == :def ? :instance : :class)
|
197
|
-
api_map.get_instance_variables(ns, scope)
|
198
|
-
end
|
199
|
-
|
200
|
-
# Get suggestions for code completion at the specified location in the
|
201
|
-
# source.
|
202
|
-
#
|
203
|
-
# @return [Array<Solargraph::Suggestion>] The completion suggestions
|
204
|
-
def suggest_at index, filtered: true
|
205
|
-
return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
|
206
|
-
signature = get_signature_at(index)
|
207
|
-
unless signature.include?('.')
|
208
|
-
if signature.start_with?(':')
|
209
|
-
return api_map.get_symbols
|
210
|
-
elsif signature.start_with?('@@')
|
211
|
-
return get_class_variables_at(index)
|
212
|
-
elsif signature.start_with?('@')
|
213
|
-
return get_instance_variables_at(index)
|
214
|
-
elsif signature.start_with?('$')
|
215
|
-
return api_map.get_global_variables
|
216
|
-
end
|
217
|
-
end
|
218
|
-
result = []
|
219
|
-
type = nil
|
220
|
-
if signature.include?('.')
|
221
|
-
type = infer_signature_at(index)
|
222
|
-
if type.nil? and signature.include?('.')
|
223
|
-
last_period = @code[0..index].rindex('.')
|
224
|
-
type = infer_signature_at(last_period)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
if type.nil?
|
228
|
-
unless signature.include?('.')
|
229
|
-
namespace = namespace_at(index)
|
230
|
-
if signature.include?('::')
|
231
|
-
parts = signature.split('::', -1)
|
232
|
-
ns = parts[0..-2].join('::')
|
233
|
-
result = api_map.get_constants(ns, namespace)
|
234
|
-
else
|
235
|
-
type = infer_literal_node_type(node_at(index - 2))
|
236
|
-
return [] if type.nil? and signature.empty? and !@code[0..index].rindex('.').nil? and @code[@code[0..index].rindex('.')..-1].strip == '.'
|
237
|
-
if type.nil?
|
238
|
-
result.concat get_local_variables_and_methods_at(index)
|
239
|
-
result.concat ApiMap.keywords
|
240
|
-
result.concat api_map.get_constants('', namespace)
|
241
|
-
result.concat api_map.get_constants('')
|
242
|
-
result.concat api_map.get_instance_methods('Kernel', namespace)
|
243
|
-
result.concat api_map.get_methods('', namespace)
|
244
|
-
result.concat api_map.get_instance_methods('', namespace)
|
245
|
-
else
|
246
|
-
result.concat api_map.get_instance_methods(type) unless @code[index - 1] != '.'
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
else
|
251
|
-
result.concat api_map.get_instance_methods(type) unless (type == '' and signature.include?('.'))
|
252
|
-
end
|
253
|
-
result.keep_if{|s| s.kind != Solargraph::Suggestion::METHOD or s.label.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}
|
254
|
-
result = reduce_starting_with(result, word_at(index)) if filtered
|
255
|
-
# Use a stable sort to keep the class order (e.g., local methods before superclass methods)
|
256
|
-
result.uniq(&:path).sort_by.with_index{ |x, idx| [x.label, idx] }
|
257
|
-
end
|
258
|
-
|
259
|
-
def signatures_at index
|
260
|
-
sig = signature_index_before(index)
|
261
|
-
return [] if sig.nil?
|
262
|
-
word = word_at(sig)
|
263
|
-
sugg = suggest_at(sig - word.length)
|
264
|
-
sugg.select{|s| s.label == word}
|
265
|
-
end
|
266
|
-
|
267
|
-
# @return [Array<Solargraph::Suggestion>]
|
268
|
-
def define_symbol_at index
|
269
|
-
return [] if string_at?(index)
|
270
|
-
signature = get_signature_at(index, final: true)
|
271
|
-
return [] if signature.to_s.empty?
|
272
|
-
node = parent_node_from(index, :class, :module, :def, :defs) || @node
|
273
|
-
ns_here = namespace_from(node)
|
274
|
-
unless signature.include?('.')
|
275
|
-
if local_variable_in_node?(signature, node)
|
276
|
-
return get_local_variables_from(node).select{|s| s.label == signature}
|
277
|
-
elsif signature.start_with?('@@')
|
278
|
-
return api_map.get_class_variables(ns_here).select{|s| s.label == signature}
|
279
|
-
elsif signature.start_with?('@')
|
280
|
-
return api_map.get_instance_variables(ns_here, (node.type == :def ? :instance : :class)).select{|s| s.label == signature}
|
281
|
-
end
|
282
|
-
end
|
283
|
-
path = infer_path_from_signature_and_node(signature, node)
|
284
|
-
ps = []
|
285
|
-
ps = api_map.get_path_suggestions(path) unless path.nil?
|
286
|
-
return ps unless ps.empty?
|
287
|
-
ps = api_map.get_path_suggestions(signature)
|
288
|
-
return ps unless ps.empty?
|
289
|
-
scope = (node.type == :def ? :instance : :class)
|
290
|
-
final = []
|
291
|
-
if scope == :instance
|
292
|
-
final.concat api_map.get_instance_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
|
293
|
-
else
|
294
|
-
final.concat api_map.get_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
|
295
|
-
end
|
296
|
-
if final.empty? and !signature.include?('.')
|
297
|
-
fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
|
298
|
-
final.concat api_map.get_path_suggestions(fqns) unless fqns.nil? or fqns.empty?
|
299
|
-
end
|
300
|
-
final
|
301
|
-
end
|
302
|
-
|
303
|
-
def resolve_object_at index
|
304
|
-
define_symbol_at index
|
305
|
-
end
|
306
|
-
|
307
|
-
# Infer the type of the signature located at the specified index.
|
308
|
-
#
|
309
|
-
# @example
|
310
|
-
# # Given the following code:
|
311
|
-
# nums = [1, 2, 3]
|
312
|
-
# nums.join
|
313
|
-
# # ...and given an index that points at the end of "nums.join",
|
314
|
-
# # infer_signature_at will identify nums as an Array and the return
|
315
|
-
# # type of Array#join as a String, so the signature's type will be
|
316
|
-
# # String.
|
317
|
-
#
|
318
|
-
# @return [String]
|
319
|
-
def infer_signature_at index
|
320
|
-
beg_sig, signature = get_signature_data_at(index)
|
321
|
-
# Check for literals first
|
322
|
-
return 'Integer' if signature.match(/^[0-9]+?\.?$/)
|
323
|
-
literal = nil
|
324
|
-
if (signature.empty? and @code[index - 1] == '.') or signature == '[].'
|
325
|
-
literal = node_at(index - 2)
|
326
|
-
else
|
327
|
-
literal = node_at(1 + beg_sig)
|
328
|
-
end
|
329
|
-
type = infer_literal_node_type(literal)
|
330
|
-
if type.nil?
|
331
|
-
node = parent_node_from(index, :class, :module, :def, :defs, :block) || @node
|
332
|
-
result = infer_signature_from_node signature, node
|
333
|
-
if result.nil? or result.empty?
|
334
|
-
# The rest of this routine is dedicated to method and block parameters
|
335
|
-
arg = nil
|
336
|
-
if node.type == :def or node.type == :defs or node.type == :block
|
337
|
-
# Check for method arguments
|
338
|
-
parts = signature.split('.', 2)
|
339
|
-
# @type [Solargraph::Suggestion]
|
340
|
-
arg = get_method_arguments_from(node).keep_if{|s| s.to_s == parts[0] }.first
|
341
|
-
unless arg.nil?
|
342
|
-
if parts[1].nil?
|
343
|
-
result = arg.return_type
|
344
|
-
else
|
345
|
-
result = api_map.infer_signature_type(parts[1], arg.return_type, scope: :instance)
|
346
|
-
end
|
347
|
-
end
|
348
|
-
end
|
349
|
-
if arg.nil?
|
350
|
-
# Check for yieldparams
|
351
|
-
parts = signature.split('.', 2)
|
352
|
-
yp = get_yieldparams_at(index).keep_if{|s| s.to_s == parts[0]}.first
|
353
|
-
unless yp.nil?
|
354
|
-
if parts[1].nil? or parts[1].empty?
|
355
|
-
result = yp.return_type
|
356
|
-
else
|
357
|
-
newsig = parts[1..-1].join('.')
|
358
|
-
result = api_map.infer_signature_type(newsig, yp.return_type, scope: :instance)
|
359
|
-
end
|
360
|
-
end
|
361
|
-
end
|
362
|
-
#elsif match = result.match(/^\$(\-?[0-9]*)$/)
|
363
|
-
# STDERR.puts "TODO: handle expression variable #{match[1]}"
|
364
|
-
end
|
365
|
-
else
|
366
|
-
if signature.empty? or signature == '[].'
|
367
|
-
result = type
|
368
|
-
else
|
369
|
-
cursed = get_signature_index_at(index)
|
370
|
-
if signature.start_with?('[].')
|
371
|
-
rest = signature[3..-1]
|
372
|
-
else
|
373
|
-
if signature.start_with?('.')
|
374
|
-
rest = signature[literal.loc.expression.end_pos+(cursed-literal.loc.expression.end_pos)..-1]
|
375
|
-
else
|
376
|
-
rest = signature
|
377
|
-
end
|
378
|
-
end
|
379
|
-
return type if rest.nil?
|
380
|
-
lit_code = @code[literal.loc.expression.begin_pos..literal.loc.expression.end_pos]
|
381
|
-
rest = rest[lit_code.length..-1] if rest.start_with?(lit_code)
|
382
|
-
rest = rest[1..-1] if rest.start_with?('.')
|
383
|
-
rest = rest[0..-2] if rest.end_with?('.')
|
384
|
-
if rest.empty?
|
385
|
-
result = type
|
386
|
-
else
|
387
|
-
result = api_map.infer_signature_type(rest, type, scope: :instance)
|
388
|
-
end
|
389
|
-
end
|
390
|
-
end
|
391
|
-
result
|
392
|
-
end
|
393
|
-
|
394
|
-
def local_variable_in_node?(name, node)
|
395
|
-
return true unless find_local_variable_node(name, node).nil?
|
396
|
-
if node.type == :def or node.type == :defs
|
397
|
-
args = get_method_arguments_from(node).keep_if{|a| a.label == name}
|
398
|
-
return true unless args.empty?
|
399
|
-
end
|
400
|
-
false
|
401
|
-
end
|
402
|
-
|
403
|
-
def infer_signature_from_node signature, node, call_node: nil
|
404
|
-
inferred = nil
|
405
|
-
parts = signature.split('.')
|
406
|
-
ns_here = namespace_from(node)
|
407
|
-
if parts[0] and parts[0].include?('::')
|
408
|
-
sub = get_namespace_or_constant(parts[0], ns_here)
|
409
|
-
unless sub.nil?
|
410
|
-
return sub if signature.match(/^#{parts[0]}\.$/)
|
411
|
-
parts[0] = sub
|
412
|
-
end
|
413
|
-
end
|
414
|
-
unless signature.include?('.')
|
415
|
-
fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
|
416
|
-
return "Class<#{fqns}>" unless fqns.nil? or fqns.empty?
|
417
|
-
end
|
418
|
-
start = parts[0]
|
419
|
-
return nil if start.nil?
|
420
|
-
remainder = parts[1..-1]
|
421
|
-
if start.start_with?('@@')
|
422
|
-
cv = api_map.get_class_variable_pins(ns_here).select{|s| s.name == start}.first
|
423
|
-
unless cv.nil?
|
424
|
-
vartype = (cv.return_type || api_map.infer_assignment_node_type(cv.node, cv.namespace))
|
425
|
-
return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
|
426
|
-
end
|
427
|
-
elsif start.start_with?('@')
|
428
|
-
scope = (node.type == :def ? :instance : :class)
|
429
|
-
iv = api_map.get_instance_variable_pins(ns_here, scope).select{|s| s.name == start}.first
|
430
|
-
unless iv.nil?
|
431
|
-
vartype = (iv.return_type || api_map.infer_assignment_node_type(iv.node, iv.namespace))
|
432
|
-
return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
|
433
|
-
end
|
434
|
-
elsif start.start_with?('$')
|
435
|
-
gv = api_map.get_global_variable_pins.select{|s| s.name == start}.first
|
436
|
-
unless gv.nil?
|
437
|
-
vartype = (gv.return_type || api_map.infer_assignment_node_type(gv.node, gv.namespace))
|
438
|
-
return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
|
439
|
-
end
|
440
|
-
end
|
441
|
-
# @todo There might be some redundancy between find_local_variable_node and call_node
|
442
|
-
var = find_local_variable_node(start, node)
|
443
|
-
if var.nil?
|
444
|
-
arg = get_method_arguments_from(node).select{|s| s.label == start}.first
|
445
|
-
if arg.nil?
|
446
|
-
scope = (node.type == :def ? :instance : :class)
|
447
|
-
type = api_map.infer_signature_type(signature, ns_here, scope: scope, call_node: call_node)
|
448
|
-
return type unless type.nil?
|
449
|
-
else
|
450
|
-
type = arg.return_type
|
451
|
-
end
|
452
|
-
else
|
453
|
-
# Signature starts with a local variable
|
454
|
-
type = nil
|
455
|
-
lvp = source.local_variable_pins.select{|p| p.name == var.children[0].to_s and p.visible_from?(node) and (!p.nil_assignment? or p.return_type)}.first
|
456
|
-
unless lvp.nil?
|
457
|
-
type = lvp.return_type
|
458
|
-
if type.nil?
|
459
|
-
vsig = resolve_node_signature(var.children[1])
|
460
|
-
type = infer_signature_from_node vsig, node, call_node: lvp.assignment_node
|
461
|
-
end
|
462
|
-
end
|
463
|
-
end
|
464
|
-
unless type.nil?
|
465
|
-
if remainder.empty?
|
466
|
-
inferred = type
|
467
|
-
else
|
468
|
-
inferred = api_map.infer_signature_type(remainder.join('.'), type, scope: :instance, call_node: call_node)
|
469
|
-
end
|
470
|
-
end
|
471
|
-
if inferred.nil? and node.respond_to?(:loc)
|
472
|
-
index = node.loc.expression.begin_pos
|
473
|
-
block_node = parent_node_from(index, :block, :class, :module, :sclass, :def, :defs)
|
474
|
-
unless block_node.nil? or block_node.type != :block or block_node.children[0].nil?
|
475
|
-
scope_node = parent_node_from(index, :class, :module, :def, :defs) || @node
|
476
|
-
meth = get_yielding_method_with_yieldself(block_node, scope_node)
|
477
|
-
unless meth.nil?
|
478
|
-
match = meth.docstring.all.match(/@yieldself \[([a-z0-9:_]*)/i)
|
479
|
-
self_yield = match[1]
|
480
|
-
inferred = api_map.infer_signature_type(signature, self_yield, scope: :instance)
|
481
|
-
end
|
482
|
-
end
|
483
|
-
end
|
484
|
-
inferred
|
485
|
-
end
|
486
|
-
|
487
|
-
# Get the signature at the specified index.
|
488
|
-
# A signature is a method call that can start with a constant, method, or
|
489
|
-
# variable and does not include any method arguments. Examples:
|
490
|
-
#
|
491
|
-
# * String.new -> String.new
|
492
|
-
# * @x.bar -> @x.bar
|
493
|
-
# * y.split(', ').length -> y.split.length
|
494
|
-
#
|
495
|
-
# @param index [Integer]
|
496
|
-
# @return [String]
|
497
|
-
def get_signature_at index, final: false
|
498
|
-
sig = get_signature_data_at(index)[1]
|
499
|
-
if final
|
500
|
-
cursor = index
|
501
|
-
while @code[cursor] =~ /[a-z0-9_\?]/i
|
502
|
-
sig += @code[cursor]
|
503
|
-
cursor += 1
|
504
|
-
break if cursor >= @code.length
|
505
|
-
end
|
506
|
-
end
|
507
|
-
sig
|
508
|
-
end
|
509
|
-
|
510
|
-
def get_signature_index_at index
|
511
|
-
get_signature_data_at(index)[0]
|
512
|
-
end
|
513
|
-
|
514
|
-
# Get an array of local variables and methods that can be accessed from
|
515
|
-
# the specified location in the code.
|
516
|
-
#
|
517
|
-
# @param index [Integer]
|
518
|
-
# @return [Array<Solargraph::Suggestion>]
|
519
|
-
def get_local_variables_and_methods_at(index)
|
520
|
-
result = []
|
521
|
-
local = parent_node_from(index, :class, :module, :def, :defs) || @node
|
522
|
-
result += get_local_variables_from(node_at(index))
|
523
|
-
scope = namespace_at(index) || @node
|
524
|
-
if local.type == :def
|
525
|
-
result += api_map.get_instance_methods(scope, visibility: [:public, :private, :protected])
|
526
|
-
else
|
527
|
-
result += api_map.get_methods(scope, scope, visibility: [:public, :private, :protected])
|
528
|
-
end
|
529
|
-
if local.type == :def or local.type == :defs
|
530
|
-
result += get_method_arguments_from local
|
531
|
-
end
|
532
|
-
result.concat get_yieldparams_at(index)
|
533
|
-
result
|
534
|
-
end
|
535
|
-
|
536
|
-
#def get_call_arguments_at index
|
537
|
-
# called = parent_node_from(index, :send)
|
538
|
-
#end
|
539
|
-
|
540
|
-
private
|
541
|
-
|
542
|
-
def get_signature_data_at index
|
543
|
-
brackets = 0
|
544
|
-
squares = 0
|
545
|
-
parens = 0
|
546
|
-
signature = ''
|
547
|
-
index -=1
|
548
|
-
in_whitespace = false
|
549
|
-
while index >= 0
|
550
|
-
break if index > 0 and comment_at?(index - 1)
|
551
|
-
unless !in_whitespace and string_at?(index)
|
552
|
-
break if brackets > 0 or parens > 0 or squares > 0
|
553
|
-
char = @code[index, 1]
|
554
|
-
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\n", "\t"].include?(char)
|
555
|
-
in_whitespace = true
|
556
|
-
else
|
557
|
-
if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
|
558
|
-
unless char == '.' or @code[index+1..-1].strip.start_with?('.')
|
559
|
-
old = @code[index+1..-1]
|
560
|
-
nxt = @code[index+1..-1].lstrip
|
561
|
-
index += (@code[index+1..-1].length - @code[index+1..-1].lstrip.length)
|
562
|
-
break
|
563
|
-
end
|
564
|
-
end
|
565
|
-
if char == ')'
|
566
|
-
parens -=1
|
567
|
-
elsif char == ']'
|
568
|
-
squares -=1
|
569
|
-
elsif char == '}'
|
570
|
-
brackets -= 1
|
571
|
-
elsif char == '('
|
572
|
-
parens += 1
|
573
|
-
elsif char == '{'
|
574
|
-
brackets += 1
|
575
|
-
elsif char == '['
|
576
|
-
squares += 1
|
577
|
-
signature = ".[]#{signature}" if squares == 0 and @code[index-2] != '%'
|
578
|
-
end
|
579
|
-
if brackets.zero? and parens.zero? and squares.zero?
|
580
|
-
break if ['"', "'", ',', ';', '%'].include?(char)
|
581
|
-
signature = char + signature if char.match(/[a-z0-9:\._@\$]/i) and @code[index - 1] != '%'
|
582
|
-
break if char == '$'
|
583
|
-
if char == '@'
|
584
|
-
signature = "@#{signature}" if @code[index-1, 1] == '@'
|
585
|
-
break
|
586
|
-
end
|
587
|
-
end
|
588
|
-
in_whitespace = false
|
589
|
-
end
|
590
|
-
end
|
591
|
-
index -= 1
|
592
|
-
end
|
593
|
-
signature = signature[1..-1] if signature.start_with?('.')
|
594
|
-
[index + 1, signature]
|
595
|
-
end
|
596
|
-
|
597
|
-
# Get a node's arguments as an array of suggestions. The node's type must
|
598
|
-
# be a method (:def or :defs).
|
599
|
-
#
|
600
|
-
# @param node [AST::Node]
|
601
|
-
# @return [Array<Suggestion>]
|
602
|
-
def get_method_arguments_from node
|
603
|
-
return [] unless node.type == :def or node.type == :defs
|
604
|
-
param_hash = {}
|
605
|
-
cmnt = api_map.get_docstring_for(node)
|
606
|
-
unless cmnt.nil?
|
607
|
-
tags = cmnt.tags(:param)
|
608
|
-
tags.each do |tag|
|
609
|
-
param_hash[tag.name] = tag.types[0]
|
610
|
-
end
|
611
|
-
end
|
612
|
-
result = []
|
613
|
-
args = node.children[(node.type == :def ? 1 : 2)]
|
614
|
-
return result unless args.kind_of?(AST::Node) and args.type == :args
|
615
|
-
args.children.each do |arg|
|
616
|
-
name = arg.children[0].to_s
|
617
|
-
result.push Suggestion.new(name, kind: Suggestion::PROPERTY, insert: name, return_type: param_hash[name])
|
618
|
-
end
|
619
|
-
result
|
620
|
-
end
|
621
|
-
|
622
|
-
def get_yieldparams_at index
|
623
|
-
block_node = parent_node_from(index, :block, :class, :module, :def, :defs)
|
624
|
-
return [] if block_node.nil? or block_node.type != :block
|
625
|
-
scope_node = parent_node_from(index, :class, :module, :def, :defs) || @node
|
626
|
-
return [] if block_node.nil?
|
627
|
-
get_yieldparams_from block_node, scope_node
|
628
|
-
end
|
629
|
-
|
630
|
-
def get_yieldparams_from block_node, scope_node
|
631
|
-
return [] unless block_node.kind_of?(AST::Node) and block_node.type == :block
|
632
|
-
result = []
|
633
|
-
unless block_node.nil? or block_node.children[1].nil?
|
634
|
-
ymeth = get_yielding_method(block_node, scope_node)
|
635
|
-
yps = []
|
636
|
-
unless ymeth.nil? or ymeth.docstring.nil?
|
637
|
-
yps = ymeth.docstring.tags(:yieldparam) || []
|
638
|
-
end
|
639
|
-
self_yield = nil
|
640
|
-
meth = get_yielding_method_with_yieldself(block_node, scope_node)
|
641
|
-
unless meth.nil?
|
642
|
-
match = meth.docstring.all.match(/@yieldself \[([a-z0-9:_]*)/i)
|
643
|
-
self_yield = match[1]
|
644
|
-
if self_yield == 'self'
|
645
|
-
blocksig = resolve_node_signature(block_node.children[0]).split('.')[0..-2].join('.')
|
646
|
-
self_yield = infer_signature_from_node(blocksig, scope_node)
|
647
|
-
end
|
648
|
-
end
|
649
|
-
block_node.children[1].children.each_with_index do |a, i|
|
650
|
-
rt = nil
|
651
|
-
if yps[i].nil? or yps[i].types.nil? or yps[i].types.empty?
|
652
|
-
zsig = api_map.resolve_node_signature(block_node.children[0])
|
653
|
-
vartype = infer_signature_from_node(zsig.split('.')[0..-2].join('.'), scope_node)
|
654
|
-
subtypes = get_subtypes(vartype)
|
655
|
-
zpath = infer_path_from_signature_and_node(zsig, scope_node)
|
656
|
-
rt = subtypes[i] if METHODS_WITH_YIELDPARAM_SUBTYPES.include?(zpath)
|
657
|
-
else
|
658
|
-
rt = yps[i].types[0]
|
659
|
-
end
|
660
|
-
result.push Suggestion.new(a.children[0], kind: Suggestion::PROPERTY, return_type: rt)
|
661
|
-
end
|
662
|
-
result.concat api_map.get_instance_methods(self_yield, namespace_from(scope_node)) unless self_yield.nil?
|
663
|
-
end
|
664
|
-
result
|
665
|
-
end
|
666
|
-
|
667
|
-
def get_yielding_method block_node, scope_node
|
668
|
-
recv = resolve_node_signature(block_node.children[0].children[0])
|
669
|
-
fqns = namespace_from(block_node)
|
670
|
-
lvarnode = find_local_variable_node(recv, scope_node)
|
671
|
-
if lvarnode.nil?
|
672
|
-
#sig = api_map.infer_signature_type(recv, fqns)
|
673
|
-
sig = infer_signature_from_node(recv, scope_node)
|
674
|
-
else
|
675
|
-
tmp = resolve_node_signature(lvarnode.children[1])
|
676
|
-
sig = infer_signature_from_node tmp, scope_node
|
677
|
-
end
|
678
|
-
if sig.nil?
|
679
|
-
meths = api_map.get_methods(fqns, fqns)
|
680
|
-
else
|
681
|
-
meths = api_map.get_instance_methods(sig, fqns)
|
682
|
-
end
|
683
|
-
meths += api_map.get_methods('')
|
684
|
-
meth = meths.keep_if{ |s| s.to_s == block_node.children[0].children[1].to_s }.first
|
685
|
-
meth
|
686
|
-
end
|
687
|
-
|
688
|
-
def get_yielding_method_with_yieldself block_node, scope_node
|
689
|
-
meth = get_yielding_method block_node, scope_node
|
690
|
-
if meth.nil? or meth.docstring.nil? or !meth.docstring.all.include?('@yieldself')
|
691
|
-
meth = nil
|
692
|
-
tree = @source.tree_for(block_node)
|
693
|
-
unless tree.nil?
|
694
|
-
tree.each do |p|
|
695
|
-
break if [:def, :defs, :class, :module, :sclass].include?(p.type)
|
696
|
-
return get_yielding_method_with_yieldself(p, scope_node) if p.type == :block
|
697
|
-
end
|
698
|
-
end
|
699
|
-
end
|
700
|
-
meth
|
701
|
-
end
|
702
|
-
|
703
|
-
# @param suggestions [Array<Solargraph::Suggestion>]
|
704
|
-
# @param word [String]
|
705
|
-
def reduce_starting_with(suggestions, word)
|
706
|
-
suggestions.reject { |s|
|
707
|
-
!s.label.start_with?(word)
|
708
|
-
}
|
709
|
-
end
|
710
|
-
|
711
|
-
# Find all the local variables in the node's scope.
|
712
|
-
#
|
713
|
-
# @return [Array<Solargraph::Suggestion>]
|
714
|
-
def get_local_variables_from(node)
|
715
|
-
node ||= @node
|
716
|
-
namespace = namespace_from(node)
|
717
|
-
arr = []
|
718
|
-
nil_pins = []
|
719
|
-
val_names = []
|
720
|
-
@source.local_variable_pins.select{|p| p.visible_from?(node) }.each do |pin|
|
721
|
-
if pin.nil_assignment? and pin.return_type.nil?
|
722
|
-
nil_pins.push pin
|
723
|
-
else
|
724
|
-
unless val_names.include?(pin.name)
|
725
|
-
arr.push Suggestion.pull(pin)
|
726
|
-
val_names.push pin.name
|
727
|
-
end
|
728
|
-
end
|
729
|
-
end
|
730
|
-
nil_pins.reject{|p| val_names.include?(p.name)}.each do |pin|
|
731
|
-
arr.push Suggestion.pull(pin)
|
732
|
-
end
|
733
|
-
arr
|
734
|
-
end
|
735
|
-
|
736
|
-
def inner_node_at(index, node, arr)
|
737
|
-
node.children.each do |c|
|
738
|
-
if c.kind_of?(AST::Node) and c.respond_to?(:loc)
|
739
|
-
unless c.loc.expression.nil?
|
740
|
-
if index >= c.loc.expression.begin_pos
|
741
|
-
if c.respond_to?(:end)
|
742
|
-
if index < c.end.end_pos
|
743
|
-
arr.unshift c
|
744
|
-
end
|
745
|
-
elsif index < c.loc.expression.end_pos
|
746
|
-
arr.unshift c
|
747
|
-
end
|
748
|
-
end
|
749
|
-
end
|
750
|
-
inner_node_at(index, c, arr)
|
751
|
-
end
|
752
|
-
end
|
753
|
-
end
|
754
|
-
|
755
|
-
def find_local_variable_node name, scope
|
756
|
-
scope.children.each { |c|
|
757
|
-
if c.kind_of?(AST::Node)
|
758
|
-
if c.type == :lvasgn and c.children[0].to_s == name
|
759
|
-
return c
|
760
|
-
else
|
761
|
-
unless [:class, :module, :def, :defs].include?(c.type)
|
762
|
-
sub = find_local_variable_node(name, c)
|
763
|
-
return sub unless sub.nil?
|
764
|
-
end
|
765
|
-
end
|
766
|
-
end
|
767
|
-
}
|
768
|
-
nil
|
769
|
-
end
|
770
|
-
|
771
|
-
def get_namespace_or_constant con, namespace
|
772
|
-
parts = con.split('::')
|
773
|
-
conc = parts.shift
|
774
|
-
result = nil
|
775
|
-
is_constant = false
|
776
|
-
while parts.length > 0
|
777
|
-
result = api_map.find_fully_qualified_namespace("#{conc}::#{parts[0]}", namespace)
|
778
|
-
if result.nil? or result.empty?
|
779
|
-
pin = api_map.get_constant_pins(conc, namespace).select{|s| s.name == parts[0]}.first
|
780
|
-
return nil if pin.nil?
|
781
|
-
result = pin.return_type || api_map.infer_assignment_node_type(pin.node, namespace)
|
782
|
-
break if result.nil?
|
783
|
-
is_constant = true
|
784
|
-
conc = result
|
785
|
-
parts.shift
|
786
|
-
else
|
787
|
-
is_constant = false
|
788
|
-
conc += "::#{parts.shift}"
|
789
|
-
end
|
790
|
-
end
|
791
|
-
return result if is_constant
|
792
|
-
end
|
793
|
-
|
794
|
-
def signature_index_before index
|
795
|
-
open_parens = 0
|
796
|
-
cursor = index - 1
|
797
|
-
while cursor >= 0
|
798
|
-
break if cursor < 0
|
799
|
-
if @code[cursor] == ')'
|
800
|
-
open_parens -= 1
|
801
|
-
elsif @code[cursor] == '('
|
802
|
-
open_parens += 1
|
803
|
-
end
|
804
|
-
break if open_parens == 1
|
805
|
-
cursor -= 1
|
806
|
-
end
|
807
|
-
cursor = nil if cursor < 0
|
808
|
-
cursor
|
809
|
-
end
|
810
|
-
|
811
|
-
def infer_path_from_signature_and_node signature, node
|
812
|
-
# @todo Improve this method
|
813
|
-
parts = signature.split('.')
|
814
|
-
last = parts.pop
|
815
|
-
type = infer_signature_from_node(parts.join('.'), node)
|
816
|
-
return nil if type.nil?
|
817
|
-
"#{type.gsub(/<[a-z0-9:, ]*>/i, '')}##{last}"
|
818
|
-
end
|
819
|
-
|
820
|
-
def get_subtypes type
|
821
|
-
return nil if type.nil?
|
822
|
-
match = type.match(/<([a-z0-9_:, ]*)>/i)
|
823
|
-
return [] if match.nil?
|
824
|
-
match[1].split(',').map(&:strip)
|
825
|
-
end
|
826
|
-
|
827
|
-
# @param template [String]
|
828
|
-
def convert_erb template
|
829
|
-
result = ''
|
830
|
-
i = 0
|
831
|
-
in_code = false
|
832
|
-
if template.start_with?('<%=')
|
833
|
-
i += 3
|
834
|
-
result += ';;;'
|
835
|
-
in_code = true
|
836
|
-
elsif template.start_with? '<%'
|
837
|
-
i += 2
|
838
|
-
result += ';;'
|
839
|
-
in_code = true
|
840
|
-
end
|
841
|
-
while i < template.length
|
842
|
-
if in_code
|
843
|
-
if template[i, 2] == '%>'
|
844
|
-
i += 2
|
845
|
-
result += ';;'
|
846
|
-
in_code = false
|
847
|
-
else
|
848
|
-
result += template[i]
|
849
|
-
end
|
850
|
-
else
|
851
|
-
if template[i, 3] == '<%='
|
852
|
-
i += 2
|
853
|
-
result += ';;;'
|
854
|
-
in_code = true
|
855
|
-
elsif template[i, 2] == '<%'
|
856
|
-
i += 1
|
857
|
-
result += ';;'
|
858
|
-
in_code = true
|
859
|
-
else
|
860
|
-
result += template[i].sub(/[^\s]/, ' ')
|
861
|
-
end
|
862
|
-
end
|
863
|
-
i += 1
|
864
|
-
end
|
865
|
-
result
|
866
|
-
end
|
867
|
-
end
|
868
|
-
end
|