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
@@ -1,7 +1,7 @@
1
1
  module Solargraph
2
2
  module Pin
3
3
  class ProxyType < Base
4
- # @param location [Solargraph::Source::Location]
4
+ # @param location [Solargraph::Location]
5
5
  # @param namespace [String]
6
6
  # @param name [String]
7
7
  # @param return_type [ComplexType]
@@ -10,10 +10,6 @@ module Solargraph
10
10
  @return_complex_type = return_type
11
11
  end
12
12
 
13
- def scope
14
- return_complex_type.scope
15
- end
16
-
17
13
  def path
18
14
  @path ||= begin
19
15
  result = namespace.to_s
@@ -22,8 +18,8 @@ module Solargraph
22
18
  end
23
19
  end
24
20
 
25
- def named_context
26
- path
21
+ def context
22
+ @return_complex_type
27
23
  end
28
24
 
29
25
  # @param return_type [ComplexType]
@@ -1,7 +1,7 @@
1
1
  module Solargraph
2
2
  module Pin
3
3
  class Reference
4
- # @return [Source::Location]
4
+ # @return [Location]
5
5
  attr_reader :location
6
6
 
7
7
  # @return [String]
@@ -10,7 +10,7 @@ module Solargraph
10
10
  # @return [String]
11
11
  attr_reader :name
12
12
 
13
- # @param location [Source::Location]
13
+ # @param location [Location]
14
14
  # @param namespace [String]
15
15
  # @param name [String]
16
16
  def initialize location, namespace, name
@@ -1,7 +1,7 @@
1
1
  module Solargraph
2
2
  module Pin
3
3
  class Symbol < Base
4
- # @param location [Solargraph::Source::Location]
4
+ # @param location [Solargraph::Location]
5
5
  # @param name [String]
6
6
  def initialize location, name
7
7
  @name = name
@@ -4,9 +4,9 @@ module Solargraph
4
4
  class Method < Pin::Method
5
5
  include YardMixin
6
6
 
7
- def initialize code_object, location
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, code_object.name.to_s, comments, code_object.scope, 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))
10
10
  end
11
11
 
12
12
  def return_complex_type
@@ -6,14 +6,23 @@ module Solargraph
6
6
 
7
7
  def initialize code_object, location
8
8
  superclass = nil
9
- superclass = code_object.superclass.to_s if code_object.is_a?(YARD::CodeObjects::ClassObject) and code_object.superclass
9
+ # @todo This method of superclass detection is a bit of a hack. If
10
+ # the superclass is a Proxy, it is assumed to be undefined in its
11
+ # yardoc and converted to a fully qualified namespace.
12
+ if code_object.is_a?(YARD::CodeObjects::ClassObject) && code_object.superclass
13
+ if code_object.superclass.is_a?(YARD::CodeObjects::Proxy)
14
+ superclass = "::#{code_object.superclass}"
15
+ else
16
+ superclass = code_object.superclass.to_s
17
+ end
18
+ end
10
19
  super(location, code_object.namespace.to_s, code_object.name.to_s, comments_from(code_object), namespace_type(code_object), code_object.visibility, superclass)
11
- # code_object.class_mixins.each do |m|
12
- # extend_references.push Pin::Reference.new(location, path, m.path)
13
- # end
14
- # code_object.instance_mixins.each do |m|
15
- # include_references.push Pin::Reference.new(location, path, m.path)
16
- # end
20
+ code_object.class_mixins.each do |m|
21
+ extend_references.push Pin::Reference.new(location, path, m.path)
22
+ end
23
+ code_object.instance_mixins.each do |m|
24
+ include_references.push Pin::Reference.new(location, path, m.path)
25
+ end
17
26
  end
18
27
 
19
28
  private
