solargraph 0.28.4 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph/api_map.rb +43 -10
  3. data/lib/solargraph/api_map/store.rb +13 -0
  4. data/lib/solargraph/bundle.rb +3 -0
  5. data/lib/solargraph/complex_type.rb +5 -4
  6. data/lib/solargraph/core_fills.rb +3 -2
  7. data/lib/solargraph/language_server/host.rb +0 -1
  8. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +4 -3
  9. data/lib/solargraph/language_server/message/extended/document_gems.rb +7 -0
  10. data/lib/solargraph/language_server/transport/socket.rb +7 -1
  11. data/lib/solargraph/library.rb +1 -1
  12. data/lib/solargraph/page.rb +1 -2
  13. data/lib/solargraph/pin.rb +1 -0
  14. data/lib/solargraph/pin/base.rb +8 -1
  15. data/lib/solargraph/pin/base_variable.rb +1 -1
  16. data/lib/solargraph/pin/block_parameter.rb +5 -0
  17. data/lib/solargraph/pin/conversions.rb +1 -1
  18. data/lib/solargraph/pin/method.rb +59 -1
  19. data/lib/solargraph/pin/method_alias.rb +19 -0
  20. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  21. data/lib/solargraph/source.rb +63 -1
  22. data/lib/solargraph/source/chain/call.rb +39 -20
  23. data/lib/solargraph/source/node_chainer.rb +1 -1
  24. data/lib/solargraph/source/node_methods.rb +81 -0
  25. data/lib/solargraph/source_map.rb +15 -0
  26. data/lib/solargraph/source_map/clip.rb +1 -1
  27. data/lib/solargraph/source_map/mapper.rb +9 -421
  28. data/lib/solargraph/source_map/node_processor.rb +75 -0
  29. data/lib/solargraph/source_map/node_processor/alias_node.rb +21 -0
  30. data/lib/solargraph/source_map/node_processor/args_node.rb +28 -0
  31. data/lib/solargraph/source_map/node_processor/base.rb +68 -0
  32. data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -0
  33. data/lib/solargraph/source_map/node_processor/block_node.rb +14 -0
  34. data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -0
  35. data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -0
  36. data/lib/solargraph/source_map/node_processor/def_node.rb +54 -0
  37. data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -0
  38. data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -0
  39. data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -0
  40. data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -0
  41. data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -0
  42. data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -0
  43. data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -0
  44. data/lib/solargraph/source_map/node_processor/send_node.rb +150 -0
  45. data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -0
  46. data/lib/solargraph/source_map/region.rb +58 -0
  47. data/lib/solargraph/version.rb +1 -1
  48. data/lib/solargraph/yard_map.rb +17 -10
  49. data/lib/yard-solargraph.rb +0 -2
  50. metadata +24 -18
@@ -0,0 +1,19 @@
1
+ module Solargraph
2
+ module Pin
3
+ # Use this class to track method aliases for later remapping. Common
4
+ # examples are aliases for superclass methods or methods from included
5
+ # modules.
6
+ #
7
+ class MethodAlias < Method
8
+ attr_reader :original
9
+
10
+ def initialize location, namespace, name, scope, original
11
+ # @todo Determine how to handle these parameters. Among other things,
12
+ # determine if the visibility is defined by the location of the
13
+ # alias call or the original method.
14
+ super(location, namespace, name, '', scope, :public, [])
15
+ @original = original
16
+ end
17
+ end
18
+ end
19
+ end
@@ -6,7 +6,7 @@ module Solargraph
6
6
 
7
7
  def initialize code_object, location, name = nil, scope = nil, visibility = nil
8
8
  comments = (code_object.docstring ? code_object.docstring.all : nil)
9
- super(location, code_object.namespace.to_s, name || code_object.name.to_s, comments, scope || code_object.scope, visibility || code_object.visibility, get_parameters(code_object))
9
+ super(location, code_object.namespace.to_s, name || code_object.name.to_s, comments, scope || code_object.scope, visibility || code_object.visibility, get_parameters(code_object), nil)
10
10
  end
11
11
 
12
12
  def return_complex_type
@@ -28,7 +28,7 @@ module Solargraph
28
28
  args.push p
29
29
  end
30
30
  args
31
- end
31
+ end
32
32
  end
33
33
  end
34
34
  end
@@ -46,7 +46,12 @@ module Solargraph
46
46
  @node, @comments = Source.parse_with_comments(@code, filename)
47
47
  @parsed = true
48
48
  rescue Parser::SyntaxError, EncodingError => e
49
- @node, @comments = Source.parse_with_comments(@code.gsub(/[^s]/, ' '), filename)
49
+ # @todo 100% whitespace results in a nil node, so there's no reason to parse it.
50
+ # We still need to determine whether the resulting node should be nil or a dummy
51
+ # node with a location that encompasses the range.
52
+ # @node, @comments = Source.parse_with_comments(@code.gsub(/[^\s]/, ' '), filename)
53
+ @node = nil
54
+ @comments = []
50
55
  @parsed = false
51
56
  rescue Exception => e
52
57
  STDERR.puts e.message
@@ -171,8 +176,65 @@ module Solargraph
171
176
  @error_ranges ||= []
172
177
  end
173
178
 
