ruby-lsp 0.24.1 → 0.24.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 981f87e306aa96edd6d2e04fc274d5da24ede96d3e3333bcfdeb63039e86c9ab
4
- data.tar.gz: 5405216e985bed5ec6bce692baeeca9611980edc53fadf4655d00503488c277d
3
+ metadata.gz: 11ac9bfd9d255db5feb97c0f1d88a98dd966572ca6d60b6007384169f1b060b2
4
+ data.tar.gz: fbdc59855c67ba1741be48ccf49bd94359d5aa8aca2fb38f41e45174e4383988
5
5
  SHA512:
6
- metadata.gz: a749ebc7c81100f0c76431d68865960ad8aeb04ed3aad37dad18c008e885a50d224157a2fdcd44b15abc2d5d5f8d7a4a2345f187662707478dbc66d191c214a2
7
- data.tar.gz: 19a288a8121a4cf86b5fd60b9c6d11c4fd3cb26330f7667ac59b4742dbe91328b9f742dca5fb371bb58ad565afb9ec58516eb6b68dd85e641a800f89a23f81e9
6
+ metadata.gz: 0364d0f1049a03f9ade581ca98ddc209dd441f3e706561141ae1995903bf53e08e6d893f0650b31c7494a8333645030e1455023416b38a59002f177e3f024fcd
7
+ data.tar.gz: d4298637f7c8ba3acc70859a922ed43e4daadb3ad8d8885eb7ddb1fbadbfe078e9afe6f4fc22e60f854d5d5a16b76b9013fa36f5e3cceb62b5032bf6b17f934d
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.24.1
1
+ 0.24.2
@@ -266,7 +266,7 @@ module RubyIndexer
266
266
  def constant_completion_candidates(name, nesting)
267
267
  # If we have a top level reference, then we don't need to include completions inside the current nesting
268
268
  if name.start_with?("::")
269
- return @entries_tree.search(name.delete_prefix("::")) #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]] # rubocop:disable Layout/LineLength
269
+ return @entries_tree.search(name.delete_prefix("::")) #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
270
270
  end
271
271
 
272
272
  # Otherwise, we have to include every possible constant the user might be referring to. This is essentially the
@@ -292,7 +292,7 @@ module RubyIndexer
292
292
  # Top level constants
293
293
  entries.concat(@entries_tree.search(name))
294
294
  entries.uniq!
295
- entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]] # rubocop:disable Layout/LineLength
295
+ entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
296
296
  end
297
297
 
298
298
  # Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
@@ -232,8 +232,8 @@ module RubyIndexer
232
232
  signatures = entry.signatures
233
233
  assert_equal(2, signatures.length)
234
234
 
235
- # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] # rubocop:disable Layout/LineLength
236
- # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? # rubocop:disable Layout/LineLength
235
+ # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ]
236
+ # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]?
237
237
 
238
238
  parameters = signatures[0]&.parameters #: as !nil
239
239
  assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
@@ -56,7 +56,13 @@ module RubyLsp
56
56
  addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
57
57
 
58
58
  if include_project_addons
59
- addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
59
+ project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60
+
61
+ # Ignore add-ons from dependencies if the bundle is stored inside the project. We already found those with
62
+ # `Gem.find_files`
63
+ bundle_path = Bundler.bundle_path.to_s
64
+ project_addons.reject! { |path| path.start_with?(bundle_path) }
65
+ addon_files.concat(project_addons)
60
66
  end
61
67
 
62
68
  errors = addon_files.filter_map do |addon_path|
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  class Document
8
8
  extend T::Generic
9
9
 
10
+ class InvalidLocationError < StandardError; end
10
11
  # This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
11
12
  # This is the same number used by the TypeScript extension in VS Code
12
13
  MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
@@ -145,7 +146,14 @@ module RubyLsp
145
146
 
146
147
  #: -> Scanner
147
148
  def create_scanner
148
- Scanner.new(@source, @encoding)
149
+ case @encoding
150
+ when Encoding::UTF_8
151
+ Utf8Scanner.new(@source)
152
+ when Encoding::UTF_16LE
153
+ Utf16Scanner.new(@source)
154
+ else
155
+ Utf32Scanner.new(@source)
156
+ end
149
157
  end
150
158
 
151
159
  # @abstract
@@ -163,6 +171,11 @@ module RubyLsp
163
171
  class Replace < Edit; end
164
172
  class Delete < Edit; end