@@ -0,0 +1,103 @@
1
+ module Solargraph
2
+
3
+ class Position
4
+ # @return [Integer]
5
+ attr_reader :line
6
+
7
+ # @return [Integer]
8
+ attr_reader :character
9
+
10
+ def initialize line, character
11
+ @line = line
12
+ @character = character
13
+ end
14
+
15
+ def column
16
+ character
17
+ end
18
+
19
+ # Get a hash of the position. This representation is suitable for use in
20
+ # the language server protocol.
21
+ #
22
+ # @return [Hash]
23
+ def to_hash
24
+ {
25
+ line: line,
26
+ character: character
27
+ }
28
+ end
29
+
30
+ def inspect
31
+ "<Position #{line}, #{character}>"
32
+ end
33
+
34
+ # Get a numeric offset for the specified text and position.
35
+ #
36
+ # @param text [String]
37
+ # @param position [Position]
38
+ # @return [Integer]
39
+ def self.to_offset text, position
40
+ result = 0
41
+ feed = 0
42
+ line = position.line
43
+ column = position.character
44
+ text.lines.each do |l|
45
+ line_length = l.length
46
+ char_length = l.chomp.length
47
+ if feed == line
48
+ result += column
49
+ break
50
+ end
51
+ result += line_length
52
+ feed += 1
53
+ end
54
+ result
55
+ end
56
+
57
+ # Get a numeric offset for the specified text and a position identified
58
+ # by its line and character.
59
+ #
60
+ # @param text [String]
61
+ # @param line [Integer]
62
+ # @param character [Integer]
63
+ # @return [Integer]
64
+ def self.line_char_to_offset text, line, character
65
+ to_offset(text, Position.new(line, character))
66
+ end
67
+
68
+ # Get a position for the specified text and offset.
69
+ #
70
+ # @param text [String]
71
+ # @param offset [Integer]
72
+ # @return [Position]
73
+ def self.from_offset text, offset
74
+ cursor = 0
75
+ line = 0
76
+ character = nil
77
+ text.lines.each do |l|
78
+ line_length = l.length
79
+ char_length = l.chomp.length
80
+ if cursor + char_length >= offset
81
+ character = offset - cursor
82
+ break
83
+ end
84
+ cursor += line_length
85
+ line += 1
86
+ end
87
+ character = 0 if character.nil? and offset == cursor
88
+ raise InvalidOffsetError if character.nil?
89
+ Position.new(line, character)
90
+ end
91
+
92
+ def self.normalize object
93
+ return object if object.is_a?(Position)
94
+ return Position.new(object[0], object[1]) if object.is_a?(Array)
95
+ raise ArgumentError, "Unable to convert #{object.class} to Position"
96
+ end
97
+
98
+ def == other
99
+ return false unless other.is_a?(Position)
100
+ line == other.line and character == other.character
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,70 @@
1
+ module Solargraph
2
+ class Range
3
+ # @return [Position]
4
+ attr_reader :start
5
+
6
+ # @return [Position]
7
+ attr_reader :ending
8
+
9
+ # @param start [Position]
10
+ # @param ending [Position]
11
+ def initialize start, ending
12
+ @start = start
13
+ @ending = ending
14
+ end
15
+
16
+ # Get a hash of the range. This representation is suitable for use in
17
+ # the language server protocol.
18
+ #
19
+ # @return [Hash<Symbol, Position>]
20
+ def to_hash
21
+ {
22
+ start: start.to_hash,
23
+ end: ending.to_hash
24
+ }
25
+ end
26
+
27
+ # True if the specified position is inside the range.
28
+ #
29
+ # @param position [Solargraph::Position]
30
+ # @return [Boolean]
31
+ def contain? position
32
+ return false if position.line < start.line or position.line > ending.line
33
+ return false if position.line == start.line and position.character < start.character
34
+ return false if position.line == ending.line and position.character > ending.character
35
+ true
36
+ end
37
+
38
+ # True if the range contains the specified position and the position does not precede it.
39
+ #
40
+ # @param position [Position]
41
+ # @return [Boolean]
42
+ def include? position
43
+ contain?(position) and !(position.line == start.line and position.character == start.character)
44
+ end
45
+
46
+ # Create a range from a pair of lines and characters.
47
+ #
48
+ # @param l1 [Integer] Starting line
49
+ # @param c1 [Integer] Starting character
50
+ # @param l2 [Integer] Ending line
51
+ # @param c2 [Integer] Ending character
52
+ # @return [Range]
53
+ def self.from_to l1, c1, l2, c2
54
+ Range.new(Position.new(l1, c1), Position.new(l2, c2))
55
+ end
56
+
57
+ def self.from_node node
58
+ from_expr(node.loc.expression)
59
+ end
60
+
61
+ def self.from_expr expr
62
+ from_to(expr.line, expr.column, expr.last_line, expr.last_column)
63
+ end
64
+
65
+ def == other
66
+ return false unless other.is_a?(Range)
67
+ start == other.start and ending == other.ending
68
+ end
69
+ end
70
+ end
@@ -1,25 +1,22 @@
1
1
  require 'parser/current'
