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/shell.rb
CHANGED
@@ -3,6 +3,7 @@ require 'json'
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'rubygems/package'
|
5
5
|
require 'zlib'
|
6
|
+
require 'eventmachine'
|
6
7
|
|
7
8
|
module Solargraph
|
8
9
|
class Shell < Thor
|
@@ -39,6 +40,24 @@ module Solargraph
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
desc 'socket', 'Run a Solargraph socket server'
|
44
|
+
option :host, type: :string, aliases: :h, desc: 'The server host', default: '127.0.0.1'
|
45
|
+
option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7658
|
46
|
+
def socket
|
47
|
+
port = options[:port]
|
48
|
+
port = available_port if port.zero?
|
49
|
+
EventMachine.run do
|
50
|
+
Signal.trap("INT") do
|
51
|
+
EventMachine.stop
|
52
|
+
end
|
53
|
+
Signal.trap("TERM") do
|
54
|
+
EventMachine.stop
|
55
|
+
end
|
56
|
+
EventMachine.start_server options[:host], port, Solargraph::LanguageServer::Transport::Socket
|
57
|
+
STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
42
61
|
desc 'suggest', 'Get code suggestions for the provided input'
|
43
62
|
long_desc <<-LONGDESC
|
44
63
|
Analyze a Ruby file and output a list of code suggestions in JSON format.
|
@@ -80,7 +99,7 @@ module Solargraph
|
|
80
99
|
end
|
81
100
|
end
|
82
101
|
end
|
83
|
-
conf = Solargraph::
|
102
|
+
conf = Solargraph::Workspace::Config.new.raw_data
|
84
103
|
unless matches.empty?
|
85
104
|
matches.each do |m|
|
86
105
|
conf['extensions'].push m
|
@@ -0,0 +1,709 @@
|
|
1
|
+
require 'parser/current'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Solargraph
|
5
|
+
class Source
|
6
|
+
autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
|
7
|
+
autoload :Fragment, 'solargraph/source/fragment'
|
8
|
+
autoload :Position, 'solargraph/source/position'
|
9
|
+
autoload :Range, 'solargraph/source/range'
|
10
|
+
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :code
|
13
|
+
|
14
|
+
# @return [Parser::AST::Node]
|
15
|
+
attr_reader :node
|
16
|
+
|
17
|
+
# @return [Array]
|
18
|
+
attr_reader :comments
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
attr_reader :filename
|
22
|
+
|
23
|
+
# Get the file's modification time.
|
24
|
+
#
|
25
|
+
# @return [Time]
|
26
|
+
attr_reader :mtime
|
27
|
+
|
28
|
+
# @return [Array<Integer>]
|
29
|
+
attr_reader :stubbed_lines
|
30
|
+
|
31
|
+
attr_reader :directives
|
32
|
+
|
33
|
+
attr_reader :path_macros
|
34
|
+
|
35
|
+
attr_accessor :version
|
36
|
+
|
37
|
+
# Get the time of the last synchronization.
|
38
|
+
#
|
39
|
+
# @return [Time]
|
40
|
+
attr_reader :stime
|
41
|
+
|
42
|
+
include NodeMethods
|
43
|
+
|
44
|
+
def initialize code, node, comments, filename, stubbed_lines = []
|
45
|
+
@code = code
|
46
|
+
@fixed = code
|
47
|
+
@filename = filename
|
48
|
+
@stubbed_lines = stubbed_lines
|
49
|
+
@version = 0
|
50
|
+
process_parsed node, comments
|
51
|
+
end
|
52
|
+
|
53
|
+
def macro path
|
54
|
+
@path_macros[path]
|
55
|
+
end
|
56
|
+
|
57
|
+
def namespaces
|
58
|
+
@namespace_nodes.keys
|
59
|
+
end
|
60
|
+
|
61
|
+
def namespace_nodes
|
62
|
+
@namespace_nodes
|
63
|
+
end
|
64
|
+
|
65
|
+
def namespace_includes
|
66
|
+
@namespace_includes ||= {}
|
67
|
+
end
|
68
|
+
|
69
|
+
def namespace_extends
|
70
|
+
@namespaces_extends ||= {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def superclasses
|
74
|
+
@superclasses ||= {}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Array<Solargraph::Pin::Method>]
|
78
|
+
def method_pins
|
79
|
+
@method_pins ||= []
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Array<Solargraph::Pin::Attribute>]
|
83
|
+
def attribute_pins
|
84
|
+
@attribute_pins ||= []
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Array<Solargraph::Pin::InstanceVariable>]
|
88
|
+
def instance_variable_pins
|
89
|
+
@instance_variable_pins ||= []
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Array<Solargraph::Pin::ClassVariable>]
|
93
|
+
def class_variable_pins
|
94
|
+
@class_variable_pins ||= []
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Array<Solargraph::Pin::LocalVariable>]
|
98
|
+
def local_variable_pins
|
99
|
+
@local_variable_pins ||= []
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Array<Solargraph::Pin::GlobalVariable>]
|
103
|
+
def global_variable_pins
|
104
|
+
@global_variable_pins ||= []
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Array<Solargraph::Pin::Constant>]
|
108
|
+
def constant_pins
|
109
|
+
@constant_pins ||= []
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Array<Solargraph::Pin::Symbol>]
|
113
|
+
def symbol_pins
|
114
|
+
@symbol_pins ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Array<Solargraph::Pin::Namespace>]
|
118
|
+
def namespace_pins
|
119
|
+
@namespace_pins ||= []
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Array<String>]
|
123
|
+
def required
|
124
|
+
@required ||= []
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [YARD::Docstring]
|
128
|
+
def docstring_for node
|
129
|
+
return @docstring_hash[node.loc] if node.respond_to?(:loc)
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# @return [String]
|
134
|
+
def code_for node
|
135
|
+
b = node.location.expression.begin.begin_pos
|
136
|
+
e = node.location.expression.end.end_pos
|
137
|
+
frag = code[b..e-1].to_s
|
138
|
+
frag.strip.gsub(/,$/, '')
|
139
|
+
end
|
140
|
+
|
141
|
+
def tree_for node
|
142
|
+
@node_tree[node] || []
|
143
|
+
end
|
144
|
+
|
145
|
+
# Determine if the specified index is inside a string.
|
146
|
+
#
|
147
|
+
# @return [Boolean]
|
148
|
+
def string_at?(index)
|
149
|
+
n = node_at(index)
|
150
|
+
n.kind_of?(AST::Node) and (n.type == :str or n.type == :dstr)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the nearest node that contains the specified index.
|
154
|
+
#
|
155
|
+
# @param index [Integer]
|
156
|
+
# @return [AST::Node]
|
157
|
+
def node_at(index)
|
158
|
+
tree_at(index).first
|
159
|
+
end
|
160
|
+
|
161
|
+
# Get an array of nodes containing the specified index, starting with the
|
162
|
+
# nearest node and ending with the root.
|
163
|
+
#
|
164
|
+
# @param index [Integer]
|
165
|
+
# @return [Array<AST::Node>]
|
166
|
+
def tree_at(index)
|
167
|
+
arr = []
|
168
|
+
arr.push @node
|
169
|
+
inner_node_at(index, @node, arr)
|
170
|
+
arr
|
171
|
+
end
|
172
|
+
|
173
|
+
# Find the nearest parent node from the specified index. If one or more
|
174
|
+
# types are provided, find the nearest node whose type is in the list.
|
175
|
+
#
|
176
|
+
# @param index [Integer]
|
177
|
+
# @param types [Array<Symbol>]
|
178
|
+
# @return [AST::Node]
|
179
|
+
def parent_node_from(index, *types)
|
180
|
+
arr = tree_at(index)
|
181
|
+
arr.each { |a|
|
182
|
+
if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
|
183
|
+
return a
|
184
|
+
end
|
185
|
+
}
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [String]
|
190
|
+
def namespace_for node
|
191
|
+
parts = []
|
192
|
+
([node] + (@node_tree[node] || [])).each do |n|
|
193
|
+
next unless n.kind_of?(AST::Node)
|
194
|
+
if n.type == :class or n.type == :module
|
195
|
+
parts.unshift unpack_name(n.children[0])
|
196
|
+
end
|
197
|
+
end
|
198
|
+
parts.join('::')
|
199
|
+
end
|
200
|
+
|
201
|
+
def path_for node
|
202
|
+
path = namespace_for(node) || ''
|
203
|
+
mp = (method_pins + attribute_pins).select{|p| p.node == node}.first
|
204
|
+
unless mp.nil?
|
205
|
+
path += (mp.scope == :instance ? '#' : '.') + mp.name
|
206
|
+
end
|
207
|
+
path
|
208
|
+
end
|
209
|
+
|
210
|
+
def include? node
|
211
|
+
node_object_ids.include? node.object_id
|
212
|
+
end
|
213
|
+
|
214
|
+
def synchronize changes, version
|
215
|
+
changes.each do |change|
|
216
|
+
reparse change
|
217
|
+
end
|
218
|
+
@version = version
|
219
|
+
self
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_offset line, col
|
223
|
+
Source.get_offset(code, line, col)
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.get_offset text, line, col
|
227
|
+
offset = 0
|
228
|
+
if line > 0
|
229
|
+
text.lines[0..line - 1].each { |l|
|
230
|
+
offset += l.length
|
231
|
+
}
|
232
|
+
end
|
233
|
+
offset + col
|
234
|
+
end
|
235
|
+
|
236
|
+
def overwrite text
|
237
|
+
reparse({'text' => text})
|
238
|
+
end
|
239
|
+
|
240
|
+
def query_symbols query
|
241
|
+
return [] if query.empty?
|
242
|
+
down = query.downcase
|
243
|
+
all_symbols.select{|p| p.path.downcase.include?(down)}
|
244
|
+
end
|
245
|
+
|
246
|
+
def all_symbols
|
247
|
+
result = []
|
248
|
+
result.concat namespace_pins
|
249
|
+
result.concat method_pins
|
250
|
+
result.concat constant_pins
|
251
|
+
result
|
252
|
+
end
|
253
|
+
|
254
|
+
def locate_pin location
|
255
|
+
return nil unless location.start_with?("#{filename}:")
|
256
|
+
@all_pins.select{|pin| pin.location == location}.first
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [Solargraph::Source::Fragment]
|
260
|
+
def fragment_at line, column
|
261
|
+
Fragment.new(self, get_offset(line, column))
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def inner_node_at(index, node, arr)
|
267
|
+
node.children.each do |c|
|
268
|
+
if c.kind_of?(AST::Node) and c.respond_to?(:loc)
|
269
|
+
unless c.loc.expression.nil?
|
270
|
+
if index >= c.loc.expression.begin_pos
|
271
|
+
if c.respond_to?(:end)
|
272
|
+
if index < c.end.end_pos
|
273
|
+
arr.unshift c
|
274
|
+
end
|
275
|
+
elsif index < c.loc.expression.end_pos
|
276
|
+
arr.unshift c
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
inner_node_at(index, c, arr)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def reparse change
|
286
|
+
if change['range']
|
287
|
+
start_offset = Source.get_offset(@code, change['range']['start']['line'], change['range']['start']['character'])
|
288
|
+
end_offset = Source.get_offset(@code, change['range']['end']['line'], change['range']['end']['character'])
|
289
|
+
rewrite = (start_offset == 0 ? '' : @code[0..start_offset-1].to_s) + change['text'].force_encoding('utf-8') + @code[end_offset..-1].to_s
|
290
|
+
# return if @code == rewrite
|
291
|
+
again = true
|
292
|
+
if change['text'].match(/^[^a-z0-9\s]+?$/i)
|
293
|
+
tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
|
294
|
+
again = false
|
295
|
+
else
|
296
|
+
tmp = rewrite
|
297
|
+
end
|
298
|
+
@code = rewrite
|
299
|
+
begin
|
300
|
+
node, comments = Source.parse(tmp, filename)
|
301
|
+
process_parsed node, comments
|
302
|
+
@fixed = tmp
|
303
|
+
rescue Parser::SyntaxError => e
|
304
|
+
if again
|
305
|
+
again = false
|
306
|
+
tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
|
307
|
+
retry
|
308
|
+
else
|
309
|
+
@code = rewrite
|
310
|
+
hard_fix_node
|
311
|
+
end
|
312
|
+
end
|
313
|
+
else
|
314
|
+
tmp = change['text']
|
315
|
+
return if @code == tmp
|
316
|
+
@code = tmp
|
317
|
+
begin
|
318
|
+
node, comments = Source.parse(@code, filename)
|
319
|
+
process_parsed node, comments
|
320
|
+
@fixed = @code
|
321
|
+
rescue Parser::SyntaxError => e
|
322
|
+
hard_fix_node
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def hard_fix_node
|
328
|
+
tmp = @code.gsub(/[^\s]/, ' ')
|
329
|
+
@fixed = tmp
|
330
|
+
node, comments = Source.parse(tmp, filename)
|
331
|
+
process_parsed node, comments
|
332
|
+
end
|
333
|
+
|
334
|
+
def process_parsed node, comments
|
335
|
+
root = AST::Node.new(:source, [filename])
|
336
|
+
root = root.append node
|
337
|
+
@node = root
|
338
|
+
@comments = comments
|
339
|
+
@directives = {}
|
340
|
+
@path_macros = {}
|
341
|
+
@docstring_hash = associate_comments(node, comments)
|
342
|
+
@mtime = (!filename.nil? and File.exist?(filename) ? File.mtime(filename) : nil)
|
343
|
+
@namespace_nodes = {}
|
344
|
+
@all_nodes = []
|
345
|
+
@node_stack = []
|
346
|
+
@node_tree = {}
|
347
|
+
namespace_pins.clear
|
348
|
+
instance_variable_pins.clear
|
349
|
+
class_variable_pins.clear
|
350
|
+
local_variable_pins.clear
|
351
|
+
symbol_pins.clear
|
352
|
+
constant_pins.clear
|
353
|
+
method_pins.clear
|
354
|
+
namespace_includes.clear
|
355
|
+
namespace_extends.clear
|
356
|
+
superclasses.clear
|
357
|
+
attribute_pins.clear
|
358
|
+
@node_object_ids = nil
|
359
|
+
inner_map_node @node
|
360
|
+
@directives.each_pair do |k, v|
|
361
|
+
v.each do |d|
|
362
|
+
ns = namespace_for(k.node)
|
363
|
+
docstring = YARD::Docstring.parser.parse(d.tag.text).to_docstring
|
364
|
+
if d.tag.tag_name == 'attribute'
|
365
|
+
t = (d.tag.types.nil? || d.tag.types.empty?) ? nil : d.tag.types.flatten.join('')
|
366
|
+
if t.nil? or t.include?('r')
|
367
|
+
attribute_pins.push Solargraph::Pin::Directed::Attribute.new(self, k.node, ns, :reader, docstring, d.tag.name)
|
368
|
+
end
|
369
|
+
if t.nil? or t.include?('w')
|
370
|
+
attribute_pins.push Solargraph::Pin::Directed::Attribute.new(self, k.node, ns, :writer, docstring, "#{d.tag.name}=")
|
371
|
+
end
|
372
|
+
elsif d.tag.tag_name == 'method'
|
373
|
+
gen_src = Source.virtual("def #{d.tag.name};end", filename)
|
374
|
+
gen_pin = gen_src.method_pins.first
|
375
|
+
method_pins.push Solargraph::Pin::Directed::Method.new(gen_src, gen_pin.node, ns, :instance, :public, docstring, gen_pin.name)
|
376
|
+
elsif d.tag.tag_name == 'macro'
|
377
|
+
# @todo Handle various types of macros (attach, new, whatever)
|
378
|
+
path = path_for(k.node)
|
379
|
+
@path_macros[path] = v
|
380
|
+
else
|
381
|
+
STDERR.puts "Nothing to do for directive: #{d}"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
@all_pins = namespace_pins + instance_variable_pins + class_variable_pins + local_variable_pins + symbol_pins + constant_pins + method_pins + attribute_pins
|
386
|
+
@stime = Time.now
|
387
|
+
end
|
388
|
+
|
389
|
+
def associate_comments node, comments
|
390
|
+
return nil if comments.nil?
|
391
|
+
comment_hash = Parser::Source::Comment.associate_locations(node, comments)
|
392
|
+
yard_hash = {}
|
393
|
+
comment_hash.each_pair { |k, v|
|
394
|
+
ctxt = ''
|
395
|
+
num = nil
|
396
|
+
started = false
|
397
|
+
v.each { |l|
|
398
|
+
# Trim the comment and minimum leading whitespace
|
399
|
+
p = l.text.gsub(/^#/, '')
|
400
|
+
if num.nil? and !p.strip.empty?
|
401
|
+
num = p.index(/[^ ]/)
|
402
|
+
started = true
|
403
|
+
elsif started and !p.strip.empty?
|
404
|
+
cur = p.index(/[^ ]/)
|
405
|
+
num = cur if cur < num
|
406
|
+
end
|
407
|
+
if started
|
408
|
+
ctxt += "#{p[num..-1]}\n"
|
409
|
+
end
|
410
|
+
}
|
411
|
+
parse = YARD::Docstring.parser.parse(ctxt)
|
412
|
+
unless parse.directives.empty?
|
413
|
+
@directives[k] ||= []
|
414
|
+
@directives[k].concat parse.directives
|
415
|
+
end
|
416
|
+
yard_hash[k] = parse.to_docstring
|
417
|
+
}
|
418
|
+
yard_hash
|
419
|
+
end
|
420
|
+
|
421
|
+
def inner_map_node node, tree = [], visibility = :public, scope = :instance, fqn = nil, stack = []
|
422
|
+
stack.push node
|
423
|
+
source = self
|
424
|
+
if node.kind_of?(AST::Node)
|
425
|
+
# @node_tree[node] = @node_stack.clone
|
426
|
+
@all_nodes.push node
|
427
|
+
# if node.type == :str or node.type == :dstr
|
428
|
+
# stack.pop
|
429
|
+
# return
|
430
|
+
# end
|
431
|
+
@node_stack.unshift node
|
432
|
+
if node.type == :class or node.type == :module
|
433
|
+
visibility = :public
|
434
|
+
if node.children[0].kind_of?(AST::Node) and node.children[0].children[0].kind_of?(AST::Node) and node.children[0].children[0].type == :cbase
|
435
|
+
tree = pack_name(node.children[0])
|
436
|
+
else
|
437
|
+
tree = tree + pack_name(node.children[0])
|
438
|
+
end
|
439
|
+
fqn = tree.join('::')
|
440
|
+
@namespace_nodes[fqn] ||= []
|
441
|
+
@namespace_nodes[fqn].push node
|
442
|
+
namespace_pins.push Solargraph::Pin::Namespace.new(self, node, tree[0..-2].join('::') || '', :public)
|
443
|
+
if node.type == :class and !node.children[1].nil?
|
444
|
+
sc = unpack_name(node.children[1])
|
445
|
+
superclasses[fqn] = sc
|
446
|
+
end
|
447
|
+
end
|
448
|
+
file = source.filename
|
449
|
+
node.children.each do |c|
|
450
|
+
if c.kind_of?(AST::Node)
|
451
|
+
@node_tree[c] = @node_stack.clone
|
452
|
+
if c.type == :ivasgn
|
453
|
+
par = find_parent(stack, :class, :module, :def, :defs)
|
454
|
+
local_scope = ( (par.kind_of?(AST::Node) and par.type == :def) ? :instance : :class )
|
455
|
+
if c.children[1].nil?
|
456
|
+
ora = find_parent(stack, :or_asgn)
|
457
|
+
unless ora.nil?
|
458
|
+
u = c.updated(:ivasgn, c.children + ora.children[1..-1], nil)
|
459
|
+
@all_nodes.push u
|
460
|
+
@node_tree[u] = @node_stack.clone
|
461
|
+
@docstring_hash[u.loc] = docstring_for(ora)
|
462
|
+
instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, u, fqn || '', local_scope)
|
463
|
+
end
|
464
|
+
else
|
465
|
+
instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, c, fqn || '', local_scope)
|
466
|
+
end
|
467
|
+
elsif c.type == :cvasgn
|
468
|
+
if c.children[1].nil?
|
469
|
+
ora = find_parent(stack, :or_asgn)
|
470
|
+
unless ora.nil?
|
471
|
+
u = c.updated(:cvasgn, c.children + ora.children[1..-1], nil)
|
472
|
+
@node_tree[u] = @node_stack.clone
|
473
|
+
@all_nodes.push u
|
474
|
+
@docstring_hash[u.loc] = docstring_for(ora)
|
475
|
+
class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, u, fqn || '')
|
476
|
+
end
|
477
|
+
else
|
478
|
+
class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, c, fqn || '')
|
479
|
+
end
|
480
|
+
elsif c.type == :lvasgn
|
481
|
+
if c.children[1].nil?
|
482
|
+
ora = find_parent(stack, :or_asgn)
|
483
|
+
unless ora.nil?
|
484
|
+
u = c.updated(:lvasgn, c.children + ora.children[1..-1], nil)
|
485
|
+
@all_nodes.push u
|
486
|
+
@node_tree[u] = @node_stack.clone
|
487
|
+
@docstring_hash[u.loc] = docstring_for(ora)
|
488
|
+
local_variable_pins.push Solargraph::Pin::LocalVariable.new(self, u, fqn || '', @node_stack.clone)
|
489
|
+
end
|
490
|
+
else
|
491
|
+
@node_tree[c] = @node_stack.clone
|
492
|
+
local_variable_pins.push Solargraph::Pin::LocalVariable.new(self, c, fqn || '', @node_stack.clone)
|
493
|
+
end
|
494
|
+
elsif c.type == :gvasgn
|
495
|
+
global_variable_pins.push Solargraph::Pin::GlobalVariable.new(self, c, fqn || '')
|
496
|
+
elsif c.type == :sym
|
497
|
+
symbol_pins.push Solargraph::Pin::Symbol.new(self, c, fqn)
|
498
|
+
elsif c.type == :casgn
|
499
|
+
constant_pins.push Solargraph::Pin::Constant.new(self, c, fqn, :public)
|
500
|
+
elsif c.type == :def and c.children[0].to_s[0].match(/[a-z]/i)
|
501
|
+
method_pins.push Solargraph::Pin::Method.new(source, c, fqn || '', scope, visibility)
|
502
|
+
elsif c.type == :defs
|
503
|
+
s_visi = visibility
|
504
|
+
s_visi = :public if scope != :class
|
505
|
+
if c.children[0].is_a?(AST::Node) and c.children[0].type == :self
|
506
|
+
dfqn = fqn || ''
|
507
|
+
else
|
508
|
+
dfqn = unpack_name(c.children[0])
|
509
|
+
end
|
510
|
+
unless dfqn.nil?
|
511
|
+
method_pins.push Solargraph::Pin::Method.new(source, c, dfqn, :class, s_visi)
|
512
|
+
inner_map_node c, tree, scope, :class, dfqn, stack
|
513
|
+
end
|
514
|
+
next
|
515
|
+
elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
|
516
|
+
visibility = c.children[1]
|
517
|
+
elsif c.type == :send and [:private_class_method].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
|
518
|
+
if c.children[2].type == :sym or c.children[2].type == :str
|
519
|
+
ref = method_pins.select{|p| p.name == c.children[2].children[0].to_s}.first
|
520
|
+
unless ref.nil?
|
521
|
+
source.method_pins.delete ref
|
522
|
+
source.method_pins.push Solargraph::Pin::Method.new(ref.source, ref.node, ref.namespace, ref.scope, :private)
|
523
|
+
end
|
524
|
+
else
|
525
|
+
inner_map_node c, tree, :private, :class, fqn, stack
|
526
|
+
next
|
527
|
+
end
|
528
|
+
elsif c.type == :send and [:private_constant].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
|
529
|
+
if c.children[2].type == :sym or c.children[2].type == :str
|
530
|
+
cn = c.children[2].children[0].to_s
|
531
|
+
ref = constant_pins.select{|p| p.name == cn}.first
|
532
|
+
if ref.nil?
|
533
|
+
ref = namespace_pins.select{|p| p.name == cn}.first
|
534
|
+
unless ref.nil?
|
535
|
+
source.namespace_pins.delete ref
|
536
|
+
source.namespace_pins.push Solargraph::Pin::Namespace.new(ref.source, ref.node, ref.namespace, :private)
|
537
|
+
end
|
538
|
+
else
|
539
|
+
source.constant_pins.delete ref
|
540
|
+
source.constant_pins.push Solargraph::Pin::Constant.new(ref.source, ref.node, ref.namespace, :private)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
next
|
544
|
+
elsif c.type == :send and c.children[1] == :include
|
545
|
+
namespace_includes[fqn] ||= []
|
546
|
+
c.children[2..-1].each do |i|
|
547
|
+
namespace_includes[fqn].push unpack_name(i)
|
548
|
+
end
|
549
|
+
elsif c.type == :send and c.children[1] == :extend
|
550
|
+
namespace_extends[fqn] ||= []
|
551
|
+
c.children[2..-1].each do |i|
|
552
|
+
namespace_extends[fqn].push unpack_name(i)
|
553
|
+
end
|
554
|
+
elsif c.type == :send and [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
|
555
|
+
c.children[2..-1].each do |a|
|
556
|
+
if c.children[1] == :attr_reader or c.children[1] == :attr_accessor
|
557
|
+
attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn || '', :reader, docstring_for(c)) #AttrPin.new(c)
|
558
|
+
end
|
559
|
+
if c.children[1] == :attr_writer or c.children[1] == :attr_accessor
|
560
|
+
attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn || '', :writer, docstring_for(c)) #AttrPin.new(c)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
elsif c.type == :sclass and c.children[0].type == :self
|
564
|
+
inner_map_node c, tree, :public, :class, fqn || '', stack
|
565
|
+
next
|
566
|
+
elsif c.type == :send and c.children[1] == :require
|
567
|
+
if c.children[2].kind_of?(AST::Node) and c.children[2].type == :str
|
568
|
+
required.push c.children[2].children[0].to_s
|
569
|
+
end
|
570
|
+
elsif c.type == :args
|
571
|
+
if @node_stack.first.type == :block
|
572
|
+
pi = 0
|
573
|
+
c.children.each do |u|
|
574
|
+
local_variable_pins.push Solargraph::Pin::BlockParameter.new(self, u, fqn || '', @node_stack.clone, pi)
|
575
|
+
pi += 1
|
576
|
+
end
|
577
|
+
else
|
578
|
+
c.children.each do |u|
|
579
|
+
local_variable_pins.push Solargraph::Pin::MethodParameter.new(self, u, fqn || '', @node_stack.clone)
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
inner_map_node c, tree, visibility, scope, fqn, stack
|
584
|
+
end
|
585
|
+
end
|
586
|
+
@node_stack.shift
|
587
|
+
end
|
588
|
+
stack.pop
|
589
|
+
end
|
590
|
+
|
591
|
+
def find_parent(stack, *types)
|
592
|
+
stack.reverse.each { |p|
|
593
|
+
return p if types.include?(p.type)
|
594
|
+
}
|
595
|
+
nil
|
596
|
+
end
|
597
|
+
|
598
|
+
def node_object_ids
|
599
|
+
@node_object_ids ||= @all_nodes.map(&:object_id)
|
600
|
+
end
|
601
|
+
|
602
|
+
class << self
|
603
|
+
# @return [Solargraph::Source]
|
604
|
+
def load filename
|
605
|
+
code = File.read(filename)
|
606
|
+
Source.load_string(code, filename)
|
607
|
+
end
|
608
|
+
|
609
|
+
# @deprecated Use load_string instead
|
610
|
+
def virtual code, filename = nil
|
611
|
+
load_string code, filename
|
612
|
+
end
|
613
|
+
|
614
|
+
# @return [Solargraph::Source]
|
615
|
+
def load_string code, filename = nil
|
616
|
+
begin
|
617
|
+
node, comments = parse(code, filename)
|
618
|
+
Source.new(code, node, comments, filename)
|
619
|
+
rescue Parser::SyntaxError => e
|
620
|
+
tmp = code.gsub(/[^ \t\r\n]/, ' ')
|
621
|
+
node, comments = parse(tmp, filename)
|
622
|
+
Source.new(code, node, comments, filename)
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
def get_position_at(code, offset)
|
627
|
+
cursor = 0
|
628
|
+
line = 0
|
629
|
+
col = nil
|
630
|
+
code.each_line do |l|
|
631
|
+
if cursor + l.length > offset
|
632
|
+
col = offset - cursor
|
633
|
+
break
|
634
|
+
end
|
635
|
+
if cursor + l.length == offset
|
636
|
+
if l.end_with?("\n")
|
637
|
+
col = 0
|
638
|
+
line += 1
|
639
|
+
break
|
640
|
+
else
|
641
|
+
col = l.length
|
642
|
+
break
|
643
|
+
end
|
644
|
+
end
|
645
|
+
# if cursor + l.length - 1 == offset and !l.end_with?("\n")
|
646
|
+
# col = l.length - 1
|
647
|
+
# break
|
648
|
+
# end
|
649
|
+
cursor += l.length
|
650
|
+
line += 1
|
651
|
+
end
|
652
|
+
raise "Invalid offset" if col.nil?
|
653
|
+
[line, col]
|
654
|
+
end
|
655
|
+
|
656
|
+
def parse code, filename = nil
|
657
|
+
parser = Parser::CurrentRuby.new(FlawedBuilder.new)
|
658
|
+
parser.diagnostics.all_errors_are_fatal = true
|
659
|
+
parser.diagnostics.ignore_warnings = true
|
660
|
+
buffer = Parser::Source::Buffer.new(filename, 1)
|
661
|
+
buffer.source = code
|
662
|
+
parser.parse_with_comments(buffer)
|
663
|
+
end
|
664
|
+
|
665
|
+
def fix code, filename = nil, offset = nil
|
666
|
+
tries = 0
|
667
|
+
# code.gsub!(/\r/, '')
|
668
|
+
offset = Source.get_offset(code, offset[0], offset[1]) if offset.kind_of?(Array)
|
669
|
+
pos = nil
|
670
|
+
pos = get_position_at(code, offset) unless offset.nil?
|
671
|
+
stubs = []
|
672
|
+
fixed_position = false
|
673
|
+
tmp = code.sub(/\.(\s*\z)$/, ' \1')
|
674
|
+
begin
|
675
|
+
node, comments = Source.parse(tmp, filename)
|
676
|
+
Source.new(code, node, comments, filename, stubs)
|
677
|
+
rescue Parser::SyntaxError => e
|
678
|
+
if tries < 10
|
679
|
+
tries += 1
|
680
|
+
# Stub periods before the offset to retain the expected node tree
|
681
|
+
if !offset.nil? and ['.', '{', '('].include?(tmp[offset-1])
|
682
|
+
tmp = tmp[0, offset-1] + ';' + tmp[offset..-1]
|
683
|
+
elsif !fixed_position and !offset.nil?
|
684
|
+
fixed_position = true
|
685
|
+
beg = beginning_of_line_from(tmp, offset)
|
686
|
+
tmp = "#{tmp[0, beg]}##{tmp[beg+1..-1]}"
|
687
|
+
stubs.push(pos[0])
|
688
|
+
elsif e.message.include?('token $end')
|
689
|
+
tmp += "\nend"
|
690
|
+
elsif e.message.include?("unexpected `@'")
|
691
|
+
tmp = tmp[0, e.diagnostic.location.begin_pos] + '_' + tmp[e.diagnostic.location.begin_pos+1..-1]
|
692
|
+
end
|
693
|
+
retry
|
694
|
+
end
|
695
|
+
STDERR.puts "Unable to parse file #{filename.nil? ? 'undefined' : filename}: #{e.message}"
|
696
|
+
node, comments = parse(code.gsub(/[^\s]/, ' '), filename)
|
697
|
+
Source.new(code, node, comments, filename)
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
def beginning_of_line_from str, i
|
702
|
+
while i > 0 and str[i-1] != "\n"
|
703
|
+
i -= 1
|
704
|
+
end
|
705
|
+
i
|
706
|
+
end
|
707
|
+
end
|
708
|
+
end
|
709
|
+
end
|