solargraph 0.18.3 → 0.19.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 (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