2
- require 'time'
3
- require 'yard'
4
2
 
5
3
  module Solargraph
4
+ # A Ruby file that has been parsed into an AST.
5
+ #
6
6
  class Source
7
7
  autoload :FlawedBuilder, 'solargraph/source/flawed_builder'
8
- autoload :Fragment, 'solargraph/source/fragment'
9
- autoload :Position, 'solargraph/source/position'
10
- autoload :Range, 'solargraph/source/range'
11
- autoload :Location, 'solargraph/source/location'
12
8
  autoload :Updater, 'solargraph/source/updater'
13
9
  autoload :Change, 'solargraph/source/change'
14
10
  autoload :Mapper, 'solargraph/source/mapper'
15
11
  autoload :NodeMethods, 'solargraph/source/node_methods'
16
- autoload :Chain, 'solargraph/source/chain'
17
12
  autoload :EncodingFixes, 'solargraph/source/encoding_fixes'
18
- autoload :CallChainer, 'solargraph/source/call_chainer'
13
+ autoload :Cursor, 'solargraph/source/cursor'
14
+ autoload :Chain, 'solargraph/source/chain'
15
+ autoload :SourceChainer, 'solargraph/source/source_chainer'
19
16
  autoload :NodeChainer, 'solargraph/source/node_chainer'
20
- autoload :Completion, 'solargraph/source/completion'
21
17
 
22
18
  include EncodingFixes
19
+ include NodeMethods
23
20
 
24
21
  # @return [String]
25
22
  attr_reader :code
@@ -27,173 +24,50 @@ module Solargraph
27
24
  # @return [Parser::AST::Node]
28
25
  attr_reader :node
29
26
 
30
- # @return [Array]
31
27
  attr_reader :comments
32
28
 
33
29
  # @return [String]
34
30
  attr_reader :filename
35
31
 
36
- # Get the file's modification time.
37
- #
38
- # @return [Time]
39
- attr_reader :mtime
40
-
41
- attr_reader :directives
42
-
43
- attr_reader :path_macros
44
-
32
+ # @todo Deprecate?
45
33
  # @return [Integer]
46
- attr_accessor :version
47
-
48
- # Get the time of the last synchronization.
49
- #
50
- # @return [Time]
51
- attr_reader :stime
52
-
53
- # @return [Array<Solargraph::Pin::Base>]
54
- attr_reader :pins
55
-
56
- # @return [Array<Solargraph::Pin::Reference>]
57
- attr_reader :requires
58
-
59
- attr_reader :domains
60
-
61
- # @return [Array<Solargraph::Pin::Base>]
62
- attr_reader :locals
63
-
64
- include NodeMethods
34
+ attr_reader :version
65
35
 
66
36
  # @param code [String]
67
37
  # @param filename [String]
68
- def initialize code, filename = nil
38
+ # @param version [Integer]
39
+ def initialize code, filename = nil, version = 0
40
+ @code = normalize(code)
41
+ @repaired = code
42
+ @filename = filename
43
+ @version = version
44
+ @domains = []
69
45
  begin
