solargraph 0.54.0 → 0.55.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/lib/solargraph/api_map/cache.rb +10 -1
  4. data/lib/solargraph/api_map/index.rb +167 -0
  5. data/lib/solargraph/api_map/store.rb +75 -122
  6. data/lib/solargraph/api_map.rb +109 -41
  7. data/lib/solargraph/bench.rb +17 -1
  8. data/lib/solargraph/complex_type/type_methods.rb +17 -11
  9. data/lib/solargraph/complex_type/unique_type.rb +181 -17
  10. data/lib/solargraph/complex_type.rb +103 -24
  11. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
  12. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  13. data/lib/solargraph/convention/struct_definition.rb +101 -0
  14. data/lib/solargraph/convention.rb +2 -0
  15. data/lib/solargraph/doc_map.rb +43 -18
  16. data/lib/solargraph/equality.rb +33 -0
  17. data/lib/solargraph/language_server/host/message_worker.rb +51 -5
  18. data/lib/solargraph/language_server/host.rb +13 -11
  19. data/lib/solargraph/language_server/message/base.rb +19 -12
  20. data/lib/solargraph/language_server/message/initialize.rb +3 -1
  21. data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
  22. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  23. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  24. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
  25. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  26. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  27. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  28. data/lib/solargraph/language_server/progress.rb +19 -2
  29. data/lib/solargraph/library.rb +31 -41
  30. data/lib/solargraph/location.rb +29 -1
  31. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  32. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  33. data/lib/solargraph/parser/node_methods.rb +15 -1
  34. data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
  35. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  36. data/lib/solargraph/parser/parser_gem/node_methods.rb +6 -4
  37. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  39. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  40. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  41. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
  42. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  43. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  44. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
  45. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  46. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  47. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  48. data/lib/solargraph/parser.rb +3 -5
  49. data/lib/solargraph/pin/base.rb +47 -17
  50. data/lib/solargraph/pin/base_variable.rb +11 -4
  51. data/lib/solargraph/pin/block.rb +8 -26
  52. data/lib/solargraph/pin/breakable.rb +9 -0
  53. data/lib/solargraph/pin/callable.rb +147 -0
  54. data/lib/solargraph/pin/closure.rb +8 -3
  55. data/lib/solargraph/pin/common.rb +2 -6
  56. data/lib/solargraph/pin/conversions.rb +3 -2
  57. data/lib/solargraph/pin/instance_variable.rb +2 -2
  58. data/lib/solargraph/pin/local_variable.rb +7 -1
  59. data/lib/solargraph/pin/method.rb +76 -48
  60. data/lib/solargraph/pin/namespace.rb +14 -11
  61. data/lib/solargraph/pin/parameter.rb +24 -17
  62. data/lib/solargraph/pin/proxy_type.rb +13 -7
  63. data/lib/solargraph/pin/signature.rb +3 -129
  64. data/lib/solargraph/pin/until.rb +18 -0
  65. data/lib/solargraph/pin/while.rb +18 -0
  66. data/lib/solargraph/pin.rb +7 -1
  67. data/lib/solargraph/position.rb +7 -0
  68. data/lib/solargraph/range.rb +9 -4
  69. data/lib/solargraph/rbs_map/conversions.rb +84 -45
  70. data/lib/solargraph/rbs_map/core_fills.rb +16 -9
  71. data/lib/solargraph/rbs_map.rb +1 -0
  72. data/lib/solargraph/shell.rb +19 -2
  73. data/lib/solargraph/source/chain/array.rb +10 -8
  74. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  75. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  76. data/lib/solargraph/source/chain/call.rb +125 -61
  77. data/lib/solargraph/source/chain/constant.rb +1 -1
  78. data/lib/solargraph/source/chain/hash.rb +8 -2
  79. data/lib/solargraph/source/chain/if.rb +5 -0
  80. data/lib/solargraph/source/chain/link.rb +28 -5
  81. data/lib/solargraph/source/chain/literal.rb +27 -2
  82. data/lib/solargraph/source/chain/or.rb +1 -1
  83. data/lib/solargraph/source/chain/z_super.rb +1 -1
  84. data/lib/solargraph/source/chain.rb +131 -63
  85. data/lib/solargraph/source/cursor.rb +3 -2
  86. data/lib/solargraph/source/source_chainer.rb +2 -2
  87. data/lib/solargraph/source.rb +104 -86
  88. data/lib/solargraph/source_map/clip.rb +8 -6
  89. data/lib/solargraph/source_map/data.rb +30 -0
  90. data/lib/solargraph/source_map.rb +28 -16
  91. data/lib/solargraph/type_checker/checks.rb +4 -0
  92. data/lib/solargraph/type_checker/rules.rb +6 -1
  93. data/lib/solargraph/type_checker.rb +48 -21
  94. data/lib/solargraph/version.rb +1 -1
  95. data/lib/solargraph/views/environment.erb +3 -5
  96. data/lib/solargraph/workspace/config.rb +7 -3
  97. data/lib/solargraph/workspace.rb +1 -1
  98. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -0
  99. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  100. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  101. data/lib/solargraph/yard_map/mapper.rb +1 -0
  102. data/lib/solargraph/yardoc.rb +1 -1
  103. data/lib/solargraph.rb +1 -0
  104. data/solargraph.gemspec +5 -5
  105. metadata +41 -25
