solargraph 0.25.1 → 0.26.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +18 -16
  3. data/lib/solargraph/api_map.rb +100 -161
  4. data/lib/solargraph/api_map/source_to_yard.rb +9 -9
  5. data/lib/solargraph/api_map/store.rb +50 -13
  6. data/lib/solargraph/basic_type.rb +33 -0
  7. data/lib/solargraph/basic_type_methods.rb +111 -0
  8. data/lib/solargraph/complex_type.rb +51 -89
  9. data/lib/solargraph/core_fills.rb +12 -8
  10. data/lib/solargraph/diagnostics/type_not_defined.rb +2 -2
  11. data/lib/solargraph/language_server.rb +3 -0
  12. data/lib/solargraph/language_server/completion_item_kinds.rb +2 -0
  13. data/lib/solargraph/language_server/error_codes.rb +2 -0
  14. data/lib/solargraph/language_server/host.rb +53 -6
  15. data/lib/solargraph/language_server/message.rb +13 -0
  16. data/lib/solargraph/language_server/message/text_document/definition.rb +4 -6
  17. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -1
  18. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -1
  19. data/lib/solargraph/language_server/message_types.rb +2 -0
  20. data/lib/solargraph/language_server/request.rb +4 -0
  21. data/lib/solargraph/language_server/symbol_kinds.rb +28 -26
  22. data/lib/solargraph/language_server/transport.rb +3 -0
  23. data/lib/solargraph/language_server/uri_helpers.rb +2 -0
  24. data/lib/solargraph/library.rb +12 -7
  25. data/lib/solargraph/pin.rb +1 -1
  26. data/lib/solargraph/pin/attribute.rb +5 -5
  27. data/lib/solargraph/pin/base.rb +51 -16
  28. data/lib/solargraph/pin/base_variable.rb +25 -7
  29. data/lib/solargraph/pin/block.rb +18 -1
  30. data/lib/solargraph/pin/block_parameter.rb +42 -5
  31. data/lib/solargraph/pin/conversions.rb +4 -2
  32. data/lib/solargraph/pin/method.rb +6 -6
  33. data/lib/solargraph/pin/method_parameter.rb +6 -6
  34. data/lib/solargraph/pin/namespace.rb +7 -2
  35. data/lib/solargraph/pin/proxy_type.rb +39 -0
  36. data/lib/solargraph/pin/symbol.rb +20 -12
  37. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  38. data/lib/solargraph/source.rb +89 -38
  39. data/lib/solargraph/source/call_chainer.rb +273 -0
  40. data/lib/solargraph/source/chain.rb +104 -0
  41. data/lib/solargraph/source/chain/call.rb +72 -0
  42. data/lib/solargraph/source/chain/class_variable.rb +11 -0
  43. data/lib/solargraph/source/chain/constant.rb +17 -0
  44. data/lib/solargraph/source/chain/definition.rb +16 -0
  45. data/lib/solargraph/source/chain/global_variable.rb +11 -0
  46. data/lib/solargraph/source/chain/head.rb +20 -0
  47. data/lib/solargraph/source/chain/instance_variable.rb +11 -0
  48. data/lib/solargraph/source/chain/link.rb +33 -0
  49. data/lib/solargraph/source/chain/literal.rb +21 -0
  50. data/lib/solargraph/source/chain/variable.rb +11 -0
  51. data/lib/solargraph/source/change.rb +3 -1
  52. data/lib/solargraph/{api_map → source}/completion.rb +3 -1
  53. data/lib/solargraph/source/encoding_fixes.rb +21 -0
  54. data/lib/solargraph/source/fragment.rb +139 -284
  55. data/lib/solargraph/source/mapper.rb +27 -16
  56. data/lib/solargraph/source/node_chainer.rb +94 -0
  57. data/lib/solargraph/source/node_methods.rb +2 -2
  58. data/lib/solargraph/source/position.rb +4 -0
  59. data/lib/solargraph/source/range.rb +10 -2
  60. data/lib/solargraph/version.rb +1 -1
  61. data/lib/solargraph/yard_map.rb +13 -2
  62. metadata +20 -6
  63. data/lib/solargraph/api_map/probe.rb +0 -251
  64. data/lib/solargraph/api_map/type_methods.rb +0 -40
  65. data/lib/solargraph/pin/proxy_method.rb +0 -30