70
- @code = normalize(code)
71
- @fixed = @code
72
- @filename = filename
73
- @version = 0
74
- @domains = []
75
- parse
46
+ @node, @comments = Source.parse_with_comments(@code, filename)
47
+ @parsed = true
76
48
  rescue Parser::SyntaxError, EncodingError => e
77
- hard_fix_node
49
+ @node, @comments = Source.parse_with_comments(@code.gsub(/[^s]/, ' '), filename)
50
+ @parsed = false
78
51
  rescue Exception => e
79
52
  STDERR.puts e.message
80
53
  STDERR.puts e.backtrace
81
54
  raise "Error parsing #{filename || '(source)'}: [#{e.class}] #{e.message}"
55
+ ensure
56
+ @code.freeze
82
57
  end
83
58
  end
84
59
 
85
- # @param range [Solargraph::Source::Range]
60
+ # @param range [Solargraph::Range]
86
61
  def at range
87
62
  from_to range.start.line, range.start.character, range.ending.line, range.ending.character
88
63
  end
89
64
 
90
65
  def from_to l1, c1, l2, c2
91
- b = Solargraph::Source::Position.line_char_to_offset(@code, l1, c1)
92
- e = Solargraph::Source::Position.line_char_to_offset(@code, l2, c2)
66
+ b = Solargraph::Position.line_char_to_offset(@code, l1, c1)
67
+ e = Solargraph::Position.line_char_to_offset(@code, l2, c2)
93
68
  @code[b..e-1]
94
69
  end
95
70
 
96
- def macro path
97
- @path_macros[path]
98
- end
99
-
100
- # @todo Temporary blank
101
- def path_macros
102
- @path_macros ||= {}
103
- end
104
-
105
- # @todo Name problem
106
- # @return [Array<Solargraph::Pin::Reference>]
107
- def required
108
- requires
109
- end
110
-
111
- # @return [Array<String>]
112
- def namespaces
113
- @namespaces ||= pins.select{|pin| pin.kind == Pin::NAMESPACE}.map(&:path)
114
- end
115
-
116
- # @param fqns [String] The namespace (nil for all)
117
- # @return [Array<Solargraph::Pin::Namespace>]
118
- def namespace_pins fqns = nil
119
- pins.select{|pin| pin.kind == Pin::NAMESPACE}
120
- end
121
-
122
- # @param fqns [String] The namespace (nil for all)
123
- # @return [Array<Solargraph::Pin::Method>]
124
- def method_pins fqns = nil
125
- pins.select{|pin| pin.kind == Solargraph::Pin::METHOD or pin.kind == Solargraph::Pin::ATTRIBUTE}
126
- end
127
-
128
- # @return [Array<Solargraph::Pin::Attribute>]
129
- def attribute_pins
130
- pins.select{|pin| pin.kind == Pin::ATTRIBUTE}
131
- end
132
-
133
- # @return [Array<Solargraph::Pin::InstanceVariable>]
134
- def instance_variable_pins
135
- pins.select{|pin| pin.kind == Pin::INSTANCE_VARIABLE}
136
- end
137
-
138
- # @return [Array<Solargraph::Pin::ClassVariable>]
139
- def class_variable_pins
140
- pins.select{|pin| pin.kind == Pin::CLASS_VARIABLE}
141
- end
142
-
143
- # @return [Array<Solargraph::Pin::GlobalVariable>]
144
- def global_variable_pins
145
- pins.select{|pin| pin.kind == Pin::GLOBAL_VARIABLE}
146
- end
147
-
148
- # @return [Array<Solargraph::Pin::Constant>]
149
- def constant_pins
150
- pins.select{|pin| pin.kind == Pin::CONSTANT}
151
- end
152
-
153
- # @return [Array<Solargraph::Pin::Symbol>]
154
- def symbol_pins
155
- @symbols
156
- end
157
-
158
- # @return [Array<Solargraph::Pin::Symbol>]
159
- def symbols
160
- symbol_pins
161
- end
162
-
163
- # @param name [String]
164
- # @return [Array<Source::Location>]
165
- def references name
166
- inner_node_references(name, node).map do |n|
167
- offset = Position.to_offset(code, get_node_start_position(n))
168
- soff = code.index(name, offset)
169
- eoff = soff + name.length
170
- Location.new(
171
- filename,
172
- Solargraph::Source::Range.new(
173
- Position.from_offset(code, soff),
174
- Position.from_offset(code, eoff)
175
- )
176
- )
177
- end
178
- end
179
-
180
- def locate_named_path_pin line, character
181
- _locate_pin line, character, Pin::NAMESPACE, Pin::METHOD
182
- end
183
-
184
- # Locate the namespace pin at the specified line and character.
185
- #
186
- # @param line [line]
187
- # @param character [character]
188
- # @return [Pin::Namespace]
189
- def locate_namespace_pin line, character
190
- _locate_pin line, character, Pin::NAMESPACE
191
- end
192
-
193
- def locate_block_pin line, character
194
- _locate_pin line, character, Pin::NAMESPACE, Pin::METHOD, Pin::BLOCK
195
- end
196
-
197
71
  # Get the nearest node that contains the specified index.
