solargraph 0.26.1 → 0.27.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +5 -2
  3. data/lib/solargraph/api_map.rb +236 -234
  4. data/lib/solargraph/api_map/store.rb +18 -53
  5. data/lib/solargraph/bundle.rb +22 -0
  6. data/lib/solargraph/complex_type.rb +9 -5
  7. data/lib/solargraph/complex_type/type_methods.rb +113 -0
  8. data/lib/solargraph/complex_type/unique_type.rb +35 -0
  9. data/lib/solargraph/core_fills.rb +1 -0
  10. data/lib/solargraph/diagnostics.rb +6 -4
  11. data/lib/solargraph/diagnostics/base.rb +3 -0
  12. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  13. data/lib/solargraph/diagnostics/rubocop.rb +21 -6
  14. data/lib/solargraph/diagnostics/type_not_defined.rb +4 -3
  15. data/lib/solargraph/diagnostics/update_errors.rb +18 -0
  16. data/lib/solargraph/language_server/host.rb +90 -222
  17. data/lib/solargraph/language_server/host/cataloger.rb +68 -0
  18. data/lib/solargraph/language_server/host/diagnoser.rb +85 -0
  19. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +35 -24
  20. data/lib/solargraph/language_server/message/text_document/completion.rb +6 -8
  21. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  22. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +0 -1
  23. data/lib/solargraph/language_server/transport/socket.rb +4 -6
  24. data/lib/solargraph/language_server/transport/stdio.rb +4 -6
  25. data/lib/solargraph/library.rb +152 -99
  26. data/lib/solargraph/live_map.rb +1 -1
  27. data/lib/solargraph/location.rb +28 -0
  28. data/lib/solargraph/pin.rb +2 -0
  29. data/lib/solargraph/pin/attribute.rb +26 -12
  30. data/lib/solargraph/pin/base.rb +15 -35
  31. data/lib/solargraph/pin/base_variable.rb +7 -15
  32. data/lib/solargraph/pin/block.rb +5 -9
  33. data/lib/solargraph/pin/block_parameter.rb +9 -7
  34. data/lib/solargraph/pin/conversions.rb +5 -5
  35. data/lib/solargraph/pin/duck_method.rb +1 -1
  36. data/lib/solargraph/pin/instance_variable.rb +0 -4
  37. data/lib/solargraph/pin/keyword.rb +4 -0
  38. data/lib/solargraph/pin/localized.rb +5 -3
  39. data/lib/solargraph/pin/method.rb +11 -0
  40. data/lib/solargraph/pin/namespace.rb +7 -3
  41. data/lib/solargraph/pin/proxy_type.rb +3 -7
  42. data/lib/solargraph/pin/reference.rb +2 -2
  43. data/lib/solargraph/pin/symbol.rb +1 -1
  44. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  45. data/lib/solargraph/pin/yard_pin/namespace.rb +16 -7
  46. data/lib/solargraph/position.rb +103 -0
  47. data/lib/solargraph/range.rb +70 -0
  48. data/lib/solargraph/source.rb +159 -328
  49. data/lib/solargraph/source/chain.rb +38 -55
  50. data/lib/solargraph/source/chain/call.rb +47 -29
  51. data/lib/solargraph/source/chain/class_variable.rb +2 -2
  52. data/lib/solargraph/source/chain/constant.rb +3 -3
  53. data/lib/solargraph/source/chain/definition.rb +7 -3
  54. data/lib/solargraph/source/chain/global_variable.rb +1 -1
  55. data/lib/solargraph/source/chain/head.rb +22 -9
  56. data/lib/solargraph/source/chain/instance_variable.rb +2 -2
  57. data/lib/solargraph/source/chain/link.rb +4 -4
  58. data/lib/solargraph/source/chain/literal.rb +1 -1
  59. data/lib/solargraph/source/chain/variable.rb +2 -2
  60. data/lib/solargraph/source/change.rb +0 -6
  61. data/lib/solargraph/source/cursor.rb +161 -0
  62. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  63. data/lib/solargraph/source/node_chainer.rb +28 -21
  64. data/lib/solargraph/source/node_methods.rb +1 -1
  65. data/lib/solargraph/source/source_chainer.rb +217 -0
  66. data/lib/solargraph/source_map.rb +138 -0
  67. data/lib/solargraph/source_map/clip.rb +123 -0
  68. data/lib/solargraph/{source → source_map}/completion.rb +3 -3
  69. data/lib/solargraph/{source → source_map}/mapper.rb +143 -41
  70. data/lib/solargraph/version.rb +1 -1
  71. data/lib/solargraph/workspace.rb +13 -20
  72. data/lib/solargraph/yard_map.rb +77 -48
  73. metadata +17 -11
  74. data/lib/solargraph/basic_type.rb +0 -33
  75. data/lib/solargraph/basic_type_methods.rb +0 -111
  76. data/lib/solargraph/source/call_chainer.rb +0 -273
  77. data/lib/solargraph/source/fragment.rb +0 -342
  78. data/lib/solargraph/source/location.rb +0 -23
  79. data/lib/solargraph/source/position.rb +0 -95
  80. data/lib/solargraph/source/range.rb +0 -64