@@ -51,8 +51,13 @@ module Solargraph
51
51
  @path ||= (namespace.empty? ? '' : "#{namespace}::") + name
52
52
  end
53
53
 
54
- def return_complex_types
55
- @return_complex_types ||= ComplexType.parse( (type == :class ? 'Class' : 'Module') + "<#{path}>" )
54
+ def return_complex_type
55
+ @return_complex_type ||= ComplexType.parse( (type == :class ? 'Class' : 'Module') + "<#{path}>" )
56
+ end
57
+
58
+ def infer api_map
59
+ # Assuming that namespace pins are always fully qualified
60
+ return_complex_type
56
61
  end
57
62
  end
58
63
  end
@@ -0,0 +1,39 @@
1
+ module Solargraph
2
+ module Pin
3
+ class ProxyType < Base
4
+ # @param location [Solargraph::Source::Location]
5
+ # @param namespace [String]
6
+ # @param name [String]
7
+ # @param return_type [ComplexType]
8
+ def initialize location, namespace, name, return_type
9
+ super(location, namespace, name, '')
10
+ @return_complex_type = return_type
11
+ end
12
+
13
+ def scope
14
+ return_complex_type.scope
15
+ end
16
+
17
+ def path
18
+ @path ||= begin
19
+ result = namespace.to_s
20
+ result += '::' unless result.empty? or name.to_s.empty?
21
+ result += name.to_s
22
+ end
23
+ end
24
+
25
+ def named_context
26
+ path
27
+ end
28
+
29
+ # @param return_type [ComplexType]
30
+ # @return [ProxyType]
31
+ def self.anonymous return_type
32
+ parts = return_type.namespace.split('::')
33
+ namespace = parts[0..-2].join('::').to_s
34
+ name = parts.last.to_s
35
+ ProxyType.new(nil, namespace, name, return_type)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,19 +1,15 @@
1
1
  module Solargraph
2
2
  module Pin
3
- class Symbol
4
- include Conversions
5
-
6
- attr_reader :location
7
-
8
- attr_reader :name
9
-
3
+ class Symbol < Base
4
+ # @param location [Solargraph::Source::Location]
5
+ # @param name [String]
10
6
  def initialize location, name
11
7
  @name = name
12
8
  @location = location
13
9
  end
14
10
 
15
- def filename
16
- location.filename
11
+ def namespace
12
+ ''
17
13
  end
18
14
 
19
15
  def kind
@@ -21,7 +17,7 @@ module Solargraph
21
17
  end
22
18
 
23
19
  def path
24
- nil
20
+ ''
25
21
  end
26
22
 
27
23
  def identifier
@@ -32,8 +28,20 @@ module Solargraph
32
28
  Solargraph::LanguageServer::CompletionItemKinds::KEYWORD
33
29
  end
34
30
 
35
- def return_type
36
- 'Symbol'
31
+ def comments
32
+ ''
33
+ end
34
+
35
+ def return_complex_type
36
+ @return_complex_type ||= Solargraph::ComplexType::SYMBOL
37
+ end
38
+
39
+ def directives
40
+ []
41
+ end
42
+
43
+ def deprecated?
44
+ false
37
45
  end
38
46
  end
39
47
  end
@@ -9,8 +9,8 @@ module Solargraph
9
9
  super(location, code_object.namespace.to_s, code_object.name.to_s, comments, code_object.scope, code_object.visibility, get_parameters(code_object))
10
10
  end
11
11
 
