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
@@ -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