ruby-lsp 0.23.23 → 0.26.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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +25 -11
- data/exe/ruby-lsp-test-exec +3 -15
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +7 -1
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -4
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +10 -19
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +29 -7
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -2
- data/lib/ruby_indexer/test/index_test.rb +39 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +24 -0
- data/lib/ruby_indexer/test/method_test.rb +17 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
- data/lib/ruby_lsp/addon.rb +44 -15
- data/lib/ruby_lsp/base_server.rb +34 -26
- data/lib/ruby_lsp/document.rb +162 -52
- data/lib/ruby_lsp/erb_document.rb +8 -3
- data/lib/ruby_lsp/global_state.rb +21 -0
- data/lib/ruby_lsp/internal.rb +0 -2
- data/lib/ruby_lsp/listeners/completion.rb +14 -3
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/listeners/spec_style.rb +7 -8
- data/lib/ruby_lsp/listeners/test_discovery.rb +18 -15
- data/lib/ruby_lsp/listeners/test_style.rb +14 -13
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +9 -3
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +10 -6
- data/lib/ruby_lsp/requests/rename.rb +8 -6
- data/lib/ruby_lsp/requests/request.rb +6 -7
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +1 -3
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -8
- data/lib/ruby_lsp/ruby_document.rb +10 -5
- data/lib/ruby_lsp/server.rb +93 -108
- data/lib/ruby_lsp/setup_bundler.rb +59 -25
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +0 -10
- data/lib/ruby_lsp/test_helper.rb +1 -4
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +13 -8
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +17 -4
- data/lib/ruby_lsp/utils.rb +47 -11
- data/static_docs/break.md +103 -0
- metadata +2 -16
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -1,7 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "sorbet-runtime"
|
5
4
|
require "bundler"
|
6
5
|
require "bundler/cli"
|
7
6
|
require "bundler/cli/install"
|
@@ -12,19 +11,24 @@ require "digest"
|
|
12
11
|
require "time"
|
13
12
|
require "uri"
|
14
13
|
|
15
|
-
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
16
|
-
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
17
|
-
# exact locked versions of dependencies.
|
14
|
+
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
15
|
+
# use the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
16
|
+
# the exact locked versions of dependencies.
|
18
17
|
|
19
18
|
Bundler.ui.level = :silent
|
20
19
|
|
21
20
|
module RubyLsp
|
22
21
|
class SetupBundler
|
23
|
-
extend T::Sig
|
24
|
-
|
25
22
|
class BundleNotLocked < StandardError; end
|
26
23
|
class BundleInstallFailure < StandardError; end
|
27
24
|
|
25
|
+
module ThorPatch
|
26
|
+
#: -> IO
|
27
|
+
def stdout
|
28
|
+
$stderr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
28
32
|
FOUR_HOURS = 4 * 60 * 60 #: Integer
|
29
33
|
|
30
34
|
#: (String project_path, **untyped options) -> void
|
@@ -61,6 +65,7 @@ module RubyLsp
|
|
61
65
|
@bundler_version = bundler_version #: Gem::Version?
|
62
66
|
@rails_app = rails_app? #: bool
|
63
67
|
@retry = false #: bool
|
68
|
+
@needs_update_path = @custom_dir + "needs_update" #: Pathname
|
64
69
|
end
|
65
70
|
|
66
71
|
# Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
|
@@ -229,6 +234,14 @@ module RubyLsp
|
|
229
234
|
# If no error occurred, then clear previous errors
|
230
235
|
@error_path.delete if @error_path.exist?
|
231
236
|
$stderr.puts("Ruby LSP> Composed bundle installation complete")
|
237
|
+
rescue Errno::EPIPE
|
238
|
+
# If the $stderr pipe was closed by the client, for example when closing the editor during running bundle
|
239
|
+
# install, we don't want to write the error to a file or else we will report to telemetry on the next launch and
|
240
|
+
# it does not represent an actual error.
|
241
|
+
#
|
242
|
+
# This situation may happen because while running bundle install, the server is not yet ready to receive
|
243
|
+
# shutdown requests and we may continue doing work until the process is killed.
|
244
|
+
@error_path.delete if @error_path.exist?
|
232
245
|
rescue => e
|
233
246
|
# Write the error object to a file so that we can read it from the parent process
|
234
247
|
@error_path.write(Marshal.dump(e))
|
@@ -256,19 +269,50 @@ module RubyLsp
|
|
256
269
|
#: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
|
257
270
|
def run_bundle_install_directly(env, force_install: false)
|
258
271
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
272
|
+
return update(env) if @needs_update_path.exist?
|
259
273
|
|
260
274
|
# The ENV can only be merged after checking if an update is required because we depend on the original value of
|
261
275
|
# ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
|
262
|
-
|
263
|
-
ENV
|
264
|
-
.merge!(env)
|
276
|
+
FileUtils.touch(@needs_update_path) if should_bundle_update?
|
277
|
+
ENV.merge!(env)
|
265
278
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
279
|
+
$stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
|
280
|
+
missing_gems = bundle_check
|
281
|
+
|
282
|
+
unless missing_gems.empty?
|
283
|
+
$stderr.puts(<<~MESSAGE)
|
284
|
+
Ruby LSP> Running bundle install because the following gems are not installed:
|
285
|
+
#{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
|
286
|
+
MESSAGE
|
287
|
+
|
288
|
+
bundle_install
|
270
289
|
end
|
271
290
|
|
291
|
+
$stderr.puts("Ruby LSP> Bundle already satisfied")
|
292
|
+
env
|
293
|
+
rescue => e
|
294
|
+
$stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
|
295
|
+
bundle_install
|
296
|
+
env
|
297
|
+
end
|
298
|
+
|
299
|
+
# Essentially the same as bundle check, but simplified
|
300
|
+
#: -> Array[Gem::Specification]
|
301
|
+
def bundle_check
|
302
|
+
definition = Bundler.definition
|
303
|
+
definition.validate_runtime!
|
304
|
+
definition.check!
|
305
|
+
definition.missing_specs
|
306
|
+
end
|
307
|
+
|
308
|
+
#: -> void
|
309
|
+
def bundle_install
|
310
|
+
Bundler::CLI::Install.new({ "no-cache" => true }).run
|
311
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
312
|
+
end
|
313
|
+
|
314
|
+
#: (Hash[String, String]) -> Hash[String, String]
|
315
|
+
def update(env)
|
272
316
|
# Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
|
273
317
|
# source control changes
|
274
318
|
gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
|
@@ -276,11 +320,9 @@ module RubyLsp
|
|
276
320
|
|
277
321
|
Bundler::CLI::Update.new({ conservative: true }, gems).run
|
278
322
|
correct_relative_remote_paths if @custom_lockfile.exist?
|
323
|
+
@needs_update_path.delete
|
279
324
|
@last_updated_path.write(Time.now.iso8601)
|
280
325
|
env
|
281
|
-
rescue Bundler::GemNotFound, Bundler::GitError
|
282
|
-
# If a gem is not installed, skip the upgrade and try to install it with a single retry
|
283
|
-
@retry ? env : run_bundle_install_directly(env, force_install: true)
|
284
326
|
end
|
285
327
|
|
286
328
|
#: (Hash[String, String] env) -> Hash[String, String]
|
@@ -440,15 +482,7 @@ module RubyLsp
|
|
440
482
|
def patch_thor_to_print_progress_to_stderr!
|
441
483
|
return unless defined?(Bundler::Thor::Shell::Basic)
|
442
484
|
|
443
|
-
Bundler::Thor::Shell::Basic.prepend(
|
444
|
-
extend T::Sig
|
445
|
-
|
446
|
-
sig { returns(IO) }
|
447
|
-
def stdout
|
448
|
-
$stderr
|
449
|
-
end
|
450
|
-
end)
|
451
|
-
|
485
|
+
Bundler::Thor::Shell::Basic.prepend(ThorPatch)
|
452
486
|
Bundler.ui.level = :info
|
453
487
|
end
|
454
488
|
end
|
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
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,9 +5,6 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
class NonExistingDocumentError < StandardError; end
|
7
7
|
|
8
|
-
#: Hash[Symbol, RequestConfig]
|
9
|
-
attr_accessor :features_configuration
|
10
|
-
|
11
8
|
#: String
|
12
9
|
attr_accessor :client_name
|
13
10
|
|
@@ -15,13 +12,6 @@ module RubyLsp
|
|
15
12
|
def initialize(global_state)
|
16
13
|
@global_state = global_state
|
17
14
|
@state = {} #: Hash[String, Document[untyped]]
|
18
|
-
@features_configuration = {
|
19
|
-
inlayHint: RequestConfig.new({
|
20
|
-
enableAll: false,
|
21
|
-
implicitRescue: false,
|
22
|
-
implicitHashValue: false,
|
23
|
-
}),
|
24
|
-
} #: Hash[Symbol, RequestConfig]
|
25
15
|
@client_name = "Unknown" #: String
|
26
16
|
end
|
27
17
|
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -4,13 +4,10 @@
|
|
4
4
|
# NOTE: This module is intended to be used by addons for writing their own tests, so keep that in mind if changing.
|
5
5
|
|
6
6
|
module RubyLsp
|
7
|
+
# @requires_ancestor: Kernel
|
7
8
|
module TestHelper
|
8
9
|
class TestError < StandardError; end
|
9
10
|
|
10
|
-
extend T::Helpers
|
11
|
-
|
12
|
-
requires_ancestor { Kernel }
|
13
|
-
|
14
11
|
#: [T] (?String? source, ?URI::Generic uri, ?stub_no_typechecker: bool, ?load_addons: bool) { (RubyLsp::Server server, URI::Generic uri) -> T } -> T
|
15
12
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
16
13
|
&block)
|
@@ -12,16 +12,23 @@ module RubyLsp
|
|
12
12
|
class LspReporter
|
13
13
|
include Singleton
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
# https://code.visualstudio.com/api/references/vscode-api#Position
|
16
|
+
#: type position = { line: Integer, character: Integer }
|
17
|
+
|
18
|
+
# https://code.visualstudio.com/api/references/vscode-api#Range
|
19
|
+
#: type range = { start: position, end: position }
|
20
|
+
|
21
|
+
# https://code.visualstudio.com/api/references/vscode-api#BranchCoverage
|
22
|
+
#: type branch_coverage = { executed: Integer, label: String, location: range }
|
23
|
+
|
24
|
+
# https://code.visualstudio.com/api/references/vscode-api#StatementCoverage
|
25
|
+
#: type statement_coverage = { executed: Integer, location: position, branches: Array[branch_coverage] }
|
17
26
|
|
18
27
|
#: -> void
|
19
28
|
def initialize
|
20
29
|
dir_path = File.join(Dir.tmpdir, "ruby-lsp")
|
21
30
|
FileUtils.mkdir_p(dir_path)
|
22
31
|
|
23
|
-
# Remove in 1 month once updates have rolled out
|
24
|
-
legacy_port_path = File.join(dir_path, "test_reporter_port")
|
25
32
|
port_db_path = File.join(dir_path, "test_reporter_port_db.json")
|
26
33
|
port = ENV["RUBY_LSP_REPORTER_PORT"]
|
27
34
|
|
@@ -32,8 +39,6 @@ module RubyLsp
|
|
32
39
|
elsif File.exist?(port_db_path)
|
33
40
|
db = JSON.load_file(port_db_path)
|
34
41
|
TCPSocket.new("localhost", db[Dir.pwd])
|
35
|
-
elsif File.exist?(legacy_port_path)
|
36
|
-
TCPSocket.new("localhost", File.read(legacy_port_path))
|
37
42
|
else
|
38
43
|
# For tests that don't spawn the TCP server
|
39
44
|
require "stringio"
|
@@ -129,7 +134,7 @@ module RubyLsp
|
|
129
134
|
# ["Foo", :bar, 6, 21, 6, 65] => 0
|
130
135
|
# }
|
131
136
|
# }
|
132
|
-
#: -> Hash[String,
|
137
|
+
#: -> Hash[String, statement_coverage]
|
133
138
|
def gather_coverage_results
|
134
139
|
# Ignore coverage results inside dependencies
|
135
140
|
bundle_path = Bundler.bundle_path.to_s
|
@@ -187,7 +192,7 @@ module RubyLsp
|
|
187
192
|
|
188
193
|
#: -> void
|
189
194
|
def at_exit
|
190
|
-
internal_shutdown unless invoked_shutdown
|
195
|
+
internal_shutdown unless @invoked_shutdown
|
191
196
|
end
|
192
197
|
|
193
198
|
class << self
|
@@ -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
|
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -5,7 +5,6 @@ module RubyLsp
|
|
5
5
|
# rubocop:disable RubyLsp/UseLanguageServerAliases
|
6
6
|
Interface = LanguageServer::Protocol::Interface
|
7
7
|
Constant = LanguageServer::Protocol::Constant
|
8
|
-
Transport = LanguageServer::Protocol::Transport
|
9
8
|
# rubocop:enable RubyLsp/UseLanguageServerAliases
|
10
9
|
|
11
10
|
# Used to indicate that a request shouldn't return a response
|
@@ -20,6 +19,7 @@ module RubyLsp
|
|
20
19
|
"Gemfile"
|
21
20
|
end #: String
|
22
21
|
GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp/#guessed-types"
|
22
|
+
TEST_PATH_PATTERN = "**/{test,spec,features}/**/{*_test.rb,test_*.rb,*_spec.rb,*.feature}"
|
23
23
|
|
24
24
|
# Request delegation for embedded languages is not yet standardized into the language server specification. Here we
|
25
25
|
# use this custom error class as a way to return a signal to the client that the request should be delegated to the
|
@@ -31,29 +31,30 @@ module RubyLsp
|
|
31
31
|
CODE = -32900
|
32
32
|
end
|
33
33
|
|
34
|
+
class AbstractMethodInvokedError < StandardError; end
|
35
|
+
|
34
36
|
BUNDLE_COMPOSE_FAILED_CODE = -33000
|
35
37
|
|
36
38
|
# A notification to be sent to the client
|
39
|
+
# @abstract
|
37
40
|
class Message
|
38
|
-
extend T::Sig
|
39
|
-
extend T::Helpers
|
40
|
-
|
41
41
|
#: String
|
42
42
|
attr_reader :method
|
43
43
|
|
44
44
|
#: Object
|
45
45
|
attr_reader :params
|
46
46
|
|
47
|
-
abstract!
|
48
|
-
|
49
47
|
#: (method: String, params: Object) -> void
|
50
48
|
def initialize(method:, params:)
|
51
49
|
@method = method
|
52
50
|
@params = params
|
53
51
|
end
|
54
52
|
|
55
|
-
|
56
|
-
|
53
|
+
# @abstract
|
54
|
+
#: -> Hash[Symbol, untyped]
|
55
|
+
def to_hash
|
56
|
+
raise AbstractMethodInvokedError
|
57
|
+
end
|
57
58
|
end
|
58
59
|
|
59
60
|
class Notification < Message
|
@@ -246,9 +247,6 @@ module RubyLsp
|
|
246
247
|
|
247
248
|
# A request configuration, to turn on/off features
|
248
249
|
class RequestConfig
|
249
|
-
#: Hash[Symbol, bool]
|
250
|
-
attr_accessor :configuration
|
251
|
-
|
252
250
|
#: (Hash[Symbol, bool] configuration) -> void
|
253
251
|
def initialize(configuration)
|
254
252
|
@configuration = configuration
|
@@ -258,6 +256,11 @@ module RubyLsp
|
|
258
256
|
def enabled?(feature)
|
259
257
|
@configuration[:enableAll] || @configuration[feature]
|
260
258
|
end
|
259
|
+
|
260
|
+
#: (Hash[Symbol, bool]) -> void
|
261
|
+
def merge!(hash)
|
262
|
+
@configuration.merge!(hash)
|
263
|
+
end
|
261
264
|
end
|
262
265
|
|
263
266
|
class SorbetLevel
|
@@ -302,4 +305,37 @@ module RubyLsp
|
|
302
305
|
#: -> bool
|
303
306
|
def true_or_higher? = @level == :true || @level == :strict
|
304
307
|
end
|
308
|
+
|
309
|
+
# Reads JSON RPC messages from the given IO in a loop
|
310
|
+
class MessageReader
|
311
|
+
#: (IO) -> void
|
312
|
+
def initialize(io)
|
313
|
+
@io = io
|
314
|
+
end
|
315
|
+
|
316
|
+
#: () { (Hash[Symbol, untyped]) -> void } -> void
|
317
|
+
def each_message(&block)
|
318
|
+
while (headers = @io.gets("\r\n\r\n"))
|
319
|
+
raw_message = @io.read(headers[/Content-Length: (\d+)/i, 1].to_i) #: as !nil
|
320
|
+
block.call(JSON.parse(raw_message, symbolize_names: true))
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Writes JSON RPC messages to the given IO
|
326
|
+
class MessageWriter
|
327
|
+
#: (IO) -> void
|
328
|
+
def initialize(io)
|
329
|
+
@io = io
|
330
|
+
end
|
331
|
+
|
332
|
+
#: (Hash[Symbol, untyped]) -> void
|
333
|
+
def write(message)
|
334
|
+
message[:jsonrpc] = "2.0"
|
335
|
+
json_message = message.to_json
|
336
|
+
|
337
|
+
@io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
338
|
+
@io.flush
|
339
|
+
end
|
340
|
+
end
|
305
341
|
end
|
@@ -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.
|
4
|
+
version: 0.26.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -63,20 +63,6 @@ dependencies:
|
|
63
63
|
- - "<"
|
64
64
|
- !ruby/object:Gem::Version
|
65
65
|
version: '5'
|
66
|
-
- !ruby/object:Gem::Dependency
|
67
|
-
name: sorbet-runtime
|
68
|
-
requirement: !ruby/object:Gem::Requirement
|
69
|
-
requirements:
|
70
|
-
- - ">="
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
version: 0.5.10782
|
73
|
-
type: :runtime
|
74
|
-
prerelease: false
|
75
|
-
version_requirements: !ruby/object:Gem::Requirement
|
76
|
-
requirements:
|
77
|
-
- - ">="
|
78
|
-
- !ruby/object:Gem::Version
|
79
|
-
version: 0.5.10782
|
80
66
|
description: An opinionated language server for Ruby
|
81
67
|
email:
|
82
68
|
- ruby@shopify.com
|
@@ -145,7 +131,6 @@ files:
|
|
145
131
|
- lib/ruby_lsp/listeners/spec_style.rb
|
146
132
|
- lib/ruby_lsp/listeners/test_discovery.rb
|
147
133
|
- lib/ruby_lsp/listeners/test_style.rb
|
148
|
-
- lib/ruby_lsp/load_sorbet.rb
|
149
134
|
- lib/ruby_lsp/node_context.rb
|
150
135
|
- lib/ruby_lsp/rbs_document.rb
|
151
136
|
- lib/ruby_lsp/requests/code_action_resolve.rb
|
@@ -209,6 +194,7 @@ files:
|
|
209
194
|
- lib/ruby_lsp/test_reporters/test_unit_reporter.rb
|
210
195
|
- lib/ruby_lsp/type_inferrer.rb
|
211
196
|
- lib/ruby_lsp/utils.rb
|
197
|
+
- static_docs/break.md
|
212
198
|
- static_docs/yield.md
|
213
199
|
homepage: https://github.com/Shopify/ruby-lsp
|
214
200
|
licenses:
|
data/lib/ruby_lsp/load_sorbet.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# typed: true
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "sorbet-runtime"
|
5
|
-
|
6
|
-
begin
|
7
|
-
T::Configuration.default_checked_level = :never
|
8
|
-
# Suppresses call validation errors
|
9
|
-
T::Configuration.call_validation_error_handler = ->(*arg) {}
|
10
|
-
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
11
|
-
T::Configuration.inline_type_error_handler = ->(*arg) {}
|
12
|
-
# Suppresses errors caused by incorrect parameter ordering
|
13
|
-
T::Configuration.sig_validation_error_handler = ->(*arg) {}
|
14
|
-
rescue
|
15
|
-
# Need this rescue so that if another gem has
|
16
|
-
# already set the checked level by the time we
|
17
|
-
# get to it, we don't fail outright.
|
18
|
-
nil
|
19
|
-
end
|
20
|
-
|
21
|
-
module RubyLsp
|
22
|
-
# No-op all inline type assertions defined in T
|
23
|
-
module InlineTypeAssertions
|
24
|
-
def absurd(value)
|
25
|
-
value
|
26
|
-
end
|
27
|
-
|
28
|
-
def any(type_a, type_b, *types)
|
29
|
-
T::Types::Union.new([type_a, type_b, *types])
|
30
|
-
end
|
31
|
-
|
32
|
-
def assert_type!(value, type, checked: true)
|
33
|
-
value
|
34
|
-
end
|
35
|
-
|
36
|
-
def bind(value, type, checked: true)
|
37
|
-
value
|
38
|
-
end
|
39
|
-
|
40
|
-
def cast(value, type, checked: true)
|
41
|
-
value
|
42
|
-
end
|
43
|
-
|
44
|
-
def let(value, type, checked: true)
|
45
|
-
value
|
46
|
-
end
|
47
|
-
|
48
|
-
def must(arg)
|
49
|
-
arg
|
50
|
-
end
|
51
|
-
|
52
|
-
def nilable(type)
|
53
|
-
T::Types::Union.new([type, T::Utils::Nilable::NIL_TYPE])
|
54
|
-
end
|
55
|
-
|
56
|
-
def unsafe(value)
|
57
|
-
value
|
58
|
-
end
|
59
|
-
|
60
|
-
T.singleton_class.prepend(self)
|
61
|
-
end
|
62
|
-
end
|