12
- def return_complex_types
13
- @return_complex_types ||= Solargraph::ComplexType.parse(Solargraph::CoreFills::CUSTOM_RETURN_TYPES[path]) if Solargraph::CoreFills::CUSTOM_RETURN_TYPES.has_key?(path)
12
+ def return_complex_type
13
+ @return_complex_type ||= Solargraph::ComplexType.parse(Solargraph::CoreFills::CUSTOM_RETURN_TYPES[path]) if Solargraph::CoreFills::CUSTOM_RETURN_TYPES.has_key?(path)
14
14
  super
15
15
  end
16
16
 
@@ -13,6 +13,13 @@ module Solargraph
13
13
  autoload :Change, 'solargraph/source/change'
14
14
  autoload :Mapper, 'solargraph/source/mapper'
15
15
  autoload :NodeMethods, 'solargraph/source/node_methods'
16
+ autoload :Chain, 'solargraph/source/chain'
17
+ autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
18
+ autoload :CallChainer, 'solargraph/source/call_chainer'
19
+ autoload :NodeChainer, 'solargraph/source/node_chainer'
20
+ autoload :Completion, 'solargraph/source/completion'
21
+
22
+ include EncodingFixes
16
23
 
17
24
  # @return [String]
18
25
  attr_reader :code
@@ -60,21 +67,32 @@ module Solargraph
60
67
  # @param filename [String]
61
68
  def initialize code, filename = nil
62
69
  begin
63
- @code = code
64
- @fixed = code
70
+ @code = normalize(code)
71
+ @fixed = @code
65
72
  @filename = filename
66
73
  @version = 0
67
74
  @domains = []
68
- begin
69
- parse
70
- rescue Parser::SyntaxError, EncodingError
71
- hard_fix_node
72
- end
75
+ parse
76
+ rescue Parser::SyntaxError, EncodingError => e
77
+ hard_fix_node
73
78
  rescue Exception => e
74
- raise RuntimeError, "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
79
+ STDERR.puts e.message
80
+ STDERR.puts e.backtrace
81
+ raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
75
82
  end
76
83
  end
77
84
 
85
+ # @param range [Solargraph::Source::Range]
86
+ def at range
87
+ from_to range.start.line, range.start.character, range.ending.line, range.ending.character
88
+ end
89
+
90
+ def from_to l1, c1, l2, c2
91
+ b = Solargraph::Source::Position.line_char_to_offset(@code, l1, c1)
92
+ e = Solargraph::Source::Position.line_char_to_offset(@code, l2, c2)
93
+ @code[b..e-1]
94
+ end
95
+
78
96
  def macro path
79
97
  @path_macros[path]
80
98
  end
@@ -122,11 +140,6 @@ module Solargraph
122
140
  pins.select{|pin| pin.kind == Pin::CLASS_VARIABLE}
123
141
  end
124
142
 
125
- # @return [Array<Solargraph::Pin::Base>]
126
- def locals
127
- @locals
128
- end
129
-
130
143
  # @return [Array<Solargraph::Pin::GlobalVariable>]
131
144
  def global_variable_pins
132
145
  pins.select{|pin| pin.kind == Pin::GLOBAL_VARIABLE}
@@ -168,6 +181,11 @@ module Solargraph
168
181
  _locate_pin line, character, Pin::NAMESPACE, Pin::METHOD
169
182
  end
170
183
 
184
+ # Locate the namespace pin at the specified line and character.
185
+ #
186
+ # @param line [line]
187
+ # @param character [character]
188
+ # @return [Pin::Namespace]
171
189
  def locate_namespace_pin line, character
172
190
  _locate_pin line, character, Pin::NAMESPACE
173
191
  end
@@ -191,21 +209,43 @@ module Solargraph
191
209
  # @param column [Integer]
192
210
  # @return [Boolean]
193
211
  def string_at?(line, column)