@@ -5,10 +5,17 @@ require 'solargraph/source/chain/link'
5
5
 
6
6
  module Solargraph
7
7
  class Source
8
- # A chain of constants, variables, and method calls for inferring types of
9
- # values.
8
+ #
9
+ # Represents an expression as a single call chain at the parse
10
+ # tree level, made up of constants, variables, and method calls,
11
+ # each represented as a Link object.
12
+ #
13
+ # Computes Pins and/or ComplexTypes representing the interpreted
14
+ # expression.
10
15
  #
11
16
  class Chain
17
+ include Equality
18
+
12
19
  autoload :Link, 'solargraph/source/chain/link'
13
20
  autoload :Call, 'solargraph/source/chain/call'
14
21
  autoload :QCall, 'solargraph/source/chain/q_call'
@@ -32,7 +39,7 @@ module Solargraph
32
39
  @@inference_invalidation_key = nil
33
40
  @@inference_cache = {}
34
41
 
35
- UNDEFINED_CALL = Chain::Call.new('<undefined>')
42
+ UNDEFINED_CALL = Chain::Call.new('<undefined>', nil)
36
43
  UNDEFINED_CONSTANT = Chain::Constant.new('<undefined>')
37
44
 
38
45
  # @return [::Array<Source::Chain::Link>]
@@ -40,6 +47,11 @@ module Solargraph
40
47
 
41
48
  attr_reader :node
42
49
 
50
+ # @sg-ignore Fix "Not enough arguments to Module#protected"
51
+ protected def equality_fields
52
+ [links, node]
53
+ end
54
+
43
55
  # @param node [Parser::AST::Node, nil]
44
56
  # @param links [::Array<Chain::Link>]
45
57
  # @param splat [Boolean]
@@ -61,54 +73,95 @@ module Solargraph
61
73
  @base ||= Chain.new(links[0..-2])
62
74
  end
63
75
 
64
- # @param api_map [ApiMap]
65
- # @param name_pin [Pin::Base]
66
- # @param locals [::Enumerable<Pin::LocalVariable>]
76
+ # Determine potential Pins returned by this chain of words
77
+ #
78
+ # @param api_map [ApiMap] @param name_pin [Pin::Base] A pin
79
+ # representing the place in which expression is evaluated (e.g.,
80
+ # a Method pin, or a Module or Class pin if not run within a
81
+ # method - both in terms of the closure around the chain, as well
82
+ # as the self type used for any method calls in head position.
83
+ #
84
+ # Requirements for name_pin:
85
+ #
86
+ # * name_pin.context: This should be a type representing the
87
+ # namespace where we can look up non-local variables and
88
+ # method names. If it is a Class<X>, we will look up
89
+ # :class scoped methods/variables.
67
90
  #
68
- # @return [::Array<Pin::Base>]
91
+ # * name_pin.binder: Used for method call lookups only
92
+ # (Chain::Call links). For method calls as the first
93
+ # element in the chain, 'name_pin.binder' should be the
94
+ # same as name_pin.context above. For method calls later
95
+ # in the chain (e.g., 'b' in a.b.c), it should represent
96
+ # 'a'.
97
+ #
98
+ # @param locals [::Array<Pin::LocalVariable>] Any local
99
+ # variables / method parameters etc visible by the statement
100
+ #
101
+ # @return [::Array<Pin::Base>] Pins representing possible return
102
+ # types of this method.
69
103
  def define api_map, name_pin, locals
