solargraph 0.25.1 → 0.26.0

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