solargraph 0.39.13 → 0.40.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -7
  3. data/CHANGELOG.md +984 -0
  4. data/Rakefile +12 -1
  5. data/SPONSORS.md +1 -0
  6. data/lib/solargraph.rb +2 -4
  7. data/lib/solargraph/api_map.rb +75 -74
  8. data/lib/solargraph/api_map/cache.rb +2 -2
  9. data/lib/solargraph/api_map/store.rb +4 -8
  10. data/lib/solargraph/{bundle.rb → bench.rb} +6 -2
  11. data/lib/solargraph/compat.rb +14 -0
  12. data/lib/solargraph/complex_type.rb +2 -2
  13. data/lib/solargraph/convention.rb +14 -5
  14. data/lib/solargraph/convention/base.rb +16 -8
  15. data/lib/solargraph/convention/gemfile.rb +2 -5
  16. data/lib/solargraph/convention/gemspec.rb +3 -6
  17. data/lib/solargraph/convention/rspec.rb +3 -6
  18. data/lib/solargraph/documentor.rb +2 -0
  19. data/lib/solargraph/environ.rb +11 -6
  20. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +6 -1
  21. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  22. data/lib/solargraph/library.rb +7 -7
  23. data/lib/solargraph/parser/legacy/node_chainer.rb +7 -7
  24. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +1 -1
  25. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +36 -23
  26. data/lib/solargraph/parser/node_processor/base.rb +3 -0
  27. data/lib/solargraph/parser/rubyvm/node_chainer.rb +9 -9
  28. data/lib/solargraph/parser/rubyvm/node_methods.rb +1 -1
  29. data/lib/solargraph/parser/rubyvm/node_processors.rb +1 -0
  30. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +35 -11
  31. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +1 -1
  32. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +40 -29
  33. data/lib/solargraph/pin.rb +0 -3
  34. data/lib/solargraph/pin/common.rb +1 -1
  35. data/lib/solargraph/pin/conversions.rb +3 -4
  36. data/lib/solargraph/pin/documenting.rb +3 -9
  37. data/lib/solargraph/pin/method.rb +141 -7
  38. data/lib/solargraph/pin/method_alias.rb +1 -1
  39. data/lib/solargraph/position.rb +2 -14
  40. data/lib/solargraph/shell.rb +1 -0
  41. data/lib/solargraph/source.rb +10 -6
  42. data/lib/solargraph/source/chain.rb +18 -5
  43. data/lib/solargraph/source_map.rb +4 -1
  44. data/lib/solargraph/source_map/clip.rb +3 -2
  45. data/lib/solargraph/source_map/mapper.rb +10 -6
  46. data/lib/solargraph/type_checker.rb +35 -39
  47. data/lib/solargraph/type_checker/param_def.rb +1 -1
  48. data/lib/solargraph/version.rb +1 -1
  49. data/lib/solargraph/yard_map.rb +40 -47
  50. data/lib/solargraph/yard_map/core_fills.rb +185 -0
  51. data/lib/solargraph/yard_map/helpers.rb +16 -0
  52. data/lib/solargraph/yard_map/mapper.rb +14 -8
  53. data/lib/solargraph/{pin/yard_pin/constant.rb → yard_map/mapper/to_constant.rb} +6 -6
  54. data/lib/solargraph/yard_map/mapper/to_method.rb +78 -0
  55. data/lib/solargraph/{pin/yard_pin/namespace.rb → yard_map/mapper/to_namespace.rb} +6 -6
  56. data/lib/solargraph/yard_map/rdoc_to_yard.rb +1 -1
  57. data/lib/solargraph/yard_map/stdlib_fills.rb +43 -0
  58. data/lib/solargraph/yard_map/to_method.rb +79 -0
  59. data/solargraph.gemspec +4 -4
  60. metadata +20 -34
  61. data/lib/solargraph/core_fills.rb +0 -159
  62. data/lib/solargraph/pin/attribute.rb +0 -49
  63. data/lib/solargraph/pin/base_method.rb +0 -141
  64. data/lib/solargraph/pin/yard_pin.rb +0 -12
  65. data/lib/solargraph/pin/yard_pin/method.rb +0 -80
  66. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +0 -20
  67. data/lib/solargraph/stdlib_fills.rb +0 -40
  68. data/travis-bundler.rb +0 -11