@@ -15,65 +15,55 @@ module Solargraph
15
15
  autoload :Definition, 'solargraph/source/chain/definition'
16
16
  autoload :Head, 'solargraph/source/chain/head'
17
17
 
18
- UNDEFINED_CALL = Source::Chain::Call.new('<undefined>')
19
- UNDEFINED_CONSTANT = Source::Chain::Constant.new('<undefined>')
18
+ UNDEFINED_CALL = Chain::Call.new('<undefined>')
19
+ UNDEFINED_CONSTANT = Chain::Constant.new('<undefined>')
20
20
 
21
21
  # @return [Array<Source::Chain::Link>]
22
22
  attr_reader :links
23
23
 
24
- # @param filename [String]
25
24
  # @param links [Array<Chain::Link>]
26
- def initialize filename, links
27
- @filename = filename
25
+ def initialize links
28
26
  @links = links
29
27
  @links.push UNDEFINED_CALL if @links.empty?
30
28
  end
31
29
 
32
- # @return [Array<Source::Chain::Link>]
30
+ # @return [Chain]
33
31
  def base
34
- # @todo It might make sense for the chain links to always have a root.
35
- @base ||= links[0..-2]
36
- end
37
-
38
- # @return [Source::Chain::Link]
39
- def tail
40
- @tail ||= links.last
41
- end
42
-
43
- def literal?
44
- tail.is_a?(Literal)
32
+ @base ||= Chain.new(links[0..-2])
45
33
  end
46
34
 
47
35
  # @param api_map [ApiMap]
48
- # @param context [Pin::Base]
36
+ # @param name_pin [Pin::Base]
49
37
  # @param locals [Array<Pin::Base>]
50
38
  # @return [Array<Pin::Base>]
51
- def define_with api_map, context, locals
52
- inner_define_with links, api_map, context, locals
53
- end
54
-
55
- def define_base_with api_map, context, locals
56
- inner_define_with links[0..-2], api_map, context, locals
39
+ def define api_map, name_pin, locals
40
+ return [] if undefined?
41
+ type = ComplexType::UNDEFINED
42
+ head = true
43
+ working_pin = name_pin
44
+ links[0..-2].each do |link|
45
+ pins = link.resolve(api_map, working_pin, head ? locals : [])
46
+ head = false
47
+ return [] if pins.empty?
48
+ type = ComplexType::UNDEFINED
49
+ pins.each do |pin|
50
+ type = pin.infer(api_map)
51
+ break unless type.undefined?
52
+ end
53
+ return [] if type.undefined?
54
+ working_pin = Pin::ProxyType.anonymous(type)
55
+ end
56
+ links.last.resolve(api_map, working_pin, head ? locals: [])
57
57
  end
58
58
 
59
59
  # @param api_map [ApiMap]
60
- # @param context [Pin::Base]
60
+ # @param name_pin [Pin::Base]
61
61
  # @param locals [Array<Pin::Base>]
62
62
  # @return [ComplexType]
63
- def infer_type_with api_map, context, locals
64
- # @todo Perform link inference
65
- inner_infer_type_with(links, api_map, context, locals)
66
- end
67
-
68
- def infer_base_type_with api_map, context, locals
69
- inner_infer_type_with(links[0..-2], api_map, context, locals)
70
- end
71
-
72
- private
73
-
74
- def inner_infer_type_with array, api_map, context, locals
63
+ def infer api_map, name_pin, locals
64
+ return ComplexType::UNDEFINED if undefined?
75
65
  type = ComplexType::UNDEFINED
76
- pins = inner_define_with(array, api_map, context, locals)
66
+ pins = define(api_map, name_pin, locals)
77
67
  pins.each do |pin|
78
68
  type = pin.infer(api_map)
79
69
  break unless type.undefined?
@@ -81,23 +71,16 @@ module Solargraph
81
71
  type
