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 +7 -0
- data/bin/solargraph +6 -0
- data/lib/solargraph.rb +39 -0
- data/lib/solargraph/api_map.rb +538 -0
- data/lib/solargraph/code_map.rb +404 -0
- data/lib/solargraph/live_parser.rb +265 -0
- data/lib/solargraph/mapper.rb +31 -0
- data/lib/solargraph/node_methods.rb +48 -0
- data/lib/solargraph/server.rb +21 -0
- data/lib/solargraph/shell.rb +84 -0
- data/lib/solargraph/snippets.rb +186 -0
- data/lib/solargraph/suggestion.rb +40 -0
- data/lib/solargraph/version.rb +3 -0
- data/lib/solargraph/yard_map.rb +119 -0
- data/yardoc/2.0.0.tar.gz +0 -0
- metadata +160 -0
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
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
|