ruby-lsp 0.11.2 → 0.12.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +11 -2
  4. data/exe/ruby-lsp-check +2 -1
  5. data/exe/ruby-lsp-doctor +15 -0
  6. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +125 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +10 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +205 -0
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +23 -106
  10. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +1 -1
  11. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -6
  12. data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +101 -49
  13. data/lib/ruby_indexer/ruby_indexer.rb +4 -3
  14. data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -16
  15. data/lib/ruby_indexer/test/constant_test.rb +99 -36
  16. data/lib/ruby_indexer/test/index_test.rb +1 -1
  17. data/lib/ruby_indexer/test/method_test.rb +73 -0
  18. data/lib/ruby_indexer/test/test_case.rb +5 -1
  19. data/lib/ruby_lsp/addon.rb +8 -8
  20. data/lib/ruby_lsp/document.rb +14 -14
  21. data/lib/ruby_lsp/executor.rb +89 -53
  22. data/lib/ruby_lsp/internal.rb +7 -2
  23. data/lib/ruby_lsp/listener.rb +6 -6
  24. data/lib/ruby_lsp/requests/base_request.rb +1 -9
  25. data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
  26. data/lib/ruby_lsp/requests/code_lens.rb +47 -31
  27. data/lib/ruby_lsp/requests/completion.rb +83 -32
  28. data/lib/ruby_lsp/requests/definition.rb +21 -15
  29. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  30. data/lib/ruby_lsp/requests/document_highlight.rb +508 -31
  31. data/lib/ruby_lsp/requests/document_link.rb +24 -17
  32. data/lib/ruby_lsp/requests/document_symbol.rb +42 -42
  33. data/lib/ruby_lsp/requests/folding_ranges.rb +83 -77
  34. data/lib/ruby_lsp/requests/hover.rb +22 -17
  35. data/lib/ruby_lsp/requests/inlay_hints.rb +6 -6
  36. data/lib/ruby_lsp/requests/selection_ranges.rb +13 -105
  37. data/lib/ruby_lsp/requests/semantic_highlighting.rb +92 -92
  38. data/lib/ruby_lsp/requests/support/annotation.rb +3 -3
  39. data/lib/ruby_lsp/requests/support/common.rb +5 -5
  40. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +21 -7
  41. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +19 -0
  42. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +10 -7
  43. data/lib/ruby_lsp/requests/support/sorbet.rb +28 -28
  44. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  45. data/lib/ruby_lsp/requests.rb +0 -1
  46. data/lib/ruby_lsp/setup_bundler.rb +26 -17
  47. metadata +20 -17
  48. data/lib/ruby_lsp/event_emitter.rb +0 -351
  49. data/lib/ruby_lsp/requests/support/highlight_target.rb +0 -118
@@ -2,10 +2,10 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyIndexer
5
- class IndexVisitor < YARP::Visitor
5
+ class IndexVisitor < Prism::Visitor
6
6
  extend T::Sig
7
7
 
8
- sig { params(index: Index, parse_result: YARP::ParseResult, file_path: String).void }
8
+ sig { params(index: Index, parse_result: Prism::ParseResult, file_path: String).void }
9
9
  def initialize(index, parse_result, file_path)
10
10
  @index = index
11
11
  @file_path = file_path
@@ -14,95 +14,127 @@ module RubyIndexer
14
14
  parse_result.comments.to_h do |c|
15
15
  [c.location.start_line, c]
16
16
  end,
17
- T::Hash[Integer, YARP::Comment],
17
+ T::Hash[Integer, Prism::Comment],
18
18
  )
19
19
 
20
20
  super()
21
21
  end
22
22
 
23
- sig { override.params(node: YARP::ClassNode).void }
23
+ sig { override.params(node: Prism::ClassNode).void }
24
24
  def visit_class_node(node)
25
- add_index_entry(node, Index::Entry::Class)
25
+ add_class_entry(node)
26
26
  end
27
27
 
28
- sig { override.params(node: YARP::ModuleNode).void }
28
+ sig { override.params(node: Prism::ModuleNode).void }
29
29
  def visit_module_node(node)
30
- add_index_entry(node, Index::Entry::Module)
30
+ add_module_entry(node)
31
31
  end