194
- node = node_at(line, column)
195
- # @todo raise InvalidOffset or InvalidRange or something?
196
- return false if node.nil?
197
- node.type == :str or node.type == :dstr
212
+ # node = node_at(line, column)
213
+ # # @todo raise InvalidOffset or InvalidRange or something?
214
+ # return false if node.nil?
215
+ # node.type == :str or node.type == :dstr
216
+ pos = Source::Position.new(line, column)
217
+ @strings.each do |str|
218
+ return true if str.contain?(pos)
219
+ break if str.start.line > pos.line
220
+ end
221
+ false
222
+ end
223
+
224
+ # True if the specified location is inside a comment.
225
+ #
226
+ # @param line [Integer]
227
+ # @param column [Integer]
228
+ # @return [Boolean]
229
+ def comment_at?(line, column)
230
+ pos = Source::Position.new(line, column)
231
+ @comment_ranges.each do |cmnt|
232
+ return true if cmnt.include?(pos)
233
+ break if cmnt.start.line > pos.line
234
+ end
235
+ false
198
236
  end
199
237
 
200
238
  # Get an array of nodes containing the specified index, starting with the
201
239
  # nearest node and ending with the root.
202
240
  #
203
- # @param index [Integer]
241
+ # @param line [Integer]
242
+ # @param column [Integer]
204
243
  # @return [Array<AST::Node>]
205
244
  def tree_at(line, column)
206
- offset = Position.line_char_to_offset(@code, line, column)
245
+ # offset = Position.line_char_to_offset(@code, line, column)
246
+ position = Position.new(line, column)
207
247
  stack = []
208
- inner_tree_at @node, offset, stack
248
+ inner_tree_at @node, position, stack
209
249
  stack
210
250
  end
211
251
 
@@ -219,8 +259,7 @@ module Solargraph
219
259
  @code = updater.write(original_code)
220
260
  @fixed = updater.write(original_code, true)
221
261
  @version = updater.version
222
- return if @code == original_code
223
- return unless reparse
262
+ return if @code == original_code or !reparse
224
263
  begin
225
264
  parse
226
265
  @fixed = @code
@@ -252,8 +291,8 @@ module Solargraph
252
291
  # @param location [Solargraph::Source::Location]
253
292
  # @return [Solargraph::Pin::Base]
254
293
  def locate_pin location
255
- return nil unless location.start_with?("#{filename}:")
256
- @all_pins.select{|pin| pin.location == location}.first
294
+ # return nil unless location.start_with?("#{filename}:")
295
+ pins.select{|pin| pin.location == location}
257
296
  end
258
297
 
259
298
  # @param line [Integer] A zero-based line number
@@ -284,7 +323,7 @@ module Solargraph
284
323
  def inner_node_references name, top
285
324
  result = []
286
325
  if top.kind_of?(AST::Node)
287
- if (top.type == :const and top.children[1].to_s == name) or (top.type == :send and top.children[1].to_s == name)
326
+ if top.children.any?{|c| c.to_s == name}
288
327
  result.push top
289
328
  end
290
329
  top.children.each { |c| result.concat inner_node_references(name, c) }
@@ -292,15 +331,15 @@ module Solargraph
292
331
  result
293
332
  end
294
333
 
295
- def inner_tree_at node, offset, stack
334
+ def inner_tree_at node, position, stack
296
335
  return if node.nil?
297
- stack.unshift node
298
- node.children.each do |c|
299
- next unless c.is_a?(AST::Node)
300
- next if c.loc.expression.nil?
301
- if offset >= c.loc.expression.begin_pos and offset < c.loc.expression.end_pos
302
- inner_tree_at(c, offset, stack)
303
- break
336
+ here = Range.from_to(node.loc.expression.line, node.loc.expression.column, node.loc.expression.last_line, node.loc.expression.last_column)
337
+ if here.contain?(position)
338
+ stack.unshift node
339
+ node.children.each do |c|
340
+ next unless c.is_a?(AST::Node)
341
+ next if c.loc.expression.nil?
342
+ inner_tree_at(c, position, stack)
304
343
  end
305
344
  end
306
345
  end
@@ -327,8 +366,8 @@ module Solargraph
327
366
  parser = Parser::CurrentRuby.new(FlawedBuilder.new)
