solargraph 0.12.2 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,6 +10,10 @@ module Solargraph
10
10
  @signature_types[[signature, namespace, scope]]
11
11
  end
12
12
 
13
+ def has_signature_type?(signature, namespace, scope)
14
+ @signature_types.has_key?([signature, namespace, scope])
15
+ end
16
+
13
17
  def set_signature_type signature, namespace, scope, value
14
18
  @signature_types[[signature, namespace, scope]] = value
15
19
  end
@@ -29,7 +29,7 @@ module Solargraph
29
29
 
30
30
  def process_glob glob
31
31
  result = []
32
- Dir[glob].each do |f|
32
+ Dir[File.join workspace, glob].each do |f|
33
33
  result.push File.realdirpath(f)
34
34
  end
35
35
  result
@@ -0,0 +1,297 @@
1
+ require 'parser/current'
2
+
3
+ module Solargraph
4
+ class ApiMap
5
+ class Source
6
+ attr_reader :code
7
+ attr_reader :node
8
+ attr_reader :comments
9
+ attr_reader :filename
10
+
11
+ include NodeMethods
12
+
13
+ def initialize code, node, comments, filename
14
+ @code = code
15
+ root = AST::Node.new(:begin, [filename])
16
+ root = root.append node
17
+ @node = root
18
+ @comments = comments
19
+ @docstring_hash = associate_comments(node, comments)
20
+ @filename = filename
21
+ @namespace_nodes = {}
22
+ @all_nodes = []
23
+ inner_map_node @node
24
+ end
25
+
26
+ def namespaces
27
+ @namespace_nodes.keys
28
+ end
29
+
30
+ def namespace_nodes
31
+ @namespace_nodes
32
+ end
33
+
34
+ def namespace_includes
35
+ @namespace_includes ||= {}
36
+ end
37
+
38
+ def superclasses
39
+ @superclasses ||= {}
40
+ end
41
+
42
+ def method_pins
43
+ @method_pins ||= []
44
+ end
45
+
46
+ def attribute_pins
47
+ @attribute_pins ||= []
48
+ end
49
+
50
+ def instance_variable_pins
51
+ @instance_variable_pins ||= []
52
+ end
53
+
54
+ def class_variable_pins
55
+ @class_variable_pins ||= []
56
+ end
57
+
58
+ def constant_pins
59
+ @constant_pins ||= []
60
+ end
61
+
62
+ def symbol_pins
63
+ @symbol_pins ||= []
64
+ end
65
+
66
+ def required
67
+ @required ||= []
68
+ end
69
+
70
+ def docstring_for node
71
+ @docstring_hash[node.loc]
72
+ end
73
+
74
+ def code_for node
75
+ b = node.location.expression.begin.begin_pos
76
+ e = node.location.expression.end.end_pos
77
+ frag = code[b..e-1].to_s
78
+ frag.strip.gsub(/,$/, '')
79
+ end
80
+
81
+ def include? node
82
+ @all_nodes.include? node
83
+ end
84
+
85
+ private
86
+
87
+ def associate_comments node, comments
88
+ comment_hash = Parser::Source::Comment.associate_locations(node, comments)
89
+ yard_hash = {}
90
+ comment_hash.each_pair { |k, v|
91
+ ctxt = ''
92
+ num = nil
93
+ started = false
94
+ v.each { |l|
95
+ # Trim the comment and minimum leading whitespace
96
+ p = l.text.gsub(/^#/, '')
97
+ if num.nil? and !p.strip.empty?
98
+ num = p.index(/[^ ]/)
99
+ started = true
100
+ elsif started and !p.strip.empty?
101
+ cur = p.index(/[^ ]/)
102
+ num = cur if cur < num
103
+ end
104
+ if started
105
+ ctxt += "#{p[num..-1]}\n"
106
+ end
107
+ }
108
+ yard_hash[k] = YARD::Docstring.parser.parse(ctxt).to_docstring
109
+ }
110
+ yard_hash
111
+ end
112
+
113
+ def inner_map_node node, tree = [], visibility = :public, scope = :instance, fqn = nil, stack = []
114
+ stack.push node
115
+ source = self
116
+ if node.kind_of?(AST::Node)
117
+ @all_nodes.push node
118
+ return if node.type == :str or node.type == :dstr
119
+ if node.type == :class or node.type == :module
120
+ visibility = :public
121
+ 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
122
+ tree = pack_name(node.children[0])
123
+ else
124
+ tree = tree + pack_name(node.children[0])
125
+ end
126
+ fqn = tree.join('::')
127
+ @namespace_nodes[fqn] ||= []
128
+ @namespace_nodes[fqn].push node
129
+ if node.type == :class and !node.children[1].nil?
130
+ sc = unpack_name(node.children[1])
131
+ superclasses[fqn] = sc
132
+ end
133
+ end
134
+ file = source.filename
135
+ node.children.each do |c|
136
+ if c.kind_of?(AST::Node)
137
+ if c.type == :ivasgn
138
+ par = find_parent(stack, :class, :module, :def, :defs)
139
+ local_scope = ( (par.kind_of?(AST::Node) and par.type == :def) ? :instance : :class )
140
+ if c.children[1].nil?
141
+ ora = find_parent(stack, :or_asgn)
142
+ unless ora.nil?
143
+ u = c.updated(:ivasgn, c.children + ora.children[1..-1], nil)
144
+ instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, u, fqn || '', local_scope)
145
+ end
146
+ else
147
+ instance_variable_pins.push Solargraph::Pin::InstanceVariable.new(self, c, fqn || '', local_scope)
148
+ end
149
+ elsif c.type == :cvasgn
150
+ if c.children[1].nil?
151
+ ora = find_parent(stack, :or_asgn)
152
+ unless ora.nil?
153
+ u = c.updated(:cvasgn, c.children + ora.children[1..-1], nil)
154
+ class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, u, fqn || '')
155
+ end
156
+ else
157
+ class_variable_pins.push Solargraph::Pin::ClassVariable.new(self, c, fqn || '')
158
+ end
159
+ elsif c.type == :sym
160
+ symbol_pins.push Solargraph::Pin::Symbol.new(self, c, fqn)
161
+ elsif c.type == :casgn
162
+ constant_pins.push Solargraph::Pin::Constant.new(self, c, fqn)
163
+ else
164
+ unless fqn.nil?
165
+ if c.kind_of?(AST::Node)
166
+ if c.type == :def and c.children[0].to_s[0].match(/[a-z]/i)
167
+ method_pins.push Solargraph::Pin::Method.new(source, c, fqn, scope, visibility)
168
+ inner_map_node c, tree, visibility, scope, fqn, stack
169
+ next
170
+ elsif c.type == :defs
171
+ method_pins.push Solargraph::Pin::Method.new(source, c, fqn, :class, :public)
172
+ inner_map_node c, tree, :public, :class, fqn, stack
173
+ next
174
+ elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
175
+ visibility = c.children[1]
176
+ elsif c.type == :send and c.children[1] == :include #and node.type == :class
177
+ namespace_includes[fqn] ||= []
178
+ c.children[2..-1].each do |i|
179
+ namespace_includes[fqn].push unpack_name(i)
180
+ end
181
+ elsif c.type == :send and [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
182
+ c.children[2..-1].each do |a|
183
+ if c.children[1] == :attr_reader or c.children[1] == :attr_accessor
184
+ attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn, :reader) #AttrPin.new(c)
185
+ end
186
+ if c.children[1] == :attr_writer or c.children[1] == :attr_accessor
187
+ attribute_pins.push Solargraph::Pin::Attribute.new(self, a, fqn, :writer) #AttrPin.new(c)
188
+ end
189
+ end
190
+ elsif c.type == :sclass and c.children[0].type == :self
191
+ inner_map_node c, tree, :public, :class, fqn, stack
192
+ next
193
+ end
194
+ end
195
+ end
196
+ if c.type == :send and c.children[1] == :require
197
+ if c.children[2].kind_of?(AST::Node) and c.children[2].type == :str
198
+ required.push c.children[2].children[0].to_s
199
+ end
200
+ end
201
+ inner_map_node c, tree, visibility, scope, fqn, stack
202
+ end
203
+ end
204
+ end
205
+ end
206
+ stack.pop
207
+ end
208
+
209
+ def find_parent(stack, *types)
210
+ stack.reverse.each { |p|
211
+ return p if types.include?(p.type)
212
+ }
213
+ nil
214
+ end
215
+
216
+ class << self
217
+ # @return [Solargraph::ApiMap::Source]
218
+ def load filename
219
+ code = File.read(filename).gsub(/\r/, '')
220
+ Source.virtual(filename, code)
221
+ end
222
+
223
+ def virtual filename, code
224
+ node, comments = Parser::CurrentRuby.parse_with_comments(code)
225
+ Source.new(code, node, comments, filename)
226
+ end
227
+
228
+ def fix filename, code, cursor = nil
229
+ tries = 0
230
+ code.gsub!(/\r/, '')
231
+ tmp = code
232
+ cursor = CodeMap.get_offset(code, cursor[0], cursor[1]) if cursor.kind_of?(Array)
233
+ fixed_cursor = false
234
+ begin
235
+ # HACK: The current file is parsed with a trailing underscore to fix
236
+ # incomplete trees resulting from short scripts (e.g., a lone variable
237
+ # assignment).
238
+ node, comments = Parser::CurrentRuby.parse_with_comments(tmp + "\n_")
239
+ #@node = self.api_map.append_node(node, @comments, filename)
240
+ #@parsed = tmp
241
+ #@code.freeze
242
+ #@parsed.freeze
243
+ Source.new(code, node, comments, filename)
244
+ rescue Parser::SyntaxError => e
245
+ if tries < 10
246
+ tries += 1
247
+ if tries == 10 and e.message.include?('token $end')
248
+ tmp += "\nend"
249
+ else
250
+ if !fixed_cursor and !cursor.nil? and e.message.include?('token $end') and cursor >= 2
251
+ fixed_cursor = true
252
+ spot = cursor - 2
253
+ if tmp[cursor - 1] == '.'
254
+ repl = ';'
255
+ else
256
+ repl = '#'
257
+ end
258
+ else
259
+ spot = e.diagnostic.location.begin_pos
260
+ repl = '_'
261
+ if tmp[spot] == '@' or tmp[spot] == ':'
262
+ # Stub unfinished instance variables and symbols
263
+ spot -= 1
264
+ elsif tmp[spot - 1] == '.'
265
+ # Stub unfinished method calls
266
+ repl = '#' if spot == tmp.length or tmp[spot] == '\n'
267
+ spot -= 2
268
+ else
269
+ # Stub the whole line
270
+ spot = beginning_of_line_from(tmp, spot)
271
+ repl = '#'
272
+ if tmp[spot+1..-1].rstrip == 'end'
273
+ repl= 'end;end'
274
+ end
275
+ end
276
+ end
277
+ tmp = tmp[0..spot] + repl + tmp[spot+repl.length+1..-1].to_s
278
+ end
279
+ retry
280
+ end
281
+ raise e
282
+ end
283
+ end
284
+
285
+ def beginning_of_line_from str, i
286
+ while i > 0 and str[i] != "\n"
287
+ i -= 1
288
+ end
289
+ if i > 0 and str[i..-1].strip == ''
290
+ i = beginning_of_line_from str, i -1
291
+ end
292
+ i
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -6,19 +6,13 @@ module Solargraph
6
6
  # The root node of the parsed code.