70
104
  return [] if undefined?
105
+
106
+ # working_pin is the surrounding closure pin for the link
107
+ # being processed, whose #binder method will provide the LHS /
108
+ # 'self type' of the next link (same as the #return_type method
109
+ # --the type of the result so far).
110
+ #
111
+ # @todo ProxyType uses 'type' for the binder, but '
71
112
  working_pin = name_pin
72
113
  links[0..-2].each do |link|
73
114
  pins = link.resolve(api_map, working_pin, locals)
74
- type = infer_first_defined(pins, working_pin, api_map, locals)
75
- return [] if type.undefined?
76
- working_pin = Pin::ProxyType.anonymous(type)
115
+ type = infer_from_definitions(pins, working_pin, api_map, locals)
116
+ if type.undefined?
117
+ logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" }
118
+ return []
119
+ end
120
+ # We continue to use the context from the head pin, in case
121
+ # we need it to, for instance, provide context for a block
122
+ # evaluation. However, we use the last link's return type
123
+ # for the binder, as this is chaining off of it, and the
124
+ # binder is now the lhs of the rhs we are evaluating.
125
+ working_pin = Pin::ProxyType.anonymous(name_pin.context, binder: type, closure: name_pin)
126
+ logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" }
77
127
  end
78
- links.last.last_context = name_pin
128
+ links.last.last_context = working_pin
79
129
  links.last.resolve(api_map, working_pin, locals)
80
130
  end
81
131
 
82
132
  # @param api_map [ApiMap]
83
133
  # @param name_pin [Pin::Base]
84
- # @param locals [::Enumerable<Pin::LocalVariable>]
134
+ # @param locals [::Array<Pin::LocalVariable>]
85
135
  # @return [ComplexType]
86
136
  # @sg-ignore
87
137
  def infer api_map, name_pin, locals
88
- out = nil
89
- cached = @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] unless node.nil?
90
- return cached if cached && @@inference_invalidation_key == api_map.hash
91
- out = infer_uncached api_map, name_pin, locals
92
- if @@inference_invalidation_key != api_map.hash
93
- @@inference_cache = {}
138
+ cache_key = [node, node&.location, links, name_pin&.return_type, locals]
139
+ if @@inference_invalidation_key == api_map.hash
140
+ cached = @@inference_cache[cache_key]
141
+ return cached if cached
142
+ else
94
143
  @@inference_invalidation_key = api_map.hash
144
+ @@inference_cache = {}
95
145
  end
96
- @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] = out unless node.nil?
97
- out
146
+ out = infer_uncached(api_map, name_pin, locals).downcast_to_literal_if_possible
147
+ logger.debug { "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" }
148
+ @@inference_cache[cache_key] = out
98
149
  end
99
150
 
100
151
  # @param api_map [ApiMap]
101
152
  # @param name_pin [Pin::Base]
102
- # @param locals [::Enumerable<Pin::LocalVariable>]
153
+ # @param locals [::Array<Pin::LocalVariable>]
103
154
  # @return [ComplexType]
104
155
  def infer_uncached api_map, name_pin, locals
105
- from_here = base.infer(api_map, name_pin, locals) unless links.length == 1
106
- if from_here
107
- name_pin = name_pin.proxy(from_here)
108
- end
109
156
  pins = define(api_map, name_pin, locals)
110
- type = infer_first_defined(pins, links.last.last_context, api_map, locals)
111
- maybe_nil(type)
157
+ if pins.empty?
158
+ logger.debug { "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" }
159
+ return ComplexType::UNDEFINED
160
+ end
161
+ type = infer_from_definitions(pins, links.last.last_context, api_map, locals)
162
+ out = maybe_nil(type)
163
+ logger.debug { "Chain#infer_uncached(links=#{self.links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" }
164
+ out
112
165
  end
113
166
 
114
167
  # @return [Boolean]
@@ -137,6 +190,18 @@ module Solargraph
137
190
  links.any?(&:nullable?)
138
191
  end
139
192
 
193
+ include Logging
194
+
195
+ def desc
196
+ links.map(&:desc).to_s
197
+ end
198
+
199
+ def to_s
200
+ desc
201
+ end
202
+
203
+ include Logging
204
+
140
205
  private
141
206
 
142
207
  # @param pins [::Array<Pin::Base>]
