solargraph 0.12.2 → 0.13.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.
@@ -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