solargraph 0.11.2 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fd196523191df8e781673c9b4f4ec9680132b60
4
- data.tar.gz: c341f7ecf19bb780648b125234d411891d6048e7
3
+ metadata.gz: 9a0cb900eab77f94ad22582f22dc8ea75c249485
4
+ data.tar.gz: 478c5f05faf26850590f928060b35e61903cb807
5
5
  SHA512:
6
- metadata.gz: 22635ccfb51ffcff3638ec6cac95dc550824060a74e41aedf4f11e67e2da45b320e801c789fa5d1634614e8f7838a2a4260a24711bd3ee290b2d80365ba6f2b1
7
- data.tar.gz: 6767110f8eccb4268f21b73a06f2854fc2b957da3caf3067e9ae0433a4251eb9d252e9d937c27d6926a416777e287911475a97bb8c10a5791954535022749dff
6
+ metadata.gz: afcd662f413015ece4a315039be1837a8f95b1a10806d75e6ac2ce8d08fa91d3e0720cd67aa1c383f34c6800f5d2c9a3a722c199a093663f6aefec0e93d92480
7
+ data.tar.gz: 9d9dacf3f53e4a53f69f325e9bd56d42d252cba529a91766515c2e3b9080403bd20f8dbdc801168a5b72164e12f02700b4bdc960845abb2706456391c9214e6a
@@ -0,0 +1,37 @@
1
+ module Solargraph
2
+ class ApiMap
3
+ class AttrPin
4
+ attr_reader :node
5
+
6
+ def initialize node
7
+ @node = node
8
+ end
9
+
10
+ def suggestions
11
+ @suggestions ||= generate_suggestions
12
+ end
13
+
14
+ private
15
+
16
+ def generate_suggestions
17
+ suggestions = []
18
+ c = node
19
+ if c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_reader
20
+ c.children[2..-1].each { |x|
21
+ suggestions.push Suggestion.new(x.children[0], kind: Suggestion::FIELD) if x.type == :sym
22
+ }
23
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_writer
24
+ c.children[2..-1].each { |x|
25
+ suggestions.push Suggestion.new("#{x.children[0]}=", kind: Suggestion::FIELD) if x.type == :sym
26
+ }
27
+ elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_accessor
28
+ c.children[2..-1].each { |x|
29
+ suggestions.push Suggestion.new(x.children[0], kind: Suggestion::FIELD) if x.type == :sym
30
+ suggestions.push Suggestion.new("#{x.children[0]}=", insert: "#{x.children[0]} = ", kind: Suggestion::FIELD) if x.type == :sym
31
+ }
32
+ end
33
+ suggestions
34
+ end
35
+ end
36
+ end
37
+ end
@@ -12,7 +12,7 @@ module Solargraph
12
12
  @included = []
13
13
  @excluded = []
14
14
  include_globs = ['**/*.rb']
15
- exclude_globs = ['spec/**/*']
15
+ exclude_globs = ['spec/**/*', 'test/**/*']
16
16
  unless @workspace.nil?
17
17
  sfile = File.join(@workspace, '.solargraph.yml')
18
18
  if File.file?(sfile)
@@ -0,0 +1,26 @@
1
+ module Solargraph
2
+ class ApiMap
3
+ class CvarPin
4
+ attr_reader :node
5
+ attr_reader :namespace
6
+ attr_reader :docstring
7
+
8
+ def initialize node, namespace, docstring
9
+ @node = node
10
+ @namespace = namespace
11
+ @docstring = docstring
12
+ end
13
+
14
+ def suggestion(api_map)
15
+ @suggestion ||= generate_suggestion(api_map)
16
+ end
17
+
18
+ private
19
+
20
+ def generate_suggestion(api_map)
21
+ type = api_map.infer_assignment_node_type(node, namespace)
22
+ Suggestion.new(node.children[0], kind: Suggestion::VARIABLE, documentation: docstring, return_type: type)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Solargraph
2
+ class ApiMap
3
+ class IvarPin
4
+ attr_reader :node
5
+ attr_reader :namespace
6
+ attr_reader :scope
7
+ attr_reader :docstring
8
+
9
+ def initialize node, namespace, scope, docstring
10
+ @node = node
11
+ @namespace = namespace
12
+ @scope = scope
13
+ @docstring = docstring
14
+ end
15
+
16
+ def suggestion(api_map)
17
+ @suggestion ||= generate_suggestion(api_map)
18
+ end
19
+
20
+ private
21
+
22
+ def generate_suggestion(api_map)
23
+ type = api_map.infer_assignment_node_type(node, namespace)
24
+ Suggestion.new(node.children[0], kind: Suggestion::VARIABLE, documentation: docstring, return_type: type)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module Solargraph
2
+ class ApiMap
3
+ class MethodPin
4
+ attr_reader :node
5
+ attr_reader :namespace
6
+ attr_reader :scope
7
+ attr_reader :visibility
8
+ attr_reader :docstring
9
+
10
+ def initialize node, namespace, scope, visibility, docstring
11
+ @node = node
12
+ @namespace = namespace
13
+ @scope = scope
14
+ @visibility = visibility
15
+ @docstring = docstring
16
+ end
17
+
18
+ def suggestion(api_map)
19
+ @suggestion ||= generate_suggestion(api_map)
20
+ end
21
+
22
+ private
23
+
24
+ def generate_suggestion(api_map)
25
+ i = node.type == :def ? 0 : 1
26
+ label = "#{node.children[i]}"
27
+ Suggestion.new(label, insert: node.children[i].to_s.gsub(/=/, ' = '), kind: Suggestion::METHOD, documentation: docstring, detail: namespace, arguments: get_method_args(api_map))
28
+ end
29
+
30
+ # @return [Array<String>]
31
+ def get_method_args(api_map)
32
+ list = nil
33
+ args = []
34
+ node.children.each { |c|
35
+ if c.kind_of?(AST::Node) and c.type == :args
36
+ list = c
37
+ break
38
+ end
39
+ }
40
+ return args if list.nil?
41
+ list.children.each { |c|
42
+ if c.type == :arg
43
+ args.push c.children[0].to_s
44
+ elsif c.type == :optarg
45
+ args.push "#{c.children[0]} = #{api_map.code_for(c.children[1])}"
46
+ elsif c.type == :kwarg
47
+ args.push "#{c.children[0]}:"
48
+ elsif c.type == :kwoptarg
49
+ args.push "#{c.children[0]}: #{api_map.code_for(c.children[1])}"
50
+ end
51
+ }
52
+ args
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,10 +1,18 @@
1
1
  require 'rubygems'