@@ -144,16 +209,18 @@ module Solargraph
144
209
  # @param api_map [ApiMap]
145
210
  # @param locals [::Enumerable<Pin::LocalVariable>]
146
211
  # @return [ComplexType]
147
- def infer_first_defined pins, context, api_map, locals
148
- possibles = []
212
+ def infer_from_definitions pins, context, api_map, locals
213
+ # @type [::Array<ComplexType>]
214
+ types = []
215
+ unresolved_pins = []
149
216
  # @todo this param tag shouldn't be needed to probe the type
150
217
  # @todo ...but given it is needed, typecheck should complain that it is needed
151
218
  # @param pin [Pin::Base]
152
219
  pins.each do |pin|
153
220
  # Avoid infinite recursion
154
- next if @@inference_stack.include?(pin.identity)
221
+ next if @@inference_stack.include?(pin)
155
222
 
156
- @@inference_stack.push pin.identity
223
+ @@inference_stack.push pin
157
224
  type = pin.typify(api_map)
158
225
  @@inference_stack.pop
159
226
  if type.defined?
@@ -161,45 +228,46 @@ module Solargraph
161
228
  # @todo even at strong, no typechecking complaint
162
229
  # happens when a [Pin::Base,nil] is passed into a method
163
230
  # that accepts only [Pin::Namespace] as an argument
164
- type = type.resolve_generics(pin.closure, context.return_type)
165
- end
166
- if type.defined?
167
- possibles.push type
168
- break if pin.is_a?(Pin::Method)
231
+ type = type.resolve_generics(pin.closure, context.binder)
169
232
  end
233
+ types << type
234
+ else
235
+ unresolved_pins << pin
170
236
  end
171
237
  end
172
- if possibles.empty?
173
- # Limit method inference recursion
174
- return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method)
175
-
176
- @@inference_depth += 1
177
- # @param pin [Pin::Base]
178
- pins.each do |pin|
179
- # Avoid infinite recursion
180
- next if @@inference_stack.include?(pin.identity)
181
-
182
- @@inference_stack.push pin.identity
183
- type = pin.probe(api_map)
184
- @@inference_stack.pop
185
- if type.defined?
186
- possibles.push type
187
- break if pin.is_a?(Pin::Method)
188
- end
189
- end
190
- @@inference_depth -= 1
238
+
239
+ # Limit method inference recursion
240
+ if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method)
241
+ return ComplexType::UNDEFINED
191
242
  end
192
- return ComplexType::UNDEFINED if possibles.empty?
193
243
 
194
- type = if possibles.length > 1
195
- sorted = possibles.map { |t| t.rooted? ? "::#{t}" : t.to_s }.sort { |a, _| a == 'nil' ? 1 : 0 }
196
- ComplexType.parse(*sorted)
197
- else
198
- ComplexType.parse(possibles.map(&:to_s).join(', '))
244
+ @@inference_depth += 1
245
+ # @param pin [Pin::Base]
246
+ unresolved_pins.each do |pin|
247
+ # Avoid infinite recursion
248
+ if @@inference_stack.include?(pin.identity)
249
+ next
250
+ end
251
+
252
+ @@inference_stack.push(pin.identity)
253
+ type = pin.probe(api_map)
254
+ @@inference_stack.pop
255
+ types.push type if type
199
256
  end
257
+ @@inference_depth -= 1
258
+
259
+ type = if types.empty?
260
+ ComplexType::UNDEFINED
261
+ elsif types.length > 1
262
+ # Move nil to the end by convention
263
+ sorted = types.flat_map(&:items).sort { |a, _| a.tag == 'nil' ? 1 : 0 }
264
+ ComplexType.new(sorted.uniq)
265
+ else
266
+ ComplexType.new(types)
267
+ end
200
268
  return type if context.nil? || context.return_type.undefined?
201
269
 
202
- type.self_to(context.return_type.tag)
270
+ type.self_to_type(context.return_type)
203
271
  end
204
272
 
205
273
  # @param type [ComplexType]
@@ -207,7 +275,7 @@ module Solargraph
207
275
  def maybe_nil type
208
276
  return type if type.undefined? || type.void? || type.nullable?
209
277
  return type unless nullable?
210
- ComplexType.try_parse("#{type}, nil")
278
+ ComplexType.new(type.items + [ComplexType::NIL])
211
279
  end
212
280
  end
213
281
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Solargraph
4
4
  class Source
