solargraph 0.18.3 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph/api_map/probe.rb +222 -0
  3. data/lib/solargraph/api_map/source_to_yard.rb +3 -3
  4. data/lib/solargraph/api_map/store.rb +135 -0
  5. data/lib/solargraph/api_map.rb +169 -609
  6. data/lib/solargraph/diagnostics/rubocop.rb +4 -4
  7. data/lib/solargraph/language_server/host.rb +53 -19
  8. data/lib/solargraph/language_server/message/extended/document.rb +1 -1
  9. data/lib/solargraph/language_server/message/extended/search.rb +1 -1
  10. data/lib/solargraph/language_server/message/method_not_found.rb +1 -1
  11. data/lib/solargraph/language_server/message/text_document/definition.rb +2 -15
  12. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +2 -15
  13. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +3 -15
  14. data/lib/solargraph/language_server/message_types.rb +10 -0
  15. data/lib/solargraph/language_server.rb +1 -0
  16. data/lib/solargraph/library.rb +8 -0
  17. data/lib/solargraph/node_methods.rb +6 -1
  18. data/lib/solargraph/page.rb +2 -1
  19. data/lib/solargraph/pin/attribute.rb +8 -12
  20. data/lib/solargraph/pin/base.rb +20 -95
  21. data/lib/solargraph/pin/base_variable.rb +15 -74
  22. data/lib/solargraph/pin/block.rb +21 -0
  23. data/lib/solargraph/pin/block_parameter.rb +30 -44
  24. data/lib/solargraph/pin/class_variable.rb +3 -0
  25. data/lib/solargraph/pin/constant.rb +4 -8
  26. data/lib/solargraph/pin/conversions.rb +4 -3
  27. data/lib/solargraph/pin/documenting.rb +27 -0
  28. data/lib/solargraph/pin/global_variable.rb +3 -0
  29. data/lib/solargraph/pin/instance_variable.rb +5 -4
  30. data/lib/solargraph/pin/local_variable.rb +8 -15
  31. data/lib/solargraph/pin/localized.rb +12 -0
  32. data/lib/solargraph/pin/method.rb +6 -67
  33. data/lib/solargraph/pin/method_parameter.rb +24 -11
  34. data/lib/solargraph/pin/namespace.rb +26 -35
  35. data/lib/solargraph/pin/reference.rb +15 -8
  36. data/lib/solargraph/pin/symbol.rb +34 -3
  37. data/lib/solargraph/pin/yard_object.rb +11 -4
  38. data/lib/solargraph/pin.rb +16 -2
  39. data/lib/solargraph/server.rb +2 -2
  40. data/lib/solargraph/source/change.rb +10 -13
  41. data/lib/solargraph/source/fragment.rb +42 -94
  42. data/lib/solargraph/source/location.rb +13 -0
  43. data/lib/solargraph/source/mapper.rb +426 -0
  44. data/lib/solargraph/source/position.rb +1 -0
  45. data/lib/solargraph/source/range.rb +11 -3
  46. data/lib/solargraph/source.rb +93 -284
  47. data/lib/solargraph/version.rb +1 -1
  48. data/lib/solargraph/views/_method.erb +59 -60
  49. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  50. data/lib/solargraph/views/_namespace.erb +26 -26
  51. data/lib/solargraph/views/document.erb +23 -16
  52. data/lib/solargraph/views/layout.erb +38 -10
  53. data/lib/solargraph/views/search.erb +12 -11
  54. data/lib/solargraph/workspace/config.rb +27 -6
  55. data/lib/solargraph/workspace.rb +10 -2
  56. data/lib/solargraph.rb +10 -2
  57. data/lib/yard-solargraph.rb +3 -0
  58. metadata +25 -20
  59. data/lib/solargraph/pin/directed/attribute.rb +0 -20
  60. data/lib/solargraph/pin/directed/method.rb +0 -22
  61. data/lib/solargraph/pin/directed.rb +0 -9
  62. data/lib/solargraph/pin/parameter.rb +0 -23
@@ -25,6 +25,14 @@ module Solargraph
25
25
  # @return [String] The updated text.
26
26
  def write text, nullable = false