2
2
  require 'parser/current'
3
+ require 'thread'
3
4
 
4
5
  module Solargraph
5
6
  class ApiMap
6
7
  autoload :Config, 'solargraph/api_map/config'
7
8
  autoload :Cache, 'solargraph/api_map/cache'
9
+ autoload :MethodPin, 'solargraph/api_map/method_pin'
10
+ autoload :AttrPin, 'solargraph/api_map/attr_pin'
11
+ autoload :IvarPin, 'solargraph/api_map/ivar_pin'
12
+ autoload :CvarPin, 'solargraph/api_map/cvar_pin'
13
+
14
+ @@yard_map_cache = {}
15
+ @@semaphore = Mutex.new
8
16
 
9
17
  KEYWORDS = [
10
18
  '__ENCODING__', '__LINE__', '__FILE__', 'BEGIN', 'END', 'alias', 'and',
@@ -15,9 +23,9 @@ module Solargraph
15
23
  ].freeze
16
24
 
17
25
  MAPPABLE_NODES = [
18
- :array, :hash, :str, :int, :float, :block, :class, :sclass, :module,
19
- :def, :defs, :ivasgn, :gvasgn, :lvasgn, :cvasgn, :casgn, :or_asgn,
20
- :const, :lvar, :args, :kwargs
26
+ :array, :hash, :str, :dstr, :int, :float, :block, :class, :sclass,
27
+ :module, :def, :defs, :ivasgn, :gvasgn, :lvasgn, :cvasgn, :casgn,
28
+ :or_asgn, :const, :lvar, :args, :kwargs
21
29
  ].freeze
22
30
 
23
31
  MAPPABLE_METHODS = [
@@ -57,7 +65,10 @@ module Solargraph
57
65
 
58
66
  # @return [Solargraph::YardMap]
59
67
  def yard_map
60
- @yard_map ||= YardMap.new(required: required, workspace: workspace)
68
+ @@semaphore.synchronize {
69
+ @yard_map ||= @@yard_map_cache[[required, workspace]] || Solargraph::YardMap.new(required: required, workspace: workspace)
70
+ @@yard_map_cache[[required, workspace]] ||= @yard_map
71
+ }
61
72
  end
62
73
 
63
74
  # Add a file to the map.
@@ -88,16 +99,21 @@ module Solargraph
88
99
  #
89
100
  # @return [AST::Node]
90
101
  def append_node node, comments, filename = nil
102
+ @stale = true
91
103
  @file_comments[filename] = associate_comments(node, comments)
92
104
  mapified = reduce(node, @file_comments[filename])
93
105
  root = AST::Node.new(:begin, [filename])
94
106
  root = root.append mapified
95
107
  @file_nodes[filename] = root
96
108
  @required.uniq!
97
- process_maps
109
+ #process_maps
98
110
  root
99
111
  end
100
112
 
113
+ def refresh force = false
114
+ process_maps if @stale or force
115
+ end
116
+
101
117
  # Get the docstring associated with a node.
102
118
  #
103
119
  # @param node [AST::Node]
@@ -116,6 +132,7 @@ module Solargraph
116
132
  end
117
133
 
118
134
  def namespaces
135
+ refresh
119
136
  @namespace_map.keys
120
137
  end
121
138
 
@@ -124,6 +141,7 @@ module Solargraph
124
141
  end
125
142
 
126
143
  def namespaces_in name, root = ''
144
+ refresh
127
145
  result = []
128
146
  result += inner_namespaces_in(name, root, [])
129
147
  result += yard_map.get_constants name, root
@@ -138,6 +156,7 @@ module Solargraph
138
156
  end
139
157
 
140
158
  def find_fully_qualified_namespace name, root = '', skip = []
159
+ refresh
141
160
  return nil if skip.include?(root)
142
161
  skip.push root
143
162
  if name == ''
@@ -173,25 +192,32 @@ module Solargraph
173
192
 
174
193
  def get_namespace_nodes(fqns)
175
194
  return @file_nodes.values if fqns == '' or fqns.nil?
195
+ refresh
176
196
  @namespace_map[fqns] || []
177
197
  end
178
198
 
179
199
  def get_instance_variables(namespace, scope = :instance)
180
- nodes = get_namespace_nodes(namespace) || @file_nodes.values
181
- arr = []
182
- nodes.each { |n|
183
- arr += inner_get_instance_variables(n, namespace, scope)
184
- }
185
- arr
200
+ refresh
201
+ result = []
202
+ ip = @ivar_pins[namespace]
203
+ unless ip.nil?
204
+ ip.select{ |pin| pin.scope == scope }.each do |pin|
205
+ result.push pin.suggestion(self)
206
+ end
207
+ end
208
+ result
186
209
  end
187
210
 
188
211
  def get_class_variables(namespace)
189
- nodes = get_namespace_nodes(namespace) || @file_nodes.values
190
- arr = []
191
- nodes.each { |n|
192
- arr += inner_get_class_variables(n, namespace)
193
- }
194
- arr
212
+ refresh
213
+ result = []
214
+ ip = @cvar_pins[namespace]
215
+ unless ip.nil?
216
+ ip.each do |pin|
217
+ result.push pin.suggestion(self)
218
+ end
219
+ end
220
+ result
195
221
  end
196
222
 
197
223
  def find_parent(node, *types)
@@ -388,6 +414,7 @@ module Solargraph
388
414
  #
389
415
  # @return [Array<Solargraph::Suggestion>]
390
416
  def get_methods(namespace, root = '', visibility: [:public])
417
+ refresh
391
418
  namespace = clean_namespace_string(namespace)
392
419
  meths = []
393
420
  meths += inner_get_methods(namespace, root, []) #unless has_yardoc?
@@ -405,36 +432,12 @@ module Solargraph
405
432
  end
406
433
  end
407
434
 
408
- # @return [Array<String>]
409
- def get_method_args node
410
- list = nil
411
- args = []
412
- node.children.each { |c|
413
- if c.kind_of?(AST::Node) and c.type == :args
414
- list = c
415
- break
416
- end
417
- }
418
- return args if list.nil?
419
- list.children.each { |c|
420
- if c.type == :arg
421
- args.push c.children[0]
422
- elsif c.type == :optarg
423
- args.push "#{c.children[0]} = #{code_for(c.children[1])}"
424
- elsif c.type == :kwarg
425
- args.push "#{c.children[0]}:"
426
- elsif c.type == :kwoptarg
427
- args.push "#{c.children[0]}: #{code_for(c.children[1])}"
428
- end
429
- }
430
- args
431
- end
432
-
433
435
  # Get an array of instance methods that are available in the specified
434
436
  # namespace.
435
437
  #
436
438
  # @return [Array<Solargraph::Suggestion>]
437
439
  def get_instance_methods(namespace, root = '', visibility: [:public])
440
+ refresh
438
441
  namespace = clean_namespace_string(namespace)
439
442
  if namespace.end_with?('#class')
440
443
  return get_methods(namespace.split('#').first, root, visibility: visibility)
@@ -468,7 +471,7 @@ module Solargraph
468
471
  }
