solargraph 0.26.1 → 0.27.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 (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