7
7
  #
8
8
  # @return [AST::Node]
9
- attr_accessor :node
9
+ attr_reader :node
10
10
 
11
11
  # The source code being analyzed.
12
12
  #
13
13
  # @return [String]
14
14
  attr_reader :code
15
15
 
16
- # The source code after modification to fix syntax errors during parsing.
17
- # This string will match #code if no modifications were made.
18
- #
19
- # @return [String]
20
- attr_reader :parsed
21
-
22
16
  # The filename for the source code.
23
17
  #
24
18
  # @return [String]
@@ -38,59 +32,11 @@ module Solargraph
38
32
  filename = filename.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless filename.nil? or File::ALT_SEPARATOR.nil?
39
33
  @filename = filename
40
34
  @api_map = api_map
41
- @code = code.gsub(/\r/, '')
42
- tries = 0
43
- tmp = @code
44
- cursor = CodeMap.get_offset(@code, cursor[0], cursor[1]) if cursor.kind_of?(Array)
45
- fixed_cursor = false
46
- begin
47
- # HACK: The current file is parsed with a trailing underscore to fix
48
- # incomplete trees resulting from short scripts (e.g., a lone variable
49
- # assignment).
50
- node, @comments = Parser::CurrentRuby.parse_with_comments(tmp + "\n_")
51
- @node = self.api_map.append_node(node, @comments, filename)
52
- @parsed = tmp
53
- @code.freeze
54
- @parsed.freeze
55
- rescue Parser::SyntaxError => e
56
- if tries < 10
57
- tries += 1
58
- if tries == 10 and e.message.include?('token $end')
59
- tmp += "\nend"
60
- else
61
- if !fixed_cursor and !cursor.nil? and e.message.include?('token $end') and cursor >= 2
62
- fixed_cursor = true
63
- spot = cursor - 2
64
- if tmp[cursor - 1] == '.'
65
- repl = ';'
66
- else
67
- repl = '#'
68
- end
69
- else
70
- spot = e.diagnostic.location.begin_pos
71
- repl = '_'
72
- if tmp[spot] == '@' or tmp[spot] == ':'
73
- # Stub unfinished instance variables and symbols
74
- spot -= 1
75
- elsif tmp[spot - 1] == '.'
76
- # Stub unfinished method calls
77
- repl = '#' if spot == tmp.length or tmp[spot] == '\n'
78
- spot -= 2
79
- else
80
- # Stub the whole line
81
- spot = beginning_of_line_from(tmp, spot)
82
- repl = '#'
83
- if tmp[spot+1..-1].rstrip == 'end'
84
- repl= 'end;end'
85
- end
86
- end
87
- end
88
- tmp = tmp[0..spot] + repl + tmp[spot+repl.length+1..-1].to_s
89
- end
90
- retry
91
- end
92
- raise e
93
- end
35
+ @source = self.api_map.virtualize filename, code, cursor
36
+ @node = @source.node
37
+ @code = @source.code
38
+ @comments = @source.comments
39
+ self.api_map.refresh
94
40
  end