469
472
  return nil
470
473
  end
471
-
474
+
472
475
  def self.current
473
476
  if @current.nil?
474
477
  @current = ApiMap.new
@@ -476,7 +479,7 @@ module Solargraph
476
479
  end
477
480
  @current
478
481
  end
479
-
482
+
480
483
  def get_include_strings_from *nodes
481
484
  arr = []
482
485
  nodes.each { |node|
@@ -488,7 +491,15 @@ module Solargraph
488
491
  }
489
492
  arr
490
493
  end
491
-
494
+
495
+ def code_for node
496
+ src = @file_source[get_filename_for(node)]
497
+ return nil if src.nil?
498
+ b = node.location.expression.begin.begin_pos
499
+ e = node.location.expression.end.end_pos
500
+ src[b..e].strip.gsub(/,$/, '')
501
+ end
502
+
492
503
  # Update the YARD documentation for the current workspace.
493
504
  #
494
505
  def update_yardoc
@@ -504,12 +515,16 @@ module Solargraph
504
515
  STDERR.puts "There was an error processing the workspace yardoc."
505
516
  end
506
517
  end
507
- end
518
+ @@semaphore.synchronize {
519
+ @@yard_map_cache.clear
520
+ }
521
+ end
508
522
  end
509
523
 
510
524
  private
511
525
 
512
526
  def clear
527
+ @stale = false
513
528
  @file_source = {}
514
529
  @file_nodes = {}
515
530
  @file_comments = {}
@@ -517,6 +532,12 @@ module Solargraph
517
532
  @namespace_map = {}
518
533
  @namespace_tree = {}
519
534
  @required = []
535
+ @ivar_pins = {}
536
+ @cvar_pins = {}
537
+ @method_pins = {}
538
+ @attr_nodes = {}
539
+ @namespace_includes = {}
540
+ @superclasses = {}
520
541
  end
521
542
 
522
543
  def process_maps
@@ -527,6 +548,7 @@ module Solargraph
527
548
  map_parents f
528
549
  map_namespaces f
529
550
  }
551
+ @stale = false
530
552
  end
531
553
 
532
554
  # @return [Solargraph::ApiMap::Cache]
@@ -566,109 +588,42 @@ module Solargraph
566
588
  skip.push namespace
567
589
  fqns = find_fully_qualified_namespace(namespace, root)
568
590
  return meths if fqns.nil?
569
- nodes = get_namespace_nodes(fqns)
570
- nodes.each { |n|
571
- unless yardoc_has_file?(get_filename_for(n))
572
- if n.kind_of?(AST::Node)
573
- if n.type == :class and !n.children[1].nil?
574
- s = unpack_name(n.children[1])
575
- meths += inner_get_methods(s, root, skip)
576
- end
577
- vis = [:public]
578
- vis.push :private, :protected if namespace == root
579
- meths += inner_get_methods_from_node(n, root, :class, skip, vis)
580
- end
591
+ mn = @method_pins[fqns]
592
+ unless mn.nil?
593
+ mn.select{ |pin| pin.scope == :class }.each do |pin|
594
+ meths.push pin.suggestion(self)
581
595
  end
582
- }
596
+ end
583
597
  meths.uniq
584
598
  end
585
599
 