328
367
  parser.diagnostics.all_errors_are_fatal = true
329
368
  parser.diagnostics.ignore_warnings = true
330
- buffer = Parser::Source::Buffer.new(filename, 1)
331
- buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ' ')
369
+ buffer = Parser::Source::Buffer.new(filename, 0)
370
+ buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
332
371
  parser.parse_with_comments(buffer)
333
372
  end
334
373
 
@@ -337,7 +376,9 @@ module Solargraph
337
376
  synchronize_mapped *new_map_data
338
377
  end
339
378
 
340
- def synchronize_mapped new_pins, new_locals, new_requires, new_symbols #, new_path_macros, new_domains
379
+ def synchronize_mapped new_pins, new_locals, new_requires, new_symbols, new_strings, new_comment_ranges #, new_path_macros, new_domains
380
+ @strings = new_strings
381
+ @comment_ranges = new_comment_ranges
341
382
  return if @requires == new_requires and @symbols == new_symbols and try_merge(new_pins, new_locals)
342
383
  @pins = new_pins
343
384
  @locals = new_locals
@@ -387,7 +428,6 @@ module Solargraph
387
428
  elsif d.tag.tag_name == 'method'
388
429
  gen_src = Source.new("def #{d.tag.name};end", filename)
389
430
  gen_pin = gen_src.pins.last # Method is last pin after root namespace
390
- # next if ns.nil? or ns.empty? # @todo Add methods to global namespace?
391
431
  @pins.push Solargraph::Pin::Method.new(pin.location, pin.path, gen_pin.name, docstring.all, :instance, :public, [])
392
432
  elsif d.tag.tag_name == 'macro'
393
433
  @path_macros[pin.path] = d
@@ -404,7 +444,9 @@ module Solargraph
404
444
  # @param filename [String]
405
445
  # @return [Solargraph::Source]
406
446
  def load filename
407
- code = File.read(filename)
447
+ file = File.open(filename, 'rb')
448
+ code = file.read
449
+ file.close
408
450
  Source.load_string(code, filename)
409
451
  end
410
452
 
@@ -414,6 +456,15 @@ module Solargraph
414
456
  def load_string code, filename = nil
415
457
  Source.new code, filename
416
458
  end
459
+
460
+ def parse_node code, filename
461
+ parser = Parser::CurrentRuby.new(FlawedBuilder.new)
462
+ parser.diagnostics.all_errors_are_fatal = true
463
+ parser.diagnostics.ignore_warnings = true
464
+ buffer = Parser::Source::Buffer.new(nil, 0)
465
+ buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
466
+ parser.parse(buffer)
467
+ end
417
468
  end
418
469
  end
419
470
  end