95
41
 
96
42
  # Get the ApiMap that was generated for this CodeMap.
@@ -98,8 +44,8 @@ module Solargraph
98
44
  # @return [Solargraph::ApiMap]
99
45
  def api_map
100
46
  @api_map ||= ApiMap.new(workspace)
101
- @api_map.refresh
102
- @api_map
47
+ #@api_map.refresh
48
+ #@api_map
103
49
  end
104
50
 
105
51
  # Get the offset of the specified line and column.
@@ -253,9 +199,11 @@ module Solargraph
253
199
  end
254
200
 
255
201
  def get_instance_variables_at(index)
256
- node = parent_node_from(index, :def, :defs, :class, :module)
202
+ # @todo There are a lot of other cases that need to be handled here
203
+ node = parent_node_from(index, :def, :defs, :class, :module, :sclass)
257
204
  ns = namespace_at(index) || ''
258
- api_map.get_instance_variables(ns, (node.type == :def ? :instance : :class))
205
+ scope = (node.type == :def ? :instance : :class)
206
+ api_map.get_instance_variables(ns, scope)
259
207
  end
260
208
 
261
209
  # Get suggestions for code completion at the specified location in the
@@ -266,64 +214,68 @@ module Solargraph
266
214
  return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
267
215
  result = []
268
216
  signature = get_signature_at(index)