586
- def inner_get_methods_from_node node, root, scope, skip, visibility, current_visibility = :public
587
- meths = []
588
- node.children.each { |c|
589
- if c.kind_of?(AST::Node)
590
- if c.kind_of?(AST::Node) and c.type == :send and [:public, :protected, :private].include?(c.children[1])
591
- current_visibility = c.children[1]
592
- elsif (c.type == :defs and scope == :class) or (c.type == :def and scope == :instance)
593
- next unless visibility.include?(current_visibility)
594
- docstring = get_comment_for(c)
595
- child_index = (scope == :class ? 1 : 0)
596
- label = "#{c.children[child_index]}"
597
- args = get_method_args(c)
598
- if (c.children[child_index].to_s[0].match(/[a-z_]/i) and c.children[child_index] != :def)
599
- meths.push Suggestion.new(label, insert: c.children[child_index].to_s.gsub(/=/, ' = '), kind: Suggestion::METHOD, detail: 'Method', documentation: docstring, arguments: args)
600
- end
601
- elsif c.type == :sclass and scope == :class and c.children[0].type == :self
602
- meths.concat inner_get_methods_from_node c, root, :instance, skip, visibility
603
- elsif c.type == :send and c.children[1] == :include
604
- # TODO: This might not be right. Should we be getting singleton methods
605
- # from an include, or only from an extend?
606
- i = unpack_name(c.children[2])
607
- meths.concat inner_get_methods(i, root, skip) unless i == 'Kernel'
608
- else
609
- meths.concat inner_get_methods_from_node(c, root, scope, skip, visibility, current_visibility)
610
- end
611
- end
612
- }
613
- meths
614
- end
615
-
616
600
  def inner_get_instance_methods(namespace, root, skip, visibility = [:public])
617
601
  fqns = find_fully_qualified_namespace(namespace, root)
618
602
  meths = []
619
603
  return meths if skip.include?(fqns)
620
604
  skip.push fqns
621
- nodes = get_namespace_nodes(fqns)
622
- current_scope = :public
623
- nodes.each { |n|
624
- f = get_filename_for(n)
625
- unless yardoc_has_file?(get_filename_for(n))
626
- if n.kind_of?(AST::Node)
627
- if n.type == :class and !n.children[1].nil?
628
- s = unpack_name(n.children[1])
629
- # @todo This skip might not work properly. We might need to get a
630
- # fully qualified namespace from it first
631
- meths += get_instance_methods(s, namespace, visibility: visibility - [:private]) unless skip.include?(s)
632
- end
633
- n.children.each { |c|
634
- if c.kind_of?(AST::Node) and c.type == :send and [:public, :protected, :private].include?(c.children[1])
635
- current_scope = c.children[1]
636
- elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :include and n.type == :class
637
- fqmod = find_fully_qualified_namespace(const_from(c.children[2]), root)
638
- meths += get_instance_methods(fqmod) unless fqmod.nil? or skip.include?(fqmod)
639
- else
640
- if c.kind_of?(AST::Node) and c.type == :def
641
- if visibility.include?(current_scope)
642
- cmnt = get_comment_for(c)
643
- label = "#{c.children[0]}"
644
- args = get_method_args(c)
645
- meths.push Suggestion.new(label, insert: c.children[0].to_s.gsub(/=/, ' = '), kind: Suggestion::METHOD, documentation: cmnt, detail: fqns, arguments: args) if c.children[0].to_s[0].match(/[a-z]/i)
646
- end
647
- elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_reader
648
- c.children[2..-1].each { |x|
649
- meths.push Suggestion.new(x.children[0], kind: Suggestion::FIELD) if x.type == :sym
650
- }
651
- elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_writer
652
- c.children[2..-1].each { |x|
653
- meths.push Suggestion.new("#{x.children[0]}=", kind: Suggestion::FIELD) if x.type == :sym
654
- }
655
- elsif c.kind_of?(AST::Node) and c.type == :send and c.children[1] == :attr_accessor
656
- c.children[2..-1].each { |x|
657
- meths.push Suggestion.new(x.children[0], kind: Suggestion::FIELD) if x.type == :sym
658
- meths.push Suggestion.new("#{x.children[0]}=", insert: "#{x.children[0]} = ", kind: Suggestion::FIELD) if x.type == :sym
659
- }
660
- end
661
- end
662
- }
663
- end
605
+ an = @attr_nodes[fqns]
606
+ unless an.nil?
607
+ an.each do |pin|
608
+ meths.concat pin.suggestions
664
609
  end
665
- # This is necessary to get included modules from workspace definitions
666
- if n.type == :class
667
- get_include_strings_from(n).each { |i|
668
- meths += inner_get_instance_methods(i, fqns, skip, visibility)
669
- }
610
+ end
611
+ mn = @method_pins[fqns]
612
+ unless mn.nil?
613
+ mn.select{|pin| visibility.include?(pin.visibility) and pin.scope == :instance }.each do |pin|
614
+ meths.push pin.suggestion(self)
670
615
  end
671
- }
616
+ end
617
+ if visibility.include?(:public) or visibility.include?(:protected)
618
+ sc = @superclasses[fqns]
619
+ meths.concat inner_get_instance_methods(sc, fqns, skip, visibility - [:private]) unless sc.nil?
620
+ end
621
+ im = @namespace_includes[fqns]
622
+ unless im.nil?
623
+ im.each do |i|
624
+ meths.concat inner_get_instance_methods(i, fqns, skip, visibility)
625
+ end
626
+ end
672
627
  meths.uniq
673
628
  end
674
629
 
@@ -728,38 +683,6 @@ module Solargraph
728
683
  result
729
684
  end
730
685
 
731
- def inner_get_instance_variables(node, namespace, scope)
732
- arr = []
733
- if node.kind_of?(AST::Node)
734
- node.children.each { |c|
735
- if c.kind_of?(AST::Node)
736
- is_inst = !find_parent(c, :def).nil?
737
- if c.type == :ivasgn and c.children[0] and ( (scope == :instance and is_inst) or (scope != :instance and !is_inst) )
738
- type = infer_assignment_node_type(c, namespace)
739
- arr.push Suggestion.new(c.children[0], kind: Suggestion::VARIABLE, documentation: get_comment_for(c), return_type: type)
740
- end
741
- arr += inner_get_instance_variables(c, namespace, scope) unless [:class, :module].include?(c.type)
742
- end
743
- }
744
- end
745
- arr
746
- end
747
-
748
- def inner_get_class_variables(node, namespace)
749
- arr = []
750
- if node.kind_of?(AST::Node)
751
- node.children.each { |c|
752
- next unless c.kind_of?(AST::Node)
753
- if c.type == :cvasgn
754
- type = infer_assignment_node_type(c, namespace)
755
- arr.push Suggestion.new(c.children[0], kind: Suggestion::VARIABLE, documentation: get_comment_for(c), return_type: type)
756
- end
757
- arr += inner_get_class_variables(c, namespace) unless [:class, :module].include?(c.type)
758
- }
759
- end
760
- arr
761
- end
762
-
763
686
  # Get a fully qualified namespace for the given signature.
