solargraph 0.26.1 → 0.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +5 -2
  3. data/lib/solargraph/api_map.rb +236 -234
  4. data/lib/solargraph/api_map/store.rb +18 -53
  5. data/lib/solargraph/bundle.rb +22 -0
  6. data/lib/solargraph/complex_type.rb +9 -5
  7. data/lib/solargraph/complex_type/type_methods.rb +113 -0
  8. data/lib/solargraph/complex_type/unique_type.rb +35 -0
  9. data/lib/solargraph/core_fills.rb +1 -0
  10. data/lib/solargraph/diagnostics.rb +6 -4
  11. data/lib/solargraph/diagnostics/base.rb +3 -0
  12. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  13. data/lib/solargraph/diagnostics/rubocop.rb +21 -6
  14. data/lib/solargraph/diagnostics/type_not_defined.rb +4 -3
  15. data/lib/solargraph/diagnostics/update_errors.rb +18 -0
  16. data/lib/solargraph/language_server/host.rb +90 -222
  17. data/lib/solargraph/language_server/host/cataloger.rb +68 -0
  18. data/lib/solargraph/language_server/host/diagnoser.rb +85 -0
  19. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +35 -24
  20. data/lib/solargraph/language_server/message/text_document/completion.rb +6 -8
  21. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  22. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +0 -1
  23. data/lib/solargraph/language_server/transport/socket.rb +4 -6
  24. data/lib/solargraph/language_server/transport/stdio.rb +4 -6
  25. data/lib/solargraph/library.rb +152 -99
  26. data/lib/solargraph/live_map.rb +1 -1
  27. data/lib/solargraph/location.rb +28 -0
  28. data/lib/solargraph/pin.rb +2 -0
  29. data/lib/solargraph/pin/attribute.rb +26 -12
  30. data/lib/solargraph/pin/base.rb +15 -35
  31. data/lib/solargraph/pin/base_variable.rb +7 -15
  32. data/lib/solargraph/pin/block.rb +5 -9
  33. data/lib/solargraph/pin/block_parameter.rb +9 -7
  34. data/lib/solargraph/pin/conversions.rb +5 -5
  35. data/lib/solargraph/pin/duck_method.rb +1 -1
  36. data/lib/solargraph/pin/instance_variable.rb +0 -4
  37. data/lib/solargraph/pin/keyword.rb +4 -0
  38. data/lib/solargraph/pin/localized.rb +5 -3
  39. data/lib/solargraph/pin/method.rb +11 -0
  40. data/lib/solargraph/pin/namespace.rb +7 -3
  41. data/lib/solargraph/pin/proxy_type.rb +3 -7
  42. data/lib/solargraph/pin/reference.rb +2 -2
  43. data/lib/solargraph/pin/symbol.rb +1 -1
  44. data/lib/solargraph/pin/yard_pin/method.rb +2 -2
  45. data/lib/solargraph/pin/yard_pin/namespace.rb +16 -7
  46. data/lib/solargraph/position.rb +103 -0
  47. data/lib/solargraph/range.rb +70 -0
  48. data/lib/solargraph/source.rb +159 -328
  49. data/lib/solargraph/source/chain.rb +38 -55
  50. data/lib/solargraph/source/chain/call.rb +47 -29
  51. data/lib/solargraph/source/chain/class_variable.rb +2 -2
  52. data/lib/solargraph/source/chain/constant.rb +3 -3
  53. data/lib/solargraph/source/chain/definition.rb +7 -3
  54. data/lib/solargraph/source/chain/global_variable.rb +1 -1
  55. data/lib/solargraph/source/chain/head.rb +22 -9
  56. data/lib/solargraph/source/chain/instance_variable.rb +2 -2
  57. data/lib/solargraph/source/chain/link.rb +4 -4
  58. data/lib/solargraph/source/chain/literal.rb +1 -1
  59. data/lib/solargraph/source/chain/variable.rb +2 -2
  60. data/lib/solargraph/source/change.rb +0 -6
  61. data/lib/solargraph/source/cursor.rb +161 -0
  62. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  63. data/lib/solargraph/source/node_chainer.rb +28 -21
  64. data/lib/solargraph/source/node_methods.rb +1 -1
  65. data/lib/solargraph/source/source_chainer.rb +217 -0
  66. data/lib/solargraph/source_map.rb +138 -0
  67. data/lib/solargraph/source_map/clip.rb +123 -0
  68. data/lib/solargraph/{source → source_map}/completion.rb +3 -3
  69. data/lib/solargraph/{source → source_map}/mapper.rb +143 -41
  70. data/lib/solargraph/version.rb +1 -1
  71. data/lib/solargraph/workspace.rb +13 -20
  72. data/lib/solargraph/yard_map.rb +77 -48
  73. metadata +17 -11
  74. data/lib/solargraph/basic_type.rb +0 -33
  75. data/lib/solargraph/basic_type_methods.rb +0 -111
  76. data/lib/solargraph/source/call_chainer.rb +0 -273
  77. data/lib/solargraph/source/fragment.rb +0 -342
  78. data/lib/solargraph/source/location.rb +0 -23
  79. data/lib/solargraph/source/position.rb +0 -95
  80. data/lib/solargraph/source/range.rb +0 -64
