solargraph 0.16.0 → 0.17.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.
@@ -2,6 +2,7 @@ require 'parser/current'
2
2
 
3
3
  module Solargraph
4
4
  class CodeMap
5
+ autoload :Statement, 'solargraph/code_map/statement'
5
6
 
6
7
  # The root node of the parsed code.
7
8
  #
@@ -24,10 +25,7 @@ module Solargraph
24
25
  attr_reader :filename
25
26
 
26
27
  include NodeMethods
27
-
28
- METHODS_WITH_YIELDPARAM_SUBTYPES = %w[
29
- Array#each Hash#each_pair Array#map
30
- ]
28
+ include CoreFills
31
29
 
32
30
  def initialize code: '', filename: nil, api_map: nil, cursor: nil
33
31
  # HACK: Adjust incoming filename's path separator for yardoc file comparisons
@@ -195,7 +193,7 @@ module Solargraph
195
193
  # source.
196
194
  #
197
195
  # @return [Array<Solargraph::Suggestion>] The completion suggestions
198
- def suggest_at index, filtered: true, with_snippets: false
196
+ def suggest_at index, filtered: true
199
197
  return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
200
198
  signature = get_signature_at(index)
201
199
  unless signature.include?('.')
@@ -227,23 +225,23 @@ module Solargraph
227
225
  result = api_map.get_constants(ns, namespace)
228
226
  else
229
227
  type = infer_literal_node_type(node_at(index - 2))
228
+ return [] if type.nil? and signature.empty? and !@code[0..index].rindex('.').nil? and @code[@code[0..index].rindex('.')..-1].strip == '.'
230
229
  if type.nil?
231
- result += get_snippets_at(index) if with_snippets
232
- result += get_local_variables_and_methods_at(index)
233
- result += ApiMap.get_keywords
234
- result += api_map.get_constants('', namespace)
235
- result += api_map.get_constants('')
236
- result += api_map.get_instance_methods('Kernel', namespace)
237
- result += api_map.get_methods('', namespace)
238
- #result += api_map.get_instance_methods('', namespace)
230
+ result.concat get_local_variables_and_methods_at(index)
231
+ result.concat ApiMap.keywords
232
+ result.concat api_map.get_constants('', namespace)
233
+ result.concat api_map.get_constants('')
234
+ result.concat api_map.get_instance_methods('Kernel', namespace)
235
+ result.concat api_map.get_methods('', namespace)
239
236
  else
240
- result.concat api_map.get_instance_methods(type)
237
+ result.concat api_map.get_instance_methods(type) unless @code[index - 1] != '.'
241
238
  end
242
239
  end
243
240
  end
244
241
  else
245
242
  result.concat api_map.get_instance_methods(type) unless (type == '' and signature.include?('.'))
246
243
  end
244
+ result.keep_if{|s| s.kind != Solargraph::Suggestion::METHOD or s.label.match(/^[a-z0-9_]*(\!|\?|=)?$/i)}
247
245
  result = reduce_starting_with(result, word_at(index)) if filtered
248
246
  # Use a stable sort to keep the class order (e.g., local methods before superclass methods)
249
247
  result.uniq(&:path).sort_by.with_index{ |x, idx| [x.label, idx] }
@@ -257,42 +255,44 @@ module Solargraph
257
255
  sugg.select{|s| s.label == word}
258
256
  end
259
257
 
260
- def resolve_object_at index
258
+ # @return [Array<Solargraph::Suggestion>]
259
+ def define_symbol_at index
261
260
  return [] if string_at?(index)
262
- signature = get_signature_at(index)
263
- cursor = index
264
- while @code[cursor] =~ /[a-z0-9_\?]/i
265
- signature += @code[cursor]
266
- cursor += 1
267
- break if cursor >= @code.length
268
- end
269
- return [] if signature.to_s == ''
270
- path = nil
271
- ns_here = namespace_at(index)
261
+ signature = get_signature_at(index, final: true)
262
+ return [] if signature.to_s.empty?
272
263
  node = parent_node_from(index, :class, :module, :def, :defs) || @node