82
72
  end
83
73
 
84
- def inner_define_with array, api_map, context, locals
85
- return [] if array.empty?
86
- type = ComplexType::UNDEFINED
87
- head = true
88
- # @param link [Chain::Link]
89
- array[0..-2].each do |link|
90
- pins = link.resolve_pins(api_map, context, head ? locals : [])
91
- head = false
92
- return [] if pins.empty?
93
- pins.each do |pin|
94
- type = pin.infer(api_map)
95
- break unless type.undefined?
96
- end
97
- return [] if type.undefined?
98
- context = Pin::ProxyType.anonymous(type)
99
- end
100
- array.last.resolve_pins(api_map, context, head ? locals: [])
74
+ def literal?
75
+ links.last.is_a?(Chain::Literal)
76
+ end
77
+
78
+ def undefined?
79
+ links.any?(&:undefined?)
80
+ end
81
+
82
+ def constant?
83
+ links.last.is_a?(Chain::Constant)
101
84
  end
102
85
  end
103
86
  end
@@ -13,59 +13,77 @@ module Solargraph
13
13
  @arguments = arguments
14
14
  end
15
15
 
16
- def resolve_pins api_map, context, locals
16
+ def resolve api_map, name_pin, locals
17
17
  found = locals.select{|p| p.name == word}
18
- return inferred_pins(found, api_map, context) unless found.empty?
19
- pins = api_map.get_method_stack(namespace_from_context(context), word, scope: context.scope)
18
+ return inferred_pins(found, api_map, name_pin.context, locals) unless found.empty?
19
+ pins = api_map.get_method_stack(name_pin.context.namespace, word, scope: name_pin.context.scope)
20
20
  return [] if pins.empty?
21
- pins[0] = virtual_new_pin(pins.first, context) if pins.first.path == 'Class#new'
22
- inferred_pins(pins, api_map, context)
21
+ pins.unshift virtual_new_pin(pins.first, name_pin.context) if external_constructor?(pins.first, name_pin.context)
22
+ inferred_pins(pins, api_map, name_pin.context, locals)
23
23
  end
24
24
 
25
25
  private
26
26
 
27
- # @param pin [Pin::Base]
28
- # @return [String]
29
- def namespace_from_context pin
30
- return pin.namespace if pin.kind == Pin::ATTRIBUTE or pin.kind == Pin::METHOD
31
- pin.return_complex_type.namespace
32
- end
33
-
34
27
  # Create a `new` pin to facilitate type inference. This is necessary for
35
28
  # classes from YARD and classes in the namespace that do not have an
36
29
  # `initialize` method.
37
30
  #
38
31
  # @param new_pin [Solargraph::Pin::Base]
39
- # @param context_pin [Solargraph::Pin::Base]
32
+ # @param context [Solargraph::ComplexType]
40
33
  # @return [Pin::Method]
41
- def virtual_new_pin new_pin, context_pin
42
- pin = Pin::Method.new(new_pin.location, context_pin.path, new_pin.name, '', :class, new_pin.visibility, new_pin.parameters)
34
+ def virtual_new_pin new_pin, context
35
+ # pin = Pin::Method.new(new_pin.location, context.namespace, new_pin.name, '', :class, new_pin.visibility, new_pin.parameters)
43
36
  # @todo Smelly instance variable access.
44
- pin.instance_variable_set(:@return_complex_type, ComplexType.parse(context_pin.path))
45
- pin
37
+ # pin.instance_variable_set(:@return_complex_type, ComplexType.parse(context.namespace))
38
+ # pin
39
+ Pin::ProxyType.anonymous(ComplexType.parse(context.namespace))
46
40
  end
47
41
 
48
- def self_pin(api_map, context)
49
- return Pin::ProxyType.anonymous(ComplexType.parse(context.namespace)) if context.scope == :instance
50
- # return api_map.get_path_suggestions(context.namespace)
51
- context
52
- end
53
-
54
- def inferred_pins pins, api_map, context_pin
55
- result = pins.uniq(&:location).map do |p|
42
+ def inferred_pins pins, api_map, context, locals
43
+ result = pins.map do |p|
56
44
  if CoreFills::METHODS_RETURNING_SELF.include?(p.path)
57
- next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context_pin.return_complex_type.tag}]", p.scope, p.visibility, p.parameters)
45
+ next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.tag}]", p.scope, p.visibility, p.parameters)
58
46
  end