@@ -0,0 +1,273 @@
1
+ module Solargraph
2
+ class Source
3
+ # Information about a location in a source, including the location's word
4
+ # and signature, literal values at the base of signatures, and whether the
5
+ # location is inside a string or comment. ApiMaps use Fragments to provide
6
+ # results for completion and definition queries.
7
+ #
8
+ class CallChainer
9
+ include NodeMethods
10
+
11
+ private_class_method :new
12
+
13
+ class << self
14
+ # @param source [Source]
15
+ # @param line [Integer]
16
+ # @param column [Integer]
17
+ # @return [Source::Chain]
18
+ def chain source, line, column
19
+ new(source, line, column).chain
20
+ end
21
+ end
22
+
23
+ # @param source [Solargraph::Source]
24
+ # @param line [Integer]
25
+ # @param column [Integer]
26
+ def initialize source, line, column
27
+ @source = source
28
+ # @source.code = source.code
29
+ @line = line
30
+ @column = column
31
+ @calculated_literal = false
32
+ end
33
+
34
+ # @return [Source::Chain]
35
+ def chain
36
+ links = []
37
+ # @todo Smelly colon handling
38
+ if @source.code[0..offset-1].end_with?(':') and !@source.code[0..offset-1].end_with?('::')
39
+ links.push Chain::Link.new
40
+ links.push Chain::Link.new
41
+ elsif @source.string_at?(line, column)
42
+ links.push Chain::Literal.new('String')
43
+ else
44
+ links.push Chain::Literal.new(base_literal) if base_literal?
45
+ sig = whole_signature
46
+ unless sig.empty?
47
+ sig = sig[1..-1] if sig.start_with?('.')
48
+ head = true
49
+ sig.split('.', -1).each do |word|
50
+ if word.include?('::')
51
+ # @todo Smelly way of handling constants
52
+ parts = (word.start_with?('::') ? word[2..-1] : word).split('::', -1)
53
+ last = parts.pop
54
+ links.push Chain::Constant.new(parts.join('::')) unless parts.empty?
55
+ links.push (last.nil? ? Chain::UNDEFINED_CONSTANT : Chain::Constant.new(last))
56
+ else
57
+ links.push word_to_link(word, head)
58
+ end
59
+ head = false
60
+ end
61
+ end
62
+ # Literal string hack
63
+ links.push Chain::UNDEFINED_CALL if base_literal? and @source.code[offset - 1] == '.' and links.length == 1
64
+ end
65
+ @chain ||= Chain.new(source.filename, links)
66
+ end
67
+
68
+ private
69
+
70
+ # The zero-based line number of the fragment's location.
71
+ #
72
+ # @return [Integer]
73
+ attr_reader :line
74
+
75
+ # The zero-based column number of the fragment's location.
76
+ #
77
+ # @return [Integer]
78
+ attr_reader :column
79
+
80
+ # @return [Solargraph::Source]
81
+ attr_reader :source
82
+
83
+ def word_to_link word, head
84
+ if word.start_with?('@@')
85
+ Chain::ClassVariable.new(word)
86
+ elsif word.start_with?('@')
87
+ Chain::InstanceVariable.new(word)
88
+ elsif word.start_with?('$')
89
+ Chain::GlobalVariable.new(word)
90
+ elsif word.end_with?(':')
91
+ Chain::Link.new
92
+ elsif word.empty?
93
+ Chain::UNDEFINED_CALL
94
+ elsif head and !@source.code[signature_data[0]..-1].match(/^[\s]*?#{word}[\s]*?\(/)
95
+ # The head needs to allow for ambiguous references to constants and
96
+ # methods. For example, `String` might be either. If the word is not
97
+ # followed by an open parenthesis, use Chain::Head for ambiguous
98
+ # results.
99
+ Chain::Head.new(word)
100
+ else
101
+ Chain::Call.new(word)
102
+ end
103
+ end
104
+
105
+ # An alias for #column.
106
+ #
107
+ # @return [Integer]
108
+ def character
109
+ @column
110
+ end
111
+
112
+ # @return [Source::Position]
113
+ def position
114
+ @position ||= Position.new(line, column)
115
+ end
116
+
117
+ # Get the signature up to the current offset. Given the text `foo.bar`,
118
+ # the signature at offset 5 is `foo.b`.
119
+ #
120
+ # @return [String]
121
+ def signature
122
+ @signature ||= signature_data[1].to_s
123
+ end
124
+
125
+ # Get the remainder of the word after the current offset. Given the text
126
+ # `foobar` with an offset of 3, the remainder is `bar`.
127
+ #
128
+ # @return [String]
129
+ def remainder
130
+ @remainder ||= remainder_at(offset)
131
+ end
132
+
133
+ # Get the whole signature at the current offset, including the final
134
+ # word and its remainder.
135
+ #
136
+ # @return [String]
137
+ def whole_signature
138
+ @whole_signature ||= signature + remainder
139
+ end
140
+
141
+ # True if the current offset is inside a string.
142
+ #
143
+ # @return [Boolean]
144
+ def string?
145
+ # @string ||= (node.type == :str or node.type == :dstr)
146
+ @string ||= @source.string_at?(line, character)
147
+ end
148
+
149
+ # True if the fragment is a signature that stems from a literal value.
150
+ #
151
+ # @return [Boolean]
152
+ def base_literal?
153
+ !base_literal.nil?
154
+ end
155
+
156
+ # The type of literal value at the root of the signature (or nil).
157
+ #
158
+ # @return [String]
159
+ def base_literal
160
+ if @base_literal.nil? and !@calculated_literal
161
+ @calculated_literal = true
162
+ if signature.start_with?('.')
163
+ pn = @source.node_at(line, column - 2)
164
+ @base_literal = infer_literal_node_type(pn) unless pn.nil?
165
+ end
166
+ end
167
+ @base_literal
168
+ end
169
+
170
+ # @return [Integer]
171
+ def offset
172
+ @offset ||= get_offset(line, column)
173
+ end
174
+
175
+ def get_offset line, column
176
+ Position.line_char_to_offset(@source.code, line, column)
177
+ end
178
+
179
+ def signature_data
180
+ @signature_data ||= get_signature_data_at(offset)
181
+ end
182
+
183
+ def get_signature_data_at index
184
+ brackets = 0
185
+ squares = 0
186
+ parens = 0
187
+ signature = ''
188
+ index -=1
189
+ in_whitespace = false
190
+ while index >= 0
191
+ pos = Position.from_offset(@source.code, index)
192
+ break if index > 0 and @source.comment_at?(pos.line, pos.character)
193
+ unless !in_whitespace and string?
194
+ break if brackets > 0 or parens > 0 or squares > 0
195
+ char = @source.code[index, 1]
196
+ break if char.nil? # @todo Is this the right way to handle this?
197
+ if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
198
+ in_whitespace = true
199
+ else
200
+ if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
201
+ unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
202
+ old = @source.code[index+1..-1]
203
+ nxt = @source.code[index+1..-1].lstrip
204
+ index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
205
+ break
206
+ end
207
+ end
208
+ if char == ')'
209
+ parens -=1
210
+ elsif char == ']'
211
+ squares -=1
212
+ elsif char == '}'
213
+ brackets -= 1
214
+ elsif char == '('
215
+ parens += 1
216
+ elsif char == '{'
217
+ brackets += 1
218
+ elsif char == '['
219
+ squares += 1
220
+ signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
221
+ end
222
+ if brackets.zero? and parens.zero? and squares.zero?
223
+ break if ['"', "'", ',', ';', '%'].include?(char)
224
+ signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
225
+ break if char == '$'
226
+ if char == '@'
227
+ signature = "@#{signature}" if @source.code[index-1, 1] == '@'
228
+ break
229
+ end
230
+ end
231
+ in_whitespace = false
232
+ end
233
+ end
234
+ index -= 1
235
+ end
236
+ # @todo Smelly exceptional case for integer literals
237
+ match = signature.match(/^[0-9]+/)
238
+ if match
239
+ index += match[0].length
240
+ signature = signature[match[0].length..-1].to_s
241
+ @base_literal = 'Integer'
242
+ # @todo Smelly exceptional case for array literals
243
+ elsif signature.start_with?('.[]')
244
+ index += 2
245
+ signature = signature[3..-1].to_s
246
+ @base_literal = 'Array'
247
+ elsif signature.start_with?('.')
248
+ pos = Position.from_offset(source.code, index)
249
+ node = source.node_at(pos.line, pos.character)
250
+ lit = infer_literal_node_type(node)
251
+ unless lit.nil?
252
+ signature = signature[1..-1].to_s
253
+ index += 1
254
+ @base_literal = lit
255
+ end
256
+ end
257
+ [index + 1, signature]
258
+ end
259
+
260
+ # @return [String]
261
+ def remainder_at index
262
+ cursor = index
263
+ while cursor < @source.code.length
264
+ char = @source.code[cursor, 1]
265
+ break if char.nil? or char == ''
266
+ break unless char.match(/[a-z0-9_\?\!]/i)
267
+ cursor += 1
268
+ end
269
+ @source.code[index..cursor-1].to_s
270
+ end
271
+ end
272
+ end
273
+ end