273
- parts = signature.split('.')
274
- if parts.length > 1
275
- beginner = parts[0..-2].join('.')
276
- type = infer_signature_from_node(beginner, node)
277
- ender = parts.last
278
- path = "#{type}##{ender}"
279
- else
264
+ ns_here = namespace_from(node)
265
+ unless signature.include?('.')
280
266
  if local_variable_in_node?(signature, node)
281
- path = infer_signature_from_node(signature, node)
267
+ return get_local_variables_from(node).select{|s| s.label == signature}
268
+ elsif signature.start_with?('@@')
269
+ return api_map.get_class_variables(ns_here).select{|s| s.label == signature}
282
270
  elsif signature.start_with?('@')
283
- path = api_map.infer_instance_variable(signature, ns_here, (node.type == :def ? :instance : :class))
284
- else
285
- path = signature
286
- end
287
- if path.nil?
288
- path = api_map.find_fully_qualified_namespace(signature, ns_here)
271
+ return api_map.get_instance_variables(ns_here, (node.type == :def ? :instance : :class)).select{|s| s.label == signature}
289
272
  end
290
273
  end
291
- return [] if path.nil?
292
- if path.start_with?('Class<')
293
- path.gsub!(/^Class<([a-z0-9_:]*)>#([a-z0-9_]*)$/i, '\\1.\\2')
274
+ path = infer_path_from_signature_and_node(signature, node)
275
+ ps = []
276
+ ps = api_map.get_path_suggestions(path) unless path.nil?
277
+ return ps unless ps.empty?
278
+ ps = api_map.get_path_suggestions(signature)
279
+ return ps unless ps.empty?
280
+ scope = (node.type == :def ? :instance : :class)
281
+ final = []
282
+ if scope == :instance
283
+ final.concat api_map.get_instance_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
284
+ else
285
+ final.concat api_map.get_methods('', namespace_from(node), visibility: [:public, :private, :protected]).select{|s| s.to_s == signature}
286
+ end
287
+ if final.empty? and !signature.include?('.')
288
+ fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
289
+ final.concat api_map.get_path_suggestions(fqns) unless fqns.nil? or fqns.empty?
294
290
  end
295
- api_map.get_path_suggestions(path)
291
+ final
292
+ end
293
+
294
+ def resolve_object_at index
295
+ define_symbol_at index
296
296
  end
297
297
 
298
298
  # Infer the type of the signature located at the specified index.
@@ -308,21 +308,14 @@ module Solargraph
308
308
  #
309
309
  # @return [String]
310
310
  def infer_signature_at index
311
- signature = get_signature_at(index)
311
+ beg_sig, signature = get_signature_data_at(index)
312
312
  # Check for literals first
313
313
  return 'Integer' if signature.match(/^[0-9]+?\.?$/)
314
314
  literal = nil
315
315
  if (signature.empty? and @code[index - 1] == '.') or signature == '[].'
316
316
  literal = node_at(index - 2)
317
- elsif signature.start_with?('.')
318
- literal = node_at(index - 1)
319
317
  else
320
- beg_sig = get_signature_index_at(index)
321
- if @code[beg_sig] == '.'
322
- literal = node_at(beg_sig - 1)
323
- else
324
- literal = node_at(1 + beg_sig)
325
- end
318
+ literal = node_at(1 + beg_sig)
326
319
  end
327
320
  type = infer_literal_node_type(literal)
328
321
  if type.nil?
@@ -350,17 +343,15 @@ module Solargraph
350
343
  yp = get_yieldparams_at(index).keep_if{|s| s.to_s == parts[0]}.first
351
344
  unless yp.nil?
352
345
  if parts[1].nil? or parts[1].empty?
353
- if yp.return_type.nil?
354
- STDERR.puts "Here is where we try to get the stupid, you know, the yieldparam type"
355
- else
356
- result = yp.return_type
357
- end
346
+ result = yp.return_type
358
347
  else
359
348
  newsig = parts[1..-1].join('.')
360
349
  result = api_map.infer_signature_type(newsig, yp.return_type, scope: :instance)
361
350
  end
362
351
  end
363
352
  end
353
+ #elsif match = result.match(/^\$(\-?[0-9]*)$/)
354
+ # STDERR.puts "TODO: handle expression variable #{match[1]}"
364
355
  end
365
356
  else
366
357
  if signature.empty? or signature == '[].'
@@ -400,7 +391,7 @@ module Solargraph
400
391
  false
401
392
  end
402
393
 
403
- def infer_signature_from_node signature, node
394
+ def infer_signature_from_node signature, node, call_node: nil
404
395
  inferred = nil
405
396
  parts = signature.split('.')
406
397
  ns_here = namespace_from(node)
@@ -438,33 +429,34 @@ module Solargraph
438
429
  return api_map.infer_signature_type(remainder.join('.'), vartype, scope: :instance)
439
430
  end
440
431
  end
432
+ # @todo There might be some redundancy between find_local_variable_node and call_node
441
433
  var = find_local_variable_node(start, node)
442
434
  if var.nil?
443
- scope = (node.type == :def ? :instance : :class)
444
- type = api_map.infer_signature_type(signature, ns_here, scope: scope)
445
- return type unless type.nil?
435
+ arg = get_method_arguments_from(node).select{|s| s.label == start}.first
436
+ if arg.nil?
437
+ scope = (node.type == :def ? :instance : :class)
438
+ type = api_map.infer_signature_type(signature, ns_here, scope: scope, call_node: call_node)
439
+ return type unless type.nil?
440
+ else
441
+ type = arg.return_type
442
+ end
446
443
  else
447
444
  # Signature starts with a local variable
448
445
  type = nil
449
446
  lvp = source.local_variable_pins.select{|p| p.name == var.children[0].to_s and p.visible_from?(node) and (!p.nil_assignment? or p.return_type)}.first
450
- type = lvp.return_type unless lvp.nil?
451
- if type.nil?
452
- vsig = resolve_node_signature(var.children[1])
453
- type = infer_signature_from_node vsig, node
447
+ unless lvp.nil?
448
+ type = lvp.return_type
449
+ if type.nil?
450
+ vsig = resolve_node_signature(var.children[1])
451
+ type = infer_signature_from_node vsig, node, call_node: lvp.assignment_node
452
+ end
454
453
  end
455
454
  end
456
455
  unless type.nil?
457
- if remainder[0] == 'new'
458
- remainder.shift
459
- if remainder.empty?
460
- inferred = type
461
- else
462
- inferred = api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
463
- end
464
- elsif remainder.empty?
456
+ if remainder.empty?
465
457
  inferred = type
466
458
  else
467
- inferred = api_map.infer_signature_type(remainder.join('.'), type, scope: :instance)
459
+ inferred = api_map.infer_signature_type(remainder.join('.'), type, scope: :instance, call_node: call_node)
468
460
  end
469
461
  end
470
462
  if inferred.nil? and node.respond_to?(:loc)
@@ -493,33 +485,23 @@ module Solargraph
493
485
  #
494
486
  # @param index [Integer]
495
487
  # @return [String]
496
- def get_signature_at index
497
- get_signature_data_at(index)[1]
488
+ def get_signature_at index, final: false
489
+ sig = get_signature_data_at(index)[1]
490
+ if final
491
+ cursor = index
492
+ while @code[cursor] =~ /[a-z0-9_\?]/i
493
+ sig += @code[cursor]
494
+ cursor += 1
495
+ break if cursor >= @code.length
496
+ end
497
+ end
498
+ sig
498
499
  end
499
500
 
500
501
  def get_signature_index_at index
501
502
  get_signature_data_at(index)[0]
502
503
  end
503
504
 
504
- def get_snippets_at(index)
505
- result = []
506
- Snippets.definitions.each_pair { |name, detail|
507
- matched = false
508
- prefix = detail['prefix']
509
- while prefix.length > 0
510
- if @code[index-prefix.length, prefix.length] == prefix
511
- matched = true
512
- break
513
- end
514
- prefix = prefix[0..-2]
515
- end
516
- if matched
517
- result.push Suggestion.new(detail['prefix'], kind: Suggestion::SNIPPET, detail: name, insert: detail['body'].join("\r\n"))
518
- end
519
- }
520
- result
521
- end
522
-
523
505
  # Get an array of local variables and methods that can be accessed from
524
506
  # the specified location in the code.
525
507
  #
@@ -542,6 +524,10 @@ module Solargraph
542
524
  result
543
525
  end
544
526
 
527
+ #def get_call_arguments_at index
528
+ # called = parent_node_from(index, :send)
529
+ #end
530
+
545
531
  private
546
532
 
547
533
  def get_signature_data_at index
@@ -550,32 +536,46 @@ module Solargraph
550
536
  parens = 0
551
537
  signature = ''
552
538
  index -=1
539
+ in_whitespace = false
553
540
  while index >= 0
554
- unless string_at?(index)
541
+ unless !in_whitespace and string_at?(index)
555
542
  break if brackets > 0 or parens > 0 or squares > 0
556
543
  char = @code[index, 1]
557
- if char == ')'
558
- parens -=1
559
- elsif char == ']'
560
- squares -=1
561
- elsif char == '}'
562
- brackets -= 1
563
- elsif char == '('
564
- parens += 1
565
- elsif char == '{'
566
- brackets += 1
567
- elsif char == '['
568
- squares += 1
569
- signature = ".[]#{signature}" if squares == 0 and @code[index-2] != '%'
570
- end
571
- if brackets == 0 and parens == 0 and squares == 0
572
- break if ['"', "'", ',', ' ', "\t", "\n", ';', '%'].include?(char)
573
- signature = char + signature if char.match(/[a-z0-9:\._@\$]/i) and @code[index - 1] != '%'
574
- break if char == '$'
575
- if char == '@'
576
- signature = "@#{signature}" if @code[index-1, 1] == '@'
577
- break
544
+ if brackets.zero? and parens.zero? and squares.zero? and [' ', "\n", "\t"].include?(char)
545
+ in_whitespace = true
546
+ else
547
+ if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
548
+ unless char == '.' or @code[index+1..-1].strip.start_with?('.')
549
+ old = @code[index+1..-1]
550
+ nxt = @code[index+1..-1].lstrip
551
+ index += (@code[index+1..-1].length - @code[index+1..-1].lstrip.length)
552
+ break
553
+ end
554
+ end
555
+ if char == ')'
556
+ parens -=1
557
+ elsif char == ']'
558
+ squares -=1
559
+ elsif char == '}'
560
+ brackets -= 1
561
+ elsif char == '('
562
+ parens += 1
563
+ elsif char == '{'
564
+ brackets += 1
565
+ elsif char == '['
566
+ squares += 1
567
+ signature = ".[]#{signature}" if squares == 0 and @code[index-2] != '%'
578
568
  end
569
+ if brackets.zero? and parens.zero? and squares.zero?
570
+ break if ['"', "'", ',', ';', '%'].include?(char)
571
+ signature = char + signature if char.match(/[a-z0-9:\._@\$]/i) and @code[index - 1] != '%'
572
+ break if char == '$'
573
+ if char == '@'
574
+ signature = "@#{signature}" if @code[index-1, 1] == '@'
575
+ break
576
+ end
577
+ end
578
+ in_whitespace = false
579
579
  end
580
580
  end
581
581
  index -= 1
@@ -621,23 +621,26 @@ module Solargraph
621
621
  return [] unless block_node.kind_of?(AST::Node) and block_node.type == :block
622
622
  result = []
623
623
  unless block_node.nil? or block_node.children[1].nil?
624
- meth = get_yielding_method(block_node, scope_node)
624
+ ymeth = get_yielding_method(block_node, scope_node)
625
625
  yps = []
626
- unless meth.nil? or meth.docstring.nil?
627
- yps = meth.docstring.tags(:yieldparam) || []
626
+ unless ymeth.nil? or ymeth.docstring.nil?
627
+ yps = ymeth.docstring.tags(:yieldparam) || []
628
628
  end
629
629
  self_yield = nil
630
630
  meth = get_yielding_method_with_yieldself(block_node, scope_node)
631
631
  unless meth.nil?
632
632
  match = meth.docstring.all.match(/@yieldself \[([a-z0-9:_]*)/i)
633
633
  self_yield = match[1]
634
+ if self_yield == 'self'
635
+ blocksig = resolve_node_signature(block_node.children[0]).split('.')[0..-2].join('.')
636
+ self_yield = infer_signature_from_node(blocksig, scope_node)
637
+ end
634
638
  end
635
- i = 0
636
- block_node.children[1].children.each do |a|
639
+ block_node.children[1].children.each_with_index do |a, i|
637
640
  rt = nil
638
641
  if yps[i].nil? or yps[i].types.nil? or yps[i].types.empty?
639
642
  zsig = api_map.resolve_node_signature(block_node.children[0])
640
- vartype = infer_signature_from_node(zsig.split('.').first, scope_node)
643
+ vartype = infer_signature_from_node(zsig.split('.')[0..-2].join('.'), scope_node)
641
644
  subtypes = get_subtypes(vartype)
642
645
  zpath = infer_path_from_signature_and_node(zsig, scope_node)
643
646
  rt = subtypes[i] if METHODS_WITH_YIELDPARAM_SUBTYPES.include?(zpath)
@@ -645,7 +648,6 @@ module Solargraph
645
648
  rt = yps[i].types[0]
646
649
  end
647
650
  result.push Suggestion.new(a.children[0], kind: Suggestion::PROPERTY, return_type: rt)
648
- i += 1
649
651
  end
650
652
  result.concat api_map.get_instance_methods(self_yield, namespace_from(scope_node)) unless self_yield.nil?
651
653
  end
@@ -657,7 +659,8 @@ module Solargraph
657
659
  fqns = namespace_from(block_node)
658
660
  lvarnode = find_local_variable_node(recv, scope_node)
659
661
  if lvarnode.nil?
660
- sig = api_map.infer_signature_type(recv, fqns)
662
+ #sig = api_map.infer_signature_type(recv, fqns)
663
+ sig = infer_signature_from_node(recv, scope_node)
661
664
  else
662
665
  tmp = resolve_node_signature(lvarnode.children[1])
663
666
  sig = infer_signature_from_node tmp, scope_node
@@ -800,10 +803,12 @@ module Solargraph
800
803
  parts = signature.split('.')
801
804
  last = parts.pop
802
805
  type = infer_signature_from_node(parts.join('.'), node)
806
+ return nil if type.nil?
803
807
  "#{type.gsub(/<[a-z0-9:, ]*>/i, '')}##{last}"
804
808
  end
805
809
 
806
810
  def get_subtypes type
811
+ return nil if type.nil?
807
812
  match = type.match(/<([a-z0-9_:, ]*)>/i)
808
813
  return [] if match.nil?
809
814
  match[1].split(',').map(&:strip)
@@ -0,0 +1,23 @@
1
+ module Solargraph
2
+ module CoreFills
3
+ KEYWORDS = [
4
+ '__ENCODING__', '__LINE__', '__FILE__', 'BEGIN', 'END', 'alias', 'and',
5
+ 'begin', 'break', 'case', 'class', 'def', 'defined?', 'do', 'else',
6
+ 'elsif', 'end', 'ensure', 'false', 'for', 'if', 'in', 'module', 'next',
7
+ 'nil', 'not', 'or', 'redo', 'rescue', 'retry', 'return', 'self', 'super',
8
+ 'then', 'true', 'undef', 'unless', 'until', 'when', 'while', 'yield'
9
+ ].freeze
10
+
11
+ METHODS_RETURNING_SELF = %w{
12
+ Object#clone Object#dup Object#freeze Object#taint Object#untaint
13
+ }.freeze
14
+
15
+ METHODS_RETURNING_SUBTYPES = %w{
16
+ Array#[]
17
+ }.freeze
18
+
19
+ METHODS_WITH_YIELDPARAM_SUBTYPES = %w{
20
+ Array#each Hash#each_pair Array#map
21
+ }.freeze
22
+ end
23
+ end
@@ -76,25 +76,6 @@ module Solargraph
76
76
  @@plugin_registry[name] = klass
77
77
  end
78
78
 
79
- # Register a plugin for LiveMap to use when generating suggestions.
80
- # @deprecated See Solargraph::LiveMap.register instead
81
- #
82
- # @param cls [Class<Solargraph::Plugin::Base>]
83
- def self.install cls
84
- STDERR.puts "WARNING: The Solargraph::LiveMap.install procedure for installing plugins is no longer used. This operation will be ignored."
85
- end
86
-
87
- # @deprecated
88
- def self.uninstall cls
89
- STDERR.puts "WARNING: The Solargraph::LiveMap.uninstall procedure for uninstalling plugins is no longer used. This operation will be ignored."
90
- end
91
-
92
- # @deprecated
93
- def self.plugins
94
- STDERR.puts "WARNING: Plugins have changed. The Solargraph::LiveMap.plugins attribute is no longer used."
95
- []
96
- end
97
-
98
79
  def refresh
99
80
  changed = false
100
81
  runners.each do |p|
@@ -62,7 +62,7 @@ module Solargraph
62
62
  end
63
63
 
64
64
  def location
65
- "#{source.filename}:#{node.location.expression.begin_pos}"
65
+ "#{source.filename}:#{node.location.expression.line - 1}:#{node.location.expression.column}"
66
66
  end
67
67
  end
68
68
  end
@@ -19,8 +19,12 @@ module Solargraph
19
19
  @return_type ||= literal_from_assignment
20
20
  end
21
21
 
22
+ def assignment_node
23
+ @assignment_node ||= node.children[(node.type == :casgn ? 2 : 1)]
24
+ end
25
+
22
26
  def nil_assignment?
23
- node.children[(node.type == :casgn ? 2 : 1)].type == :nil
27
+ assignment_node.type == :nil
24
28
  end
25
29
 
26
30
  def signature
@@ -30,7 +34,7 @@ module Solargraph
30
34
  private
31
35
 
32
36
  def literal_from_assignment
33
- infer_literal_node_type(node.children[(node.type == :casgn ? 2 : 1)])
37
+ infer_literal_node_type(assignment_node)
34
38
  end
35
39
  end
36
40
  end
@@ -25,7 +25,7 @@ module Solargraph
25
25
  def return_type
26
26
  if @return_type.nil? and !docstring.nil?
27
27
  tag = docstring.tag(:return)
28
- @return_type = tag.types[0] unless tag.nil?
28
+ @return_type = tag.types[0] unless tag.nil? or tag.types.nil?
29
29
  end
30
30
  @return_type
31
31
  end
@@ -71,6 +71,7 @@ module Solargraph
71
71
  result.concat con.public_instance_methods(false)
72
72
  end
73
73
  end
74
+ result.keep_if{|m| m.to_s.match(/^[a-z_]/i)}
74
75
  respond_ok (result.uniq.sort.map do |name|
75
76
  # @type [Method]
76
77
  meth = con.method(name)