198
72
  #
199
73
  # @param line [Integer]
@@ -203,38 +77,6 @@ module Solargraph
203
77
  tree_at(line, column).first
204
78
  end
205
79
 
206
- # True if the specified location is inside a string.
207
- #
208
- # @param line [Integer]
209
- # @param column [Integer]
210
- # @return [Boolean]
211
- def string_at?(line, column)
212
- # node = node_at(line, column)
213
- # # @todo raise InvalidOffset or InvalidRange or something?
214
- # return false if node.nil?
215
- # node.type == :str or node.type == :dstr
216
- pos = Source::Position.new(line, column)
217
- @strings.each do |str|
218
- return true if str.contain?(pos)
219
- break if str.start.line > pos.line
220
- end
221
- false
222
- end
223
-
224
- # True if the specified location is inside a comment.
225
- #
226
- # @param line [Integer]
227
- # @param column [Integer]
228
- # @return [Boolean]
229
- def comment_at?(line, column)
230
- pos = Source::Position.new(line, column)
231
- @comment_ranges.each do |cmnt|
232
- return true if cmnt.include?(pos)
233
- break if cmnt.start.line > pos.line
234
- end
235
- false
236
- end
237
-
238
80
  # Get an array of nodes containing the specified index, starting with the
239
81
  # nearest node and ending with the root.
240
82
  #
@@ -251,82 +93,111 @@ module Solargraph
251
93
 
252
94
  # @param updater [Source::Updater]
253
95
  # @param reparse [Boolean]
254
- # @return [void]
255
- def synchronize updater, reparse = true
96
+ # @return [Source]
97
+ def synchronize updater
256
98
  raise 'Invalid synchronization' unless updater.filename == filename
257
- original_code = @code
258
- original_fixed = @fixed
259
- @code = updater.write(original_code)
260
- @fixed = updater.write(original_code, true)
261
- @version = updater.version
262
- return if @code == original_code or !reparse
263
- begin
264
- parse
265
- @fixed = @code
266
- rescue Parser::SyntaxError, EncodingError => e
267
- @fixed = updater.repair(original_fixed)
268
- begin
269
- parse
270
- rescue Parser::SyntaxError, EncodingError => e
271
- hard_fix_node
99
+ real_code = updater.write(@code)
100
+ incr_code = updater.write(@code, true)
101
+ if real_code == @code
102
+ @version = updater.version
103
+ return self
104
+ end
105
+ synced = Source.new(incr_code, filename)
106
+ if synced.parsed?
107
+ synced.code = real_code
108
+ if synced.repaired?
109
+ synced.error_ranges.concat combine_errors(error_ranges + updater.changes.map(&:range))
272
110
  end