59
- if CoreFills::METHODS_RETURNING_SUBTYPES.include?(p.path) and !context_pin.return_complex_type.subtypes.empty?
60
- next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context_pin.return_complex_type.subtypes.first.tag}]", p.scope, p.visibility, p.parameters)
47
+ if CoreFills::METHODS_RETURNING_SUBTYPES.include?(p.path) && !context.subtypes.empty?
48
+ next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.subtypes.first.tag}]", p.scope, p.visibility, p.parameters)
61
49
  end
62
- next p if p.kind == Pin::METHOD or p.kind == Pin::ATTRIBUTE or p.kind == Pin::NAMESPACE
50
+ # @todo Temporarily disabled macros
51
+ # if p.kind == Pin::METHOD && !p.macros.empty?
52
+ # result = process_macro(p, api_map, context, locals)
53
+ # next result unless result.return_type.undefined?
54
+ # end
55
+ next p if p.kind == Pin::METHOD || p.kind == Pin::ATTRIBUTE || p.kind == Pin::NAMESPACE
63
56
  type = p.infer(api_map)
64
57
  next p if p.return_complex_type == type
65
58
  Pin::ProxyType.new(p.location, nil, p.name, type)
66
59
  end
67
60
  result
68
61
  end
62
+
63
+ def external_constructor? pin, context
64
+ pin.path == 'Class#new' || (pin.name == 'new' && pin.scope == :class && pin.context != context)
65
+ end
66
+
67
+ # @param pin [Pin::Method]
68
+ # @param api_map [ApiMap]
69
+ # @param context [ComplexType]
70
+ def process_macro pin, api_map, context, locals
71
+ pin.macros.each do |macro|
72
+ vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals)) }
73
+ txt = macro.tag.text.clone
74
+ i = 1
75
+ vals.each do |v|
76
+ txt.gsub!(/\$#{i}/, v.context.namespace)
77
+ i += 1
78
+ end
79
+ docstring = YARD::Docstring.parser.parse(txt).to_docstring
80
+ tag = docstring.tag(:return)
81
+ unless tag.nil? || tag.types.nil?
82
+ return Pin::ProxyType.anonymous(ComplexType.parse(*tag.types))
83
+ end
84
+ end
85
+ return Pin::ProxyType.new(nil, nil, nil, ComplexType::UNDEFINED)
86
+ end
69
87
  end
70
88
  end
71
89
  end
@@ -2,8 +2,8 @@ module Solargraph
2
2
  class Source
3
3
  class Chain
4
4
  class ClassVariable < Link
5
- def resolve_pins api_map, context, locals
6
- api_map.get_class_variable_pins(context.namespace).select{|p| p.name == word}
5
+ def resolve api_map, name_pin, locals
6
+ api_map.get_class_variable_pins(name_pin.context.namespace).select{|p| p.name == word}
7
7
  end
8
8
  end
9
9
  end
@@ -6,12 +6,12 @@ module Solargraph
6
6
  @word = word
7
7
  end
8
8
 
9
- # @param api_map [ApiMap]
10
- def resolve_pins api_map, context, locals
9
+ def resolve api_map, name_pin, locals
10
+ return [Pin::ROOT_PIN] if word.empty?
11
11
  parts = word.split('::')
12
12
  last = parts.pop
13
13
  first = parts.join('::').to_s
14
- api_map.get_constants(first, context.named_context).select{|p| p.name == last}
14
+ api_map.get_constants(first, name_pin.context.namespace).select{|p| p.name == last}
15
15
  end
16
16
  end
17
17
  end
@@ -2,13 +2,17 @@ module Solargraph
2
2
  class Source
3
3
  class Chain
4
4
  class Definition < Link
5
- # @param location [Solargraph::Source::Location]
5
+ # @param location [Solargraph::Location]
6
6
  def initialize location
7
7
  @location = location
8
8
  end
9
9
 
10
- def resolve_pins api_map, context, locals
11
- api_map.locate_pin(@location)
10
+ # @param api_map [ApiMap]
11
+ def resolve api_map, name_pin, locals
12
+ result = api_map.locate_pin(@location)
13
+ # result = api_map.source_map(@location.filename).locate_named_path_pin(@location.range.start.line, @location.range.start.column)
14
+ return [] if result.nil?
15
+ [result]
12
16
  end
13
17
  end
14
18
  end
@@ -2,7 +2,7 @@ module Solargraph
2
2
  class Source
3
3
  class Chain
4
4
  class GlobalVariable < Link
5
- def resolve_pins api_map, context, locals
5
+ def resolve api_map, name_pin, locals
6
6
  api_map.get_global_variable_pins.select{|p| p.name == word}
7
7
  end
8
8
  end
@@ -4,15 +4,28 @@ module Solargraph
4
4
  # Chain::Head is a link for ambiguous words, e.g.; `String` can refer to
5
5
  # either a class (`String`) or a function (`Kernel#String`).
6
6
  #
7
- class Head < Call
8
- def resolve_pins api_map, context, locals
9
- return [self_pin(api_map, context)] if word == 'self'
10
- base = super
11
- return base if locals.map(&:name).include?(word)
12
- here = []
13
- ns = api_map.qualify(word, context.named_context)
14
- here.concat api_map.get_path_suggestions(ns) unless ns.nil?
15
- here + base
7
+ # @note Chain::Head is only intended to handle `self` and `super`.
8
+ class Head < Link
9
+ def resolve api_map, name_pin, locals
10
+ return [self_pin(name_pin.context)] if word == 'self'
11
+ return super_pins(api_map, name_pin) if word == 'super'
12
+ []
13
+ end
14
+
15
+ private
16
+
17
+ # @param context [ComplexType]
18
+ # @return [Pin::ProxyType]
19
+ def self_pin(context)
20
+ Pin::ProxyType.anonymous(context)
21
+ end
22
+
23
+ # @param api_map [ApiMap]
24
+ # @param name_pin [Pin::Base]
25
+ # @return [Array<Pin::Base>]
26
+ def super_pins api_map, name_pin
27
+ pins = api_map.get_method_stack(name_pin.namespace, name_pin.name, scope: name_pin.scope)
28
+ pins.reject{|p| p.path == name_pin.path}
16
29
  end
17
30
  end
18
31
  end
@@ -2,8 +2,8 @@ module Solargraph
2
2
  class Source
3
3
  class Chain
4
4
  class InstanceVariable < Link
5
- def resolve_pins api_map, context, locals
6
- api_map.get_instance_variable_pins(context.namespace, context.scope).select{|p| p.name == word}
5
+ def resolve api_map, name_pin, locals
6
+ api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word}
7
7
  end