269
- if index == 0 or @code[index - 1].match(/[\.\s]/)
270
- type = infer_signature_at(index)
217
+ if signature.start_with?(':')
218
+ result.concat api_map.get_symbols
271
219
  else
272
- if signature.include?('.')
273
- last_period = @code[0..index].rindex('.')
274
- if last_period.nil?
275
- type = infer_signature_at(index)
276
- else
277
- type = infer_signature_at(last_period)
278
- end
220
+ if index == 0 or @code[index - 1].match(/[\.\s]/)
221
+ type = infer_signature_at(index)
279
222
  else
280
- if signature.start_with?('@@')
281
- return get_class_variables_at(index)
282
- elsif signature.start_with?('@')
283
- return get_instance_variables_at(index)
284
- elsif signature.start_with?('$')
285
- return api_map.get_global_variables
223
+ if signature.include?('.')
224
+ last_period = @code[0..index].rindex('.')
225
+ if last_period.nil?
226
+ type = infer_signature_at(index)
227
+ else
228
+ type = infer_signature_at(last_period)
229
+ end
286
230
  else
287
- type = infer_signature_at(index)
288
- end
289
- end
290
- end
291
- if type.nil?
292
- unless signature.include?('.')
293
- phrase = phrase_at(index)
294
- signature = get_signature_at(index)
295
- namespace = namespace_at(index)
296
- if phrase.include?('::')
297
- parts = phrase.split('::', -1)
298
- ns = parts[0..-2].join('::')
299
- if parts.last.include?('.')
300
- ns = parts[0..-2].join('::') + '::' + parts.last[0..parts.last.index('.')-1]
301
- result = api_map.get_methods(ns)
231
+ if signature.start_with?('@@')
232
+ return get_class_variables_at(index)
233
+ elsif signature.start_with?('@')
234
+ return get_instance_variables_at(index)
235
+ elsif signature.start_with?('$')
236
+ return api_map.get_global_variables
302
237
  else