179
+ def code_for(node)
180
+ # @todo Using node locations on code with converted EOLs seems
181
+ # slightly more efficient than calculating offsets.
182
+ # b = node.location.expression.begin.begin_pos
183
+ # e = node.location.expression.end.end_pos
184
+ b = Position.line_char_to_offset(@code, node.location.line, node.location.column)
185
+ e = Position.line_char_to_offset(@code, node.location.last_line, node.location.last_column)
186
+ frag = code[b..e-1].to_s
187
+ frag.strip.gsub(/,$/, '')
188
+ end
189
+
190
+ # @param node [Parser::AST::Node]
191
+ # @return [String]
192
+ def comments_for node
193
+ arr = associated_comments[node.loc.line]
194
+ arr ? stringify_comment_array(arr) : nil
195
+ end
196
+
197
+ def location
198
+ st = Position.new(0, 0)
199
+ en = Position.from_offset(code, code.length)
200
+ range = Range.new(st, en)
201
+ Location.new(filename, range)
202
+ end
203
+
174
204
  private
175
205
 
206
+ def associated_comments
207
+ @associated_comments ||= begin
208
+ result = {}
209
+ Parser::Source::Comment.associate_locations(node, comments).each_pair do |loc, all|
210
+ block = all.select{ |l| l.document? || code.lines[l.loc.line].strip.start_with?('#')}
211
+ next if block.empty?
212
+ result[loc.line] ||= []
213
+ result[loc.line].concat block
214
+ end
215
+ result
216
+ end
217
+ end
218
+
219
+ def stringify_comment_array comments
220
+ ctxt = ''
221
+ num = nil
222
+ started = false
223
+ comments.each { |l|
224
+ # Trim the comment and minimum leading whitespace
225
+ p = l.text.gsub(/^#/, '')
226
+ if num.nil? and !p.strip.empty?
227
+ num = p.index(/[^ ]/)
228
+ started = true
229
+ elsif started and !p.strip.empty?
230
+ cur = p.index(/[^ ]/)
231
+ num = cur if cur < num
232
+ end
233
+ ctxt += "#{p[num..-1]}\n" if started
234
+ }
235
+ ctxt
236
+ end
237
+
176
238
  # @return [Array<Range>]
177
239
  def string_ranges
178
240
  @string_ranges ||= string_ranges_in(@node)
@@ -42,16 +42,18 @@ module Solargraph
42
42
  def inferred_pins pins, api_map, context, locals
43
43
  result = pins.map do |p|
44
44
  if CoreFills::METHODS_RETURNING_SELF.include?(p.path)
45
- next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.tag}]", p.scope, p.visibility, p.parameters)
45
+ next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.tag}]", p.scope, p.visibility, p.parameters, p.node)
46
46
  end
47
47
  if CoreFills::METHODS_RETURNING_SUBTYPES.include?(p.path) && !context.subtypes.empty?
48
- next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.subtypes.first.tag}]", p.scope, p.visibility, p.parameters)
48
+ next Solargraph::Pin::Method.new(p.location, p.namespace, p.name, "@return [#{context.subtypes.first.tag}]", p.scope, p.visibility, p.parameters, p.node)
49
+ end
50
+ if p.kind == Pin::METHOD && !p.macros.empty?
51
+ result = process_macro(p, api_map, context, locals)
52
+ next result unless result.return_type.undefined?
53
+ elsif !p.directives.empty?
54
+ result = process_directive(p, api_map, context, locals)
55
+ next result unless result.return_type.undefined?
49
56
  end
50
- # @todo Temporarily disabled macros
51
- # if p.kind == Pin::METHOD && !p.macros.empty?
52
- # result = process_macro(p, api_map, context, locals)
53
- # next result unless result.return_type.undefined?
54
- # end
55
57
  next p if p.kind == Pin::METHOD || p.kind == Pin::ATTRIBUTE || p.kind == Pin::NAMESPACE
56
58
  type = p.infer(api_map)
57
59
  next p if p.return_complex_type == type
@@ -67,22 +69,39 @@ module Solargraph
67
69
  # @param pin [Pin::Method]
68
70
  # @param api_map [ApiMap]
69
71
  # @param context [ComplexType]
72
+ # @return [Pin::Base]
70
73
  def process_macro pin, api_map, context, locals
71
74
  pin.macros.each do |macro|