111
+ else
112
+ new_repair = updater.repair(@repaired)
113
+ synced = Source.new(new_repair, filename)
114
+ synced.error_ranges.concat combine_errors(error_ranges + updater.changes.map(&:range))
115
+ synced.parsed = false
116
+ synced.code = real_code
273
117
  end
118
+ synced.version = updater.version
119
+ synced
274
120
  end
275
121
 
276
- # @param query [String]
277
- # @return [Array<Solargraph::Pin::Base>]
278
- def query_symbols query
279
- return [] if query.empty?
280
- down = query.downcase
281
- all_symbols.select{|p| p.path.downcase.include?(down)}
122
+ # @param position [Position]
123
+ # @return [Source::Cursor]
124
+ def cursor_at position
125
+ Cursor.new(self, position)
282
126
  end
283
127
 
284
- # @return [Array<Solargraph::Pin::Base>]
285
- def all_symbols
286
- @all_symbols ||= pins.select{ |pin|
287
- [Pin::ATTRIBUTE, Pin::CONSTANT, Pin::METHOD, Pin::NAMESPACE].include?(pin.kind) and !pin.name.empty?
288
- }
128
+ # @return [Boolean]
129
+ def parsed?
130
+ @parsed
289
131
  end
290
132
 
291
- # @param location [Solargraph::Source::Location]
292
- # @return [Solargraph::Pin::Base]
293
- def locate_pin location
294
- # return nil unless location.start_with?("#{filename}:")
295
- pins.select{|pin| pin.location == location}
133
+ def repaired?
134
+ @is_repaired ||= (@code != @repaired)
296
135
  end
297
136
 
298
- # @param line [Integer] A zero-based line number
299
- # @param column [Integer] A zero-based column number
300
- # @return [Solargraph::Source::Fragment]
301
- def fragment_at line, column
302
- Fragment.new(self, line, column)
137
+ # @param position [Position]
138
+ # @return [Boolean]
139
+ def string_at? position
140
+ string_ranges.each do |range|
141
+ return true if range.include?(position)
142
+ break if range.ending.line > position.line
143
+ end
144
+ false
303
145
  end
304
146
 
147
+ # @param position [Position]
305
148
  # @return [Boolean]
306
- def parsed?
307
- @parsed
149
+ def comment_at? position
150
+ comment_ranges.each do |range|
151
+ return true if range.include?(position)
152
+ break if range.ending.line > position.line
153
+ end
154
+ false
155
+ end
156
+
157
+ # @param name [String]
158
+ # @return [Array<Location>]
159
+ def references name
160
+ inner_node_references(name, node).map do |n|
161
+ offset = Position.to_offset(code, get_node_start_position(n))
162
+ soff = code.index(name, offset)
163
+ eoff = soff + name.length
164
+ Location.new(
165
+ filename,
166
+ Range.new(
167
+ Position.from_offset(code, soff),
168
+ Position.from_offset(code, eoff)
169
+ )
170
+ )
171
+ end
172
+ end
173
+
174
+ # @return [Array<Range>]
175
+ def error_ranges
176
+ @error_ranges ||= []
308
177
  end
309
178
 
310
179
  private
311
180
 
312
- def _locate_pin line, character, *kinds
313
- position = Solargraph::Source::Position.new(line, character)
314
- found = nil
315
- pins.each do |pin|
316
- found = pin if (kinds.empty? or kinds.include?(pin.kind)) and pin.location.range.contain?(position)
317
- break if pin.location.range.start.line > line
181
+ # @return [Array<Range>]
182
+ def string_ranges
183
+ @string_ranges ||= string_ranges_in(@node)
184
+ end
185
+
186
+ # @return [Array<Range>]
187
+ def comment_ranges
188
+ @comment_ranges || @comments.map do |cmnt|
189
+ Range.from_expr(cmnt.loc.expression)
318
190
  end