764
687
  # The signature should be in the form of a method chain, e.g.,
765
688
  # method1.method2
@@ -864,7 +787,7 @@ module Solargraph
864
787
  result = node.updated nil, mappable
865
788
  result
866
789
  end
867
-
790
+
868
791
  def get_mappable_nodes arr, comment_hash
869
792
  result = []
870
793
  arr.each { |n|
@@ -878,7 +801,7 @@ module Solargraph
878
801
  }
879
802
  result
880
803
  end
881
-
804
+
882
805
  def minify node, comment_hash
883
806
  return node if node.type == :args
884
807
  type = node.type
@@ -917,7 +840,7 @@ module Solargraph
917
840
  # TODO: The api_map should ignore local variables.
918
841
  type = node.children[0].type
919
842
  children.push node.children[0].children[0], node.children[1]
920
- elsif [:array, :hash, :str, :int, :float].include?(node.type)
843
+ elsif [:array, :hash, :str, :dstr, :int, :float].include?(node.type)
921
844
  # @todo Do we really care about the details?
922
845
  end
923
846
  result = node.updated(type, children)
@@ -939,7 +862,7 @@ module Solargraph
939
862
  }
940
863
  end
941
864
  end
942
-
865
+
943
866
  def add_to_namespace_tree tree
944
867
  cursor = @namespace_tree
945
868
  tree.each { |t|
@@ -948,9 +871,11 @@ module Solargraph
948
871
  }
949
872
  end
950
873
 
951
- def map_namespaces node, tree = []
874
+ def map_namespaces node, tree = [], visibility = :public, scope = :instance, fqn = nil, local_scope = :class
952
875
  if node.kind_of?(AST::Node)
876
+ return if node.type == :str or node.type == :dstr
953
877
  if node.type == :class or node.type == :module
878
+ visibility = :public
954
879
  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
955
880
  tree = pack_name(node.children[0])
956
881
  else
@@ -960,21 +885,55 @@ module Solargraph
960
885
  fqn = tree.join('::')
961
886
  @namespace_map[fqn] ||= []
962
887
  @namespace_map[fqn].push node