303
- result = api_map.namespaces_in(ns, namespace)
238
+ type = infer_signature_at(index)
304
239
  end
305
- else
306
- type = infer_literal_node_type(node_at(index - 2))
307
- if type.nil?
308
- current_namespace = namespace_at(index)
309
- parts = current_namespace.to_s.split('::')
310
- result += get_snippets_at(index) if with_snippets
311
- result += get_local_variables_and_methods_at(index)
312
- result += ApiMap.get_keywords
313
- while parts.length > 0
314
- ns = parts.join('::')
315
- result += api_map.namespaces_in(ns, namespace)
316
- parts.pop
240
+ end
241
+ end
242
+ if type.nil?
243
+ unless signature.include?('.')
244
+ phrase = phrase_at(index)
245
+ signature = get_signature_at(index)
246
+ namespace = namespace_at(index)
247
+ if phrase.include?('::')
248
+ parts = phrase.split('::', -1)
249
+ ns = parts[0..-2].join('::')
250
+ if parts.last.include?('.')
251
+ ns = parts[0..-2].join('::') + '::' + parts.last[0..parts.last.index('.')-1]
252
+ result = api_map.get_methods(ns)
253
+ else
254
+ result = api_map.namespaces_in(ns, namespace)
317
255
  end
318
- result += api_map.namespaces_in('')
319
- result += api_map.get_instance_methods('Kernel')
320
256
  else