319
- # @todo Assuming the root pin is always valid
320
- found || pins.first
321
191
  end
322
192
 
323
- def inner_node_references name, top
193
+ def string_ranges_in n
324
194
  result = []
325
- if top.kind_of?(AST::Node)
326
- if top.children.any?{|c| c.to_s == name}
327
- result.push top
195
+ if n.is_a?(Parser::AST::Node)
196
+ if n.type == :str
197
+ result.push Range.from_node(n)
198
+ else
199
+ n.children.each{ |c| result.concat string_ranges_in(c) }
328
200
  end
329
- top.children.each { |c| result.concat inner_node_references(name, c) }
330
201
  end
331
202
  result
332
203
  end
@@ -344,101 +215,46 @@ module Solargraph
344
215
  end
345
216
  end
346
217
 
347
- # @return [void]
348
- def parse
349
- node, comments = inner_parse(@fixed, filename)
350
- @node = node
351
- @comments = comments
352
- process_parsed node, comments
353
- @parsed = true
218
+ def inner_node_references name, top
219
+ result = []
220
+ if top.kind_of?(AST::Node)
221
+ if top.children.any?{|c| c.to_s == name}
222
+ result.push top
223
+ end
224
+ top.children.each { |c| result.concat inner_node_references(name, c) }
225
+ end
226
+ result
354
227
  end
355
228
 
356
- def hard_fix_node
357
- @fixed = @code.gsub(/[^\s]/, '_')
358
- node, comments = inner_parse(@fixed, filename)
359
- process_parsed node, comments
360
- @node = node
361
- @comments = comments
362
- @parsed = false
229
+ # @param ranges [Array<Range>]
230
+ # @return [Array<Range>]
231
+ def combine_errors ranges
232
+ result = []
233
+ lines = []
234
+ ranges.sort{|a, b| a.start.line <=> b.start.line}.each do |rng|
235
+ next if rng.nil? || lines.include?(rng.start.line)
236
+ lines.push rng.start.line
237
+ next if comment_at?(rng.start) || rng.start.line >= code.lines.length
238
+ fcol = code.lines[rng.start.line].index(/[^\s]/) || 0
239
+ ecol = code.lines[rng.start.line].length
240
+ result.push Range.from_to(rng.start.line, fcol, rng.start.line, ecol)
241
+ end
242
+ result
363
243
  end
364
244
 
365
- def inner_parse code, filename
366
- parser = Parser::CurrentRuby.new(FlawedBuilder.new)
367
- parser.diagnostics.all_errors_are_fatal = true
368
- parser.diagnostics.ignore_warnings = true
369
- buffer = Parser::Source::Buffer.new(filename, 0)
370
- buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
371
- parser.parse_with_comments(buffer)
372
- end
245
+ protected
373
246
 
374
- def process_parsed node, comments
375
- new_map_data = Mapper.map(filename, code, node, comments)
376
- synchronize_mapped *new_map_data
377
- end
247
+ # @return [Integer]
248
+ attr_writer :version
378
249
 
379
- def synchronize_mapped new_pins, new_locals, new_requires, new_symbols, new_strings, new_comment_ranges #, new_path_macros, new_domains
380
- @strings = new_strings
381
- @comment_ranges = new_comment_ranges
382
- return if @requires == new_requires and @symbols == new_symbols and try_merge(new_pins, new_locals)
383
- @pins = new_pins
384
- @locals = new_locals
385
- @requires = new_requires
386
- @symbols = new_symbols
387
- @all_symbols = nil # Reset for future queries
388
- @domains = []
389
- @path_macros = {}
390
- dirpins = []
391
- @pins.select(&:maybe_directives?).each do |pin|
392
- dirpins.push pin unless pin.directives.empty?
393
- end
394
- process_directives dirpins
395
- @stime = Time.now
396
- end
250
+ # @return [String]
251
+ attr_writer :code
397
252
 
398
- # @param new_pins [Array<Solargraph::Pin::Base>]
399
- # @param new_locals [Array<Solargraph::Pin::Base>]
400
- # @return [Boolean]
401
- def try_merge new_pins, new_locals
402
- return false if @pins.nil? or @locals.nil? or new_pins.length != @pins.length or new_locals.length != @locals.length
403
- new_pins.each_index do |i|
404
- return false unless @pins[i].try_merge!(new_pins[i])
405
- end
406
- new_locals.each_index do |i|
407
- return false unless @locals[i].try_merge!(new_locals[i])
408
- end
409
- true
410
- end
253
+ # @return [String]
254
+ attr_accessor :repaired
411
255
 
412
- # @return [void]
413
- def process_directives pins
414
- pins.each do |pin|
415
- pin.directives.each do |d|
416
- # ns = namespace_for(k.node)
417
- ns = (pin.kind == Pin::NAMESPACE ? pin.path : pin.namespace)
418
- docstring = YARD::Docstring.parser.parse(d.tag.text).to_docstring
419
- if d.tag.tag_name == 'attribute'
420
- t = (d.tag.types.nil? || d.tag.types.empty?) ? nil : d.tag.types.flatten.join('')
421
- if t.nil? or t.include?('r')
422
- # location, namespace, name, docstring, access
423
- @pins.push Solargraph::Pin::Attribute.new(pin.location, pin.path, d.tag.name, docstring.all, :reader, :instance)
424
- end
425
- if t.nil? or t.include?('w')
426
- @pins.push Solargraph::Pin::Attribute.new(pin.location, pin.path, "#{d.tag.name}=", docstring.all, :writer, :instance)
427
- end
428
- elsif d.tag.tag_name == 'method'
429
- gen_src = Source.new("def #{d.tag.name};end", filename)
430
- gen_pin = gen_src.pins.last # Method is last pin after root namespace
431
- @pins.push Solargraph::Pin::Method.new(pin.location, pin.path, gen_pin.name, docstring.all, :instance, :public, [])
432
- elsif d.tag.tag_name == 'macro'
433
- @path_macros[pin.path] = d
434
- elsif d.tag.tag_name == 'domain'
435
- @domains.push d.tag.text
436
- else
437
- # STDERR.puts "Nothing to do for directive: #{d}"
438
- end
439
- end
440
- end
441
- end
256
+ # @return [Boolean]
257
+ attr_writer :parsed
442
258
 
443
259
  class << self
444
260
  # @param filename [String]
@@ -453,17 +269,32 @@ module Solargraph
453
269
  # @param code [String]
454
270
  # @param filename [String]
455
271
  # @return [Solargraph::Source]
456
- def load_string code, filename = nil
457
- Source.new code, filename
272
+ def load_string code, filename = nil, version = 0
273
+ Source.new code, filename, version
458
274
  end
459
275
 
460
- def parse_node code, filename
276
+ def parse_with_comments code, filename = nil
277
+ buffer = Parser::Source::Buffer.new(filename, 0)
278
+ buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
279
+ parser.parse_with_comments(buffer)
280
+ end
281
+
282
+ def parse code, filename = nil
283
+ buffer = Parser::Source::Buffer.new(filename, 0)
284
+ buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
285
+ parser.parse(buffer)
286
+ end
287
+
288
+ private
289
+
290
+ # @return [Parser::Base]
291
+ def parser
292
+ # @todo Consider setting an instance variable. We might not need to
293
+ # recreate the parser every time we use it.
461
294
  parser = Parser::CurrentRuby.new(FlawedBuilder.new)
462
295
  parser.diagnostics.all_errors_are_fatal = true
463
296
  parser.diagnostics.ignore_warnings = true
464
- buffer = Parser::Source::Buffer.new(nil, 0)
465
- buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '_')
466
- parser.parse(buffer)
297
+ parser
467
298
  end
468
299
  end
469
300
  end