32
32
 
33
- sig { override.params(node: YARP::ConstantPathWriteNode).void }
33
+ sig { override.params(node: Prism::MultiWriteNode).void }
34
+ def visit_multi_write_node(node)
35
+ value = node.value
36
+ values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
37
+
38
+ node.targets.each_with_index do |target, i|
39
+ current_value = values[i]
40
+ # The moment we find a splat on the right hand side of the assignment, we can no longer figure out which value
41
+ # gets assigned to what
42
+ values.clear if current_value.is_a?(Prism::SplatNode)
43
+
44
+ case target
45
+ when Prism::ConstantTargetNode
46
+ add_constant(target, fully_qualify_name(target.name.to_s), current_value)
47
+ when Prism::ConstantPathTargetNode
48
+ add_constant(target, fully_qualify_name(target.slice), current_value)
49
+ end
50
+ end
51
+ end
52
+
53
+ sig { override.params(node: Prism::ConstantPathWriteNode).void }
34
54
  def visit_constant_path_write_node(node)
35
55
  # ignore variable constants like `var::FOO` or `self.class::FOO`
36
56
  target = node.target
37
- return unless target.parent.nil? || target.parent.is_a?(YARP::ConstantReadNode)
57
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
38
58
 
39
59
  name = fully_qualify_name(target.location.slice)
40
60
  add_constant(node, name)
41
61
  end
42
62
 
43
- sig { override.params(node: YARP::ConstantPathOrWriteNode).void }
63
+ sig { override.params(node: Prism::ConstantPathOrWriteNode).void }
44
64
  def visit_constant_path_or_write_node(node)
45
65
  # ignore variable constants like `var::FOO` or `self.class::FOO`
46
66
  target = node.target
47
- return unless target.parent.nil? || target.parent.is_a?(YARP::ConstantReadNode)
67
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
48
68
 
49
69
  name = fully_qualify_name(target.location.slice)
50
70
  add_constant(node, name)
51
71
  end
52
72
 
53
- sig { override.params(node: YARP::ConstantPathOperatorWriteNode).void }
73
+ sig { override.params(node: Prism::ConstantPathOperatorWriteNode).void }
54
74
  def visit_constant_path_operator_write_node(node)
55
75
  # ignore variable constants like `var::FOO` or `self.class::FOO`
56
76
  target = node.target
57
- return unless target.parent.nil? || target.parent.is_a?(YARP::ConstantReadNode)
77
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
58
78
 
59
79
  name = fully_qualify_name(target.location.slice)
60
80
  add_constant(node, name)
61
81
  end
62
82
 
63
- sig { override.params(node: YARP::ConstantPathAndWriteNode).void }
83
+ sig { override.params(node: Prism::ConstantPathAndWriteNode).void }
64
84
  def visit_constant_path_and_write_node(node)
65
85
  # ignore variable constants like `var::FOO` or `self.class::FOO`
66
86
  target = node.target
67
- return unless target.parent.nil? || target.parent.is_a?(YARP::ConstantReadNode)
87
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
68
88
 
69
89
  name = fully_qualify_name(target.location.slice)
70
90
  add_constant(node, name)
71
91
  end
72
92
 
73
- sig { override.params(node: YARP::ConstantWriteNode).void }
93
+ sig { override.params(node: Prism::ConstantWriteNode).void }
74
94
  def visit_constant_write_node(node)
75
95
  name = fully_qualify_name(node.name.to_s)
76
96
  add_constant(node, name)
77
97
  end
78
98
 
79
- sig { override.params(node: YARP::ConstantOrWriteNode).void }
99
+ sig { override.params(node: Prism::ConstantOrWriteNode).void }
80
100
  def visit_constant_or_write_node(node)
81
101
  name = fully_qualify_name(node.name.to_s)
82
102
  add_constant(node, name)
83
103
  end
84
104
 
85
- sig { override.params(node: YARP::ConstantAndWriteNode).void }
105
+ sig { override.params(node: Prism::ConstantAndWriteNode).void }
86
106
  def visit_constant_and_write_node(node)
87
107
  name = fully_qualify_name(node.name.to_s)