5
- # Information about a position in a source, including the word located
6
- # there.
5
+ # Information about a single Position in a Source, including the
6
+ # word located there.
7
7
  #
8
8
  class Cursor
9
9
  # @return [Position]
@@ -35,6 +35,7 @@ module Solargraph
35
35
  # The part of the word before the current position. Given the text
36
36
  # `foo.bar`, the start_of_word at position(0, 6) is `ba`.
37
37
  #
38
+ # @sg-ignore Improve resolution of String#match below
38
39
  # @return [String]
39
40
  def start_of_word
40
41
  @start_of_word ||= begin
@@ -32,8 +32,8 @@ module Solargraph
32
32
  # @return [Source::Chain]
33
33
  def chain
34
34
  # Special handling for files that end with an integer and a period
35
- return Chain.new([Chain::Literal.new('Integer'), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
- return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
35
+ return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
+ return Chain.new([Chain::Literal.new('Symbol', phrase[1..].to_sym)]) if phrase.start_with?(':') && !phrase.start_with?('::')
37
37
  return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i)
38
38
  begin
39
39
  return Chain.new([]) if phrase.end_with?('..')
@@ -19,13 +19,22 @@ module Solargraph
19
19
  attr_reader :filename
20
20
 
21
21
  # @return [String]
22
- attr_reader :code
22
+ def code
23
+ finalize
24
+ @code
25
+ end
23
26
 
24
- # @return [Parser::AST::Node]
25
- attr_reader :node
27
+ # @return [Parser::AST::Node, nil]
28
+ def node
29
+ finalize
30
+ @node
31
+ end
26
32
 
27
33
  # @return [Hash{Integer => Array<String>}]
28
- attr_reader :comments
34
+ def comments
35
+ finalize
36
+ @comments
37
+ end
29
38
 
30
39
  # @todo Deprecate?
31
40
  # @return [Integer]
@@ -39,17 +48,6 @@ module Solargraph
39
48
  @repaired = code
40
49
  @filename = filename
41
50
  @version = version
42
- @domains = []
43
- begin
44
- @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
45
- @parsed = true
46
- rescue Parser::SyntaxError, EncodingError => e
47
- @node = nil
48
- @comments = {}
49
- @parsed = false
50
- ensure
51
- @code.freeze
52
- end
53
51
  end
54
52
 
55
53
  # @param range [Solargraph::Range]
@@ -64,9 +62,9 @@ module Solargraph
64
62
  # @param c2 [Integer]
65
63
  # @return [String]
66
64
  def from_to l1, c1, l2, c2
67
- b = Solargraph::Position.line_char_to_offset(@code, l1, c1)
68
- e = Solargraph::Position.line_char_to_offset(@code, l2, c2)
69
- @code[b..e-1]
65
+ b = Solargraph::Position.line_char_to_offset(code, l1, c1)
66
+ e = Solargraph::Position.line_char_to_offset(code, l2, c2)
67
+ code[b..e-1]
70
68
  end
71
69
 
72
70
  # Get the nearest node that contains the specified index.
@@ -85,10 +83,9 @@ module Solargraph
85
83
  # @param column [Integer]
86
84
  # @return [Array<AST::Node>]
87
85
  def tree_at(line, column)
88
- # offset = Position.line_char_to_offset(@code, line, column)
89
86
  position = Position.new(line, column)
90
87
  stack = []
91
- inner_tree_at @node, position, stack
88
+ inner_tree_at node, position, stack
92
89
  stack
93
90
  end
94
91
 
@@ -104,68 +101,53 @@ module Solargraph
104
101
  @version = updater.version
105
102
  return self
106
103
  end
107
- synced = Source.new(real_code, filename)
108
- if synced.parsed?
109
- synced.version = updater.version
110
- return synced
104
+ Source.new(@code, filename, updater.version).tap do |src|
105
+ src.repaired = @repaired
106
+ src.error_ranges.concat error_ranges
107
+ src.changes.concat(changes + updater.changes)
111
108
  end
112
- incr_code = updater.repair(@repaired)
113
- synced = Source.new(incr_code, filename)
114
- synced.error_ranges.concat (error_ranges + updater.changes.map(&:range))
115
- synced.code = real_code
116
- synced.version = updater.version
117
- synced
118
109
  end
119
110
 
120
111
  # @param position [Position, Array(Integer, Integer)]
121
112
  # @return [Source::Cursor]
122
113
  def cursor_at position
114
+ finalize
123
115
  Cursor.new(self, position)
124
116
  end
125
117
 
126
118
  # @return [Boolean]
127
119
  def parsed?
120
+ finalize
128
121
  @parsed
129
122
  end
130
123
 
131
124
  def repaired?
132
- @is_repaired ||= (@code != @repaired)
125
+ code != @repaired
133
126
  end
134
127
 
135
128
  # @param position [Position]
136
129
  # @return [Boolean]
137
130
  def string_at? position
138
- if Parser.rubyvm?
139
- string_ranges.each do |range|
140
- if synchronized?
141
- return true if range.include?(position) || range.ending == position
142
- else
143
- return true if last_updater && last_updater.changes.one? && range.contain?(last_updater.changes.first.range.start)
144
- end
145
- end
146
- false
147
- else
148
- return false if Position.to_offset(code, position) >= code.length
149
- string_nodes.each do |node|
150
- range = Range.from_node(node)
151
- next if range.ending.line < position.line
152
- break if range.ending.line > position.line
153
- return true if node.type == :str && range.include?(position) && range.start != position
154
- return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position
155
- if node.type == :dstr
156
- inner = node_at(position.line, position.column)
157
- next if inner.nil?
158
- inner_range = Range.from_node(inner)
159
- next unless range.include?(inner_range.ending)
160
- return true if inner.type == :str
161
- inner_code = at(Solargraph::Range.new(inner_range.start, position))
162
- return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') ||
163
- (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}'))
164
- end
165
- break if range.ending.line > position.line
131
+ return false if Position.to_offset(code, position) >= code.length
132
+ string_nodes.each do |node|
133
+ range = Range.from_node(node)
134
+ next if range.ending.line < position.line
135
+ break if range.ending.line > position.line
136
+ return true if node.type == :str && range.include?(position) && range.start != position
137
+ return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position
138
+ if node.type == :dstr
139
+ inner = node_at(position.line, position.column)
140
+ next if inner.nil?
141
+ inner_range = Range.from_node(inner)
142
+ next unless range.include?(inner_range.ending)
143
+ return true if inner.type == :str
144
+ inner_code = at(Solargraph::Range.new(inner_range.start, position))
145
+ return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') ||
146
+ (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}'))
166
147
  end
167
- false
148
+ break if range.ending.line > position.line
168
149
  end
150
+ false
169
151
  end
170
152
 
171
153
  # @return [::Array<Range>]
@@ -199,8 +181,8 @@ module Solargraph
199
181
  # @return [String]
200
182
  def code_for(node)
201
183
  rng = Range.from_node(node)
202
- b = Position.line_char_to_offset(@code, rng.start.line, rng.start.column)
203
- e = Position.line_char_to_offset(@code, rng.ending.line, rng.ending.column)
184
+ b = Position.line_char_to_offset(code, rng.start.line, rng.start.column)
185
+ e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column)
204
186
  frag = code[b..e-1].to_s
