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 +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +2 -2
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_lsp/addon.rb +7 -1
- data/lib/ruby_lsp/document.rb +140 -44
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +5 -1
- data/lib/ruby_lsp/ruby_document.rb +1 -1
- data/lib/ruby_lsp/server.rb +1 -1
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +0 -4
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +17 -4
- data/static_docs/break.md +103 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11ac9bfd9d255db5feb97c0f1d88a98dd966572ca6d60b6007384169f1b060b2
|
4
|
+
data.tar.gz: fbdc59855c67ba1741be48ccf49bd94359d5aa8aca2fb38f41e45174e4383988
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0364d0f1049a03f9ade581ca98ddc209dd441f3e706561141ae1995903bf53e08e6d893f0650b31c7494a8333645030e1455023416b38a59002f177e3f024fcd
|
7
|
+
data.tar.gz: d4298637f7c8ba3acc70859a922ed43e4daadb3ad8d8885eb7ddb1fbadbfe078e9afe6f4fc22e60f854d5d5a16b76b9013fa36f5e3cceb62b5032bf6b17f934d
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.24.
|
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]]
|
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]]
|
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] ]
|
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] ]?
|
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))
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -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
|
-
|
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|
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -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
|
-
|
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
|
-
#:
|
174
|
-
def initialize
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
214
|
-
|
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
|
-
|
221
|
-
|
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
|
-
|
306
|
+
@pos + line_characters
|
225
307
|
end
|
308
|
+
end
|
226
309
|
|
227
|
-
|
228
|
-
|
229
|
-
#: (
|
230
|
-
def
|
231
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
332
|
+
@pos += 1
|
333
|
+
@current_line += 1
|
238
334
|
end
|
239
335
|
|
240
|
-
|
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
|
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,
|
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
|
-
|
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)]
|
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?
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -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
|
data/lib/ruby_lsp/static_docs.rb
CHANGED
@@ -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
|
-
#: (
|
78
|
-
def prerecord(
|
79
|
-
|
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 = "#{
|
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.
|
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:
|