27
27
  if nullable and !range.nil? and new_text.match(/[\.\[\{\(@\$:]$/)
28
+ if new_text == ':'
29
+ offset = Position.to_offset(text, range.start)
30
+ if text[offset - 1] == ':'
31
+ p = Position.from_offset(text, offset - 1)
32
+ r = Change.new(Range.new(p, range.start), ' ')
33
+ text = r.write(text)
34
+ end
35
+ end
28
36
  commit text, "#{new_text[0..-2]} "
29
37
  elsif range.nil?
30
38
  new_text
@@ -50,21 +58,10 @@ module Solargraph
50
58
  private
51
59
 
52
60
  def commit text, insert
53
- start_offset = get_offset(text, range.start.line, range.start.character)
54
- end_offset = get_offset(text, range.end.line, range.end.character)
61
+ start_offset = Position.to_offset(text, range.start)
62
+ end_offset = Position.to_offset(text, range.ending)
55
63
  (start_offset == 0 ? '' : text[0..start_offset-1].to_s) + insert.force_encoding('utf-8') + text[end_offset..-1].to_s
56
64
  end
57
-
58
- def get_offset text, line, column
59
- offset = 0
60
- feed = 0
61
- text.lines.each do |l|
62
- break if line == feed
63
- offset += l.length
64
- feed += 1
65
- end
66
- offset + column
67
- end
68
65
  end
69
66
  end
70
67
  end
@@ -12,21 +12,20 @@ module Solargraph
12
12
  # @param source [Solargraph::Source]
13
13
  # @param line [Integer]
14
14
  # @param column [Integer]
15
- def initialize source, line, column, tree
16
- # @todo Split this object from the source. The source can change; if
17
- # it does, this object's data should not.
15
+ def initialize source, line, column
18
16
  @source = source
19
17
  @code = source.code
20
18
  @line = line
21
19
  @column = column
22
- @tree = tree
20
+ @calculated_literal = false
23
21
  end
24
22
 
25
- # Get the node at the current offset.
26
- #
27
- # @return [Parser::AST::Node]
28
- def node
29
- @node ||= @tree.first
23
+ def character
24
+ @column
25
+ end
26
+
27
+ def position
28
+ @position ||= Position.new(line, column)
30
29
  end
31
30
 
32
31
  # Get the fully qualified namespace at the current offset.
@@ -34,14 +33,8 @@ module Solargraph
34
33
  # @return [String]
35
34
  def namespace
36
35
  if @namespace.nil?
37
- parts = []
38
- @tree.each do |n|
39
- next unless n.kind_of?(AST::Node)
40
- if n.type == :class or n.type == :module
41
- parts.unshift unpack_name(n.children[0])
42
- end
43
- end
44
- @namespace = parts.join('::')
36
+ pin = @source.locate_namespace_pin(line, character)
37
+ @namespace = (pin.nil? ? '' : pin.path)
45
38
  end
46
39
  @namespace
47
40
  end
@@ -74,16 +67,7 @@ module Solargraph
74
67
  def scope
75
68
  if @scope.nil?
76
69
  @scope = :class
77
- tree = @tree.clone
78
- until tree.empty?
79
- cursor = tree.shift
80
- break if cursor.type == :class or cursor.type == :module
81
- if cursor.type == :def
82
- pin = @source.method_pins.select{|pin| pin.contain?(offset)}.first
83
- # @todo The pin should never be nil here, but we're guarding it just in case
84
- @scope = (pin.nil? ? :instance : pin.scope)
85
- end
86
- end
70
+ @scope = :instance if named_path.kind == Pin::METHOD and named_path.scope == :instance
87
71
  end
88
72
  @scope
89
73
  end
@@ -193,7 +177,8 @@ module Solargraph
193
177
  #
194
178
  # @return [Boolean]
195
179
  def string?
196
- @string ||= (node.type == :str or node.type == :dstr)
180
+ # @string ||= (node.type == :str or node.type == :dstr)
181
+ @string ||= @source.string_at?(line, character)
197
182
  end
198
183
 
199
184
  # True if the current offset is inside a comment.
@@ -219,83 +204,35 @@ module Solargraph
219
204
  @whole_word_range ||= word_range_at(offset, true)
220
205
  end
221
206
 
222
- # Get an array of all the local variables in the source that are visible
223
- # from the current offset.
224
- #
225
- # @return [Array<Solargraph::Pin::LocalVariable>]
226
- def local_variable_pins name = nil
227
- @local_variable_pins ||= prefer_non_nil_variables(@source.local_variable_pins.select{|pin| pin.visible_from?(node)})
228
- return @local_variable_pins if name.nil?
229
- @local_variable_pins.select{|pin| pin.name == name}
207
+ def block
208
+ @block ||= @source.locate_block_pin(line, character)
230
209
  end
231
210
 
232
- def calculated_signature
233
- @calculated_signature ||= calculate
211
+ def named_path
212
+ @named_path ||= @source.locate_named_path_pin(line, character)
234
213
  end
235
214
 
236
- def calculated_whole_signature
237
- @calculated_whole_signature ||= calculated_signature + remainder
215
+ def locals
216
+ @locals ||= @source.locals.select{|pin| pin.visible_from?(block, position)}
238
217
  end
239
218
 
240
- def calculated_base
241
- if @calculated_base.nil?
242
- @calculated_base = calculated_signature[0..-2] if calculated_signature.end_with?('.')
243
- @calculated_base ||= calculated_signature.split('.')[0..-2].join('.')
244
- end
245
- @calculated_base
219
+ def base_literal?
220
+ !base_literal.nil?
246
221
  end
247
222
 
248
- private
249
-
250
- def calculate
251
- return signature if signature.empty? or signature.nil?
252
- if signature.start_with?('.')
253
- return signature if column < 2
254
- # @todo Smelly exceptional case for arrays
255
- return signature.sub(/^\.\[\]/, 'Array.new') if signature.start_with?('.[].')
256
- pn = @source.node_at(line, column - 2)
257
- unless pn.nil?
258
- literal = infer_literal_node_type(pn)
259
- unless literal.nil?
260
- return "#{literal}.new#{signature}"
261
- end
262
- end
263
- return signature
264
- end
265
- # @todo Smelly exceptional case for integers
266
- base, rest = signature.split('.', 2)
267
- base.sub!(/^[0-9]+?$/, 'Integer.new')
268
- var = local_variable_pins(base).first
269
- unless var.nil?
270
- done = []
271
- until var.nil?
272
- break if done.include?(var)
273
- done.push var
274
- type = var.calculated_signature
275
- break if type.nil?
276
- base = type
277
- var = local_variable_pins(base).first
278
- end
279
- end
280
- base = @source.qualify(base, namespace)
281
- base + (rest.nil? ? '' : ".#{rest}")
282
- end
283
-
284
- # @todo DRY this method. It exists in ApiMap.
285
- # @return [Array<Solargraph::Pin::Base>]
286
- def prefer_non_nil_variables pins
287
- result = []
288
- nil_pins = []
289
- pins.each do |pin|
290
- if pin.nil_assignment? and pin.return_type.nil?
291
- nil_pins.push pin
292
- else
293
- result.push pin
223
+ def base_literal
224
+ if @base_literal.nil? and !@calculated_literal
225
+ @calculated_literal = true
226
+ if signature.start_with?('.')
227
+ pn = @source.node_at(line, column - 2)
228
+ @base_literal = infer_literal_node_type(pn) unless pn.nil?
294
229
  end
295
230
  end
296
- result + nil_pins
231
+ @base_literal
297
232
  end
298
233
 
234
+ private
235
+
299
236
  # @return [Integer]
300
237
  def offset
301
238
  @offset ||= get_offset(line, column)
@@ -366,7 +303,18 @@ module Solargraph
366
303
  end
367
304
  index -= 1
368
305
  end
369
- # @todo Smelly exceptional case for numbers
306
+ # @todo Smelly exceptional case for integer literals
307
+ match = signature.match(/^[0-9]+/)
308
+ if match
309
+ index += match[0].length
310
+ signature = signature[match[0].length..-1].to_s
311
+ @base_literal = 'Integer'
312
+ # @todo Smelly exceptional case for array literals
313
+ elsif signature.start_with?('.[]')
314
+ index += 3
315
+ signature = signature[index+3..-1].to_s
316
+ @base_literal = 'Array'
317
+ end
370
318
  [index + 1, signature]
371
319
  end
372
320
 
@@ -0,0 +1,13 @@
1
+ module Solargraph
2
+ class Source
3
+ class Location
4
+ attr_reader :filename
5
+ attr_reader :range
6
+
7
+ def initialize filename, range
8
+ @filename = filename
9
+ @range = range
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,426 @@
1
+ module Solargraph
2
+ class Source
3
+ class Mapper
4
+ include NodeMethods
5
+
6
+ private_class_method :new
7
+
8
+ # @return [Array<Solargraph::Pin::Base>]
9
+ def map filename, code, node, comments
10
+ @filename = filename
11
+ @code = code
12
+ @node = node
13
+ @comments = comments
14
+ @node_stack = []
15
+ # @node_tree = []
16
+ @comment_hash = {}
17
+ @directives = {}
18
+ @docstring_hash = associate_comments(node, comments)
19
+ # @todo Stuff that needs to be resolved
20
+ @variables = []
21
+ @path_macros = {}
22
+
23
+ @pins = []
24
+ @requires = []
25
+ @symbols = []
26
+ @locals = []
27
+
28
+ # HACK make sure the first node gets processed
29
+ root = AST::Node.new(:source, [filename])
30
+ root = root.append node
31
+ # @todo Is the root namespace a class or a module? Assuming class for now.
32
+ @pins.push Pin::Namespace.new(get_node_location(nil), '', '', nil, :class, :public, nil)
33
+ process root
34
+ process_directives
35
+ [@pins, @locals, @requires, @symbols, @path_macros]
36
+ end
37
+
38
+ class << self
39
+ # @return [Array<Solargraph::Pin::Base>]
40
+ def map filename, code, node, comments
41
+ new.map filename, code, node, comments
42
+ end
43
+ end
44
+
45
+ # @param node [Parser::AST::Node]
46
+ # @return [String]
47
+ def code_for node
48
+ b = node.location.expression.begin.begin_pos
49
+ e = node.location.expression.end.end_pos
50
+ frag = @code[b..e-1].to_s
51
+ frag.strip.gsub(/,$/, '')
52
+ end
53
+
54
+ def comment_hash
55
+ @comment_hash
56
+ end
57
+
58
+ def filename
59
+ @filename
60
+ end
61
+
62
+ def comments
63
+ @comments
64
+ end
65
+
66
+ def pins
67
+ @pins ||= []
68
+ end
69
+
70
+ def process node, tree = [], visibility = :public, scope = :instance, fqn = '', stack = []
71
+ stack.push node
72
+ if node.kind_of?(AST::Node)
73
+ @node_stack.unshift node
74
+ if node.type == :class or node.type == :module
75
+ visibility = :public
76
+ if node.children[0].kind_of?(AST::Node) and node.children[0].children[0].kind_of?(AST::Node) and node.children[0].children[0].type == :cbase
77
+ tree = pack_name(node.children[0])
78
+ else
79
+ tree = tree + pack_name(node.children[0])
80
+ end
81
+ fqn = tree.join('::')
82
+ sc = nil
83
+ if node.type == :class and !node.children[1].nil?
84
+ sc = unpack_name(node.children[1])
85
+ end
86
+ pins.push Solargraph::Pin::Namespace.new(get_node_location(node), tree[0..-2].join('::') || '', pack_name(node.children[0]).last.to_s, docstring_for(node), node.type, visibility, sc)
87
+ end
88
+ file = filename
89
+ node.children.each do |c|
90
+ if c.kind_of?(AST::Node)
91
+ if c.type == :ivasgn
92
+ here = get_node_start_position(c)
93
+ context = get_named_path_pin(here)
94
+ if c.children[1].nil?
95
+ ora = find_parent(stack, :or_asgn)
96
+ unless ora.nil?
97
+ u = c.updated(:ivasgn, c.children + ora.children[1..-1], nil)
98
+ @docstring_hash[u.loc] = docstring_for(ora)
99
+ pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(u), fqn || '', c.children[0].to_s, docstring_for(u), resolve_node_signature(u.children[1]), infer_literal_node_type(u.children[1]), context)
100
+ end
101
+ else
102
+ pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(c), fqn || '',c.children[0].to_s, docstring_for(c), resolve_node_signature(c.children[1]), infer_literal_node_type(c.children[1]), context)
103
+ end
104
+ elsif c.type == :cvasgn
105
+ here = get_node_start_position(c)
106
+ context = get_named_path_pin(here)
107
+ if c.children[1].nil?
108
+ ora = find_parent(stack, :or_asgn)
109
+ unless ora.nil?
110
+ u = c.updated(:cvasgn, c.children + ora.children[1..-1], nil)
111
+ @docstring_hash[u.loc] = docstring_for(ora)
112
+ pins.push Solargraph::Pin::ClassVariable.new(get_node_location(u), fqn || '', c.children[0].to_s, docstring_for(u), resolve_node_signature(u.children[1]), infer_literal_node_type(u.children[1]), context)
113
+ end
114
+ else
115
+ pins.push Solargraph::Pin::ClassVariable.new(get_node_location(c), fqn || '', c.children[0].to_s, docstring_for(c), resolve_node_signature(c.children[1]), infer_literal_node_type(c.children[1]), context)
116
+ end
117
+ elsif c.type == :lvasgn
118
+ here = get_node_start_position(c)
119
+ context = get_named_path_pin(here)
120
+ block = get_block_pin(here)
121
+ presence = Source::Range.new(here, block.location.range.ending)
122
+ if c.children[1].nil?
123
+ ora = find_parent(stack, :or_asgn)
124
+ unless ora.nil?
125
+ u = c.updated(:lvasgn, c.children + ora.children[1..-1], nil)
126
+ @docstring_hash[u.loc] = docstring_for(ora)
127
+ @locals.push Solargraph::Pin::LocalVariable.new(get_node_location(u), fqn, u.children[0].to_s, docstring_for(u), resolve_node_signature(c.children[1]), infer_literal_node_type(c.children[1]), context, block, presence)
128
+ end
129
+ else
130
+ @locals.push Solargraph::Pin::LocalVariable.new(get_node_location(c), fqn, c.children[0].to_s, docstring_for(c), resolve_node_signature(c.children[1]), infer_literal_node_type(c.children[1]), context, block, presence)
131
+ end
132
+ elsif c.type == :gvasgn
133
+ pins.push Solargraph::Pin::GlobalVariable.new(get_node_location(c), fqn, c.children[0].to_s, docstring_for(c), resolve_node_signature(c.children[1]), infer_literal_node_type(c.children[1]), @pins.first)
134
+ elsif c.type == :sym
135
+ @symbols.push Solargraph::Pin::Symbol.new(get_node_location(c), ":#{c.children[0]}")
136
+
137
+ elsif c.type == :casgn
138
+ here = get_node_start_position(c)
139
+ block = get_block_pin(here)
140
+ pins.push Solargraph::Pin::Constant.new(get_node_location(c), fqn, c.children[1].to_s, docstring_for(c), resolve_node_signature(c.children[2]), infer_literal_node_type(c.children[2]), block, :public)
141
+ elsif c.type == :def and c.children[0].to_s[0].match(/[a-z]/i)
142
+ methpin = Solargraph::Pin::Method.new(get_node_location(c), fqn || '', c.children[(c.type == :def ? 0 : 1)].to_s, docstring_for(c), scope, visibility, get_method_args(c))
143
+ if methpin.name == 'initialize' and methpin.scope == :instance
144
+ pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, 'new', methpin.docstring, :class, :public, methpin.parameters)
145
+ # @todo Smelly instance variable access.
146
+ pins.last.instance_variable_set(:@return_type, methpin.namespace)
147
+ pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, methpin.name, methpin.docstring, methpin.scope, :private, methpin.parameters)
148
+ else
149
+ pins.push methpin
150
+ end
151
+ elsif c.type == :defs
152
+ s_visi = visibility
153
+ s_visi = :public if scope != :class
154
+ if c.children[0].is_a?(AST::Node) and c.children[0].type == :self
155
+ dfqn = fqn || ''
156
+ else
157
+ dfqn = unpack_name(c.children[0])
158
+ end
159
+ unless dfqn.nil?
160
+ pins.push Solargraph::Pin::Method.new(get_node_location(c), dfqn, "#{c.children[(node.type == :def ? 0 : 1)]}", docstring_for(c), :class, s_visi, get_method_args(c))
161
+ process c, tree, scope, :class, dfqn, stack
162
+ end
163
+ next
164
+ elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
165
+ visibility = c.children[1]
166
+ elsif c.type == :send and [:private_class_method].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
167
+ if c.children[2].type == :sym or c.children[2].type == :str
168
+ ref = pins.select{|p| p.namespace == (fqn || '') and p.name == c.children[2].children[0].to_s}.first
169
+ unless ref.nil?
170
+ pins.delete ref
171
+ pins.push Solargraph::Pin::Method.new(ref.location, ref.namespace, ref.name, ref.docstring, ref.scope, :private, ref.parameters)
172
+ end
173
+ else
174
+ process c, tree, :private, :class, fqn, stack
175
+ next
176
+ end
177
+ elsif c.type == :send and [:private_constant].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
178
+ if c.children[2].type == :sym or c.children[2].type == :str
179
+ # @todo What to do about references?
180
+ cn = c.children[2].children[0].to_s
181
+ ref = pins.select{|p| p.namespace == (fqn || '') and p.name == cn}.first
182
+ unless ref.nil?
183
+ pins.delete ref
184
+ # Might be either a namespace or constant
185
+ if ref.kind == Pin::CONSTANT
186
+ pins.push ref.class.new(ref.location, ref.namespace, ref.name, ref.docstring, ref.signature, ref.return_type, ref.context, :private)
187
+ else
188
+ pins.push ref.class.new(ref.location, ref.namespace, ref.name, ref.docstring, ref.type, :private, (ref.superclass_reference.nil? ? nil : ref.superclass_reference.name))
189
+ end
190
+ end
191
+ end
192
+ next
193
+ elsif c.type == :send and c.children[1] == :include and c.children[0].nil?
194
+ last_node = get_last_in_stack_not_begin(stack)
195
+ if last_node.nil? or last_node.type == :class or last_node.type == :module or last_node.type == :source
196
+ if c.children[2].kind_of?(AST::Node) and c.children[2].type == :const
197
+ c.children[2..-1].each do |i|
198
+ nspin = @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.path == fqn}.last
199
+ unless nspin.nil?
200
+ iref = Pin::Reference.new(get_node_location(c), nspin.path, unpack_name(i))
201
+ nspin.include_references.push(iref)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ elsif c.type == :send and c.children[1] == :extend and c.children[0].nil?
207
+ last_node = get_last_in_stack_not_begin(stack)
208
+ if last_node.nil? or last_node.type == :class or last_node.type == :module or last_node.type == :source
209
+ if c.children[2].kind_of?(AST::Node) and c.children[2].type == :const
210
+ # namespace_extends[fqn || ''] ||= []
211
+ c.children[2..-1].each do |i|
212
+ nspin = @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.path == fqn}.last
213
+ unless nspin.nil?
214
+ ref = Pin::Reference.new(get_node_location(c), nspin.path, unpack_name(i))
215
+ nspin.extend_references.push(ref)
216
+ end
217
+ end
218
+ end
219
+ end
220
+ elsif c.type == :send and [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
221
+ c.children[2..-1].each do |a|
222
+ if c.children[1] == :attr_reader or c.children[1] == :attr_accessor
223
+ pins.push Solargraph::Pin::Attribute.new(get_node_location(c), fqn || '', "#{a.children[0]}", docstring_for(c), :reader) #AttrPin.new(c)
224
+ end
225
+ if c.children[1] == :attr_writer or c.children[1] == :attr_accessor
226
+ pins.push Solargraph::Pin::Attribute.new(get_node_location(c), fqn || '', "#{a.children[0]}=", docstring_for(c), :writer) #AttrPin.new(c)
227
+ end
228
+ end
229
+ elsif c.type == :sclass and c.children[0].type == :self
230
+ process c, tree, :public, :class, fqn || '', stack
231
+ next
232
+ elsif c.type == :send and c.children[1] == :require
233
+ if c.children[2].kind_of?(AST::Node) and c.children[2].type == :str
234
+ @requires.push c.children[2].children[0].to_s
235
+ end
236
+ elsif c.type == :args
237
+ if @node_stack.first.type == :block
238
+ pi = 0
239
+ c.children.each do |u|
240
+ # @todo Fix this
241
+ # pins.push Solargraph::Pin::BlockParameter.new(self, u, fqn || '', @node_stack.clone, pi)
242
+ here = get_node_start_position(c)
243
+ blk = get_block_pin(here)
244
+ @locals.push Solargraph::Pin::BlockParameter.new(get_node_location(u), fqn || '', "#{u.children[0]}", docstring_for(c), blk)
245
+ blk.parameters.push @locals.push.last
246
+ pi += 1
247
+ end
248
+ else
249
+ c.children.each do |u|
250
+ here = get_node_start_position(c)
251
+ blk = get_block_pin(here)
252
+ @locals.push Solargraph::Pin::MethodParameter.new(get_node_location(u), fqn || '', "#{u.children[0]}", docstring_for(c), blk)
253
+ end
254
+ end
255
+ elsif c.type == :block
256
+ @pins.push Solargraph::Pin::Block.new(get_node_location(c), fqn || '', '', docstring_for(c), resolve_node_signature(c.children[0]))
257
+ end
258
+ process c, tree, visibility, scope, fqn, stack
259
+ end
260
+ end
261
+ @node_stack.shift
262
+ end
263
+ stack.pop
264
+ end
265
+
266
+ # def path_for node
267
+ # path = namespace_for(node) || ''
268
+ # mp = (method_pins + attribute_pins).select{|p| p.node == node}.first
269
+ # unless mp.nil?
270
+ # path += (mp.scope == :instance ? '#' : '.') + mp.name
271
+ # end
272
+ # path
273
+ # end
274
+
275
+ def get_last_in_stack_not_begin stack
276
+ index = stack.length - 1
277
+ last = stack[index]
278
+ while !last.nil? and last.type == :begin
279
+ index -= 1
280
+ last = stack[index]
281
+ end
282
+ last
283
+ end
284
+
285
+ def get_block_pin position
286
+ @pins.select{|pin| [Pin::BLOCK, Pin::NAMESPACE, Pin::METHOD].include?(pin.kind) and pin.location.range.contain?(position)}.last
287
+ end
288
+
289
+ def get_named_path_pin position
290
+ @pins.select{|pin| [Pin::NAMESPACE, Pin::METHOD].include?(pin.kind) and pin.location.range.contain?(position)}.last
291
+ end
292
+
293
+ def get_namespace_pin position
294
+ @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.location.range.contain?(position)}.last
295
+ end
296
+
297
+ # @return [YARD::Docstring]
298
+ def docstring_for node
299
+ return @docstring_hash[node.loc] if node.respond_to?(:loc)
300
+ nil
301
+ end
302
+
303
+ def get_node_start_position(node)
304
+ Position.new(node.loc.line - 1, node.loc.column)
305
+ end
306
+
307
+ def get_node_location(node)
308
+ if node.nil?
309
+ st = Position.new(0, 0)
310
+ en = Position.from_offset(@code, @code.length)
311
+ else
312
+ st = Position.new(node.loc.line - 1, node.loc.column)
313
+ en = Position.new(node.loc.last_line - 1, node.loc.last_column)
314
+ end
315
+ range = Range.new(st, en)
316
+ Location.new(filename, range)
317
+ end
318
+
319
+ def associate_comments node, comments
320
+ return nil if comments.nil?
321
+ comment_hash = Parser::Source::Comment.associate_locations(node, comments)
322
+ yard_hash = {}
323
+ comment_hash.each_pair { |k, v|
324
+ ctxt = ''
325
+ num = nil
326
+ started = false
327
+ v.each { |l|
328
+ # Trim the comment and minimum leading whitespace
329
+ p = l.text.gsub(/^#/, '')
330
+ if num.nil? and !p.strip.empty?
331
+ num = p.index(/[^ ]/)
332
+ started = true
333
+ elsif started and !p.strip.empty?
334
+ cur = p.index(/[^ ]/)
335
+ num = cur if cur < num
336
+ end
337
+ if started
338
+ ctxt += "#{p[num..-1]}\n"
339
+ end
340
+ }
341
+ parse = YARD::Docstring.parser.parse(ctxt)
342
+ unless parse.directives.empty?
343
+ @directives[k] ||= []
344
+ @directives[k].concat parse.directives
345
+ end
346
+ yard_hash[k] = parse.to_docstring
347
+ }
348
+ yard_hash
349
+ end
350
+
351
+ def namespace_for(node)
352
+ position = Source::Position.new(node.loc.line - 1, node.loc.column)
353
+ @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.location.range.contain?(position)}.last
354
+ end
355
+
356
+ def process_directives
357
+ @directives.each_pair do |k, v|
358
+ v.each do |d|
359
+ ns = namespace_for(k.node)
360
+ docstring = YARD::Docstring.parser.parse(d.tag.text).to_docstring
361
+ if d.tag.tag_name == 'attribute'
362
+ t = (d.tag.types.nil? || d.tag.types.empty?) ? nil : d.tag.types.flatten.join('')
363
+ if t.nil? or t.include?('r')
364
+ # location, namespace, name, docstring, access
365
+ pins.push Solargraph::Pin::Attribute.new(get_node_location(k.node), namespace_for(k.node).path, d.tag.name, docstring, :reader)
366
+ end
367
+ if t.nil? or t.include?('w')
368
+ pins.push Solargraph::Pin::Attribute.new(get_node_location(k.node), namespace_for(k.node).path, "#{d.tag.name}=", docstring, :reader)
369
+ end
370
+ elsif d.tag.tag_name == 'method'
371
+ gen_src = Source.new("def #{d.tag.name};end", filename)
372
+ gen_pin = gen_src.pins.last # Method is last pin after root namespace
373
+ nsp = namespace_for(k.node)
374
+ next if nsp.nil? # @todo Add methods to global namespace?
375
+ @pins.push Solargraph::Pin::Method.new(get_node_location(k.node), nsp.path, gen_pin.name, docstring, :instance, :public, [])
376
+ elsif d.tag.tag_name == 'macro'
377
+ # @todo Handle various types of macros (attach, new, whatever)
378
+ # path = path_for(k.node)
379
+ here = get_node_start_position(k.node)
380
+ pin = @pins.select{|pin| [Pin::NAMESPACE, Pin::METHOD].include?(pin.kind) and pin.location.range.contain?(here)}.first
381
+ @path_macros[pin.path] = v
382
+ else
383
+ # STDERR.puts "Nothing to do for directive: #{d}"
384
+ end
385
+ end
386
+ end
387
+ end
388
+
389
+ def find_parent(stack, *types)
390
+ stack.reverse.each { |p|
391
+ return p if types.include?(p.type)
392
+ }
393
+ nil
394
+ end
395
+
396
+ def get_method_args node
397
+ return [] if node.nil?
398
+ list = nil
399
+ args = []
400
+ node.children.each { |c|
401
+ if c.kind_of?(AST::Node) and c.type == :args
402
+ list = c
403
+ break
404
+ end
405
+ }
406
+ return args if list.nil?
407
+ list.children.each { |c|
408
+ if c.type == :arg
409
+ args.push c.children[0].to_s
410
+ elsif c.type == :restarg
411
+ args.push "*#{c.children[0]}"
412
+ elsif c.type == :optarg
413
+ args.push "#{c.children[0]} = #{code_for(c.children[1])}"
414
+ elsif c.type == :kwarg
415
+ args.push "#{c.children[0]}:"
416
+ elsif c.type == :kwoptarg
417
+ args.push "#{c.children[0]}: #{code_for(c.children[1])}"
418
+ elsif c.type == :blockarg
419
+ args.push "&#{c.children[0]}"
420
+ end
421
+ }
422
+ args
423
+ end
424
+ end
425
+ end
426
+ end
@@ -59,6 +59,7 @@ module Solargraph
59
59
  cursor += line_length
60
60
  line += 1
61
61
  end
62
+ character = 0 if character.nil? and offset == cursor
62
63
  raise InvalidOffsetError if character.nil?
63
64
  Position.new(line, character)
64
65
  end