solargraph 0.11.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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