88
108
  add_constant(node, name)
89
109
  end
90
110
 
91
- sig { override.params(node: YARP::ConstantOperatorWriteNode).void }
111
+ sig { override.params(node: Prism::ConstantOperatorWriteNode).void }
92
112
  def visit_constant_operator_write_node(node)
93
113
  name = fully_qualify_name(node.name.to_s)
94
114
  add_constant(node, name)
95
115
  end
96
116
 
97
- sig { override.params(node: YARP::CallNode).void }
117
+ sig { override.params(node: Prism::CallNode).void }
98
118
  def visit_call_node(node)
99
119
  message = node.message
100
120
  handle_private_constant(node) if message == "private_constant"
101
121
  end
102
122
 
123
+ sig { override.params(node: Prism::DefNode).void }
124
+ def visit_def_node(node)
125
+ method_name = node.name.to_s
126
+ comments = collect_comments(node)
127
+ case node.receiver
128
+ when nil
129
+ @index << Entry::InstanceMethod.new(method_name, @file_path, node.location, comments, node.parameters)
130
+ when Prism::SelfNode
131
+ @index << Entry::SingletonMethod.new(method_name, @file_path, node.location, comments, node.parameters)
132
+ end
133
+ end
134
+
103
135
  private
104
136
 
105
- sig { params(node: YARP::CallNode).void }
137
+ sig { params(node: Prism::CallNode).void }
106
138
  def handle_private_constant(node)
107
139
  arguments = node.arguments&.arguments
108
140
  return unless arguments
@@ -110,9 +142,9 @@ module RubyIndexer
110
142
  first_argument = arguments.first
111
143
 
112
144
  name = case first_argument
113
- when YARP::StringNode
145
+ when Prism::StringNode
114
146
  first_argument.content
115
- when YARP::SymbolNode
147
+ when Prism::SymbolNode
116
148
  first_argument.value
117
149
  end
118
150
 
@@ -130,58 +162,78 @@ module RubyIndexer
130
162
  sig do
131
163
  params(
132
164
  node: T.any(
133
- YARP::ConstantWriteNode,
134
- YARP::ConstantOrWriteNode,
135
- YARP::ConstantAndWriteNode,
136
- YARP::ConstantOperatorWriteNode,
137
- YARP::ConstantPathWriteNode,
138
- YARP::ConstantPathOrWriteNode,
139
- YARP::ConstantPathOperatorWriteNode,
140
- YARP::ConstantPathAndWriteNode,
165
+ Prism::ConstantWriteNode,
166
+ Prism::ConstantOrWriteNode,
167
+ Prism::ConstantAndWriteNode,
168
+ Prism::ConstantOperatorWriteNode,
169
+ Prism::ConstantPathWriteNode,
170
+ Prism::ConstantPathOrWriteNode,
171
+ Prism::ConstantPathOperatorWriteNode,
172
+ Prism::ConstantPathAndWriteNode,
173
+ Prism::ConstantTargetNode,
174
+ Prism::ConstantPathTargetNode,
141
175
  ),
142
176
  name: String,
177
+ value: T.nilable(Prism::Node),
143
178
  ).void
144
179
  end
145
- def add_constant(node, name)
146
- value = node.value
180
+ def add_constant(node, name, value = nil)
181
+ value = node.value unless node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
147
182
  comments = collect_comments(node)
148
183
 
149
184
  @index << case value
150
- when YARP::ConstantReadNode, YARP::ConstantPathNode
151
- Index::Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
152
- when YARP::ConstantWriteNode, YARP::ConstantAndWriteNode, YARP::ConstantOrWriteNode,
153
- YARP::ConstantOperatorWriteNode
185
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
186
+ Entry::UnresolvedAlias.new(value.slice, @stack.dup, name, @file_path, node.location, comments)
187
+ when Prism::ConstantWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantOrWriteNode,
188
+ Prism::ConstantOperatorWriteNode
154
189
 
155
190
  # If the right hand side is another constant assignment, we need to visit it because that constant has to be
156
191
  # indexed too
157
192
  visit(value)