8
8
  end
9
9
  end
@@ -17,10 +17,10 @@ module Solargraph
17
17
  end
18
18
 
19
19
  # @param api_map [ApiMap]
20
- # @param context [ComplexType]
21
- # @param locals [Array<Solargraph::Pin::Base>]
22
- # @return [Array<Solargraph::Pin::Base>]
23
- def resolve_pins api_map, context, locals
20
+ # @param name_pin [Pin::Base]
21
+ # @param locals [Array<Pin::Base>]
22
+ # @return [Array<Pin::Base>]
23
+ def resolve api_map, name_pin, locals
24
24
  []
25
25
  end
26
26
 
@@ -12,7 +12,7 @@ module Solargraph
12
12
  @complex_type = ComplexType.parse(type).first
13
13
  end
14
14
 
15
- def resolve_pins api_map, context, locals
15
+ def resolve api_map, name_pin, locals
16
16
  [Pin::ProxyType.anonymous(@complex_type)]
17
17
  end
18
18
  end
@@ -2,8 +2,8 @@ module Solargraph
2
2
  class Source
3
3
  class Chain
4
4
  class Variable < Link
5
- def resolve_pins api_map, context, locals
6
- api_map.get_instance_variable_pins(context.namespace, context.scope).select{|p| p.name == word}
5
+ def resolve api_map, name_pin, locals
6
+ api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word}
7
7
  end
8
8
  end
9
9
  end
@@ -38,12 +38,6 @@ module Solargraph
38
38
  break
39
39
  end
40
40
  commit text, "#{new_text[0..-2]} "
41
- elsif nullable and !range.nil? and new_text.empty?
42
- offset = Position.to_offset(text, range.start)
43
- if offset > 0 and text[offset - 1] == '.'
44
- text = text[0..offset - 1] + ' ' + text[offset..-1]
45
- end
46
- commit text, new_text
47
41
  elsif range.nil?
48
42
  new_text
49
43
  else