165
173
 
174
+ # Parent class for all position scanners. Scanners are used to translate a position given by the editor into a
175
+ # string index that we can use to find the right place in the document source. The logic for finding the correct
176
+ # index depends on the encoding negotiated with the editor, so we have different subclasses for each encoding.
177
+ # See https://microsoft.github.io/language-server-protocol/specification/#positionEncodingKind for more information
178
+ # @abstract
166
179
  class Scanner
167
180
  extend T::Sig
168
181
 
@@ -170,74 +183,157 @@ module RubyLsp
170
183
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
171
184
  SURROGATE_PAIR_START = 0xFFFF #: Integer
172
185
 
173
- #: (String source, Encoding encoding) -> void
174
- def initialize(source, encoding)
186
+ #: -> void
187
+ def initialize
175
188
  @current_line = 0 #: Integer
176
189
  @pos = 0 #: Integer
177
- @bytes_or_codepoints = encoding == Encoding::UTF_8 ? source.bytes : source.codepoints #: Array[Integer]
178
- @encoding = encoding
179
190
  end
180
191
 
181
- # Finds the character index inside the source string for a given line and column
192
+ # Finds the character index inside the source string for a given line and column. This method always returns the
193
+ # character index regardless of whether we are searching positions based on bytes, code units, or codepoints.
194
+ # @abstract
195
+ #: (Hash[Symbol, untyped] position) -> Integer
196
+ def find_char_position(position); end
197
+ end
198
+
199
+ # For the UTF-8 encoding, positions correspond to bytes
200
+ class Utf8Scanner < Scanner
201
+ #: (String source) -> void
202
+ def initialize(source)
203
+ super()
204
+ @bytes = source.bytes #: Array[Integer]
205
+ @character_length = 0 #: Integer
206
+ end
207
+
208
+ # @override
182
209
  #: (Hash[Symbol, untyped] position) -> Integer
183
210
  def find_char_position(position)
184
- # Find the character index for the beginning of the requested line
211
+ # Each group of bytes is a character. We advance based on the number of bytes to count how many full characters
212
+ # we have in the requested offset
185
213
  until @current_line == position[:line]
186
- @pos += 1 until LINE_BREAK == @bytes_or_codepoints[@pos]
214
+ byte = @bytes[@pos] #: Integer?
215
+ raise InvalidLocationError unless byte
216
+
217
+ until LINE_BREAK == byte
218
+ @pos += character_byte_length(byte)
219
+ @character_length += 1
220
+ byte = @bytes[@pos]
221
+ raise InvalidLocationError unless byte
222
+ end
223
+
187
224
  @pos += 1
225
+ @character_length += 1
188
226
  @current_line += 1
189
227
  end
190
228
 
191
- # For UTF-8, the code unit length is the same as bytes, but we want to return the character index
192
- requested_position = if @encoding == Encoding::UTF_8
193
- character_offset = 0
194
- i = @pos
195
-
196
- # Each group of bytes is a character. We advance based on the number of bytes to count how many full
197
- # characters we have in the requested offset
198
- while i < @pos + position[:character] && i < @bytes_or_codepoints.length
199
- byte = @bytes_or_codepoints[i] #: as !nil
200
- i += if byte < 0x80 # 1-byte character
201
- 1
202
- elsif byte < 0xE0 # 2-byte character
203
- 2
204
- elsif byte < 0xF0 # 3-byte character
205
- 3
206
- else # 4-byte character
207
- 4
208
- end
209
-
210
- character_offset += 1
229
+ # @character_length has the number of characters until the beginning of the line. We don't accumulate on it for
230
+ # the character part because locating the same position twice must return the same value
231
+ line_byte_offset = 0
232
+ line_characters = 0
233
+
234
+ while line_byte_offset < position[:character]
235
+ byte = @bytes[@pos + line_byte_offset] #: Integer?
236
+ raise InvalidLocationError unless byte
237
+
238
+ line_byte_offset += character_byte_length(byte)
239
+ line_characters += 1
240
+ end
241
+
242
+ @character_length + line_characters
243
+ end
244
+
245
+ private
246
+
247
+ #: (Integer) -> Integer
248
+ def character_byte_length(byte)
249
+ if byte < 0x80 # 1-byte character
250
+ 1
251
+ elsif byte < 0xE0 # 2-byte character
252
+ 2
253
+ elsif byte < 0xF0 # 3-byte character
254
+ 3
255
+ else # 4-byte character
256
+ 4
257
+ end
258
+ end
259
+ end
260
+
261
+ # For the UTF-16 encoding, positions correspond to UTF-16 code units, which count characters beyond the surrogate
262
+ # pair as length 2
263
+ class Utf16Scanner < Scanner
264
+ #: (String) -> void
265
+ def initialize(source)
266
+ super()
267
+ @codepoints = source.codepoints #: Array[Integer]
268
+ end
269
+
270
+ # @override
271
+ #: (Hash[Symbol, untyped] position) -> Integer
272
+ def find_char_position(position)
273
+ # Find the character index for the beginning of the requested line
274
+ until @current_line == position[:line]
275
+ codepoint = @codepoints[@pos] #: Integer?
276
+ raise InvalidLocationError unless codepoint
277
+
278
+ until LINE_BREAK == @codepoints[@pos]
279
+ @pos += 1
280
+ codepoint = @codepoints[@pos] #: Integer?
281
+ raise InvalidLocationError unless codepoint
211
282
  end