@@ -27,6 +27,7 @@ module Solargraph
27
27
  @region = region
28
28
  @pins = pins
29
29
  @locals = locals
30
+ @processed_children = false
30
31
  end
31
32
 
32
33
  # Subclasses should override this method to generate new pins.
@@ -41,6 +42,8 @@ module Solargraph
41
42
  # @param subregion [Region]
42
43
  # @return [void]
43
44
  def process_children subregion = region
45
+ return if @processed_children
46
+ @processed_children = true
44
47
  node.children.each do |child|
45
48
  next unless Parser.is_ast_node?(child)
46
49
  NodeProcessor.process(child, subregion, pins, locals)
@@ -15,7 +15,7 @@ module Solargraph
15
15
  def initialize node, filename = nil, in_block = false
16
16
  @node = node
17
17
  @filename = filename
18
- @in_block = in_block
18
+ @in_block = in_block ? 1 : 0
19
19
  end
20
20
 
21
21
  # @return [Source::Chain]
@@ -52,27 +52,27 @@ module Solargraph
52
52
  return generate_links(n.children[0]) if n.type == :SPLAT
53
53
  result = []
54
54
  if n.type == :ITER
55
- @in_block = true
55
+ @in_block += 1
56
56
  result.concat generate_links(n.children[0])
57
- @in_block = false
57
+ @in_block -= 1
58
58
  elsif n.type == :CALL || n.type == :OPCALL
59
59
  n.children[0..-3].each do |c|
60
60
  result.concat generate_links(c)
61
61
  end
62
- result.push Chain::Call.new(n.children[-2].to_s, node_to_argchains(n.children.last), @in_block || block_passed?(n))
62
+ result.push Chain::Call.new(n.children[-2].to_s, node_to_argchains(n.children.last), @in_block > 0 || block_passed?(n))
63
63
  elsif n.type == :ATTRASGN
64
64
  result.concat generate_links(n.children[0])
65
- result.push Chain::Call.new(n.children[1].to_s, node_to_argchains(n.children[2]), @in_block || block_passed?(n))
65
+ result.push Chain::Call.new(n.children[1].to_s, node_to_argchains(n.children[2]), @in_block > 0 || block_passed?(n))
66
66
  elsif n.type == :VCALL
67
- result.push Chain::Call.new(n.children[0].to_s, [], @in_block || block_passed?(n))
67
+ result.push Chain::Call.new(n.children[0].to_s, [], @in_block > 0 || block_passed?(n))
68
68
  elsif n.type == :FCALL
69
- result.push Chain::Call.new(n.children[0].to_s, node_to_argchains(n.children[1]), @in_block || block_passed?(n))
69
+ result.push Chain::Call.new(n.children[0].to_s, node_to_argchains(n.children[1]), @in_block > 0 || block_passed?(n))
70
70
  elsif n.type == :SELF
71
71
  result.push Chain::Head.new('self')
72
72
  elsif n.type == :ZSUPER
73
- result.push Chain::ZSuper.new('super', @in_block || block_passed?(n))
73
+ result.push Chain::ZSuper.new('super', @in_block > 0 || block_passed?(n))
74
74
  elsif n.type == :SUPER
75
- result.push Chain::Call.new('super', node_to_argchains(n.children.last), @in_block || block_passed?(n))
75
+ result.push Chain::Call.new('super', node_to_argchains(n.children.last), @in_block > 0 || block_passed?(n))
76
76
  elsif [:COLON2, :COLON3, :CONST].include?(n.type)
77
77
  const = unpack_name(n)
78
78
  result.push Chain::Constant.new(const)
@@ -197,7 +197,7 @@ module Solargraph
197
197
  result.push node
198
198
  result.concat get_return_nodes_only(node.children[1])
199
199
  elsif node.type == :CASE
200
- node.children.each do |cc|
200
+ node.children[1..-1].each do |cc|
201
201
  result.concat reduce_to_value_nodes(cc.children[1..-1])
202
202
  end
203
203
  else
@@ -54,6 +54,7 @@ module Solargraph
54
54
  register :KW_ARG, Rubyvm::NodeProcessors::KwArgNode
55
55
  register :ITER, Rubyvm::NodeProcessors::BlockNode