158
- Index::Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
159
- when YARP::ConstantPathWriteNode, YARP::ConstantPathOrWriteNode, YARP::ConstantPathOperatorWriteNode,
160
- YARP::ConstantPathAndWriteNode
193
+ Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
194
+ when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
195
+ Prism::ConstantPathAndWriteNode
161
196
 
162
197
  visit(value)
163
- Index::Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
198
+ Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
164
199
  else
165
- Index::Entry::Constant.new(name, @file_path, node.location, comments)
200
+ Entry::Constant.new(name, @file_path, node.location, comments)
166
201
  end
167
202
  end
168
203
 
169
- sig { params(node: T.any(YARP::ClassNode, YARP::ModuleNode), klass: T.class_of(Index::Entry)).void }
170
- def add_index_entry(node, klass)
204
+ sig { params(node: Prism::ModuleNode).void }
205
+ def add_module_entry(node)
171
206
  name = node.constant_path.location.slice
207
+ return visit_child_nodes(node) unless /^[A-Z:]/.match?(name)
172
208
 
173
- unless /^[A-Z:]/.match?(name)
174
- return visit_child_nodes(node)
175
- end
209
+ comments = collect_comments(node)
210
+
211
+ @index << Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
212
+ @stack << name
213
+ visit_child_nodes(node)
214
+ @stack.pop
215
+ end
216
+
217
+ sig { params(node: Prism::ClassNode).void }
218
+ def add_class_entry(node)
219
+ name = node.constant_path.location.slice
220
+ return visit_child_nodes(node) unless /^[A-Z:]/.match?(name)
176
221
 
177
222
  comments = collect_comments(node)
178
- @index << klass.new(fully_qualify_name(name), @file_path, node.location, comments)
223
+
224
+ superclass = node.superclass
225
+ parent_class = case superclass
226
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
227
+ superclass.slice
228
+ end
229
+
230
+ @index << Entry::Class.new(fully_qualify_name(name), @file_path, node.location, comments, parent_class)
179
231
  @stack << name
180
232
  visit(node.body)
181
233
  @stack.pop
182
234
  end
183
235
 
184
- sig { params(node: YARP::Node).returns(T::Array[String]) }
236
+ sig { params(node: Prism::Node).returns(T::Array[String]) }
185
237
  def collect_comments(node)
186
238
  comments = []
187
239
 
@@ -7,16 +7,17 @@ require "did_you_mean"
7
7
  require "ruby_indexer/lib/ruby_indexer/indexable_path"
8
8
  require "ruby_indexer/lib/ruby_indexer/visitor"
9
9
  require "ruby_indexer/lib/ruby_indexer/index"
10
+ require "ruby_indexer/lib/ruby_indexer/entry"
10
11
  require "ruby_indexer/lib/ruby_indexer/configuration"
11
12
  require "ruby_indexer/lib/ruby_indexer/prefix_tree"
12
13
 
13
14
  module RubyIndexer
15
+ @configuration = T.let(Configuration.new, Configuration)
16
+
14
17
  class << self
15
18
  extend T::Sig
16
19
 
17
20
  sig { returns(Configuration) }
18
- def configuration
19
- @configuration ||= T.let(Configuration.new, T.nilable(Configuration))
20
- end
21
+ attr_reader :configuration
21
22
  end
22
23
  end
@@ -11,7 +11,7 @@ module RubyIndexer
11
11
  end
12
12
  RUBY
13
13
 
14
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
14
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
15
15
  end
16
16
 
17
17
  def test_class_with_statements
@@ -21,7 +21,7 @@ module RubyIndexer
21
21
  end
22
22
  RUBY
23
23
 
24
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:2-3")
24
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:2-3")
25
25
  end
26
26
 
27
27
  def test_colon_colon_class
@@ -30,7 +30,7 @@ module RubyIndexer
30
30
  end
31
31
  RUBY
32
32
 
33
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
33
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
34
34
  end
35
35
 
36
36
  def test_colon_colon_class_inside_class
@@ -41,8 +41,8 @@ module RubyIndexer
41
41
  end
42
42
  RUBY
43
43
 
44
- assert_entry("Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:3-3")
45
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
44
+ assert_entry("Bar", Entry::Class, "/fake/path/foo.rb:0-0:3-3")
45
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
46
46
  end