@@ -9,7 +9,7 @@ module Solargraph
9
9
  # @return [String]
10
10
  def normalize string
11
11
  begin
12
- string.force_encoding('UTF-8')
12
+ string.clone.force_encoding('UTF-8')
13
13
  rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError => e
14
14
  # @todo Improve error handling
15
15
  STDERR.puts "Normalize error: #{e.message}"
@@ -1,11 +1,11 @@
1
1
  module Solargraph
2
2
  class Source
3
3
  class NodeChainer
4
- include NodeMethods
4
+ include Source::NodeMethods
5
5
 
6
- def initialize filename, node
7
- @filename = filename
6
+ def initialize node, filename = nil
8
7
  @node = node
8
+ @filename = filename
9
9
  # @source = source
10
10
  # @line = line
11
11
  # @column = column
@@ -14,23 +14,23 @@ module Solargraph
14
14
  # @return [Source::Chain]
15
15
  def chain
16
16
  links = generate_links(@node)
17
- Chain.new(@filename, links)
17
+ Chain.new(links)
18
18
  end
19
19
 
20
20
  class << self
21
- # @param source [Source]
22
- # @param line [Integer]
23
- # @param column [Integer]
24
- # @return [Source::Chain]
25
- def chain filename, node
26
- NodeChainer.new(filename, node).chain
21
+ # @param node [Parser::AST::Node]
22
+ # @param filename [String]
23
+ # @return [Chain]
24
+ def chain node, filename = nil
25
+ NodeChainer.new(node, filename).chain
27
26
  end
28
27
 
29
28
  # @param code [String]
29
+ # @param filename [String, nil]
30
30
  # @return [Chain]
31
- def load_string(filename, code)
32
- node = Source.parse_node(code.sub(/\.$/, ''), filename)
33
- chain = Chain.new(filename, node)
31
+ def load_string(code)
32
+ node = Source.parse(code.sub(/\.$/, ''))
33
+ chain = NodeChainer.new(node).chain
34
34
  chain.links.push(Chain::Link.new) if code.end_with?('.')
35
35
  chain
36
36
  end
@@ -54,24 +54,29 @@ module Solargraph
54
54
  result.concat generate_links(n.children[0])
55
55
  args = []
56
56
  n.children[2..-1].each do |c|
57
- # @todo Handle link parameters
58
- # args.push Chain.new(source, c.loc.last_line - 1, c.loc.column)
57
+ args.push NodeChainer.chain(c)
59
58
  end
60
59
  result.push Chain::Call.new(n.children[1].to_s, args)
61
60
  elsif n.children[0].nil?
62
61
  args = []
63
62
  n.children[2..-1].each do |c|
64
- # @todo Handle link parameters
65
- # args.push Chain.new(source, c.loc.last_line - 1, c.loc.column)
63
+ args.push NodeChainer.chain(c)
66
64
  end
67
65
  result.push Chain::Call.new(n.children[1].to_s, args)
68
66
  else
69
67
  raise "No idea what to do with #{n}"
70
68
  end
71
69
  elsif n.type == :self
72
- result.push Chain::Call.new('self')
70
+ result.push Chain::Head.new('self')
71
+ elsif n.type == :zsuper
72
+ result.push Chain::Head.new('super')
73
73
  elsif n.type == :const
74
- result.push Chain::Constant.new(unpack_name(n))
74
+ # result.push Chain::Constant.new(unpack_name(n))
75
+ const = unpack_name(n)
76
+ parts = const.split('::')
77
+ last = parts.pop
78
+ result.push Chain::Constant.new(parts.join('::')) unless parts.empty?
79
+ result.push Chain::Constant.new(last)
75
80
  elsif [:lvar, :lvasgn].include?(n.type)
76
81
  result.push Chain::Call.new(n.children[0].to_s)
77
82
  elsif [:ivar, :ivasgn].include?(n.type)
@@ -81,8 +86,10 @@ module Solargraph
81
86
  elsif [:gvar, :gvasgn].include?(n.type)
82
87
  result.push Chain::GlobalVariable.new(n.children[0].to_s)
83
88
  elsif [:class, :module, :def, :defs].include?(n.type)
84
- location = Solargraph::Source::Location.new(@filename, Source::Range.from_to(n.loc.expression.line - 1, n.loc.expression.column, n.loc.expression.last_line - 1, n.loc.expression.last_column))
85
- result.push Chain::Definition.new(location)
89
+ # location = Solargraph::Location.new(@filename, Range.from_to(n.loc.expression.line, n.loc.expression.column, n.loc.expression.last_line, n.loc.expression.last_column))
90
+ # result.push Chain::Definition.new(location)
91
+ # @todo Undefined or what?
92
+ result.push Chain::UNDEFINED_CALL
86
93
  else
87
94
  lit = infer_literal_node_type(n)
88
95
  result.push (lit ? Chain::Literal.new(lit) : Chain::Link.new)
@@ -15,7 +15,7 @@ module Solargraph
15
15
  node.children.each { |n|
16
16
  if n.kind_of?(AST::Node)
17
17
  if n.type == :cbase
18
- parts = pack_name(n)
18
+ parts = [''] + pack_name(n)
19
19
  else
20
20
  parts += pack_name(n)
21
21
  end
@@ -0,0 +1,217 @@
1
+ module Solargraph
2
+ class Source
3
+ # Information about a location in a source, including the location's word
4
+ # and signature, literal values at the base of signatures, and whether the
5
+ # location is inside a string or comment. ApiMaps use Fragments to provide
6
+ # results for completion and definition queries.
7
+ #
8
+ class SourceChainer
9
+ include Source::NodeMethods
10
+
11
+ private_class_method :new
12
+
13
+ class << self
14
+ # @param source [Source]
15
+ # @param position [Position]
16
+ # @return [Source::Chain]
17
+ def chain source, position
18
+ raise "Not a source" unless source.is_a?(Source)
19
+ new(source, position).chain
20
+ end
21
+ end
22
+
23
+ # @param source [Source]
24
+ # @param position [Position]
25
+ def initialize source, position
26
+ @source = source
27
+ # @source.code = source.code
28
+ @position = position
29
+ # @todo Get rid of line/column
30
+ @line = position.line
31
+ @column = position.column
32
+ @calculated_literal = false
33
+ end
34
+
35
+ # @return [Source::Chain]
36
+ def chain
37
+ return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
38
+ begin
39
+ if source.repaired? && source.parsed?
40
+ node = source.node_at(fixed_position.line, fixed_position.column)
41
+ elsif source.parsed?
42
+ node = source.node_at(position.line, position.column)
43
+ else
44
+ node = Source.parse(fixed_phrase)
45
+ end
46
+ rescue Parser::SyntaxError
47
+ return Chain.new([Chain::UNDEFINED_CALL])
48
+ end
49
+ return Chain.new([Chain::UNDEFINED_CALL]) if node.nil? || (node.type == :sym && !phrase.start_with?(':'))
50
+ chain = NodeChainer.chain(node, source.filename)
51
+ if source.repaired? || !source.parsed?
52
+ if end_of_phrase.strip == '.'
53
+ chain.links.push Chain::UNDEFINED_CALL
54
+ elsif end_of_phrase.strip == '::'
55
+ chain.links.push Chain::UNDEFINED_CONSTANT
56
+ end
57
+ elsif end_of_phrase.strip == '::'
58
+ chain.links.pop
59
+ chain.links.push Chain::UNDEFINED_CONSTANT
60
+ end
61
+ chain
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :position
67
+
68
+ # The zero-based line number of the fragment's location.
69
+ #
70
+ # @return [Integer]
71
+ attr_reader :line
72
+
73
+ # The zero-based column number of the fragment's location.
74
+ #
75
+ # @return [Integer]
76
+ attr_reader :column
77
+
78
+ # @return [Solargraph::Source]
79
+ attr_reader :source
80
+
81
+ def phrase
82
+ @phrase ||= source.code[signature_data[0]..offset-1]
83
+ end
84
+
85
+ def fixed_phrase
86
+ @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)]
87
+ end
88
+
89
+ def fixed_position
90
+ @fixed_position ||= Position.from_offset(source.code, offset - end_of_phrase.length)
91
+ end
92
+
93
+ def end_of_phrase
94
+ @end_of_phrase ||= begin
95
+ match = phrase.match(/[\s]*(\.{1}|::)[\s]*$/)
96
+ if match
97
+ match[0]
98
+ else
99
+ ''
100
+ end
101
+ end
102
+ end
103
+
104
+ # An alias for #column.
105
+ #
106
+ # @return [Integer]
107
+ def character
108
+ @column
109
+ end
110
+
111
+ # @return [Position]
112
+ def position
113
+ @position ||= Position.new(line, column)
114
+ end
115
+
116
+ # True if the current offset is inside a string.
117
+ #
118
+ # @return [Boolean]
119
+ def string?
120
+ # @string ||= (node.type == :str or node.type == :dstr)
121
+ @string ||= @source.string_at?(position)
122
+ end
123
+
124
+ # @return [Integer]
125
+ def offset
126
+ @offset ||= get_offset(line, column)
127
+ end
128
+
129
+ def get_offset line, column
130
+ Position.line_char_to_offset(@source.code, line, column)
131
+ end
132
+
133
+ def signature_data
134
+ @signature_data ||= get_signature_data_at(offset)
135
+ end
136
+
137
+ def get_signature_data_at index
138
+ brackets = 0
139
+ squares = 0
140
+ parens = 0
141
+ signature = ''
142
+ index -=1
143
+ in_whitespace = false
144
+ while index >= 0
145
+ pos = Position.from_offset(@source.code, index)
146
+ break if index > 0 and @source.comment_at?(pos)
147
+ unless !in_whitespace and string?
148
+ break if brackets > 0 or parens > 0 or squares > 0
149
+ char = @source.code[index, 1]
150
+ break if char.nil? # @todo Is this the right way to handle this?
151
+ if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
152
+ in_whitespace = true
153
+ else
154
+ if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
155
+ unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
156
+ old = @source.code[index+1..-1]
157
+ nxt = @source.code[index+1..-1].lstrip
158
+ index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
159
+ break
160
+ end
161
+ end
162
+ if char == ')'
163
+ parens -=1
164
+ elsif char == ']'
165
+ squares -=1
166
+ elsif char == '}'
167
+ brackets -= 1
168
+ elsif char == '('
169
+ parens += 1
170
+ elsif char == '{'
171
+ brackets += 1
172
+ elsif char == '['
173
+ squares += 1
174
+ signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
175
+ end
176
+ if brackets.zero? and parens.zero? and squares.zero?
177
+ break if ['"', "'", ',', ';', '%'].include?(char)
178
+ signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
179
+ break if char == '$'
180
+ if char == '@'
181
+ signature = "@#{signature}" if @source.code[index-1, 1] == '@'
182
+ break
183
+ end
184
+ elsif parens == 1 || brackets == 1 || squares == 1
185
+ break
186
+ end
187
+ in_whitespace = false
188
+ end
189
+ end
190
+ index -= 1
191
+ end
192
+ # @todo Smelly exceptional case for integer literals
193
+ # match = signature.match(/^[0-9]+/)
194
+ # if match
195
+ # index += match[0].length
196
+ # signature = signature[match[0].length..-1].to_s
197
+ # @base_literal = 'Integer'
198
+ # # @todo Smelly exceptional case for array literals
199
+ # elsif signature.start_with?('.[]')
200
+ # index += 2
201
+ # signature = signature[3..-1].to_s
202
+ # @base_literal = 'Array'
203
+ # elsif signature.start_with?('.')
204
+ # pos = Position.from_offset(@source.code, index)
205
+ # node = @source.node_at(pos.line, pos.character)
206
+ # lit = infer_literal_node_type(node)
207
+ # unless lit.nil?
208
+ # signature = signature[1..-1].to_s
209
+ # index += 1
210
+ # @base_literal = lit
211
+ # end
212
+ # end
213
+ [index + 1, signature]
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,138 @@
1
+ module Solargraph
2
+ # An index of pins and other ApiMap-related data for a Source.
3
+ #
4
+ class SourceMap
5
+ autoload :Mapper, 'solargraph/source_map/mapper'
6
+ autoload :Clip, 'solargraph/source_map/clip'
7
+ autoload :Completion, 'solargraph/source_map/completion'
8
+
9
+ # @return [Source]
10
+ attr_reader :source
11
+
12
+ # @return [Array<Pin::Base>]
13
+ attr_reader :pins
14
+
15
+ # @return [Array<Pin::Base>]
16
+ attr_reader :locals
17
+
18
+ # @return [Array<Pin::Reference>]
19
+ attr_reader :requires
20
+
21
+ def initialize source, pins, locals, requires, symbols
22
+ # HACK: Keep the library from changing this
23
+ @source = source.dup
24
+ @pins = pins
25
+ @locals = locals
26
+ @requires = requires
27
+ @pins.concat symbols
28
+ end
29
+
30
+ def filename
31
+ source.filename
32
+ end
33
+
34
+ def code
35
+ source.code
36
+ end
37
+
38
+ # @param position [Position]
39
+ # @return [Boolean]
40
+ def string_at? position
41
+ @source.string_at?(position)
42
+ end
43
+
44
+ # @param position [Position]
45
+ # @return [Boolean]
46
+ def comment_at? position
47
+ @source.comment_at?(position)
48
+ end
49
+
50
+ def document_symbols
51
+ @document_symbols ||= pins.select { |pin|
52
+ [Pin::ATTRIBUTE, Pin::CONSTANT, Pin::METHOD, Pin::NAMESPACE].include?(pin.kind) and !pin.path.empty?
53
+ }
54
+ end
55
+
56
+ def query_symbols query
57
+ document_symbols.select{|pin| pin.path.include?(query)}
58
+ end
59
+
60
+ # @param position [Position]
61
+ # @return [Solargraph::SourceMap::Fragment]
62
+ def cursor_at position
63
+ Source::Cursor.new(source, position)
64
+ end
65
+
66
+ def first_pin path
67
+ pins.select { |p| p.path == path }.first
68
+ end
69
+
70
+ # @param location [Solargraph::Location]
71
+ # @return [Solargraph::Pin::Base]
72
+ def locate_pin location
73
+ # return nil unless location.start_with?("#{filename}:")
74
+ pins.select{|pin| pin.location == location}.first
75
+ end
76
+
77
+ def locate_named_path_pin line, character
78
+ _locate_pin line, character, Pin::NAMESPACE, Pin::METHOD
79
+ end
80
+
81
+ def locate_block_pin line, character
82
+ _locate_pin line, character, Pin::NAMESPACE, Pin::METHOD, Pin::BLOCK
83
+ end
84
+
85
+ # @param other_map [SourceMap]
86
+ def try_merge! other_map
87
+ 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
88
+ pins.each_index do |i|
89
+ return false unless pins[i].try_merge!(other_map.pins[i])
90
+ end
91
+ locals.each_index do |i|
92
+ return false unless locals[i].try_merge!(other_map.locals[i])
93
+ end
94
+ @source = other_map.source
95
+ true
96
+ end
97
+
98
+ # @param name [String]
99
+ # @return [Array<Location>]
100
+ def references name
101
+ source.references name
102
+ end
103
+
104
+ class << self
105
+ # @return [SourceMap]
106
+ def load filename
107
+ source = Solargraph::Source.load(filename)
108
+ SourceMap.map(source)
109
+ end
110
+
111
+ # @return [SourceMap]
112
+ def load_string code, filename = nil
113
+ source = Solargraph::Source.load_string(code, filename)
114
+ SourceMap.map(source)
115
+ end
116
+
117
+ # @param source [Source]
118
+ # @return [SourceMap]
119
+ def map source
120
+ result = SourceMap::Mapper.map(source)
121
+ new(source, *result)
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def _locate_pin line, character, *kinds
128
+ position = Position.new(line, character)
129
+ found = nil
130
+ pins.each do |pin|
131
+ found = pin if (kinds.empty? || kinds.include?(pin.kind)) && pin.location.range.contain?(position)
132
+ break if pin.location.range.start.line > line
133
+ end
134
+ # @todo Assuming the root pin is always valid
135
+ found || pins.first
136
+ end
137
+ end
138
+ end