56
56
  register :LAMBDA, Rubyvm::NodeProcessors::BlockNode
57
+ register :FOR, Rubyvm::NodeProcessors::BlockNode
57
58
  register :OP_ASGN_OR, Rubyvm::NodeProcessors::OrasgnNode
58
59
  register :LIT, Rubyvm::NodeProcessors::LitNode
59
60
  end
@@ -6,17 +6,31 @@ module Solargraph
6
6
  module NodeProcessors
7
7
  class ArgsNode < Parser::NodeProcessor::Base
8
8
  def process
9
- if region.closure.is_a?(Pin::BaseMethod) || region.closure.is_a?(Pin::Block)
10
- node.children[0].times do |i|
11
- locals.push Solargraph::Pin::Parameter.new(
12
- location: region.closure.location,
13
- closure: region.closure,
14
- comments: comments_for(node),
15
- name: region.lvars[i].to_s,
16
- presence: region.closure.location.range,
17
- decl: :arg
18
- )
19
- region.closure.parameters.push locals.last
9
+ if region.closure.is_a?(Pin::Method) || region.closure.is_a?(Pin::Block)
10
+ if region.lvars[0].nil?
11
+ node.children[0].times do |i|
12
+ locals.push Solargraph::Pin::Parameter.new(
13
+ location: region.closure.location,
14
+ closure: region.closure,
15
+ comments: comments_for(node),
16
+ name: extract_name(node.children[i + 1]),
17
+ presence: region.closure.location.range,
18
+ decl: :arg
19
+ )
20
+ region.closure.parameters.push locals.last
21
+ end
22
+ else
23
+ node.children[0].times do |i|
24
+ locals.push Solargraph::Pin::Parameter.new(
25
+ location: region.closure.location,
26
+ closure: region.closure,
27
+ comments: comments_for(node),
28
+ name: region.lvars[i].to_s,
29
+ presence: region.closure.location.range,
30
+ decl: :arg
31
+ )
32
+ region.closure.parameters.push locals.last
33
+ end
20
34
  end
21
35
  # @todo Optional args, keyword args, etc.
22
36
  if node.children[6]
@@ -55,6 +69,16 @@ module Solargraph
55
69
  end
56
70
  process_children
57
71
  end
72
+
73
+ private
74
+
75
+ def extract_name var
76
+ if Parser.is_ast_node?(var)
77
+ var.children[0].to_s
78
+ else
79
+ var.to_s
80
+ end
81
+ end
58
82
  end
59
83
  end
60
84
  end
@@ -19,7 +19,7 @@ module Solargraph
19
19
  if region.visibility == :module_function
20
20
  rng = Range.from_node(node)
21
21
  named_path = named_path_pin(rng.start)
22
- if named_path.is_a?(Pin::BaseMethod)
22
+ if named_path.is_a?(Pin::Method)
23
23
  pins.push Solargraph::Pin::InstanceVariable.new(
24
24
  location: loc,
25
25
  closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, name: region.closure.name),
@@ -9,21 +9,7 @@ module Solargraph
9
9
 
10
10
  def process
11
11
  if [:private, :public, :protected].include?(node.children[0])
12
- if node.type == :FCALL && Parser.is_ast_node?(node.children.last)
13
- node.children.last.children[0..-2].each do |child|
14
- # next unless child.is_a?(AST::Node) && (child.type == :sym || child.type == :str)
15
- next unless child.type == :LIT || child.type == :STR
16
- name = child.children[0].to_s
17
- matches = pins.select{ |pin| pin.is_a?(Pin::BaseMethod) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)}
18
- matches.each do |pin|
19
- # @todo Smelly instance variable access
20
- pin.instance_variable_set(:@visibility, node.children[0])
21
- end
22
- end
23
- else
24
- # @todo Smelly instance variable access
25
- region.instance_variable_set(:@visibility, node.children[0])
26
- end
12
+ process_visibility
27
13
  elsif node.children[0] == :module_function
28
14
  process_module_function
29
15
  elsif node.children[0] == :require
@@ -33,8 +19,7 @@ module Solargraph
33
19
  elsif node.children[0] == :alias_method
34
20
  process_alias_method
35
21
  elsif node.children[0] == :private_class_method
36
- # Processing a private class can potentially handle children on its own
37
- return if process_private_class_method
22
+ process_private_class_method
38
23
  elsif [:attr_reader, :attr_writer, :attr_accessor].include?(node.children[0])
39
24
  process_attribute
40
25
  elsif node.children[0] == :include
@@ -53,6 +38,28 @@ module Solargraph
53
38
 
54
39
  private
55
40
 
41
+ # @return [void]
42
+ def process_visibility
43
+ if node.type == :FCALL && Parser.is_ast_node?(node.children.last)
44
+ node.children.last.children[0..-2].each do |child|
45
+ # next unless child.is_a?(AST::Node) && (child.type == :sym || child.type == :str)
46
+ if child.type == :LIT || child.type == :STR
47
+ name = child.children[0].to_s
48
+ matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)}
49
+ matches.each do |pin|
50
+ # @todo Smelly instance variable access
51
+ pin.instance_variable_set(:@visibility, node.children[0])
52
+ end
53
+ else
54
+ process_children region.update(visibility: node.children[0])
55
+ end
56
+ end
57
+ else
58
+ # @todo Smelly instance variable access
59
+ region.instance_variable_set(:@visibility, node.children[0])
60
+ end
61
+ end
62
+
56
63
  # @return [void]
57
64
  def process_attribute
58
65
  return unless Parser.is_ast_node?(node.children[1])
@@ -62,26 +69,30 @@ module Solargraph
62
69
  clos = region.closure
63
70
  cmnt = comments_for(node)
64
71
  if node.children[0] == :attr_reader || node.children[0] == :attr_accessor