212
283
 
213
- @pos + character_offset
214
- else
215
- @pos + position[:character]
284
+ @pos += 1
285
+ @current_line += 1
216
286
  end
217
287
 
218
288
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
219
289
  # need to adjust for surrogate pairs
220
- if @encoding == Encoding::UTF_16LE
221
- requested_position -= utf_16_character_position_correction(@pos, requested_position)
290
+ line_characters = 0
291
+ line_code_units = 0
292
+
293
+ while line_code_units < position[:character]
294
+ code_point = @codepoints[@pos + line_characters]
295
+ raise InvalidLocationError unless code_point
296
+
297
+ line_code_units += if code_point > SURROGATE_PAIR_START
298
+ 2 # Surrogate pair, so we skip the next code unit
299
+ else
300
+ 1 # Single code unit character
301
+ end
302
+
303
+ line_characters += 1
222
304
  end
223
305
 
224
- requested_position
306
+ @pos + line_characters
225
307
  end
308
+ end
226
309
 
227
- # Subtract 1 for each character after 0xFFFF in the current line from the column position, so that we hit the
228
- # right character in the UTF-8 representation
229
- #: (Integer current_position, Integer requested_position) -> Integer
230
- def utf_16_character_position_correction(current_position, requested_position)
231
- utf16_unicode_correction = 0
310
+ # For the UTF-32 encoding, positions correspond directly to codepoints
311
+ class Utf32Scanner < Scanner
312
+ #: (String) -> void
313
+ def initialize(source)
314
+ super()
315
+ @codepoints = source.codepoints #: Array[Integer]
316
+ end
232
317
 
233
- until current_position == requested_position
234
- codepoint = @bytes_or_codepoints[current_position]
235
- utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
318
+ # @override
319
+ #: (Hash[Symbol, untyped] position) -> Integer
320
+ def find_char_position(position)
321
+ # Find the character index for the beginning of the requested line
322
+ until @current_line == position[:line]
323
+ codepoint = @codepoints[@pos] #: Integer?
324
+ raise InvalidLocationError unless codepoint
325
+
326
+ until LINE_BREAK == @codepoints[@pos]
327
+ @pos += 1
328
+ codepoint = @codepoints[@pos] #: Integer?
329
+ raise InvalidLocationError unless codepoint
330
+ end
236
331
 
237
- current_position += 1
332
+ @pos += 1
333
+ @current_line += 1
238
334
  end
239
335
 
240
- utf16_unicode_correction
336
+ @pos + position[:character]
241
337
  end
242
338
  end
243
339
  end
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  include Requests::Support::Common
8
8
 