72
- vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals)) }
73
- txt = macro.tag.text.clone
74
- i = 1
75
- vals.each do |v|
76
- txt.gsub!(/\$#{i}/, v.context.namespace)
77
- i += 1
78
- end
79
- docstring = YARD::Docstring.parser.parse(txt).to_docstring
80
- tag = docstring.tag(:return)
81
- unless tag.nil? || tag.types.nil?
82
- return Pin::ProxyType.anonymous(ComplexType.parse(*tag.types))
83
- end
75
+ result = inner_process_macro(pin, macro, api_map, context, locals)
76
+ return result unless result.return_type.undefined?
77
+ end
78
+ Pin::ProxyType.new(nil, nil, nil, ComplexType::UNDEFINED)
79
+ end
80
+
81
+ def process_directive pin, api_map, context, locals
82
+ pin.directives.each do |dir|
83
+ macro = api_map.named_macro(dir.tag.name)
84
+ next if macro.nil?
85
+ result = inner_process_macro(pin, macro, api_map, context, locals)
86
+ return result unless result.return_type.undefined?
87
+ end
88
+ Pin::ProxyType.new(nil, nil, nil, ComplexType::UNDEFINED)
89
+ end
90
+
91
+ def inner_process_macro pin, macro, api_map, context, locals
92
+ vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals)) }
93
+ txt = macro.tag.text.clone
94
+ i = 1
95
+ vals.each do |v|
96
+ txt.gsub!(/\$#{i}/, v.context.namespace)
97
+ i += 1
98
+ end
99
+ docstring = YARD::Docstring.parser.parse(txt).to_docstring
100
+ tag = docstring.tag(:return)
101
+ unless tag.nil? || tag.types.nil?
102
+ return Pin::ProxyType.anonymous(ComplexType.parse(*tag.types))
84
103
  end
85
- return Pin::ProxyType.new(nil, nil, nil, ComplexType::UNDEFINED)
104
+ Pin::ProxyType.new(nil, nil, nil, ComplexType::UNDEFINED)
86
105
  end
87
106
  end
88
107
  end
@@ -41,7 +41,7 @@ module Solargraph
41
41
  # @param n [Parser::AST::Node]
42
42
  # @return [Array<Chain::Link>]
43
43
  def generate_links n
44
- return [] if n.nil?
44
+ return [] unless n.is_a?(Parser::AST::Node)
45
45
  return generate_links(n.children[0]) if n.type == :begin
46
46
  # @todo This might not be right. It's weird either way.
47
47
  # return generate_links(n.children[2] || n.children[0]) if n.type == :block
@@ -60,6 +60,8 @@ module Solargraph
60
60
  return 'Float'
61
61
  elsif node.type == :sym
62
62
  return 'Symbol'
63
+ elsif node.type == :regexp
64
+ return 'Regexp'
63
65
  # @todo Maybe ignore nils
64
66
  # elsif node.type == :nil
65
67
  # return 'NilClass'
@@ -106,6 +108,85 @@ module Solargraph
106
108
  end
107
109
  signature
108
110
  end
111
+
112
+ def returns_from node
113
+ DeepInference.get_return_nodes(node)
114
+ end
115
+
116
+ module DeepInference
117
+ class << self
118
+ CONDITIONAL = [:if, :unless]
119
+ REDUCEABLE = [:begin, :kwbegin]
120
+ SKIPPABLE = [:def, :defs, :class, :sclass, :module]
121
+
122
+ def get_return_nodes node
123
+ result = []
124
+ if REDUCEABLE.include?(node.type)
125
+ result.concat get_return_nodes_from_children(node)
126
+ elsif CONDITIONAL.include?(node.type)
127
+ result.concat reduce_to_value_nodes(node.children[1..-1])
128
+ else
129
+ result.push node
130
+ end
131
+ result
132
+ end
133
+
134
+ private
135
+
136
+ def get_return_nodes_from_children parent
137
+ result = []
138
+ nodes = parent.children.select{|n| n.is_a?(AST::Node)}
139
+ nodes[0..-2].each do |node|
140
+ next if SKIPPABLE.include?(node.type)
141
+ if node.type == :return
142
+ result.concat reduce_to_value_nodes([node.children[0]])
143
+ # @todo Maybe return the result here because the rest of the code is
144
+ # unreachable
145
+ return result
146
+ else
147
+ result.concat get_return_nodes_only(node)
148
+ end
149
+ end
150
+ result.concat reduce_to_value_nodes([nodes.last]) unless nodes.last.nil?
151
+ result
152
+ end
153
+
154
+ def get_return_nodes_only parent
155
+ result = []
156
+ nodes = parent.children.select{|n| n.is_a?(AST::Node)}
157
+ nodes.each do |node|
158
+ next if SKIPPABLE.include?(node.type)
159
+ if node.type == :return
160
+ result.concat reduce_to_value_nodes([node.children[0]])
161
+ # @todo Maybe return the result here because the rest of the code is
162
+ # unreachable
163
+ else
164
+ result.concat get_return_nodes_only(node)
165
+ end
166
+ end
167
+ result
168
+ end
169
+
170
+ def reduce_to_value_nodes nodes
171
+ result = []
172
+ nodes.each do |node|
173
+ if REDUCEABLE.include?(node.type)
174
+ result.concat get_return_nodes_from_children(node)
175
+ # node.children.each do |child|
176
+ # result.concat reduce_to_value_nodes(child)
177
+ # end
178
+ elsif CONDITIONAL.include?(node.type)
179
+ result.concat reduce_to_value_nodes(node.children[1..-1])
180
+ elsif node.type == :return
181
+ result.concat get_return_nodes(node.children[0])
182
+ else
183
+ result.push node
184
+ end
185
+ end
186
+ result
187
+ end
188
+ end
189
+ end
109
190
  end
110
191
  end
111
192
  end
@@ -2,9 +2,11 @@ module Solargraph
2
2
  # An index of pins and other ApiMap-related data for a Source.
3
3
  #
4
4
  class SourceMap
5
+ autoload :NodeProcessor, 'solargraph/source_map/node_processor'
5
6
  autoload :Mapper, 'solargraph/source_map/mapper'
6
7
  autoload :Clip, 'solargraph/source_map/clip'
7
8
  autoload :Completion, 'solargraph/source_map/completion'
9
+ autoload :Region, 'solargraph/source_map/region'
8
10
 
9
11
  # @return [Source]
10
12
  attr_reader :source
@@ -15,6 +17,9 @@ module Solargraph
15
17
  # @return [Array<Pin::Base>]
16
18
  attr_reader :locals
17
19
 
20
+ # @param source [Source]
21
+ # @param pins [Array<Pin::Base>]
22
+ # @param locals [Array<Pin::Base>]
18
23
  def initialize source, pins, locals
19
24
  # HACK: Keep the library from changing this
20
25
  @source = source.dup
@@ -22,14 +27,17 @@ module Solargraph
22
27
  @locals = locals
23
28
  end
24
29
 
30
+ # @return [String]
25
31
  def filename
26
32
  source.filename
27
33
  end
28
34
 
35
+ # @return [String]
29
36
  def code
30
37
  source.code
31
38
  end
32
39
 
40
+ # @return [Array<Pin::Reference::Require>]
33
41
  def requires
34
42
  @requires ||= pins.select{|p| p.kind == Pin::REQUIRE_REFERENCE}
35
43
  end
@@ -46,12 +54,15 @@ module Solargraph
46
54
  @source.comment_at?(position)
47
55
  end
48
56
 
57
+ # @return [Array<Pin::Base>]
49
58
  def document_symbols
50
59
  @document_symbols ||= pins.select { |pin|
51
60
  [Pin::ATTRIBUTE, Pin::CONSTANT, Pin::METHOD, Pin::NAMESPACE].include?(pin.kind) and !pin.path.empty?
52
61
  }
53
62
  end
54
63
 
64
+ # @param query [String]
65
+ # @return [Array<Pin::Base>]
55
66
  def query_symbols query
56
67
  document_symbols.select{|pin| pin.path.include?(query)}
57
68
  end
@@ -82,6 +93,7 @@ module Solargraph
82
93
  end
83
94
 
84
95
  # @param other_map [SourceMap]
96
+ # @return Boolean
85
97
  def try_merge! other_map
86
98
  return false if pins.length != other_map.pins.length || locals.length != other_map.locals.length || requires.map(&:name).uniq.sort != other_map.requires.map(&:name).uniq.sort
87
99
  pins.each_index do |i|
@@ -101,12 +113,15 @@ module Solargraph
101
113
  end
102
114
 
103
115
  class << self
116
+ # @param filename [String]
104
117
  # @return [SourceMap]
105
118
  def load filename
106
119
  source = Solargraph::Source.load(filename)
107
120
  SourceMap.map(source)
108
121
  end
109
122
 
123
+ # @param code [String]
124
+ # @param filename [String, nil]
110
125
  # @return [SourceMap]
111
126
  def load_string code, filename = nil
112
127
  source = Solargraph::Source.load_string(code, filename)
@@ -29,7 +29,7 @@ module Solargraph
29
29
  type = cursor.chain.base.infer(api_map, context_pin, locals)
30
30
  else
31
31
  full = cursor.chain.links.first.word
32
- if full.include?('::')
32
+ if full.include?('::') && cursor.chain.links.length == 1
33
33
  type = ComplexType.parse(full.split('::')[0..-2].join('::'))
34
34
  elsif cursor.chain.links.length > 1
35
35
  type = ComplexType.parse(full)
@@ -13,29 +13,14 @@ module Solargraph
13
13
  # Generate the data.
14
14
  #
15
15
  # @return [Array]
16
- def map filename, code, node, comments
17
- @filename = filename
18
- @code = code
19
- @node = node
20
- @comments = comments
21
- @node_stack = []
22
- @directives = {}
23
- @comment_ranges = comments.map do |c|
24
- Range.from_to(c.loc.expression.line, c.loc.expression.column, c.loc.expression.last_line, c.loc.expression.last_column)
25
- end
26
- @node_comments = associate_comments(node, comments)
27
- @pins = []
28
- @locals = []
29
- @strings = []
30
-
31
- # HACK make sure the first node gets processed
32
- root = AST::Node.new(:source, [filename])
33
- root = root.append node
34
- # @todo Is the root namespace a class or a module? Assuming class for now.
35
- @pins.push Pin::Namespace.new(get_node_location(nil), '', '', nil, :class, :public)
36
- process root
16
+ def map source
17
+ @filename = source.filename
18
+ @code = source.code
19
+ @comments = source.comments
20
+ @pins = Solargraph::SourceMap::NodeProcessor.process(source.node, Solargraph::SourceMap::Region.new(source: source), [])
37
21
  process_comment_directives
38
- [@pins, @locals]
22
+ locals = @pins.select{|p| [Pin::LocalVariable, Pin::MethodParameter, Pin::BlockParameter].include?(p.class)}
23
+ [@pins - locals, locals]
39
24
  end
40
25
 
41
26
  def unmap filename, code
@@ -50,23 +35,10 @@ module Solargraph
50
35
  # @return [Array]
51
36
  def map source
52
37
  return new.unmap(source.filename, source.code) unless source.parsed?
53
- new.map source.filename, source.code, source.node, source.comments
38
+ new.map source
54
39
  end
55
40
  end
56
41
 
57
- # @param node [Parser::AST::Node]
58
- # @return [String]
59
- def code_for node
60
- # @todo Using node locations on code with converted EOLs seems
61
- # slightly more efficient than calculating offsets.
62
- b = node.location.expression.begin.begin_pos
63
- e = node.location.expression.end.end_pos
64
- # b = Position.line_char_to_offset(@code, node.location.line - 1, node.location.column)
65
- # e = Position.line_char_to_offset(@code, node.location.last_line - 1, node.location.last_column)
66
- frag = source_from_parser[b..e-1].to_s
67
- frag.strip.gsub(/,$/, '')
68
- end
69
-
70
42
  # @return [String]
71
43
  def filename
72
44
  @filename
@@ -77,346 +49,6 @@ module Solargraph
77
49
  @pins ||= []
78
50
  end
79
51
 
80
- # @param node [Parser::AST::Node]
81
- def process node, tree = [], visibility = :public, scope = :instance, fqn = '', stack = []
82
- return unless node.is_a?(AST::Node)
83
- return if node.type == :str
84
- stack.push node
85
- if node.kind_of?(AST::Node)
86
- @node_stack.unshift node
87
- if node.type == :class or node.type == :module
88
- visibility = :public
89
- 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
90
- tree = pack_name(node.children[0])
91
- tree.shift if tree.first.empty?
92
- else
93
- tree = tree + pack_name(node.children[0])
94
- end
95
- fqn = tree.join('::')
96
- sc = nil
97
- if node.type == :class and !node.children[1].nil?
98
- sc = unpack_name(node.children[1])
99
- end
100
- pins.push Solargraph::Pin::Namespace.new(get_node_location(node), tree[0..-2].join('::') || '', pack_name(node.children[0]).last.to_s, comments_for(node), node.type, visibility)
101
- pins.push Pin::Reference::Superclass.new(pins.last.location, pins.last.path, sc) unless sc.nil?
102
- end
103
- file = filename
104
- node.children.each do |c|
105
- if c.kind_of?(AST::Node)
106
- if c.type == :ivasgn
107
- here = get_node_start_position(c)
108
- named_path = get_named_path_pin(here)
109
- if c.children[1].nil?
110
- ora = find_parent(stack, :or_asgn)
111
- unless ora.nil?
112
- u = c.updated(:ivasgn, c.children + ora.children[1..-1], nil)
113
- pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(u), fqn || '', c.children[0].to_s, comments_for(u), u.children[1], infer_literal_node_type(u.children[1]), named_path.context)
114
- if visibility == :module_function and named_path.kind == Pin::METHOD
115
- other = ComplexType.parse("Module<#{named_path.context.namespace}>")
116
- pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(u), fqn || '', c.children[0].to_s, comments_for(u), u.children[1], infer_literal_node_type(u.children[1]), other) #unless other.nil?
117
- end
118
- end
119
- else
120
- pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(c), fqn || '',c.children[0].to_s, comments_for(c), c.children[1], infer_literal_node_type(c.children[1]), named_path.context)
121
- if visibility == :module_function and named_path.kind == Pin::METHOD
122
- other = ComplexType.parse("Module<#{named_path.context.namespace}>")
123
- pins.push Solargraph::Pin::InstanceVariable.new(get_node_location(c), fqn || '',c.children[0].to_s, comments_for(c), c.children[1], infer_literal_node_type(c.children[1]), other)
124
- end
125
- end
126
- elsif c.type == :cvasgn
127
- here = get_node_start_position(c)
128
- context = get_named_path_pin(here)
129
- if c.children[1].nil?
130
- ora = find_parent(stack, :or_asgn)
131
- unless ora.nil?
132
- u = c.updated(:cvasgn, c.children + ora.children[1..-1], nil)
133
- pins.push Solargraph::Pin::ClassVariable.new(get_node_location(u), fqn || '', c.children[0].to_s, comments_for(u), u.children[1], infer_literal_node_type(u.children[1]), context.context)
134
- end
135
- else
136
- pins.push Solargraph::Pin::ClassVariable.new(get_node_location(c), fqn || '', c.children[0].to_s, comments_for(c), c.children[1], infer_literal_node_type(c.children[1]), context.context)
137
- end
138
- elsif c.type == :lvasgn
139
- here = get_node_start_position(c)
140
- context = get_named_path_pin(here)
141
- block = get_block_pin(here)
142
- presence = Range.new(here, block.location.range.ending)
143
- if c.children[1].nil?
144
- ora = find_parent(stack, :or_asgn)
145
- unless ora.nil?
146
- u = c.updated(:lvasgn, c.children + ora.children[1..-1], nil)
147
- @locals.push Solargraph::Pin::LocalVariable.new(get_node_location(u), fqn, u.children[0].to_s, comments_for(ora), u.children[1], infer_literal_node_type(c.children[1]), context.context, block, presence)
148
- end
149
- else
150
- @locals.push Solargraph::Pin::LocalVariable.new(get_node_location(c), fqn, c.children[0].to_s, comments_for(c), c.children[1], infer_literal_node_type(c.children[1]), context.context, block, presence)
151
- end
152
- elsif c.type == :gvasgn
153
- if c.children[1].nil?
154
- ora = find_parent(stack, :or_asgn)
155
- unless ora.nil?
156
- u = c.updated(:gvasgn, c.children + ora.children[1..-1], nil)
157
- pins.push Solargraph::Pin::GlobalVariable.new(get_node_location(c), fqn, u.children[0].to_s, comments_for(c), u.children[1], infer_literal_node_type(c.children[1]), @pins.first.context)
158
- end
159
- else
160
- pins.push Solargraph::Pin::GlobalVariable.new(get_node_location(c), fqn, c.children[0].to_s, comments_for(c), c.children[1], infer_literal_node_type(c.children[1]), @pins.first.context)
161
- end
162
- elsif c.type == :sym
163
- pins.push Solargraph::Pin::Symbol.new(get_node_location(c), ":#{c.children[0]}")
164
- elsif c.type == :casgn
165
- here = get_node_start_position(c)
166
- block = get_block_pin(here)
167
- pins.push Solargraph::Pin::Constant.new(get_node_location(c), fqn, c.children[1].to_s, comments_for(c), c.children[2], infer_literal_node_type(c.children[2]), block.context, :public)
168
- elsif c.type == :def
169
- methpin = Solargraph::Pin::Method.new(get_node_location(c), fqn || '', c.children[(c.type == :def ? 0 : 1)].to_s, comments_for(c), scope, visibility, get_method_args(c))
170
- if methpin.name == 'initialize' and methpin.scope == :instance
171
- pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, 'new', methpin.comments, :class, :public, methpin.parameters)
172
- # @todo Smelly instance variable access.
173
- pins.last.instance_variable_set(:@return_complex_type, ComplexType.parse(methpin.namespace))
174
- pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, methpin.name, methpin.comments, methpin.scope, :private, methpin.parameters)
175
- elsif visibility == :module_function
176
- pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, methpin.name, methpin.comments, :class, :public, methpin.parameters)
177
- pins.push Solargraph::Pin::Method.new(methpin.location, methpin.namespace, methpin.name, methpin.comments, :instance, :private, methpin.parameters)
178
- else
179
- pins.push methpin
180
- end
181
- elsif c.type == :defs
182
- s_visi = visibility
183
- s_visi = :public if s_visi == :module_function or scope != :class
184
- if c.children[0].is_a?(AST::Node) and c.children[0].type == :self
185
- dfqn = fqn || ''
186
- else
187
- dfqn = unpack_name(c.children[0])
188
- end
189
- unless dfqn.nil?
190
- pins.push Solargraph::Pin::Method.new(get_node_location(c), dfqn, "#{c.children[(node.type == :def ? 0 : 1)]}", comments_for(c), :class, s_visi, get_method_args(c))
191
- process c, tree, scope, :class, dfqn, stack
192
- end
193
- next
194
- elsif c.type == :send and [:public, :protected, :private].include?(c.children[1])
195
- visibility = c.children[1]
196
- elsif c.type == :send and [:private_class_method].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
197
- if c.children[2].type == :sym or c.children[2].type == :str
198
- ref = pins.select{|p| p.namespace == (fqn || '') and p.name == c.children[2].children[0].to_s}.first
199
- unless ref.nil?
200
- pins.delete ref
201
- pins.push Solargraph::Pin::Method.new(ref.location, ref.namespace, ref.name, ref.comments, ref.scope, :private, ref.parameters)
202
- end
203
- else
204
- process c, tree, :private, :class, fqn, stack
205
- next
206
- end
207
- elsif c.type == :send and [:private_constant].include?(c.children[1]) and c.children[2].kind_of?(AST::Node)
208
- if c.children[2].type == :sym or c.children[2].type == :str
209
- # @todo What to do about references?
210
- cn = c.children[2].children[0].to_s
211
- ref = pins.select{|p| p.namespace == (fqn || '') and p.name == cn}.first
212
- unless ref.nil?
213
- pins.delete ref
214
- # Might be either a namespace or constant
215
- if ref.kind == Pin::CONSTANT
216
- pins.push ref.class.new(ref.location, ref.namespace, ref.name, ref.comments, ref.signature, ref.return_type, ref.context, :private)
217
- else
218
- # pins.push ref.class.new(ref.location, ref.namespace, ref.name, ref.comments, ref.type, :private, (ref.superclass_reference.nil? ? nil : ref.superclass_reference.name))
219
- pins.push ref.class.new(ref.location, ref.namespace, ref.name, ref.comments, ref.type, :private)
220
- end
221
- end
222
- end
223
- next
224
- elsif c.type == :send and c.children[1] == :module_function
225
- if c.children[2].nil?
226
- visibility = :module_function
227
- elsif c.children[2].type == :sym or c.children[2].type == :str
228
- # @todo What to do about references?
229
- c.children[2..-1].each do |x|
230
- cn = x.children[0].to_s
231
- ref = pins.select{|p| p.namespace == (fqn || '') and p.name == cn}.first
232
- unless ref.nil?
233
- pins.delete ref
234
- mm = Solargraph::Pin::Method.new(ref.location, ref.namespace, ref.name, ref.comments, :class, :public, ref.parameters)
235
- cm = Solargraph::Pin::Method.new(ref.location, ref.namespace, ref.name, ref.comments, :instance, :private, ref.parameters)
236
- pins.push mm, cm
237
- pins.select{|pin| pin.kind == Pin::INSTANCE_VARIABLE and pin.context == ref.context}.each do |ivar|
238
- pins.delete ivar
239
- pins.push Solargraph::Pin::InstanceVariable.new(ivar.location, ivar.namespace, ivar.name, ivar.comments, ivar.signature, ivar.instance_variable_get(:@literal), mm)
240
- pins.push Solargraph::Pin::InstanceVariable.new(ivar.location, ivar.namespace, ivar.name, ivar.comments, ivar.signature, ivar.instance_variable_get(:@literal), cm)
241
- end
242
- end
243
- end
244
- elsif c.children[2].type == :def
245
- # @todo A single function
246
- process c, tree, :module_function, :class, fqn, stack
247
- next
248
- end
249
- elsif c.type == :send and c.children[1] == :include and c.children[0].nil?
250
- last_node = get_last_in_stack_not_begin(stack)
251
- if last_node.nil? or last_node.type == :class or last_node.type == :module or last_node.type == :source
252
- if c.children[2].kind_of?(AST::Node) and c.children[2].type == :const
253
- c.children[2..-1].each do |i|
254
- nspin = @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.path == fqn}.last
255
- unless nspin.nil?
256
- # iref = Pin::Reference.new(get_node_location(c), nspin.path, unpack_name(i))
257
- # nspin.include_references.push(iref)
258
- pins.push Pin::Reference::Include.new(get_node_location(c), nspin.path, unpack_name(i))
259
- end
260
- end
261
- end
262
- end
263
- elsif c.type == :send and c.children[1] == :extend and c.children[0].nil?
264
- last_node = get_last_in_stack_not_begin(stack)
265
- if last_node.nil? or last_node.type == :class or last_node.type == :module or last_node.type == :source
266
- c.children[2..-1].each do |i|
267
- nspin = @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.path == fqn}.last
268
- unless nspin.nil?
269
- ref = nil
270
- if i.type == :self
271
- # ref = Pin::Reference.new(get_node_location(c), nspin.path, nspin.path)
272
- ref = Pin::Reference::Extend.new(get_node_location(c), nspin.path, nspin.path)
273
- elsif i.type == :const
274
- # ref = Pin::Reference.new(get_node_location(c), nspin.path, unpack_name(i))
275
- ref = Pin::Reference::Extend.new(get_node_location(c), nspin.path, unpack_name(i))
276
- end
277
- # nspin.extend_references.push(ref) unless ref.nil?
278
- pins.push ref unless ref.nil?
279
- end
280
- end
281
- end
282
- elsif c.type == :send && [:attr_reader, :attr_writer, :attr_accessor].include?(c.children[1])
283
- c.children[2..-1].each do |a|
284
- if c.children[1] == :attr_reader or c.children[1] == :attr_accessor
285
- pins.push Solargraph::Pin::Attribute.new(get_node_location(c), fqn || '', "#{a.children[0]}", comments_for(c), :reader, scope, visibility)
286
- end
287
- if c.children[1] == :attr_writer or c.children[1] == :attr_accessor
288
- pins.push Solargraph::Pin::Attribute.new(get_node_location(c), fqn || '', "#{a.children[0]}=", comments_for(c), :writer, scope, visibility)
289
- end
290
- end
291
- elsif c.type == :alias
292
- pin = pins.select{|p| p.name == c.children[1].children[0].to_s && p.namespace == fqn && p.scope == scope}.first
293
- unless pin.nil?
294
- if pin.is_a?(Solargraph::Pin::Method)
295
- pins.push Solargraph::Pin::Method.new(get_node_location(c), pin.namespace, c.children[0].children[0].to_s, comments_for(c) || pin.comments, pin.scope, pin.visibility, pin.parameters)
296
- elsif pin.is_a?(Solargraph::Pin::Attribute)
297
- pins.push Solargraph::Pin::Attribute.new(get_node_location(c), pin.namespace, c.children[0].children[0].to_s, comments_for(c) || pin.comments, pin.access, pin.scope, pin.visibility)
298
- end
299
- end
300
- elsif c.type == :send && c.children[1] == :alias_method && c.children[2] && c.children[2] && c.children[2].type == :sym && c.children[3] && c.children[3].type == :sym
301
- pin = pins.select{|p| p.name == c.children[3].children[0].to_s && p.namespace == fqn && p.scope == scope}.first
302
- unless pin.nil?
303
- if pin.is_a?(Solargraph::Pin::Method)
304
- pins.push Solargraph::Pin::Method.new(get_node_location(c), pin.namespace, c.children[2].children[0].to_s, comments_for(c) || pin.comments, pin.scope, pin.visibility, pin.parameters)
305
- elsif pin.is_a?(Solargraph::Pin::Attribute)
306
- pins.push Solargraph::Pin::Attribute.new(get_node_location(c), pin.namespace, c.children[2].children[0].to_s, comments_for(c) || pin.comments, pin.access, pin.scope, pin.visibility)
307
- end
308
- end
309
- elsif c.type == :sclass && c.children[0].type == :self
310
- process c, tree, :public, :class, fqn || '', stack
311
- next
312
- elsif c.type == :send && c.children[1] == :require
313
- if c.children[2].kind_of?(AST::Node) and c.children[2].type == :str
314
- # @requires.push Solargraph::Pin::Reference.new(get_node_location(c), fqn, c.children[2].children[0].to_s)
315
- pins.push Pin::Reference::Require.new(get_node_location(c), c.children[2].children[0].to_s)
316
- end
317
- elsif c.type == :args
318
- if @node_stack.first.type == :block
319
- pi = 0
320
- c.children.each do |u|
321
- here = get_node_start_position(c)
322
- blk = get_block_pin(here)
323
- @locals.push Solargraph::Pin::BlockParameter.new(get_node_location(u), fqn || '', "#{u.children[0]}", comments_for(c), blk)
324
- blk.parameters.push @locals.push.last
325
- pi += 1
326
- end
327
- else
328
- c.children.each do |u|
329
- here = get_node_start_position(u)
330
- context = get_named_path_pin(here)
331
- block = get_block_pin(here)
332
- presence = Range.new(here, block.location.range.ending)
333
- @locals.push Solargraph::Pin::MethodParameter.new(get_node_location(u), fqn, u.children[0].to_s, comments_for(c), resolve_node_signature(u.children[1]), infer_literal_node_type(u.children[1]), context, block, presence)
334
- end
335
- end
336
- elsif c.type == :block
337
- here = get_node_start_position(c)
338
- named_path = get_named_path_pin(here)
339
- @pins.push Solargraph::Pin::Block.new(get_node_location(c), fqn || '', '', comments_for(c), c.children[0], named_path.context)
340
- end
341
- process c, tree, visibility, scope, fqn, stack
342
- end
343
- end
344
- @node_stack.shift
345
- end
346
- stack.pop
347
- end
348
-
349
- def get_last_in_stack_not_begin stack
350
- index = stack.length - 1
351
- last = stack[index]
352
- while !last.nil? and last.type == :begin
353
- index -= 1
354
- last = stack[index]
355
- end
356
- last
357
- end
358
-
359
- def get_block_pin position
360
- @pins.select{|pin| [Pin::BLOCK, Pin::NAMESPACE, Pin::METHOD].include?(pin.kind) and pin.location.range.contain?(position)}.last
361
- end
362
-
363
- def get_named_path_pin position
364
- @pins.select{|pin| [Pin::NAMESPACE, Pin::METHOD].include?(pin.kind) and pin.location.range.contain?(position)}.last
365
- end
366
-
367
- def get_namespace_pin position
368
- @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.location.range.contain?(position)}.last
369
- end
370
-
371
- # @return [String]
372
- def comments_for node
373
- result = @node_comments[node.loc]
374
- return nil if result.nil?
375
- result
376
- end
377
-
378
- # @param node [Parser::AST::Node]
379
- # @return [Solargraph::Location]
380
- def get_node_location(node)
381
- if node.nil?
382
- st = Position.new(0, 0)
383
- en = Position.from_offset(@code, @code.length)
384
- else
385
- st = Position.new(node.loc.line, node.loc.column)
386
- en = Position.new(node.loc.last_line, node.loc.last_column)
387
- end
388
- range = Range.new(st, en)
389
- Location.new(filename, range)
390
- end
391
-
392
- # @param node [Parser::AST::Node]
393
- # @param comments [Array]
394
- # @return [Hash]
395
- def associate_comments node, comments
396
- return nil if comments.nil?
397
- comment_hash = Parser::Source::Comment.associate_locations(node, comments)
398
- result_hash = {}
399
- comment_hash.each_pair { |k, v|
400
- ctxt = ''
401
- num = nil
402
- started = false
403
- v.each { |l|
404
- # Trim the comment and minimum leading whitespace
405
- p = l.text.gsub(/^#/, '')
406
- if num.nil? and !p.strip.empty?
407
- num = p.index(/[^ ]/)
408
- started = true
409
- elsif started and !p.strip.empty?
410
- cur = p.index(/[^ ]/)
411
- num = cur if cur < num
412
- end
413
- ctxt += "#{p[num..-1]}\n" if started
414
- }
415
- result_hash[k] = ctxt
416
- }
417
- result_hash
418
- end
419
-
420
52
  # @param node [Parser::AST::Node]
421
53
  # @return [Solargraph::Pin::Namespace]
422
54
  def namespace_for(node)
@@ -428,46 +60,6 @@ module Solargraph
428
60
  @pins.select{|pin| pin.kind == Pin::NAMESPACE and pin.location.range.contain?(position)}.last
429
61
  end
430
62
 
431
- def find_parent(stack, *types)
432
- stack.reverse.each { |p|
433
- return p if types.include?(p.type)
434
- }
435
- nil
436
- end
437
-
438
- def get_method_args node
439
- return [] if node.nil?
440
- list = nil
441
- args = []
442
- node.children.each { |c|
443
- if c.kind_of?(AST::Node) and c.type == :args
444
- list = c
445
- break
446
- end
447
- }
448
- return args if list.nil?
449
- list.children.each { |c|
450
- if c.type == :arg
451
- args.push c.children[0].to_s
452
- elsif c.type == :restarg
453
- args.push "*#{c.children[0]}"
454
- elsif c.type == :optarg
455
- args.push "#{c.children[0]} = #{code_for(c.children[1])}"
456
- elsif c.type == :kwarg
457
- args.push "#{c.children[0]}:"
458
- elsif c.type == :kwoptarg
459
- args.push "#{c.children[0]}: #{code_for(c.children[1])}"
460
- elsif c.type == :blockarg
461
- args.push "&#{c.children[0]}"
462
- end
463
- }
464
- args
465
- end
466
-
467
- def source_from_parser
468
- @source_from_parser ||= @code.gsub(/\r\n/, "\n")
469
- end
470
-
471
63
  def process_comment position, comment
472
64
  cmnt = remove_inline_comment_hashes(comment)
473
65
  return unless cmnt =~ /(@\!method|@\!attribute|@\!domain|@\!macro)/
@@ -485,7 +77,7 @@ module Solargraph
485
77
  namespace = namespace_at(position)
486
78
  gen_src = Solargraph::SourceMap.load_string("def #{directive.tag.name};end")
487
79
  gen_pin = gen_src.pins.last # Method is last pin after root namespace
488
- @pins.push Solargraph::Pin::Method.new(location, namespace.path, gen_pin.name, docstring.all, :instance, :public, gen_pin.parameters)
80
+ @pins.push Solargraph::Pin::Method.new(location, namespace.path, gen_pin.name, docstring.all, :instance, :public, gen_pin.parameters, nil)
489
81
  when 'attribute'
490
82
  namespace = namespace_at(position)
491
83
  t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('')
@@ -499,10 +91,6 @@ module Solargraph
499
91
  when 'domain'
500
92
  namespace = namespace_at(position)
501
93
  namespace.domains.push directive.tag.text
502
- when 'macro'
503
- nxt_pos = Position.new(position.line + 1, @code.lines[position.line + 1].length)
504
- path_pin = get_named_path_pin(nxt_pos)
505
- path_pin.macros.push directive
506
94
  end
507
95
  end
508
96