888
+ if node.type == :class and !node.children[1].nil?
889
+ sc = unpack_name(node.children[1])
890
+ @superclasses[fqn] = sc
891
+ end
892
+ end
893
+ file = get_filename_for(node)
894
+ in_yardoc = yardoc_has_file?(file)
895
+ node.children.each do |c|
896
+ if c.kind_of?(AST::Node)
897
+ if c.type == :ivasgn
898
+ @ivar_pins[fqn] ||= []
899
+ @ivar_pins[fqn].push IvarPin.new(c, fqn, local_scope, get_comment_for(c))
900
+ elsif c.type == :cvasgn
901
+ @cvar_pins[fqn] ||= []
902
+ @cvar_pins[fqn].push CvarPin.new(c, fqn, get_comment_for(c))
903
+ else
904
+ unless fqn.nil? or in_yardoc
905
+ if c.kind_of?(AST::Node)
906
+ if c.type == :def and c.children[0].to_s[0].match(/[a-z]/i)
907
+ @method_pins[fqn] ||= []
908
+ @method_pins[fqn].push MethodPin.new(c, fqn, scope, visibility, get_comment_for(c))
909
+ map_namespaces c, tree, visibility, scope, fqn, :instance
910
+ next
911
+ elsif c.type == :defs
912
+ @method_pins[fqn] ||= []
913
+ @method_pins[fqn].push MethodPin.new(c, fqn, :class, :public, get_comment_for(c))
914
+ map_namespaces c, tree, :public, :class, fqn
915
+ next
916
+ elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
917
+ visibility = c.children[1]
918
+ elsif c.type == :send and c.children[1] == :include and node.type == :class
919
+ @namespace_includes[fqn] ||= []
920
+ @namespace_includes[fqn].push unpack_name(c.children[2])
921
+ elsif c.type == :send and [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
922
+ @attr_nodes[fqn] ||= []
923
+ @attr_nodes[fqn].push AttrPin.new(c)
924
+ elsif c.type == :sclass and c.children[0].type == :self
925
+ map_namespaces c, tree, :public, :class, fqn
926
+ next
927
+ end
928
+ end
929
+ end
930
+ map_namespaces c, tree, visibility, scope, fqn
931
+ end
932
+ end
963
933
  end
964
- node.children.each { |c|
965
- map_namespaces c, tree
966
- }
967
934
  end
968
935
  end
969
936
 
970
- def code_for node
971
- src = @file_source[get_filename_for(node)]
972
- return nil if src.nil?
973
- b = node.location.expression.begin.begin_pos
974
- e = node.location.expression.end.end_pos
975
- src[b..e].strip.gsub(/,$/, '')
976
- end
977
-
978
937
  def clean_namespace_string namespace
979
938
  result = namespace.to_s.gsub(/<.*$/, '')
980
939
  if result == 'Class' and namespace.include?('<')
@@ -263,74 +263,65 @@ module Solargraph
263
263
  def suggest_at index, filtered: false, with_snippets: false
264
264
  return [] if string_at?(index) or string_at?(index - 1) or comment_at?(index)
265
265
  result = []
266
- phrase = phrase_at(index)
267
266
  signature = get_signature_at(index)
268
- namespace = namespace_at(index)
269
- if signature.include?('.') or @code[index - signature.length - 1] == '.'
270
- # Check for literals first
271
- nearest = @code[0, index].rindex('.')
272
- revised = signature[0..nearest-index-1]
273
- revised = revised[2..-1] if revised.start_with?('[]')
274
- cursed = get_signature_index_at(index)
275
- frag = @code[cursed..index]
276
- literal = nil
277
- if frag.start_with?('.')
278
- literal = node_at(cursed - 1)
279
- else
280
- literal = node_at(cursed + 1)
281
- end
282
- type = infer_literal_node_type(literal)
283
- if type.nil?
284
- type = infer_signature_at(nearest) unless revised.empty?
285
- if !type.nil?
286
- result.concat api_map.get_instance_methods(type) unless type.nil?
287
- elsif !revised.include?('.')
288
- fqns = api_map.find_fully_qualified_namespace(revised, namespace)
289
- result.concat api_map.get_methods(fqns) unless fqns.nil?
290
- end
267
+ if index == 0 or @code[index - 1].match(/[\.\s]/)
268
+ type = infer_signature_at(index)
269
+ else
270
+ if signature.include?('.')
271
+ last_period = @code[0..index].rindex('.')
272
+ if last_period.nil?
273
+ type = infer_signature_at(index)
274
+ else
275
+ type = infer_signature_at(last_period)
276
+ end
291
277
  else
292
- rest = revised
293
- rest = rest[1..-1] if rest.start_with?('.')
294
- if rest.nil? or rest.empty?
295
- result.concat api_map.get_instance_methods(type)
278
+ if signature.start_with?('@@')
279
+ return get_class_variables_at(index)
280
+ elsif signature.start_with?('@')
281
+ return get_instance_variables_at(index)
282
+ elsif signature.start_with?('$')
283
+ return api_map.get_global_variables
296
284
  else
297
- intype = api_map.infer_signature_type(rest, type, scope: :instance)
298
- result.concat api_map.get_instance_methods(intype)
285
+ type = infer_signature_at(index)
299
286
  end
300
287
  end
301
- elsif signature.start_with?('@@')
302
- result.concat get_class_variables_at(index)
303
- elsif signature.start_with?('@')
304
- result.concat get_instance_variables_at(index)
305
- elsif phrase.start_with?('$')
306
- result.concat api_map.get_global_variables
307
- elsif phrase.include?('::')
308
- parts = phrase.split('::', -1)
309
- ns = parts[0..-2].join('::')
310
- if parts.last.include?('.')
311
- ns = parts[0..-2].join('::') + '::' + parts.last[0..parts.last.index('.')-1]
312
- result = api_map.get_methods(ns)
313
- else
314
- result = api_map.namespaces_in(ns, namespace)
315
- end
316
- else
317
- type = infer_literal_node_type(node_at(index - 2))
318
- if type.nil?
319
- current_namespace = namespace_at(index)
320
- parts = current_namespace.to_s.split('::')
321
- result += get_snippets_at(index) if with_snippets
322
- result += get_local_variables_and_methods_at(index)
323
- result += ApiMap.get_keywords
324
- while parts.length > 0
325
- ns = parts.join('::')
326
- result += api_map.namespaces_in(ns, namespace)
327
- parts.pop
288
+ end
289
+ if type.nil?
290
+ unless signature.include?('.')
291
+ phrase = phrase_at(index)
292
+ signature = get_signature_at(index)
293
+ namespace = namespace_at(index)
294
+ if phrase.include?('::')
295
+ parts = phrase.split('::', -1)
296
+ ns = parts[0..-2].join('::')
297
+ if parts.last.include?('.')
298
+ ns = parts[0..-2].join('::') + '::' + parts.last[0..parts.last.index('.')-1]
299
+ result = api_map.get_methods(ns)
300
+ else
301
+ result = api_map.namespaces_in(ns, namespace)
302
+ end
303
+ else
304
+ type = infer_literal_node_type(node_at(index - 2))
305
+ if type.nil?
306
+ current_namespace = namespace_at(index)
307
+ parts = current_namespace.to_s.split('::')
308
+ result += get_snippets_at(index) if with_snippets
309
+ result += get_local_variables_and_methods_at(index)
310
+ result += ApiMap.get_keywords
311
+ while parts.length > 0
312
+ ns = parts.join('::')
313
+ result += api_map.namespaces_in(ns, namespace)
314
+ parts.pop
315
+ end
316
+ result += api_map.namespaces_in('')
317
+ result += api_map.get_instance_methods('Kernel')
318
+ else
319
+ result.concat api_map.get_instance_methods(type)
320
+ end
328
321
  end
329
- result += api_map.namespaces_in('')
330
- result += api_map.get_instance_methods('Kernel')
331
- else
332
- result.concat api_map.get_instance_methods(type)
333
322
  end
323
+ else
324
+ result.concat api_map.get_instance_methods(type)
334
325
  end
335
326
  result = reduce_starting_with(result, word_at(index)) if filtered
336
327
  result.uniq{|s| s.path}.sort{|a,b| a.label <=> b.label}
@@ -375,7 +366,10 @@ module Solargraph
375
366
  end
376
367
  end
377
368
  return [] if path.nil?
378
- return api_map.yard_map.objects(path, ns_here)
369
+ if path.start_with?('Class<')
370
+ path.gsub!(/^Class<([a-z0-9_:]*)>#([a-z0-9_]*)$/i, '\\1.\\2')
371
+ end
372
+ api_map.yard_map.objects(path, ns_here)
379
373
  end
380
374
 
381
375
  # Infer the type of the signature located at the specified index.
@@ -392,36 +386,67 @@ module Solargraph
392
386
  # @return [String]
393
387
  def infer_signature_at index
394
388
  signature = get_signature_at(index)
395
- node = parent_node_from(index, :class, :module, :def, :defs) || @node
396
- result = infer_signature_from_node signature, node
397
- if result.nil? or result.empty?
398
- arg = nil
399
- if node.type == :def or node.type == :defs or node.type == :block
400
- # Check for method arguments
401
- parts = signature.split('.', 2)
402
- # @type [Solargraph::Suggestion]
403
- arg = get_method_arguments_from(node).keep_if{|s| s.to_s == parts[0] }.first
404
- unless arg.nil?
405
- if parts[1].nil?
406
- result = arg.return_type
407
- else
408
- result = api_map.infer_signature_type(parts[1], parts[0], :instance)
389
+ # Check for literals first
390
+ return 'Integer' if signature.match(/^[0-9]+?\.?$/)
391
+ literal = nil
392
+ if (signature.empty? and @code[index - 1] == '.') or signature == '[].'
393
+ literal = node_at(index - 2)
394
+ elsif signature.start_with?('.')
395
+ literal = node_at(index - 1)
396
+ else
397
+ beg_sig = get_signature_index_at(index)
398
+ literal = node_at(1 + beg_sig)
399
+ end
400
+ type = infer_literal_node_type(literal)
401
+ if type.nil?
402
+ node = parent_node_from(index, :class, :module, :def, :defs) || @node
403
+ result = infer_signature_from_node signature, node
404
+ if result.nil? or result.empty?
405
+ arg = nil
406
+ if node.type == :def or node.type == :defs or node.type == :block
407
+ # Check for method arguments
408
+ parts = signature.split('.', 2)
409
+ # @type [Solargraph::Suggestion]
410
+ arg = get_method_arguments_from(node).keep_if{|s| s.to_s == parts[0] }.first
411
+ unless arg.nil?
412
+ if parts[1].nil?
413
+ result = arg.return_type
414
+ else
415
+ result = api_map.infer_signature_type(parts[1], parts[0], scope: :instance)
416
+ end
409
417
  end
410
418
  end
411
- end
412
- if arg.nil?
413
- # Check for yieldparams
414
- parts = signature.split('.', 2)
415
- yp = get_yieldparams_at(index).keep_if{|s| s.to_s == parts[0]}.first
416
- unless yp.nil?
417
- if parts[1].nil? or parts[1].empty?
418
- result = yp.return_type
419
- else
420
- newsig = parts[1..-1].join('.')
421
- result = api_map.infer_signature_type(newsig, yp.return_type, scope: :instance)
419
+ if arg.nil?
420
+ # Check for yieldparams
421
+ parts = signature.split('.', 2)
422
+ yp = get_yieldparams_at(index).keep_if{|s| s.to_s == parts[0]}.first
423
+ unless yp.nil?
424
+ if parts[1].nil? or parts[1].empty?
425
+ result = yp.return_type
426
+ else
427
+ newsig = parts[1..-1].join('.')
428
+ result = api_map.infer_signature_type(newsig, yp.return_type, scope: :instance)
429
+ end
422
430
  end
423
431
  end
424
432
  end
433
+ else
434
+ if signature.empty?
435
+ result = type
436
+ else
437
+ cursed = get_signature_index_at(index)
438
+ rest = signature[literal.loc.expression.end_pos+(cursed-literal.loc.expression.end_pos)..-1]
439
+ return type if rest.nil?
440
+ lit_code = @code[literal.loc.expression.begin_pos..literal.loc.expression.end_pos]
441
+ rest = rest[lit_code.length..-1] if rest.start_with?(lit_code)
442
+ rest = rest[1..-1] if rest.start_with?('.')
443
+ rest = rest[0..-2] if rest.end_with?('.')
444
+ if rest.empty?
445
+ result = type
446
+ else
447
+ result = api_map.infer_signature_type(rest, type, scope: :instance)
448
+ end
449
+ end
425
450
  end
426
451
  result
427
452
  end
@@ -439,6 +464,10 @@ module Solargraph
439
464
  inferred = nil
440
465
  parts = signature.split('.')
441
466
  ns_here = namespace_from(node)
467
+ unless signature.include?('.')
468
+ fqns = api_map.find_fully_qualified_namespace(signature, ns_here)
469
+ return "Class<#{fqns}>" unless fqns.nil?
470
+ end
442
471
  start = parts[0]
443
472
  return nil if start.nil?
444
473
  remainder = parts[1..-1]
@@ -41,7 +41,7 @@ module Solargraph
41
41
 
42
42
  def infer_literal_node_type node
43
43
  return nil unless node.kind_of?(AST::Node)
44
- if node.type == :str
44
+ if node.type == :str or node.type == :dstr
45
45
  return 'String'
46
46
  elsif node.type == :array
47
47
  return 'Array'
@@ -15,6 +15,11 @@ module Solargraph
15
15
  GC.start
16
16
  end
17
17
 
18
+ def self.wait
19
+ @@semaphore.lock
20
+ @@semaphore.unlock
21
+ end
22
+
18
23
  post '/prepare' do
19
24
  STDERR.puts "Preparing #{params['workspace']}"
20
25
  Server.prepare_workspace params['workspace']
@@ -44,7 +49,7 @@ module Solargraph
44
49
  begin
45
50
  sugg = []
46
51
  workspace = params['workspace'] || nil
47
- Server.prepare_workspace workspace unless @@api_hash.has_key?(workspace)
52
+ #Server.prepare_workspace workspace unless @@api_hash.has_key?(workspace)
48
53
  @@semaphore.synchronize {
49
54
  code_map = CodeMap.new(code: params['text'], filename: params['filename'], api_map: @@api_hash[workspace], cursor: [params['line'].to_i, params['column'].to_i])
50
55
  offset = code_map.get_offset(params['line'].to_i, params['column'].to_i)
@@ -63,7 +68,7 @@ module Solargraph
63
68
  begin
64
69
  sugg = []
65
70
  workspace = params['workspace'] || nil
66
- Server.prepare_workspace workspace unless @@api_hash.has_key?(workspace)
71
+ #Server.prepare_workspace workspace unless @@api_hash.has_key?(workspace)
67
72
  @@semaphore.synchronize {
68
73
  code_map = CodeMap.new(code: params['text'], filename: params['filename'], api_map: @@api_hash[workspace], cursor: [params['line'].to_i, params['column'].to_i])
69
74
  offset = code_map.get_offset(params['line'].to_i, params['column'].to_i)
@@ -121,12 +126,14 @@ module Solargraph
121
126
  end
122
127
 
123
128
  def prepare_workspace directory
124
- api_map = Solargraph::ApiMap.new(directory)
125
- api_map.update_yardoc
126
- @@semaphore.synchronize {
127
- @@api_hash[directory] = api_map
128
- }
129
- end
129
+ Thread.new do
130
+ @@semaphore.synchronize {
131
+ api_map = Solargraph::ApiMap.new(directory)
132
+ api_map.update_yardoc
133
+ @@api_hash[directory] = api_map
134
+ }
135
+ end
136
+ end
130
137
  end
131
138
 
132
139
  class Helpers
@@ -86,7 +86,8 @@ module Solargraph
86
86
  file.puts "include:",
87
87
  " - ./**/*.rb",
88
88
  "exclude:",
89
- " - spec/**/*"
89
+ " - spec/**/*",
90
+ " - test/**/*"
90
91
  end
91
92
  STDOUT.puts "Configuration file initialized."
92
93
  end
@@ -39,14 +39,9 @@ module Solargraph
39
39
  def return_type
40
40
  if @return_type.nil?
41
41
  if code_object.nil?
42
- unless documentation.nil?
43
- if documentation.kind_of?(YARD::Docstring)
44
- t = documentation.tag(:return)
45
- @return_type = t.types[0] unless t.nil? or t.types.nil?
46
- else
47
- match = documentation.match(/@return \[([a-z0-9:_]*)/i)
48
- @return_type = match[1] unless match.nil?
49
- end
42
+ if documentation.kind_of?(YARD::Docstring)
43
+ t = documentation.tag(:return)
44
+ @return_type = t.types[0] unless t.nil? or t.types.nil?
50
45
  end
51
46
  else
52
47
  o = code_object.tag(:overload)
@@ -63,8 +58,8 @@ module Solargraph
63
58
 
64
59
  def documentation
65
60
  if @documentation.nil?
66
- unless @code_object.nil?
67
- @documentation = @code_object.docstring unless @code_object.docstring.nil?
61
+ unless @code_object.nil? or @code_object.docstring.nil?
62
+ @documentation = @code_object.docstring
68
63
  end
69
64
  end
70
65
  @documentation
@@ -1,3 +1,3 @@
1
1
  module Solargraph
2
- VERSION = '0.11.2'
2
+ VERSION = '0.12.0'
3
3
  end
@@ -1,7 +1,5 @@
1
- require 'rubygems'
2
1
  require 'parser/current'
3
2
  require 'yard'
4
- require 'bundler'
5
3
 
6
4
  module Solargraph
7
5
 
@@ -10,7 +8,7 @@ module Solargraph
10
8
 
11
9
  attr_reader :workspace
12
10
 
13
- def initialize required: [], workspace: nil, with_bundled: false
11
+ def initialize required: [], workspace: nil
14
12
  @workspace = workspace
15
13
  unless workspace.nil?
16
14
  wsy = File.join(workspace, '.yardoc')
@@ -34,26 +32,9 @@ module Solargraph
34
32
  }
35
33
  yardocs.push File.join(Dir.home, '.solargraph', 'cache', '2.0.0', 'yardoc')
36
34
  yardocs.uniq!
37
- include_bundled_gems if with_bundled
38
35
  cache_core
39
36
  end
40
37
 
41
- def include_bundled_gems
42
- return if workspace.nil?
43
- lockfile = File.join(workspace, 'Gemfile.lock')
44
- return unless File.file?(lockfile)
45
- parser = Bundler::LockfileParser.new(Bundler.read_file(lockfile))
46
- parser.specs.each do |s|
47
- STDERR.puts "Specs include #{s.name}"
48
- gy = YARD::Registry.yardoc_file_for_gem(s.name)
49
- if gy.nil?
50
- STDERR.puts "Bundled gem not found: #{s.name}"
51
- else
52
- yardocs.unshift gy unless yardocs.include?(gy)
53
- end
54
- end
55
- end
56
-
57
38
  # @return [Array<String>]
58
39
  def yardocs
59
40
  @yardocs ||= []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fred Snyder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-05 00:00:00.000000000 Z
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -124,30 +124,30 @@ dependencies:
124
124
  name: debase
125
125
  requirement: !ruby/object:Gem::Requirement
126
126
  requirements:
127
- - - ">="
127
+ - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: '0'
129
+ version: '0.2'
130
130
  type: :development
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
- - - ">="
134
+ - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '0'
136
+ version: '0.2'
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: ruby-debug-ide
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
- - - ">="
141
+ - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: '0'
143
+ version: '0.6'
144
144
  type: :development
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
- - - ">="
148
+ - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '0'
150
+ version: '0.6'
151
151
  description: IDE tools for code completion and inline documentation
152
152
  email: admin@castwide.com
153
153
  executables:
@@ -158,8 +158,12 @@ files:
158
158
  - bin/solargraph
159
159
  - lib/solargraph.rb
160
160
  - lib/solargraph/api_map.rb
161
+ - lib/solargraph/api_map/attr_pin.rb
161
162
  - lib/solargraph/api_map/cache.rb
162
163
  - lib/solargraph/api_map/config.rb
164
+ - lib/solargraph/api_map/cvar_pin.rb
165
+ - lib/solargraph/api_map/ivar_pin.rb
166
+ - lib/solargraph/api_map/method_pin.rb
163
167
  - lib/solargraph/code_map.rb
164
168
  - lib/solargraph/node_methods.rb
165
169
  - lib/solargraph/server.rb