9
9
  ALLOWED_TARGETS = [
10
+ Prism::BreakNode,
10
11
  Prism::CallNode,
11
12
  Prism::ConstantReadNode,
12
13
  Prism::ConstantWriteNode,
@@ -54,6 +55,7 @@ module RubyLsp
54
55
 
55
56
  dispatcher.register(
56
57
  self,
58
+ :on_break_node_enter,
57
59
  :on_constant_read_node_enter,
58
60
  :on_constant_write_node_enter,
59
61
  :on_constant_path_node_enter,
@@ -84,6 +86,11 @@ module RubyLsp
84
86
  )
85
87
  end
86
88
 
89
+ #: (Prism::BreakNode node) -> void
90
+ def on_break_node_enter(node)
91
+ handle_keyword_documentation(node.keyword)
92
+ end
93
+
87
94
  #: (Prism::StringNode node) -> void
88
95
  def on_string_node_enter(node)
89
96
  if @path && File.basename(@path) == GEMFILE_NAME
@@ -365,7 +365,7 @@ module RubyLsp
365
365
  end
366
366
  end
367
367
 
368
- node = node #: as Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode # rubocop:disable Layout/LineLength
368
+ node = node #: as Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode
369
369
 
370
370
  node_context = @document.locate_node(
371
371
  {
@@ -55,7 +55,7 @@ module RubyLsp
55
55
  )
56
56
  end
57
57
 
58
- target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode, # rubocop:disable Layout/LineLength
58
+ target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,
59
59
 
60
60
  reference_target = create_reference_target(target, node_context)
61
61
  return @locations unless reference_target
@@ -111,7 +111,11 @@ module RubyLsp
111
111
  rescue ::RuboCop::ValidationError => error
112
112
  raise ConfigurationError, error.message
113
113
  rescue StandardError => error
114
- raise InternalRuboCopError, error
114
+ # Maintain the original backtrace so that debugging cops that are breaking is easier, but re-raise as a
115
+ # different error class
116
+ internal_error = InternalRuboCopError.new(error)
117
+ internal_error.set_backtrace(error.backtrace)
118
+ raise internal_error
115
119
  end
116
120
 
117
121
  #: -> String
@@ -26,7 +26,7 @@ module RubyLsp
26
26
  queue = node.child_nodes.compact #: Array[Prism::Node?]
27
27
  closest = node
28
28
  parent = nil #: Prism::Node?
29
- nesting_nodes = [] #: Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] # rubocop:disable Layout/LineLength
29
+ nesting_nodes = [] #: Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)]
30
30
 
31
31
  nesting_nodes << node if node.is_a?(Prism::ProgramNode)
32
32
  call_node = nil #: Prism::CallNode?
@@ -115,7 +115,7 @@ module RubyLsp
115
115
  end
116
116
  rescue DelegateRequestError
117
117
  send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
118
- rescue StandardError, LoadError => e
118
+ rescue StandardError, LoadError, SystemExit => e
119
119
  # If an error occurred in a request, we have to return an error response or else the editor will hang
120
120
  if message[:id]
121
121
  # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
@@ -14,6 +14,7 @@ module RubyLsp
14
14
 
15
15
  # A map of keyword => short documentation to be displayed on hover or completion
16
16
  KEYWORD_DOCS = {
17
+ "break" => "Terminates the execution of a block or loop",
17
18
  "yield" => "Invokes the passed block with the given arguments",
18
19
  }.freeze #: Hash[String, String]
19
20
  end
@@ -20,8 +20,6 @@ module RubyLsp
20
20
  dir_path = File.join(Dir.tmpdir, "ruby-lsp")
21
21
  FileUtils.mkdir_p(dir_path)
22
22
 
23
- # Remove in 1 month once updates have rolled out
24
- legacy_port_path = File.join(dir_path, "test_reporter_port")
25
23
  port_db_path = File.join(dir_path, "test_reporter_port_db.json")
26
24
  port = ENV["RUBY_LSP_REPORTER_PORT"]
27
25
 
@@ -32,8 +30,6 @@ module RubyLsp
32
30
  elsif File.exist?(port_db_path)
33
31
  db = JSON.load_file(port_db_path)
34
32
  TCPSocket.new("localhost", db[Dir.pwd])
35
- elsif File.exist?(legacy_port_path)
36
- TCPSocket.new("localhost", File.read(legacy_port_path))
37
33
  else
38
34
  # For tests that don't spawn the TCP server
39
35
  require "stringio"
@@ -74,12 +74,25 @@ module RubyLsp
74
74
  end
75
75
  end
76
76
 
77
- #: (singleton(Minitest::Test) test_class, String method_name) -> void
78
- def prerecord(test_class, method_name)
79
- uri, line = LspReporter.instance.uri_and_line_for(test_class.instance_method(method_name))
77
+ #: (untyped, String) -> void
78
+ def prerecord(test_class_or_wrapper, method_name)
79
+ # In frameworks like Rails, they can control the Minitest execution by wrapping the test class
80
+ # But they conform to responding to `name`, so we can use that as a guarantee
81
+ # We are interested in the test class, not the wrapper
82
+ name = test_class_or_wrapper.name
83
+
84
+ klass = begin
85
+ Object.const_get(name) # rubocop:disable Sorbet/ConstantsFromStrings
86
+ rescue NameError
87
+ # Handle Minitest specs that create classes with invalid constant names like "MySpec::when something is true"
88
+ # If we can't resolve the constant, it means we were given the actual test class object, not the wrapper
89
+ test_class_or_wrapper
90
+ end
91
+
92
+ uri, line = LspReporter.instance.uri_and_line_for(klass.instance_method(method_name))
80
93
  return unless uri
81
94
 
82
- id = "#{test_class.name}##{handle_spec_test_id(method_name, line)}"
95
+ id = "#{name}##{handle_spec_test_id(method_name, line)}"
83
96
  LspReporter.instance.start_test(id: id, uri: uri, line: line)
84
97
  end
85
98
 
@@ -0,0 +1,103 @@
1
+ # Break
2
+
3
+ In Ruby, the `break` keyword is used to exit a loop or block prematurely. Unlike `next` which skips to the next iteration, `break` terminates the loop entirely and continues with the code after the loop.
4
+
5
+ ```ruby
6
+ # Basic break usage in a loop
7
+ 5.times do |i|
8
+ break if i == 3
9
+
10
+ puts i
11
+ end
12
+ # Output:
13
+ # 0
14
+ # 1
15
+ # 2
16
+ ```
17
+
18
+ The `break` statement can be used with any of Ruby's iteration methods or loops.
19
+
20
+ ```ruby
21
+ array = [1, 2, 3, 4, 5]
22
+
23
+ # Break in each iteration
24
+ array.each do |num|
25
+ break if num > 3
26
+
27
+ puts "Number: #{num}"
28
+ end
29
+ # Output:
30
+ # Number: 1
31
+ # Number: 2
32
+ # Number: 3
33
+
34
+ # Break in an infinite loop
35
+ count = 0
36
+ loop do
37
+ count += 1
38
+ break if count >= 3
39
+
40
+ puts "Count: #{count}"
41
+ end
42
+ # Output:
43
+ # Count: 1
44
+ # Count: 2
45
+ ```
46
+
47
+ ## Break with a Value
48
+
49
+ When used inside a block, `break` can return a value that becomes the result of the method call.
50
+
51
+ ```ruby
52
+ # Break with a return value in map
53
+ result = [1, 2, 3, 4, 5].map do |num|
54
+ break "Too large!" if num > 3
55
+
56
+ num * 2
57
+ end
58
+ puts result # Output: "Too large!"
59
+
60
+ # Break with a value in find
61
+ number = (1..10).find do |n|
62
+ break n if n > 5 && n.even?
63
+ end
64
+ puts number # Output: 6
65
+ ```
66
+
67
+ ## Break in Nested Loops
68
+
69
+ When using `break` in nested loops, it only exits the innermost loop. To break from nested loops, you typically need to use a flag or return.
70
+
71
+ ```ruby
72
+ # Break in nested iteration
73
+ (1..3).each do |i|
74
+ puts "Outer: #{i}"
75
+
76
+ (1..3).each do |j|
77
+ break if j == 2
78
+
79
+ puts " Inner: #{j}"
80
+ end
81
+ end
82
+ # Output:
83
+ # Outer: 1
84
+ # Inner: 1
85
+ # Outer: 2
86
+ # Inner: 1
87
+ # Outer: 3
88
+ # Inner: 1
89
+
90
+ # Breaking from nested loops with a flag
91
+ found = false
92
+ (1..3).each do |i|
93
+ (1..3).each do |j|
94
+ if i * j == 4
95
+ found = true
96
+ break
97
+ end
98
+ end
99
+ break if found
100
+ end
101
+ ```
102
+
103
+ The `break` keyword is essential for controlling loop execution and implementing early exit conditions. It's particularly useful when you've found what you're looking for and don't need to continue iterating.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.1
4
+ version: 0.24.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -209,6 +209,7 @@ files:
209
209
  - lib/ruby_lsp/test_reporters/test_unit_reporter.rb
210
210
  - lib/ruby_lsp/type_inferrer.rb
211
211
  - lib/ruby_lsp/utils.rb
212
+ - static_docs/break.md
212
213
  - static_docs/yield.md
213
214
  homepage: https://github.com/Shopify/ruby-lsp
214
215
  licenses: