solargraph 0.1.0

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