solargraph 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: acab722ec6710f43f14625320985dbcd30dd2e19
4
+ data.tar.gz: c19b6542c62b928a4b585df460a131e70ab58912
5
+ SHA512:
6
+ metadata.gz: e310d1270047cd3dbd9f21c4249efeaf3342417233bf1484f0e4cb86d944b1a6c24bf900a4a00580c86413f9b130f51289a705579e533f5bbdeab02958b13b49
7
+ data.tar.gz: 6815c3a16e85e7246e86ca3fe7358b4732b91fac3a09867fd149c32d9be7903436ac10f72650ddfb472164ca0be7bd322822da8be43385261844a9c581f109c5
data/bin/solargraph ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift '/home/fred/solargraph-ruby/lib'
4
+ require 'solargraph'
5
+
6
+ Solargraph::Shell.start(ARGV)
data/lib/solargraph.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'solargraph/version'
2
+
3
+ module Solargraph
4
+ autoload :Analyzer, 'solargraph/analyzer'
5
+ autoload :Shell, 'solargraph/shell'
6
+ autoload :LiveParser, 'solargraph/live_parser'
7
+ autoload :ApiMap, 'solargraph/api_map'
8
+ autoload :CodeMap, 'solargraph/code_map'
9
+ autoload :NodeMethods, 'solargraph/node_methods'
10
+ autoload :Suggestion, 'solargraph/suggestion'
11
+ autoload :Snippets, 'solargraph/snippets'
12
+ autoload :Mapper, 'solargraph/mapper'
13
+ autoload :Server, 'solargraph/server'
14
+ autoload :YardMap, 'solargraph/yard_map'
15
+
16
+ YARDOC_PATH = File.realpath(File.dirname(__FILE__) + "/../yardoc")
17
+ end
18
+
19
+ # Make sure the core and stdlib documentation is available
20
+ cache_dir = File.join(Dir.home, '.solargraph', 'cache')
21
+ version_dir = File.join(cache_dir, '2.0.0')
22
+ unless File.exist?(version_dir)
23
+ FileUtils.mkdir_p cache_dir
24
+ FileUtils.cp File.join(Solargraph::YARDOC_PATH, '2.0.0.tar.gz')
25
+ tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(File.join(cache_dir, '2.0.0.tar.gz')))
26
+ tar_extract.rewind
27
+ tar_extract.each do |entry|
28
+ if entry.directory?
29
+ FileUtils.mkdir_p File.join(cache_dir, entry.full_name)
30
+ else
31
+ FileUtils.mkdir_p File.join(cache_dir, File.dirname(entry.full_name))
32
+ File.open(File.join(cache_dir, entry.full_name), 'wb') do |f|
33
+ f << entry.read
34
+ end
35
+ end
36
+ end
37
+ tar_extract.close
38
+ FileUtils.rm File.join(cache_dir, '2.0.0.tar.gz')
39
+ end
@@ -0,0 +1,538 @@
1
+ require 'rubygems'
2
+ require 'parser/current'
3
+ require 'yard'
4
+
5
+ module Solargraph
6
+ class ApiMap
7
+ KEYWORDS = [
8
+ '__ENCODING__', '__LINE__', '__FILE__', 'BEGIN', 'END', 'alias', 'and',
9
+ 'begin', 'break', 'case', 'class', 'def', 'defined?', 'do', 'else',
10
+ 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in', 'module', 'next',
11
+ 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return', 'self', 'super',
12
+ 'then', 'true', 'undef', 'unless', 'until', 'when', 'while', 'yield'
13
+ ]
14
+
15
+ MAPPABLE_METHODS = [
16
+ :include, :require, :autoload, :attr_reader, :attr_writer, :attr_accessor, :private, :public, :protected
17
+ ]
18
+ include NodeMethods
19
+
20
+ attr_reader :workspace
21
+
22
+ def initialize workspace = nil
23
+ @workspace = workspace
24
+ #process_workspace
25
+ clear
26
+ end
27
+
28
+ def clear
29
+ @file_nodes = {}
30
+ @file_comments = {}
31
+ @parent_stack = {}
32
+ @namespace_map = {}
33
+ @namespace_tree = {}
34
+ #@pending_requires = []
35
+ @required = []
36
+ end
37
+
38
+ #def process_workspace
39
+ # clear
40
+ # return if @workspace.nil?
41
+ # process_files
42
+ # process_requires
43
+ # process_maps
44
+ #end
45
+
46
+ def append_file filename
47
+ append_source File.read(filename), filename
48
+ end
49
+
50
+ def append_source text, filename = nil
51
+ node, comments = Parser::CurrentRuby.parse_with_comments(text)
52
+ append_node(node, comments, filename)
53
+ end
54
+
55
+ def append_node node, comments, filename = nil
56
+ mapified = mapify(node)
57
+ root = AST::Node.new(:begin, [filename])
58
+ mapified.children.each { |c|
59
+ root = root.append c
60
+ }
61
+ @file_nodes[filename] = root
62
+ @file_comments[filename] = associate_comments(mapified, comments)
63
+ @required.uniq!
64
+ process_maps
65
+ end
66
+
67
+ def associate_comments node, comments
68
+ comment_hash = Parser::Source::Comment.associate(node, comments)
69
+ yard_hash = {}
70
+ comment_hash.each_pair { |k, v|
71
+ #ctxt = v.map(&:text).join("\r\n")
72
+ ctxt = ''
73
+ v.each { |l|
74
+ ctxt += l.text.gsub(/^#/, '') + "\n"
75
+ }
76
+ parser = YARD::DocstringParser.new
77
+ yard_hash[k] = parser.parse(ctxt).to_docstring
78
+ }
79
+ yard_hash
80
+ end
81
+
82
+ def get_comment_for node
83
+ filename = get_filename_for(node)
84
+ @file_comments[filename][node] unless @file_comments[filename].nil?
85
+ end
86
+
87
+ def self.get_keywords without_snippets: false
88
+ result = []
89
+ keywords = KEYWORDS
90
+ keywords -= Snippets.keywords if without_snippets
91
+ keywords.each { |k|
92
+ result.push Suggestion.new(k, kind: Suggestion::KEYWORD, detail: 'Keyword')
93
+ }
94
+ result
95
+ end
96
+
97
+ def process_maps
98
+ @parent_stack = {}
99
+ @namespace_map = {}
100
+ @namespace_tree = {}
101
+ @file_nodes.values.each { |f|
102
+ map_parents f #AST::Node.new(:tmp, @file_nodes.values)
103
+ map_namespaces f #AST::Node.new(:tmp, @file_nodes.values)
104
+ }
105
+ end
106
+
107
+ def namespaces
108
+ @namespace_map.keys
109
+ end
110
+
111
+ def namespace_exists? name, root = ''
112
+ !find_fully_qualified_namespace(name, root).nil?
113
+ end
114
+
115
+ def namespaces_in name, root = '' #, skip = []
116
+ result = []
117
+ result += inner_namespaces_in(name, root, [])
118
+ yard = YardMap.new(required: @required, workspace: @workspace)
119
+ result += yard.get_constants name, root
120
+ fqns = find_fully_qualified_namespace(name, root)
121
+ unless fqns.nil?
122
+ nodes = get_namespace_nodes(fqns)
123
+ get_include_strings_from(*nodes).each { |i|
124
+ result += yard.get_constants(i, root)
125
+ }
126
+ end
127
+ result
128
+ end
129
+
130
+ def inner_namespaces_in name, root, skip
131
+ result = []
132
+ fqns = find_fully_qualified_namespace(name, root)
133
+ if fqns.nil?
134
+ return result
135
+ else
136
+ return result if skip.include?(fqns)
137
+ skip.push fqns
138
+ cursor = @namespace_tree
139
+ parts = fqns.split('::')
140
+ parts.each { |p|
141
+ cursor = cursor[p]
142
+ }
143
+ unless cursor.nil?
144
+ cursor.keys.each { |k|
145
+ result.push Suggestion.new(k, kind: Suggestion::CLASS)
146
+ }
147
+ nodes = get_namespace_nodes(fqns)
148
+ nodes.each { |n|
149
+ get_include_strings_from(n).each { |i|
150
+ result += inner_namespaces_in(i, fqns, skip)
151
+ }
152
+ }
153
+ end
154
+ end
155
+ result
156
+ end
157
+
158
+ def find_fully_qualified_namespace name, root = '', skip = []
159
+ return nil if skip.include?(root)
160
+ skip.push root
161
+ if name == ''
162
+ if root == ''
163
+ return ''
164
+ else
165
+ return find_fully_qualified_namespace(root, '', skip)
166
+ end
167
+ else
168
+ if (root == '')
169
+ return name unless @namespace_map[name].nil?
170
+ get_include_strings_from(*@file_nodes.values).each { |i|
171
+ reroot = "#{root == '' ? '' : root + '::'}#{i}"
172
+ recname = find_fully_qualified_namespace name, reroot, skip
173
+ return recname unless recname.nil?
174
+ }
175
+ else
176
+ roots = root.to_s.split('::')
177
+ while roots.length > 0
178
+ fqns = roots.join('::') + '::' + name
179
+ return fqns unless @namespace_map[fqns].nil?
180
+ roots.pop
181
+ end
182
+ return name unless @namespace_map[name].nil?
183
+ get_include_strings_from(*@file_nodes.values).each { |i|
184
+ recname = find_fully_qualified_namespace name, i, skip
185
+ return recname unless recname.nil?
186
+ }
187
+ end
188
+ end
189
+ nil
190
+ end
191
+
192
+ def get_namespace_nodes(fqns)
193
+ return @file_nodes.values if fqns == ''
194
+ @namespace_map[fqns] || []
195
+ end
196
+
197
+ def get_instance_variables(namespace, scope = :instance)
198
+ nodes = get_namespace_nodes(namespace) || @file_nodes.values
199
+ arr = []
200
+ nodes.each { |n|
201
+ arr += inner_get_instance_variables(n, scope)
202
+ }
203
+ arr
204
+ end
205
+
206
+ def find_parent(node, *types)
207
+ parents = @parent_stack[node]
208
+ parents.each { |p|
209
+ return p if types.include?(p.type)
210
+ }
211
+ nil
212
+ end
213
+
214
+ def get_root_for(node)
215
+ @parent_stack[node].last unless @parent_stack[node].nil?
216
+ end
217
+
218
+ def get_filename_for(node)
219
+ root = get_root_for(node)
220
+ root.children[0]
221
+ end
222
+
223
+ def inner_get_instance_variables(node, scope)
224
+ arr = []
225
+ if node.kind_of?(AST::Node)
226
+ node.children.each { |c|
227
+ if c.kind_of?(AST::Node)
228
+ #next if [:class, :module].include?(c.type)
229
+ is_inst = !find_parent(c, :def).nil?
230
+ if c.type == :ivasgn and ( (scope == :instance and is_inst) or (scope != :instance and !is_inst) )
231
+ arr.push Suggestion.new(c.children[0], kind: Suggestion::VARIABLE)
232
+ end
233
+ arr += inner_get_instance_variables(c, scope) unless [:class, :module].include?(c.type)
234
+ end
235
+ }
236
+ end
237
+ arr
238
+ end
239
+
240
+ def infer_instance_variable(var, namespace, scope = :instance)
241
+ vn = nil
242
+ if namespace_exists?(namespace)
243
+ get_namespace_nodes(namespace).each { |node|
244
+ vn = find_instance_variable_assignment(var, node, scope)
245
+ break unless vn.nil?
246
+ }
247
+ end
248
+ infer(vn.children[1]) unless vn.nil?
249
+ end
250
+
251
+ def find_instance_variable_assignment(var, node, scope)
252
+ node.children.each { |c|
253
+ if c.kind_of?(AST::Node)
254
+ is_inst = !find_parent(c, :def).nil?
255
+ if c.type == :ivasgn and ( (scope == :instance and is_inst) or (scope != :instance and !is_inst) )
256
+ if c.children[0].to_s == var
257
+ return c
258
+ end
259
+ else
260
+ inner = find_instance_variable_assignment(var, c, scope)
261
+ return inner unless inner.nil?
262
+ end
263
+ end
264
+ }
265
+ nil
266
+ end
267
+
268
+ def get_global_variables
269
+ # TODO I bet these aren't getting mapped at all. Damn.
270
+ []
271
+ end
272
+
273
+ def get_namespace_type namespace, root = ''
274
+ type = nil
275
+ fqns = find_fully_qualified_namespace(namespace, root)
276
+ nodes = get_namespace_nodes(fqns)
277
+ unless nodes.nil? or nodes.empty? or !nodes[0].kind_of?(AST::Node)
278
+ type = nodes[0].type if [:class, :module].include?(nodes[0].type)
279
+ end
280
+ type
281
+ end
282
+
283
+ def get_methods(namespace, root = '')
284
+ meths = inner_get_methods(namespace, root, [])
285
+ yard = YardMap.new(required: @required, workspace: @workspace)
286
+ meths += yard.get_methods(namespace, root)
287
+ type = get_namespace_type(namespace, root)
288
+ if type == :class
289
+ meths += yard.get_instance_methods('Class')
290
+ elsif type == :module
291
+ meths += yard.get_methods('Module')
292
+ end
293
+ meths
294
+ end
295
+
296
+ def inner_get_methods(namespace, root = '', skip = [])
297
+ meths = []
298
+ return meths if skip.include?(namespace)
299
+ skip.push namespace
300
+ fqns = find_fully_qualified_namespace(namespace, root)
301
+ return meths if fqns.nil?
302
+ nodes = get_namespace_nodes(fqns)
303
+ nodes.each { |n|
304
+ if n.kind_of?(AST::Node)
305
+ if n.type == :class and !n.children[1].nil?
306
+ s = unpack_name(n.children[1])
307
+ meths += inner_get_methods(s, root, skip)
308
+ end
309
+ n.children.each { |c|
310
+ if c.kind_of?(AST::Node) and c.type == :defs
311
+ docstring = get_comment_for(c)
312
+ meths.push Suggestion.new(c.children[1], kind: Suggestion::METHOD, documentation: docstring) if c.children[1].to_s[0].match(/[a-z_]/i) and c.children[1] != :def
313
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :include
314
+ # TODO This might not be right. Should we be getting singleton methods
315
+ # from an include, or only from an extend?
316
+ i = unpack_name(c.children[2])
317
+ meths += inner_get_methods(i, root, skip) unless i == 'Kernel'
318
+ end
319
+ }
320
+ end
321
+ }
322
+ meths.uniq
323
+ end
324
+
325
+ def get_instance_methods(namespace, root = '')
326
+ meths = inner_get_instance_methods(namespace, root, [])
327
+ yard = YardMap.new(required: @required, workspace: @workspace)
328
+ type = get_namespace_type(namespace, root)
329
+ if type == :class
330
+ meths += yard.get_instance_methods('Object')
331
+ elsif type == :module
332
+ meths += yard.get_instance_methods('Module')
333
+ end
334
+ meths += yard.get_instance_methods(namespace, root)
335
+ sc = get_superclass(namespace, root)
336
+ until sc.nil?
337
+ meths += yard.get_instance_methods(sc, root)
338
+ sc = get_superclass(sc)
339
+ end
340
+ meths
341
+ end
342
+
343
+ def get_superclass(namespace, root = '')
344
+ fqns = find_fully_qualified_namespace(namespace, root)
345
+ nodes = get_namespace_nodes(fqns)
346
+ nodes.each { |n|
347
+ if n.kind_of?(AST::Node)
348
+ if n.type == :class and !n.children[1].nil?
349
+ return unpack_name(n.children[1])
350
+ end
351
+ end
352
+ }
353
+ return nil
354
+ end
355
+
356
+ def inner_get_instance_methods(namespace, root, skip)
357
+ fqns = find_fully_qualified_namespace(namespace, root)
358
+ meths = []
359
+ return meths if skip.include?(fqns)
360
+ skip.push fqns
361
+ nodes = get_namespace_nodes(fqns)
362
+ nodes.each { |n|
363
+ if n.kind_of?(AST::Node)
364
+ if n.type == :class and !n.children[1].nil?
365
+ s = unpack_name(n.children[1])
366
+ meths += inner_get_instance_methods(s, namespace, skip)
367
+ end
368
+ current_scope = :public
369
+ n.children.each { |c|
370
+ if c.kind_of?(AST::Node) and c.type == :send and [:public, :protected, :private].include?(c.children[1])
371
+ # TODO: Determine the current scope so we can decide whether to
372
+ # exclude protected or private methods. Right now we're just
373
+ # assuming public only
374
+ elsif current_scope == :public
375
+ if c.kind_of?(AST::Node) and c.type == :def
376
+ cmnt = get_comment_for(c)
377
+ meths.push Suggestion.new(c.children[0], kind: Suggestion::METHOD, documentation: cmnt) if c.children[0].to_s[0].match(/[a-z]/i)
378
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_reader
379
+ c.children[2..-1].each { |x|
380
+ meths.push Suggestion.new(x.children[0], kind: Suggestion::METHOD) if x.type == :sym
381
+ }
382
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_writer
383
+ c.children[2..-1].each { |x|
384
+ meths.push Suggestion.new("#{x.children[0]}=", kind: Suggestion::METHOD) if x.type == :sym
385
+ }
386
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_accessor
387
+ c.children[2..-1].each { |x|
388
+ meths.push Suggestion.new(x.children[0], kind: Suggestion::METHOD) if x.type == :sym
389
+ meths.push Suggestion.new("#{x.children[0]}=", kind: Suggestion::METHOD) if x.type == :sym
390
+ }
391
+ end
392
+ end
393
+ get_include_strings_from(n).each { |i|
394
+ meths += inner_get_instance_methods(i, fqns, skip)
395
+ }
396
+ }
397
+ end
398
+ }
399
+ meths.uniq
400
+ end
401
+
402
+ def self.current
403
+ if @current.nil?
404
+ @current = ApiMap.new
405
+ @current.merge(Parser::CurrentRuby.parse(File.read("#{Solargraph::STUB_PATH}/ruby/2.3.0/core.rb")))
406
+ end
407
+ @current
408
+ end
409
+
410
+ def get_include_strings_from *nodes
411
+ arr = []
412
+ nodes.each { |node|
413
+ next unless node.kind_of?(AST::Node)
414
+ arr.push unpack_name(node.children[2]) if (node.type == :send and node.children[1] == :include)
415
+ node.children.each { |n|
416
+ arr += get_include_strings_from(n) if n.kind_of?(AST::Node) and n.type != :class and n.type != :module
417
+ }
418
+ }
419
+ arr
420
+ end
421
+
422
+ private
423
+
424
+ def mapify node
425
+ root = node
426
+ if !root.kind_of?(AST::Node) or root.type != :begin
427
+ root = AST::Node.new(:begin, [node], {})
428
+ end
429
+ root = reduce root
430
+ root
431
+ end
432
+
433
+ def mappable?(node)
434
+ # TODO Add node.type :casgn (constant assignment)
435
+ if node.kind_of?(AST::Node) and (node.type == :class or node.type == :module or node.type == :def or node.type == :defs or node.type == :ivasgn or node.type == :gvasgn or node.type == :or_asgn)
436
+ true
437
+ elsif node.kind_of?(AST::Node) and node.type == :send and node.children[0] == nil and MAPPABLE_METHODS.include?(node.children[1])
438
+ true
439
+ else
440
+ false
441
+ end
442
+ end
443
+
444
+ def reduce node
445
+ mappable = get_mappable_nodes(node.children)
446
+ result = node.updated nil, mappable
447
+ result
448
+ end
449
+
450
+ def get_mappable_nodes arr
451
+ result = []
452
+ arr.each { |n|
453
+ if mappable?(n)
454
+ min = minify(n)
455
+ result.push min
456
+ else
457
+ next unless n.kind_of?(AST::Node)
458
+ result += get_mappable_nodes(n.children)
459
+ end
460
+ }
461
+ result
462
+ end
463
+
464
+ def minify node
465
+ return node if node.type == :args
466
+ type = node.type
467
+ children = []
468
+ if node.type == :class
469
+ children += node.children[0, 2]
470
+ children += get_mappable_nodes(node.children[2..-1])
471
+ elsif node.type == :def
472
+ children += node.children[0, 2]
473
+ children += get_mappable_nodes(node.children[2..-1])
474
+ elsif node.type == :defs
475
+ children += node.children[0, 3]
476
+ children += get_mappable_nodes(node.children[3..-1])
477
+ elsif node.type == :module
478
+ children += node.children[0, 1]
479
+ children += get_mappable_nodes(node.children[1..-1])
480
+ elsif node.type == :ivasgn or node.type == :gvasgn
481
+ children += node.children
482
+ elsif node.type == :send and node.children[1] == :include
483
+ children += node.children[0,3]
484
+ elsif node.type == :send and node.children[1] == :require
485
+ @required.push(node.children[2].children[0])
486
+ children += node.children[0, 3]
487
+ elsif node.type == :send and node.children[1] == :autoload
488
+ @required.push(node.children[3].children[0])
489
+ type = :require
490
+ children += node.children[1, 3]
491
+ elsif node.type == :send
492
+ children += node.children
493
+ elsif node.type == :or_asgn
494
+ # TODO: The api_map should ignore local variables.
495
+ type = node.children[0].type
496
+ children.push node.children[0].children[0], node.children[1]
497
+ end
498
+ result = node.updated(type, children)
499
+ result
500
+ end
501
+
502
+ def map_parents node, tree = []
503
+ if node.kind_of?(AST::Node) #and (node.type == :class or node.type == :module)
504
+ @parent_stack[node] = tree
505
+ node.children.each { |c|
506
+ map_parents c, [node] + tree
507
+ }
508
+ end
509
+ end
510
+
511
+ def add_to_namespace_tree tree
512
+ cursor = @namespace_tree
513
+ tree.each { |t|
514
+ cursor[t.to_s] ||= {}
515
+ cursor = cursor[t.to_s]
516
+ }
517
+ end
518
+
519
+ def map_namespaces node, tree = []
520
+ if node.kind_of?(AST::Node)
521
+ if node.type == :class or node.type == :module
522
+ if node.children[0].children[0].kind_of?(AST::Node) and node.children[0].children[0].type == :cbase
523
+ tree = pack_name(node.children[0])
524
+ else
525
+ tree = tree + pack_name(node.children[0])
526
+ end
527
+ add_to_namespace_tree tree
528
+ fqn = tree.join('::')
529
+ @namespace_map[fqn] ||= []
530
+ @namespace_map[fqn].push node
531
+ end
532
+ node.children.each { |c|
533
+ map_namespaces c, tree
534
+ }
535
+ end
536
+ end
537
+ end
538
+ end