@@ -0,0 +1,161 @@
1
+ module Solargraph
2
+ class Source
3
+ # Information about a position in a source, including the word located
4
+ # there.
5
+ #
6
+ class Cursor
7
+ # @return [Position]
8
+ attr_reader :position
9
+
10
+ # @return [Source]
11
+ attr_reader :source
12
+
13
+ # @param source [Source]
14
+ # @param position [Position, Array(Integer, Integer)]
15
+ def initialize source, position
16
+ @source = source
17
+ @position = if position.is_a?(Array)
18
+ Position.new(position[0], position[1])
19
+ else
20
+ position
21
+ end
22
+ end
23
+
24
+ # @return [String]
25
+ def filename
26
+ source.filename
27
+ end
28
+
29
+ # The whole word at the current position. Given the text `foo.bar`, the
30
+ # word at position(0,6) is `bar`.
31
+ #
32
+ # @return [String]
33
+ def word
34
+ @word ||= start_of_word + end_of_word
35
+ end
36
+
37
+ # The part of the word before the current position. Given the text
38
+ # `foo.bar`, the start_of_word at position(0, 6) is `ba`.
39
+ #
40
+ # @return [String]
41
+ def start_of_word
42
+ @start_of_word ||= begin
43
+ match = source.code[0..offset-1].to_s.match(start_word_pattern)
44
+ result = (match ? match[0] : '')
45
+ # Including the preceding colon if the word appears to be a symbol
46
+ result = ":#{result}" if source.code[0..offset-result.length-1].end_with?(':') and !source.code[0..offset-result.length-1].end_with?('::')
47
+ result
48
+ end
49
+ end
50
+
51
+ # The part of the word after the current position. Given the text
52
+ # `foo.bar`, the end_of_word at position (0,6) is `r`.
53
+ #
54
+ # @return [String]
55
+ def end_of_word
56
+ @end_of_word ||= begin
57
+ match = source.code[offset..-1].to_s.match(end_word_pattern)
58
+ match ? match[0] : ''
59
+ end
60
+ end
61
+
62
+ def start_of_constant?
63
+ source.code[offset-2, 2] == '::'
64
+ end
65
+
66
+ # The range of the word at the current position.
67
+ #
68
+ # @return [Range]
69
+ def range
70
+ @range ||= begin
71
+ s = Position.from_offset(source.code, offset - start_of_word.length)
72
+ e = Position.from_offset(source.code, offset + end_of_word.length)
73
+ Solargraph::Range.new(s, e)
74
+ end
75
+ end
76
+
77
+ # @return [Chain]
78
+ def chain
79
+ @chain ||= SourceChainer.chain(source, position)
80
+ end
81
+
82
+ # @return [Boolean]
83
+ def argument?
84
+ @argument ||= !signature_position.nil?
85
+ end
86
+
87
+ # @return [Boolean]
88
+ def comment?
89
+ @comment ||= source.comment_at?(position)
90
+ end
91
+
92
+ # @return [Boolean]
93
+ def string?
94
+ @string ||= source.string_at?(position)
95
+ end
96
+
97
+ # @return [Cursor, nil]
98
+ def recipient
99
+ return nil unless argument?
100
+ @recipient ||= Cursor.new(source, signature_position)
101
+ end
102
+
103
+ def node_position
104
+ @node_position ||= begin
105
+ if start_of_word.empty?
106
+ match = source.code[0, offset].match(/[\s]*(\.|:+)[\s]*$/)
107
+ if match
108
+ Position.from_offset(source.code, offset - match[0].length)
109
+ else
110
+ position
111
+ end
112
+ else
113
+ position
114
+ end
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # @return [Integer]
121
+ def offset
122
+ @offset ||= Position.to_offset(source.code, position)
123
+ end
124
+
125
+ # A regular expression to find the start of a word from an offset.
126
+ #
127
+ # @return [Regexp]
128
+ def start_word_pattern
129
+ /(@{1,2}|\$)?([a-z0-9_]|[^\u0000-\u007F])*\z/i
130
+ end
131
+
132
+ # A regular expression to find the end of a word from an offset.
133
+ #
134
+ # @return [Regexp]
135
+ def end_word_pattern
136
+ /^([a-z0-9_]|[^\u0000-\u007F])*[\?\!]?/i
137
+ end
138
+
139
+ def signature_position
140
+ if @signature_position.nil?
141
+ open_parens = 0
142
+ cursor = offset - 1
143
+ while cursor >= 0
144
+ break if cursor < 0
145
+ if source.code[cursor] == ')'
146
+ open_parens -= 1
147
+ elsif source.code[cursor] == '('
148
+ open_parens += 1
149
+ end
150
+ break if open_parens == 1
151
+ cursor -= 1
152
+ end
153
+ if cursor >= 0
154
+ @signature_position = Position.from_offset(source.code, cursor)
155
+ end
156
+ end
157
+ @signature_position
158
+ end
159
+ end
160
+ end
161
+ end