65
- pins.push Solargraph::Pin::Attribute.new(
72
+ pins.push Solargraph::Pin::Method.new(
66
73
  location: loc,
67
74
  closure: clos,
68
75
  name: a.children[0].to_s,
69
76
  comments: cmnt,
70
- access: :reader,
71
77
  scope: region.scope || :instance,
72
- visibility: region.visibility
78
+ visibility: region.visibility,
79
+ attribute: true
73
80
  )
74
81
  end
75
82
  if node.children[0] == :attr_writer || node.children[0] == :attr_accessor
76
- pins.push Solargraph::Pin::Attribute.new(
83
+ pins.push Solargraph::Pin::Method.new(
77
84
  location: loc,
78
85
  closure: clos,
79
86
  name: "#{a.children[0]}=",
80
87
  comments: cmnt,
81
- access: :writer,
82
88
  scope: region.scope || :instance,
83
- visibility: region.visibility
89
+ visibility: region.visibility,
90
+ attribute: true
84
91
  )
92
+ pins.last.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
93
+ if pins.last.return_type.defined?
94
+ pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
95
+ end
85
96
  end
86
97
  end
87
98
  end
@@ -91,7 +102,8 @@ module Solargraph
91
102
  return unless Parser.is_ast_node?(node.children.last)
92
103
  node.children.last.children[0..-2].each do |i|
93
104
  next unless [:COLON2, :COLON3, :CONST].include?(i.type)
94
- pins.push Pin::Reference::Include.new(
105
+ type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include
106
+ pins.push type.new(
95
107
  location: get_node_location(i),
96
108
  closure: region.closure,
97
109
  name: unpack_name(i)
@@ -165,7 +177,7 @@ module Solargraph
165
177
  node.children.last.children[0..-2].each do |x|
166
178
  next unless [:LIT, :STR].include?(x.type)
167
179
  cn = x.children[0].to_s
168
- ref = pins.select{|p| [Solargraph::Pin::Method, Solargraph::Pin::Attribute].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn}.first
180
+ ref = pins.select { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == cn }.first
169
181
  unless ref.nil?
170
182
  pins.delete ref
171
183
  mm = Solargraph::Pin::Method.new(
@@ -214,6 +226,7 @@ module Solargraph
214
226
 
215
227
  # @return [void]
216
228
  def process_private_constant
229
+ # @todo Bare `private_constant` causes an error
217
230
  node.children.last.children[0..-2].each do |child|
218
231
  if [:LIT, :STR].include?(child.type)
219
232
  cn = child.children[0].to_s
@@ -241,20 +254,18 @@ module Solargraph
241
254
  )
242
255
  end
243
256
 
244
- # @return [Boolean]
257
+ # @return [void]
245
258
  def process_private_class_method
246
259
  return false unless Parser.is_ast_node?(node.children.last)
247
260
  if node.children.last.children.first.type == :DEFN
248
261
  process_children region.update(scope: :class, visibility: :private)
249
- true
250
262
  else
251
263
  node.children.last.children[0..-2].each do |child|
252
264
  if child.type == :LIT && child.children.first.is_a?(::Symbol)
253
265
  sym_name = child.children.first.to_s
254
- ref = pins.select{|p| [Solargraph::Pin::Method, Solargraph::Pin::Attribute].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == sym_name}.first
266
+ ref = pins.select { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == sym_name }.first
255
267
  # HACK: Smelly instance variable access
256
268
  ref.instance_variable_set(:@visibility, :private) unless ref.nil?
257
- false
258
269
  end
259
270
  end
260
271
  end
@@ -9,10 +9,8 @@ module Solargraph
9
9
  autoload :Common, 'solargraph/pin/common'
10
10
  autoload :Conversions, 'solargraph/pin/conversions'
11
11
  autoload :Base, 'solargraph/pin/base'
12
- autoload :BaseMethod, 'solargraph/pin/base_method'
13
12
  autoload :Method, 'solargraph/pin/method'
14
13
  autoload :MethodAlias, 'solargraph/pin/method_alias'
15
- autoload :Attribute, 'solargraph/pin/attribute'
16
14
  autoload :BaseVariable, 'solargraph/pin/base_variable'
17
15
  autoload :InstanceVariable, 'solargraph/pin/instance_variable'
18
16
  autoload :ClassVariable, 'solargraph/pin/class_variable'
@@ -30,7 +28,6 @@ module Solargraph
30
28
  autoload :Localized, 'solargraph/pin/localized'
31
29
  autoload :ProxyType, 'solargraph/pin/proxy_type'
32
30
  autoload :DuckMethod, 'solargraph/pin/duck_method'
33
- autoload :YardPin, 'solargraph/pin/yard_pin'
34
31
  autoload :Singleton, 'solargraph/pin/singleton'
35
32
  autoload :KeywordParam, 'solargraph/pin/keyword_param'
36
33
 
@@ -54,7 +54,7 @@ module Solargraph
54
54
  until here.nil?
55
55
  if here.is_a?(Pin::Namespace)
56
56
  return here.return_type
57
- elsif here.is_a?(Pin::BaseMethod)
57
+ elsif here.is_a?(Pin::Method)
58
58
  if here.scope == :instance
59
59
  return ComplexType.try_parse(here.context.namespace)
60
60
  else
@@ -23,16 +23,15 @@ module Solargraph
23
23
 
24
24
  # @return [Hash]
25
25
  def resolve_completion_item
26
- if @resolve_completion_item.nil?
26
+ @resolve_completion_item ||= begin
27
27
  extra = {}
28
28
  alldoc = ''
29
29
  # alldoc += link_documentation unless link_documentation.nil?
30
30
  # alldoc += "\n\n" unless alldoc.empty?
31
31
  alldoc += documentation unless documentation.nil?
32
32
  extra[:documentation] = alldoc unless alldoc.empty?
33
- @resolve_completion_item = completion_item.merge(extra)
33
+ completion_item.merge(extra)
34
34
  end
35
- @resolve_completion_item
36
35
  end
37
36
 
38
37
  # @return [Hash]
@@ -48,7 +47,7 @@ module Solargraph
48
47
  # This property is not cached in an instance variable because it can
49
48
  # change when pins get proxied.
50
49
  detail = String.new
51
- detail += "(#{parameters.map(&:full).join(', ')}) " unless !is_a?(Pin::BaseMethod) || parameters.empty?
50
+ detail += "(#{parameters.map(&:full).join(', ')}) " unless !is_a?(Pin::Method) || parameters.empty?
52
51
  detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined?
53
52
  detail.strip!
54
53
  return nil if detail.empty?
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'maruku'
3
+ require 'kramdown'
4
+ require 'kramdown-parser-gfm'
4
5
  require 'yard'
5
6
  require 'reverse_markdown'
6
7
  require 'solargraph/converters/dl'
@@ -8,9 +9,6 @@ require 'solargraph/converters/dt'
8
9
  require 'solargraph/converters/dd'
9
10
  require 'solargraph/converters/misc'
10
11
 
11
- # HACK: Setting :html_parser through `Maruku.new` does not work
12
- MaRuKu::Globals[:html_parser] = 'nokogiri'
13
-
14
12
  module Solargraph
15
13
  module Pin
16
14
  # A module to add the Pin::Base#documentation method.
@@ -41,11 +39,7 @@ module Solargraph
41
39
 
42
40
  def to_s
43
41
  return "\n```ruby\n#{@plaintext}#{@plaintext.end_with?("\n") ? '' : "\n"}```\n\n" if code?
44
- ReverseMarkdown.convert unescape_brackets(Maruku.new(escape_brackets(@plaintext), on_error: :raise).to_html)
45
- rescue MaRuKu::Exception
46
- # Maruku exceptions usually indicate that the documentation is in
47
- # RDoc syntax.
48
- ReverseMarkdown.convert YARD::Templates::Helpers::Markup::RDocMarkup.new(@plaintext).to_html
42
+ ReverseMarkdown.convert unescape_brackets(Kramdown::Document.new(escape_brackets(@plaintext), input: 'GFM').to_html)
49
43
  end
50
44
 
51
45
  private
@@ -2,18 +2,29 @@
2
2
 
3
3
  module Solargraph
4
4
  module Pin
5
- class Method < BaseMethod
5
+ # The base class for method and attribute pins.
6
+ #
7
+ class Method < Closure
6
8
  include Solargraph::Parser::NodeMethods
7
9
 
8
10
  # @return [Array<Pin::Parameter>]
9
11
  attr_reader :parameters
10
12
 
11
- # @param args [Array<String>]
12
- # @param node [Parser::AST::Node, nil]
13
- def initialize parameters: [], node: nil, **splat
13
+ # @return [::Symbol] :public, :private, or :protected
14
+ attr_reader :visibility
15
+
16
+ # @return [Parser::AST::Node]
17
+ attr_reader :node
18
+
19
+ # @param visibility [::Symbol] :public, :protected, or :private
20
+ # @param explicit [Boolean]
21
+ def initialize visibility: :public, explicit: true, parameters: [], node: nil, attribute: false, **splat
14
22
  super(**splat)
23
+ @visibility = visibility
24
+ @explicit = explicit
15
25
  @parameters = parameters
16
26
  @node = node
27
+ @attribute = attribute
17
28
  end
18
29
 
19
30
  # @return [Array<String>]
@@ -22,11 +33,70 @@ module Solargraph
22
33
  end
23
34
 
24
35
  def completion_item_kind
25
- Solargraph::LanguageServer::CompletionItemKinds::METHOD
36
+ attribute? ? Solargraph::LanguageServer::CompletionItemKinds::PROPERTY : Solargraph::LanguageServer::CompletionItemKinds::METHOD
26
37
  end
27
38
 
28
39
  def symbol_kind
29
- LanguageServer::SymbolKinds::METHOD
40
+ attribute? ? Solargraph::LanguageServer::SymbolKinds::PROPERTY : LanguageServer::SymbolKinds::METHOD
41
+ end
42
+
43
+ def return_type
44
+ @return_type ||= generate_complex_type
45
+ end
46
+
47
+ def path
48
+ @path ||= "#{namespace}#{(scope == :instance ? '#' : '.')}#{name}"
49
+ end
50
+
51
+ def typify api_map
52
+ decl = super
53
+ return decl unless decl.undefined?
54
+ type = see_reference(api_map) || typify_from_super(api_map)
55
+ return type.qualify(api_map, namespace) unless type.nil?
56
+ name.end_with?('?') ? ComplexType::BOOLEAN : ComplexType::UNDEFINED
57
+ end
58
+
59
+ def documentation
60
+ if @documentation.nil?
61
+ @documentation ||= super || ''
62
+ param_tags = docstring.tags(:param)
63
+ unless param_tags.nil? or param_tags.empty?
64
+ @documentation += "\n\n" unless @documentation.empty?
65
+ @documentation += "Params:\n"
66
+ lines = []
67
+ param_tags.each do |p|
68
+ l = "* #{p.name}"
69
+ l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty?
70
+ l += " #{p.text}"
71
+ lines.push l
72
+ end
73
+ @documentation += lines.join("\n")
74
+ end
75
+ return_tags = docstring.tags(:return)
76
+ unless return_tags.empty?
77
+ @documentation += "\n\n" unless @documentation.empty?
78
+ @documentation += "Returns:\n"
79
+ lines = []
80
+ return_tags.each do |r|
81
+ l = "*"
82
+ l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty?
83
+ l += " #{r.text}"
84
+ lines.push l
85
+ end
86
+ @documentation += lines.join("\n")
87
+ end
88
+ @documentation += "\n\n" unless @documentation.empty?
89
+ @documentation += "Visibility: #{visibility}"
90
+ end
91
+ @documentation.to_s
92
+ end
93
+
94
+ def explicit?
95
+ @explicit
96
+ end
97
+
98
+ def attribute?
99
+ @attribute
30
100
  end
31
101
 
32
102
  def nearly? other
@@ -37,7 +107,7 @@ module Solargraph
37
107
  end
38
108
 
39
109
  def probe api_map
40
- infer_from_return_nodes(api_map)
110
+ attribute? ? infer_from_iv(api_map) : infer_from_return_nodes(api_map)
41
111
  end
42
112
 
43
113
  def try_merge! pin
@@ -70,6 +140,57 @@ module Solargraph
70
140
 
71
141
  private
72
142
 
143
+ # @return [ComplexType]
144
+ def generate_complex_type
145
+ tags = docstring.tags(:return).map(&:types).flatten.reject(&:nil?)
146
+ return ComplexType::UNDEFINED if tags.empty?
147
+ ComplexType.try_parse *tags
148
+ end
149
+
150
+ # @param api_map [ApiMap]
151
+ # @return [ComplexType, nil]
152
+ def see_reference api_map
153
+ docstring.ref_tags.each do |ref|
154
+ next unless ref.tag_name == 'return' && ref.owner
155
+ result = resolve_reference(ref.owner.to_s, api_map)
156
+ return result unless result.nil?
157
+ end
158
+ match = comments.match(/^[ \t]*\(see (.*)\)/m)
159
+ return nil if match.nil?
160
+ resolve_reference match[1], api_map
161
+ end
162
+
163
+ # @param api_map [ApiMap]
164
+ # @return [ComplexType, nil]
165
+ def typify_from_super api_map
166
+ stack = api_map.get_method_stack(namespace, name, scope: scope).reject { |pin| pin.path == path }
167
+ return nil if stack.empty?
168
+ stack.each do |pin|
169
+ return pin.return_type unless pin.return_type.undefined?
170
+ end
171
+ nil
172
+ end
173
+
174
+ # @param ref [String]
175
+ # @param api_map [ApiMap]
176
+ # @return [ComplexType]
177
+ def resolve_reference ref, api_map
178
+ parts = ref.split(/[\.#]/)
179
+ if parts.first.empty? || parts.one?
180
+ path = "#{namespace}#{ref}"
181
+ else
182
+ fqns = api_map.qualify(parts.first, namespace)
183
+ return ComplexType::UNDEFINED if fqns.nil?
184
+ path = fqns + ref[parts.first.length] + parts.last
185
+ end
186
+ pins = api_map.get_path_pins(path)
187
+ pins.each do |pin|
188
+ type = pin.typify(api_map)
189
+ return type unless type.undefined?
190
+ end
191
+ nil
192
+ end
193
+
73
194
  # @return [Parser::AST::Node, nil]
74
195
  def method_body_node
75
196
  return nil if node.nil?
@@ -106,6 +227,19 @@ module Solargraph
106
227
  return ComplexType::UNDEFINED if result.empty?
107
228
  ComplexType.try_parse(*result.map(&:tag).uniq)
108
229
  end
230
+
231
+ def infer_from_iv api_map
232
+ types = []
233
+ varname = "@#{name.gsub(/=$/, '')}"
234
+ pins = api_map.get_instance_variable_pins(binder.namespace, binder.scope).select { |iv| iv.name == varname }
235
+ pins.each do |pin|
236
+ type = pin.typify(api_map)
237
+ type = pin.probe(api_map) if type.undefined?
238
+ types.push type if type.defined?
239
+ end
240
+ return ComplexType::UNDEFINED if types.empty?
241
+ ComplexType.try_parse(*types.map(&:tag).uniq)
242
+ end
109
243
  end
110
244
  end
111
245
  end