47
47
 
48
48
  def test_namespaced_class
@@ -51,7 +51,7 @@ module RubyIndexer
51
51
  end
52
52
  RUBY
53
53
 
54
- assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
54
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
55
55
  end
56
56
 
57
57
  def test_dynamically_namespaced_class
@@ -69,7 +69,7 @@ module RubyIndexer
69
69
  end
70
70
  RUBY
71
71
 
72
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
72
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
73
73
  end
74
74
 
75
75
  def test_module_with_statements
@@ -79,7 +79,7 @@ module RubyIndexer
79
79
  end
80
80
  RUBY
81
81
 
82
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:2-3")
82
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:2-3")
83
83
  end
84
84
 
85
85
  def test_colon_colon_module
@@ -88,7 +88,7 @@ module RubyIndexer
88
88
  end
89
89
  RUBY
90
90
 
91
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
91
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
92
92
  end
93
93
 
94
94
  def test_namespaced_module
@@ -97,7 +97,7 @@ module RubyIndexer
97
97
  end
98
98
  RUBY
99
99
 
100
- assert_entry("Foo::Bar", Index::Entry::Module, "/fake/path/foo.rb:0-0:1-3")
100
+ assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
101
101
  end
102
102
 
103
103
  def test_dynamically_namespaced_module
@@ -124,11 +124,11 @@ module RubyIndexer
124
124
  end
125
125
  RUBY
126
126
 
127
- assert_entry("Foo", Index::Entry::Module, "/fake/path/foo.rb:0-0:10-3")
128
- assert_entry("Foo::Bar", Index::Entry::Class, "/fake/path/foo.rb:1-2:2-5")
129
- assert_entry("Foo::Baz", Index::Entry::Module, "/fake/path/foo.rb:4-2:9-5")
130
- assert_entry("Foo::Baz::Qux", Index::Entry::Class, "/fake/path/foo.rb:5-4:8-7")
131
- assert_entry("Foo::Baz::Qux::Something", Index::Entry::Class, "/fake/path/foo.rb:6-6:7-9")
127
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:10-3")
128
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
129
+ assert_entry("Foo::Baz", Entry::Module, "/fake/path/foo.rb:4-2:9-5")
130
+ assert_entry("Foo::Baz::Qux", Entry::Class, "/fake/path/foo.rb:5-4:8-7")
131
+ assert_entry("Foo::Baz::Qux::Something", Entry::Class, "/fake/path/foo.rb:6-6:7-9")
132
132
  end
133
133
 
134
134
  def test_deleting_from_index_based_on_file_path
@@ -137,7 +137,7 @@ module RubyIndexer
137
137
  end
138
138
  RUBY
139
139
 
140
- assert_entry("Foo", Index::Entry::Class, "/fake/path/foo.rb:0-0:1-3")
140
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
141
141
 
142
142
  @index.delete(IndexablePath.new(nil, "/fake/path/foo.rb"))
143
143
  refute_entry("Foo")
@@ -239,5 +239,38 @@ module RubyIndexer
239
239
  d_const = @index["A::D"].first
240
240
  assert_equal(:public, d_const.visibility)
241
241
  end
242
+
243
+ def test_keeping_track_of_super_classes
244
+ index(<<~RUBY)
245
+ class Foo < Bar
246
+ end
247
+
248
+ class Baz
249
+ end
250
+
251
+ module Something
252
+ class Baz
253
+ end
254
+
255
+ class Qux < ::Baz
256
+ end
257
+ end
258
+
259
+ class FinalThing < Something::Baz
260
+ end
261
+ RUBY
262
+
263
+ foo = T.must(@index["Foo"].first)
264
+ assert_equal("Bar", foo.parent_class)
265
+
266
+ baz = T.must(@index["Baz"].first)
267
+ assert_nil(baz.parent_class)
268
+
269
+ qux = T.must(@index["Something::Qux"].first)
270
+ assert_equal("::Baz", qux.parent_class)
271
+
272
+ final_thing = T.must(@index["FinalThing"].first)
273
+ assert_equal("Something::Baz", final_thing.parent_class)
274
+ end
242
275
  end
243
276
  end