205
187
  frag.strip.gsub(/,$/, '')
206
188
  end
@@ -225,15 +207,9 @@ module Solargraph
225
207
  Location.new(filename, range)
226
208
  end
227
209
 
228
- FOLDING_NODE_TYPES = if Parser.rubyvm?
229
- %i[
230
- CLASS SCLASS MODULE DEFN DEFS IF WHILE UNLESS ITER STR HASH ARRAY LIST
231
- ].freeze
232
- else
233
- %i[
210
+ FOLDING_NODE_TYPES = %i[
234
211
  class sclass module def defs if str dstr array while unless kwbegin hash block
235
212
  ].freeze
236
- end
237
213
 
238
214
  # Get an array of ranges that can be folded, e.g., the range of a class
239
215
  # definition or an if condition.
@@ -251,8 +227,7 @@ module Solargraph
251
227
  end
252
228
 
253
229
  def synchronized?
254
- @synchronized = true if @synchronized.nil?
255
- @synchronized
230
+ true
256
231
  end
257
232
 
258
233
  # Get a hash of comments grouped by the line numbers of the associated code.
@@ -262,8 +237,9 @@ module Solargraph
262
237
  @associated_comments ||= begin
263
238
  result = {}
264
239
  buffer = String.new('')
240
+ # @type [Integer, nil]
265
241
  last = nil
266
- @comments.each_pair do |num, snip|
242
+ comments.each_pair do |num, snip|
267
243
  if !last || num == last + 1
268
244
  buffer.concat "#{snip.text}\n"
269
245
  else
@@ -295,12 +271,9 @@ module Solargraph
295
271
  def inner_folding_ranges top, result = [], parent = nil
296
272
  return unless Parser.is_ast_node?(top)
297
273
  if FOLDING_NODE_TYPES.include?(top.type)
298
- # @todo Smelly exception for hash's first-level array in RubyVM
299
- unless [:ARRAY, :LIST].include?(top.type) && parent == :HASH
300
- range = Range.from_node(top)
301
- if result.empty? || range.start.line > result.last.start.line
302
- result.push range unless range.ending.line - range.start.line < 2
303
- end
274
+ range = Range.from_node(top)
275
+ if result.empty? || range.start.line > result.last.start.line
276
+ result.push range unless range.ending.line - range.start.line < 2
304
277
  end
305
278
  end
306
279
  top.children.each do |child|
@@ -334,19 +307,19 @@ module Solargraph
334
307
 
335
308
  # A hash of line numbers and their associated comments.
336
309
  #
337
- # @return [Hash{Integer => Array<String>}]
310
+ # @return [Hash{Integer => Array<String>, nil}]
338
311
  def stringified_comments
339
312
  @stringified_comments ||= {}
340
313
  end
341
314
 
342
315
  # @return [Array<Parser::AST::Node>]
343
316
  def string_nodes
344
- @string_nodes ||= string_nodes_in(@node)
317
+ @string_nodes ||= string_nodes_in(node)
345
318
  end
346
319
 
347
320
  # @return [Array<Range>]
348
321
  def comment_ranges
349
- @comment_ranges ||= @comments.values.map(&:range)
322
+ @comment_ranges ||= comments.values.map(&:range)
350
323
  end
351
324
 
352
325
  # Get an array of foldable comment block ranges. Blocks are excluded if
@@ -402,16 +375,59 @@ module Solargraph
402
375
 
403
376
  protected
404
377
 
378
+ # @return [Array<Change>]
379
+ def changes
380
+ @changes ||= []
381
+ end
382
+
405
383
  # @return [String]
406
384
  attr_writer :filename
407
385
 
408
386
  # @return [Integer]
409
387
  attr_writer :version
410
388
 
389
+ def finalize
390
+ return if @finalized && changes.empty?
391
+
392
+ changes.each do |change|
393
+ @code = change.write(@code)
394
+ end
395
+ @finalized = true
396
+ begin
397
+ @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename)
398
+ @parsed = true
399
+ @repaired = @code
400
+ rescue Parser::SyntaxError, EncodingError => e
401
+ @node = nil
402
+ @comments = {}
403
+ @parsed = false
404
+ ensure
405
+ @code.freeze
406
+ end
407
+ if !@parsed && !changes.empty?
408
+ changes.each do |change|
409
+ @repaired = change.repair(@repaired)
410
+ end
411
+ error_ranges.concat(changes.map(&:range))
412
+ begin
413
+ @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename)
414
+ @parsed = true
415
+ rescue Parser::SyntaxError, EncodingError => e
416
+ @node = nil
417
+ @comments = {}
418
+ @parsed = false
419
+ end
420
+ elsif @parsed
421
+ error_ranges.clear
422
+ end
423
+ changes.clear
424
+ end
425
+
411
426
  # @param val [String]
412
427
  # @return [String]
413
428
  def code=(val)
414
- @code_lines= nil
429
+ @code_lines = nil
430
+ @finalized = false
415
431
  @code = val
416
432
  end
417
433
 
@@ -422,7 +438,12 @@ module Solargraph
422
438
  attr_writer :error_ranges
423
439
 
424
440
  # @return [String]
425
- attr_accessor :repaired
441
+ attr_writer :repaired
442
+
443
+ def repaired
444
+ finalize
445
+ @repaired
446
+ end
426
447
 
427
448
  # @return [Boolean]
428
449
  attr_writer :parsed
@@ -433,9 +454,6 @@ module Solargraph
433
454
  # @return [Boolean]
434
455
  attr_writer :synchronized
435
456
 
436
- # @return [Source::Updater]
437
- attr_accessor :last_updater
438
-
439
457
  private
440
458
 
441
459
  # @return [Array<String>]