solargraph 0.17.4 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|