321
- result.concat api_map.get_instance_methods(type)
257
+ type = infer_literal_node_type(node_at(index - 2))
258
+ if type.nil?
259
+ current_namespace = namespace_at(index)
260
+ parts = current_namespace.to_s.split('::')
261
+ result += get_snippets_at(index) if with_snippets
262
+ result += get_local_variables_and_methods_at(index)
263
+ result += ApiMap.get_keywords
264
+ while parts.length > 0
265
+ ns = parts.join('::')
266
+ result += api_map.namespaces_in(ns, namespace)
267
+ parts.pop
268
+ end
269
+ result += api_map.namespaces_in('')
270
+ result += api_map.get_instance_methods('Kernel')
271
+ else
272
+ result.concat api_map.get_instance_methods(type)
273
+ end
322
274
  end
323
275
  end
276
+ else
277
+ result.concat api_map.get_instance_methods(type)
324
278
  end
325
- else
326
- result.concat api_map.get_instance_methods(type)
327
279
  end
328
280
  result = reduce_starting_with(result, word_at(index)) if filtered
329
281
  result.uniq{|s| s.path}.sort{|a,b| a.label <=> b.label}
@@ -493,16 +445,20 @@ module Solargraph
493
445
  return nil if start.nil?
494
446
  remainder = parts[1..-1]
495
447
  if start.start_with?('@@')
496
- type = api_map.infer_class_variable(start, ns_here)
497
- return nil if type.nil?
498
- return type if remainder.empty?
499
- return api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
448
+ #type = api_map.infer_class_variable(start, ns_here)
449
+ #return nil if type.nil?
450
+ #return type if remainder.empty?
451
+ #return api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
452
+ cv = api_map.get_class_variables(ns_here).select{|s| s.label == start}.first
453
+ return cv.return_type unless cv.nil?
500
454
  elsif start.start_with?('@')
501
- scope = (node.type == :def ? :instance : :scope)
502
- type = api_map.infer_instance_variable(start, ns_here, scope)
503
- return nil if type.nil?
504
- return type if remainder.empty?
505
- return api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
455
+ scope = (node.type == :def ? :instance : :class)
456
+ #type = api_map.infer_instance_variable(start, ns_here, scope)
457
+ #return nil if type.nil?
458
+ #return type if remainder.empty?
459
+ #return api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
460
+ iv = api_map.get_instance_variables(ns_here, scope).select{|s| s.label == start}.first
461
+ return iv.return_type unless iv.nil?
506
462
  end
507
463
  var = find_local_variable_node(start, node)
508
464
  if var.nil?
@@ -537,7 +493,7 @@ module Solargraph
537
493
 
538
494
  def get_type_comment node
539
495
  obj = nil
540
- cmnt = api_map.get_comment_for(node)
496
+ cmnt = @source.docstring_for(node)
541
497
  unless cmnt.nil?
542
498
  tag = cmnt.tag(:type)
543
499
  obj = tag.types[0] unless tag.nil? or tag.types.empty?
@@ -806,7 +762,9 @@ module Solargraph
806
762
  while parts.length > 0
807
763
  result = api_map.find_fully_qualified_namespace("#{conc}::#{parts[0]}", namespace)
808
764
  if result.nil? or result.empty?
809
- sugg = api_map.namespaces_in(conc, namespace).select{ |s| s.label == parts[0] }.first
765
+ #sugg = api_map.namespaces_in(conc, namespace).select{ |s| s.label == parts[0] }.first
766
+ bla = api_map.get_constants(conc, namespace)
767
+ sugg = api_map.get_constants(conc, namespace).select{|s| s.label == parts[0]}.first
810
768
  return nil if sugg.nil?
811
769
  result = sugg.return_type
812
770
  break if result.nil?
@@ -819,17 +777,6 @@ module Solargraph
819
777
  end
820
778
  end
821
779
  return result if is_constant
822
- nil
823
- end
824
-
825
- def beginning_of_line_from str, i
826
- while i > 0 and str[i] != "\n"
827
- i -= 1
828
- end
829
- if i > 0 and str[i..-1].strip == ''
830
- i = beginning_of_line_from str, i -1
831
- end
832
- i
833
780
  end
834
781
 
835
782
  def signature_index_before index