diff --git a/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb b/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb index b78b5460e..e521a4d07 100644 --- a/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +++ b/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb @@ -32,10 +32,14 @@ class InstanceVariableTarget < Target #: String attr_reader :name - #: (String name) -> void - def initialize(name) + #: Array[String] + attr_reader :owner_ancestors + + #: (String name, Array[String] owner_ancestors) -> void + def initialize(name, owner_ancestors) super() @name = name + @owner_ancestors = owner_ancestors end end @@ -322,7 +326,10 @@ def collect_constant_references(name, location) def collect_instance_variable_references(name, location, declaration) return unless @target.is_a?(InstanceVariableTarget) && name == @target.name - @references << Reference.new(name, location, declaration: declaration) + receiver_type = Index.actual_nesting(@stack, nil).join("::") + if @target.owner_ancestors.include?(receiver_type) + @references << Reference.new(name, location, declaration: declaration) + end end end end diff --git a/lib/ruby_indexer/test/reference_finder_test.rb b/lib/ruby_indexer/test/reference_finder_test.rb index 4f11f3be4..ed5028d5a 100644 --- a/lib/ruby_indexer/test/reference_finder_test.rb +++ b/lib/ruby_indexer/test/reference_finder_test.rb @@ -216,22 +216,43 @@ def foo assert_equal(11, refs[2].location.start_line) end - def test_finds_instance_variable_read_references - refs = find_instance_variable_references("@foo", <<~RUBY) + def test_finds_instance_variable_references + refs = find_instance_variable_references("@name", ["Foo"], <<~RUBY) class Foo - def foo - @foo + def initialize + @name = "foo" + end + def name + @name + end + def name_capital + @name[0] + end + end + + class Bar + def initialize + @name = "foo" + end + def name + @name end end RUBY - assert_equal(1, refs.size) + assert_equal(3, refs.size) - assert_equal("@foo", refs[0].name) + assert_equal("@name", refs[0].name) assert_equal(3, refs[0].location.start_line) + + assert_equal("@name", refs[1].name) + assert_equal(6, refs[1].location.start_line) + + assert_equal("@name", refs[2].name) + assert_equal(9, refs[2].location.start_line) end def test_finds_instance_variable_write_references - refs = find_instance_variable_references("@foo", <<~RUBY) + refs = find_instance_variable_references("@foo", ["Foo"], <<~RUBY) class Foo def write @foo = 1 @@ -252,26 +273,70 @@ def write assert_equal(7, refs[4].location.start_line) end - def test_finds_instance_variable_references_ignore_context - refs = find_instance_variable_references("@name", <<~RUBY) - class Foo + def test_finds_instance_variable_references_in_owner_ancestors + refs = find_instance_variable_references("@name", ["Foo", "Base", "Top", "Parent"], <<~RUBY) + module Base + def change_name(name) + @name = name + end def name + @name + end + + module ::Top + def name + @name + end + end + end + + class Parent + def initialize + @name = "parent" + end + def name_capital + @name[0] + end + end + + class Foo < Parent + include Base + def initialize @name = "foo" end + def name + @name + end end + class Bar def name @name = "bar" end end RUBY - assert_equal(2, refs.size) + assert_equal(7, refs.size) assert_equal("@name", refs[0].name) assert_equal(3, refs[0].location.start_line) assert_equal("@name", refs[1].name) - assert_equal(8, refs[1].location.start_line) + assert_equal(6, refs[1].location.start_line) + + assert_equal("@name", refs[2].name) + assert_equal(11, refs[2].location.start_line) + + assert_equal("@name", refs[3].name) + assert_equal(18, refs[3].location.start_line) + + assert_equal("@name", refs[4].name) + assert_equal(21, refs[4].location.start_line) + + assert_equal("@name", refs[5].name) + assert_equal(28, refs[5].location.start_line) + + assert_equal("@name", refs[6].name) + assert_equal(31, refs[6].location.start_line) end def test_accounts_for_reopened_classes @@ -310,8 +375,8 @@ def find_method_references(method_name, source) find_references(target, source) end - def find_instance_variable_references(instance_variable_name, source) - target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name) + def find_instance_variable_references(instance_variable_name, owner_ancestors, source) + target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name, owner_ancestors) find_references(target, source) end diff --git a/lib/ruby_lsp/listeners/hover.rb b/lib/ruby_lsp/listeners/hover.rb index c533853f5..1d931d103 100644 --- a/lib/ruby_lsp/listeners/hover.rb +++ b/lib/ruby_lsp/listeners/hover.rb @@ -7,6 +7,9 @@ class Hover include Requests::Support::Common ALLOWED_TARGETS = [ + Prism::BeginNode, + Prism::BreakNode, + Prism::CaseNode, Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantWriteNode, @@ -54,6 +57,7 @@ def initialize(response_builder, global_state, uri, node_context, dispatcher, so dispatcher.register( self, + :on_break_node_enter, :on_constant_read_node_enter, :on_constant_write_node_enter, :on_constant_path_node_enter, @@ -244,6 +248,11 @@ def on_class_variable_write_node_enter(node) handle_class_variable_hover(node.name.to_s) end + #: (Prism::BreakNode node) -> void + def on_break_node_enter(node) + handle_keyword_documentation(node.keyword) + end + private #: ((Prism::InterpolatedStringNode | Prism::StringNode) node) -> void diff --git a/lib/ruby_lsp/requests/references.rb b/lib/ruby_lsp/requests/references.rb index 1c6e1fe6a..2142eb26d 100644 --- a/lib/ruby_lsp/requests/references.rb +++ b/lib/ruby_lsp/requests/references.rb @@ -101,7 +101,11 @@ def create_reference_target(target_node, node_context) Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode, Prism::InstanceVariableWriteNode - RubyIndexer::ReferenceFinder::InstanceVariableTarget.new(target_node.name.to_s) + receiver_type = @global_state.type_inferrer.infer_receiver_type(node_context) + return unless receiver_type + + ancestors = @global_state.index.linearized_ancestors_of(receiver_type.name) + RubyIndexer::ReferenceFinder::InstanceVariableTarget.new(target_node.name.to_s, ancestors) when Prism::CallNode, Prism::DefNode RubyIndexer::ReferenceFinder::MethodTarget.new(target_node.name.to_s) end diff --git a/lib/ruby_lsp/static_docs.rb b/lib/ruby_lsp/static_docs.rb index 2749ff868..d56e04dd7 100644 --- a/lib/ruby_lsp/static_docs.rb +++ b/lib/ruby_lsp/static_docs.rb @@ -15,5 +15,8 @@ module RubyLsp # A map of keyword => short documentation to be displayed on hover or completion KEYWORD_DOCS = { "yield" => "Invokes the passed block with the given arguments", + "case" => "Starts a case expression for pattern matching or multiple condition checking", + "begin" => "Starts an exception handling block or ensures code is executed in order", + "break" => "Terminates the execution of a block, loop, or method", }.freeze #: Hash[String, String] end diff --git a/static_docs/alias.md b/static_docs/alias.md new file mode 100644 index 000000000..83e51ad71 --- /dev/null +++ b/static_docs/alias.md @@ -0,0 +1,132 @@ +# Alias + +In Ruby, the `alias` keyword creates an alternative name for an existing method or constant. This allows you to call the same method using different names, which is particularly useful for method deprecation, creating shortcuts, or improving code readability. + +```ruby +# Basic method aliasing +class User + def full_name + "#{first_name} #{last_name}" + end + + alias name full_name +end + +user = User.new +user.full_name # => "John Smith" +user.name # => "John Smith" +``` + +When you create an alias, it creates a copy of the method at the time the alias is defined. This means that if you later modify the original method, the alias will still use the old version. + +```ruby +class Calculator + def add(a, b) + a + b + end + + alias plus add + + # Modifying the original method + def add(a, b) + puts "Adding #{a} and #{b}" + a + b + end +end + +calc = Calculator.new +calc.plus(2, 3) # => 5 (no output) +calc.add(2, 3) # Prints "Adding 2 and 3" then returns 5 +``` + +## Using alias_method + +Ruby also provides `alias_method`, which is more flexible as it can accept dynamic method names and is more commonly used in modern Ruby: + +```ruby +class Service + def process_data + puts "Processing..." + end + + # Using alias_method with symbols + alias_method :execute, :process_data + + # Can also use strings + alias_method "run", "process_data" +end + +service = Service.new +service.process_data # => "Processing..." +service.execute # => "Processing..." +service.run # => "Processing..." +``` + +## Common Use Cases + +### Method Deprecation + +```ruby +class API + def fetch_users + # New implementation + User.all.includes(:preferences) + end + + alias get_users fetch_users + + def get_users + warn "[DEPRECATED] `get_users` is deprecated. Please use `fetch_users` instead" + fetch_users + end +end +``` + +### Creating Shorter Names + +```ruby +class StringUtils + def self.convert_to_uppercase(text) + text.upcase + end + + class << self + alias up convert_to_uppercase + end +end + +StringUtils.convert_to_uppercase("hello") # => "HELLO" +StringUtils.up("hello") # => "HELLO" +``` + +### Aliasing Operators + +```ruby +class Vector + def initialize(x, y) + @x = x + @y = y + end + + def add(other) + Vector.new(@x + other.x, @y + other.y) + end + + # Make + work the same as add + alias + add + + protected + + attr_reader :x, :y +end + +v1 = Vector.new(1, 2) +v2 = Vector.new(3, 4) +v3 = v1 + v2 # Same as v1.add(v2) +``` + +Remember that while aliasing can be useful, it should be used judiciously. Too many aliases can make code harder to understand and maintain. It's best used for: +- Creating more intuitive method names +- Supporting backward compatibility +- Implementing operator overloading +- Creating shortcuts for frequently used methods \ No newline at end of file diff --git a/static_docs/begin.md b/static_docs/begin.md new file mode 100644 index 000000000..23f6c6549 --- /dev/null +++ b/static_docs/begin.md @@ -0,0 +1,160 @@ +# Begin + +In Ruby, the `begin` keyword serves two main purposes: starting an exception handling block and ensuring code executes in a specific order. It's commonly used with `rescue`, `else`, and `ensure` clauses. + +## Exception Handling + +The most common use of `begin` is for exception handling: + +```ruby +begin + # Code that might raise an exception + result = dangerous_operation +rescue StandardError => e + # Handle the error + puts "Error occurred: #{e.message}" +else + # Runs only if no exception was raised + puts "Operation succeeded with result: #{result}" +ensure + # Always runs, whether an exception occurred or not + cleanup_resources +end +``` + +You can rescue multiple exception types and handle them differently: + +```ruby +begin + response = HTTP.get("https://api.example.com/data") + data = JSON.parse(response.body) +rescue HTTP::ConnectionError => e + puts "Network error: #{e.message}" +rescue JSON::ParserError => e + puts "Invalid JSON response: #{e.message}" +rescue StandardError => e + puts "Unexpected error: #{e.message}" +end +``` + +## Ensuring Code Execution Order + +The `begin` keyword can also ensure that a block of code executes in order, particularly useful in method definitions: + +```ruby +def process_file(path) + file = File.open(path) + begin + content = file.read + process_content(content) + ensure + file.close + end +end +``` + +## Inline Exception Handling + +Ruby allows a more concise syntax for simple exception handling using `begin` inline: + +```ruby +# Single-line rescue +result = begin + potentially_dangerous_operation +rescue StandardError + default_value +end + +# Equivalent to: +result = potentially_dangerous_operation rescue default_value +``` + +## Begin with Retry + +The `begin` block can be used with `retry` to attempt an operation multiple times: + +```ruby +attempts = 0 +begin + response = HTTP.get("https://api.example.com/data") +rescue HTTP::ConnectionError => e + attempts += 1 + if attempts < 3 + puts "Retrying... (attempt #{attempts + 1})" + retry + else + puts "Failed after #{attempts} attempts" + raise + end +end +``` + +## Implicit Begin Blocks + +Ruby provides implicit `begin` blocks in certain contexts, such as method definitions and class bodies: + +```ruby +# Explicit begin block +def save_record + begin + perform_save + rescue ActiveRecord::RecordInvalid => e + handle_validation_error(e) + end +end + +# Equivalent implicit begin block +def save_record + perform_save +rescue ActiveRecord::RecordInvalid => e + handle_validation_error(e) +end +``` + +## Best Practices + +1. Only rescue specific exceptions you can handle: +```ruby +begin + # Some code +rescue SpecificError => e + # Handle specific error +end +``` + +2. Use implicit begin blocks when possible for cleaner code: +```ruby +def process_data + # Implicit begin + parse_data + save_result +rescue ParseError => e + log_error(e) +end +``` + +3. Always include an `ensure` block when resources need to be cleaned up: +```ruby +def process_file(path) + file = File.open(path) + begin + process_content(file.read) + ensure + file.close + end +end +``` + +4. Use `else` clause for code that should only run if no exceptions occur: +```ruby +begin + result = perform_operation +rescue OperationError => e + handle_error(e) +else + # Only runs if no exception occurred + log_success(result) +ensure + cleanup +end +``` \ No newline at end of file diff --git a/static_docs/break.md b/static_docs/break.md new file mode 100644 index 000000000..745fdd38e --- /dev/null +++ b/static_docs/break.md @@ -0,0 +1,101 @@ +# Break + +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. + +```ruby +# Basic break usage in a loop +5.times do |i| + break if i == 3 + + puts i +end +# Output: +# 0 +# 1 +# 2 +``` + +The `break` statement can be used with any of Ruby's iteration methods or loops. + +```ruby +# Using break with different types of loops +array = [1, 2, 3, 4, 5] + +# With each +array.each do |num| + break if num > 3 + + puts "Number: #{num}" +end +# Output: +# Number: 1 +# Number: 2 +# Number: 3 + +# With infinite loop +i = 0 +loop do + i += 1 + break if i >= 5 + + puts "Count: #{i}" +end +``` + +## Break with a Value + +When used inside a block, `break` can return a value that becomes the result of the method call. + +```ruby +# Using break with a return value +result = [1, 2, 3, 4, 5].map do |num| + break "Too large!" if num > 3 + + num * 2 +end +puts result # Output: "Too large!" + +# Break in find method +number = (1..100).find do |n| + break n if n > 50 && n.even? +end +puts number # Output: 52 +``` + +## Break in Nested Loops + +When using `break` in nested loops, it only exits the innermost loop unless explicitly used with a label (not commonly used in Ruby). + +```ruby +# Break in nested iteration +result = (1..3).each do |i| + puts "Outer loop: #{i}" + + (1..3).each do |j| + break if j == 2 + + puts " Inner loop: #{j}" + end +end +# Output: +# Outer loop: 1 +# Inner loop: 1 +# Outer loop: 2 +# Inner loop: 1 +# Outer loop: 3 +# Inner loop: 1 + +# Breaking from nested loops using a flag +found = false +(1..3).each do |i| + (1..3).each do |j| + if i * j == 4 + found = true + break + end + end + break if found +end +``` + +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. \ No newline at end of file diff --git a/static_docs/case.md b/static_docs/case.md new file mode 100644 index 000000000..c1d24300e --- /dev/null +++ b/static_docs/case.md @@ -0,0 +1,99 @@ +# Case Statement + +In Ruby, the `case` statement provides a clean way to express conditional logic when you need to compare a value against multiple conditions. It's similar to if/else chains but often more readable and concise. + +```ruby +# Basic case statement comparing a value against multiple conditions +grade = "A" + +case grade +when "A" + puts "Excellent!" +when "B" + puts "Good job!" +when "C" + puts "Fair" +else + puts "Need improvement" +end +``` + +The `case` statement can also work with ranges, multiple values, and even custom matching using the `===` operator. + +```ruby +# Case statement with ranges and multiple conditions +score = 85 + +case score +when 90..100 + puts "A grade" +when 80..89 + puts "B grade" +when 70..79 + puts "C grade" +else + puts "Need improvement" +end + +# Case with multiple values in a single when clause +day = "Saturday" + +case day +when "Saturday", "Sunday" + puts "Weekend!" +else + puts "Weekday" +end +``` + +## Pattern Matching (Ruby 2.7+) + +Starting from Ruby 2.7, `case` statements support pattern matching, which provides powerful ways to match and destructure data. + +```ruby +# Pattern matching with arrays +data = [1, 2, 3] + +case data +when [1, 2, 3] + puts "Exact match!" +when [1, *rest] + puts "Starts with 1, followed by #{rest}" +when Array + puts "Any array" +else + puts "Not an array" +end + +# Pattern matching with hashes (Ruby 3.0+) +user = { name: "Alice", age: 30 } + +case user +in { name: "Alice", age: } + puts "Alice is #{age} years old" +in { name:, age: 20.. } + puts "#{name} is at least 20" +else + puts "No match" +end +``` + +## Case without an argument + +Ruby also allows `case` statements without an explicit argument, which acts like a series of if/elsif conditions. + +```ruby +# Case statement without an argument +# rubocop:disable Style/EmptyCaseCondition +case +when Time.now.saturday? + puts "It's Saturday!" +when Time.now.sunday? + puts "It's Sunday!" +else + puts "It's a weekday" +end +# rubocop:enable Style/EmptyCaseCondition +``` + +The case statement is particularly useful when you have multiple conditions to check against a single value, or when you want to use pattern matching to destructure complex data structures. diff --git a/static_docs/do.md b/static_docs/do.md new file mode 100644 index 000000000..f812f4f91 --- /dev/null +++ b/static_docs/do.md @@ -0,0 +1,89 @@ +# Do + +In Ruby, the `do` keyword is used to create blocks of code, typically in conjunction with iterators, loops, or method definitions. It's often interchangeable with curly braces `{}`, but with different precedence rules and conventional usage patterns. + +```ruby +# Basic do...end block with an iterator +[1, 2, 3].each do |number| + puts number +end +# Output: +# 1 +# 2 +# 3 +``` + +## Do vs Curly Braces + +While `do...end` and `{}` can often be used interchangeably, there are conventional and practical differences. + +```ruby +# Single-line blocks typically use curly braces +[1, 2, 3].map { |n| n * 2 } + +# Multi-line blocks typically use do...end +[1, 2, 3].map do |n| + result = n * 2 + result + 1 +end + +# Precedence differences +puts [1, 2, 3].map { |n| n * 2 } # Works as expected +puts [1, 2, 3].map do |n| n * 2 end # May not work as expected due to precedence +``` + +## Do with While and Until + +The `do` keyword is also used with `while` and `until` loops to create do-while style loops where the condition is checked after the first iteration. + +```ruby +# Basic while loop +i = 0 +while i < 3 do # 'do' is optional here + puts i + i += 1 +end + +# do...while equivalent (condition checked after) +i = 0 +begin + puts i + i += 1 +end while i < 3 + +# until with do +j = 0 +until j > 3 do # 'do' is optional here + puts j + j += 1 +end +``` + +## Do in Method Definitions + +When defining methods that take blocks, the block can be passed using either `do...end` or curly braces. + +```ruby +# Method that takes a block +def repeat_twice + 2.times do + yield if block_given? + end +end + +# Using the method with do...end +repeat_twice do + puts "Hello!" +end +# Output: +# Hello! +# Hello! + +# Using the method with curly braces +repeat_twice { puts "Hi!" } +# Output: +# Hi! +# Hi! +``` + +The `do` keyword is fundamental to Ruby's block syntax and is particularly useful for creating readable, multi-line blocks of code. The choice between `do...end` and `{}` is often a matter of convention and readability, with `do...end` being preferred for multi-line blocks. \ No newline at end of file diff --git a/static_docs/else.md b/static_docs/else.md new file mode 100644 index 000000000..eb887d060 --- /dev/null +++ b/static_docs/else.md @@ -0,0 +1,85 @@ +# Else + +The `else` keyword in Ruby is used to define code that should be executed when a condition in an `if` statement or `case` expression evaluates to `false`. It provides an alternative execution path when the main condition is not met. + +## Basic Usage with If Statements + +```ruby +if condition + # Code executed when condition is true +else + # Code executed when condition is false +end +``` + +For example: + +```ruby +age = 15 + +if age >= 18 + puts "You can vote!" +else + puts "You're too young to vote." +end +# => You're too young to vote. +``` + +## Multiple Conditions with Else + +The `else` clause is always the final branch in a conditional statement. It catches all cases that weren't matched by previous conditions: + +```ruby +score = 85 + +if score >= 90 + puts "A grade" +elsif score >= 80 + puts "B grade" +else + puts "C grade or lower" +end +# => B grade +``` + +## Using Else with Case Statements + +The `else` keyword can also be used with `case` statements as a default branch when no other conditions match: + +```ruby +fruit = "orange" + +case fruit +when "apple" + puts "It's an apple" +when "banana" + puts "It's a banana" +else + puts "It's something else" +end +# => It's something else +``` + +## Else in Ternary Operations + +Ruby also allows using a condensed if/else format called a ternary operator: + +```ruby +age = 20 +message = age >= 18 ? "Adult" : "Minor" +# => "Adult" +``` + +## Else with Unless + +The `else` keyword can be used with `unless`, which is equivalent to `if !condition`: + +```ruby +unless user.admin? + puts "Access denied" +else + puts "Welcome, admin!" +end +``` + +Remember that while `else` provides a way to handle alternative cases, too many conditional branches can make code harder to maintain. Consider refactoring complex conditional logic into separate methods or using polymorphism when appropriate. \ No newline at end of file diff --git a/static_docs/elsif.md b/static_docs/elsif.md new file mode 100644 index 000000000..cb872de50 --- /dev/null +++ b/static_docs/elsif.md @@ -0,0 +1,135 @@ +# Elsif + +The `elsif` keyword in Ruby allows you to check multiple conditions in sequence. It's used to add additional conditions to an `if` statement when the previous condition is false, providing a way to handle multiple distinct cases. + +## Basic Usage + +```ruby +if condition1 + # Code executed when condition1 is true +elsif condition2 + # Code executed when condition1 is false AND condition2 is true +else + # Code executed when all conditions are false +end +``` + +For example: + +```ruby +temperature = 75 + +if temperature > 90 + puts "It's very hot!" +elsif temperature > 70 + puts "It's warm." +elsif temperature > 50 + puts "It's cool." +else + puts "It's cold!" +end +# => It's warm. +``` + +## Multiple Elsif Branches + +You can chain multiple `elsif` statements to handle various conditions: + +```ruby +grade = 85 + +if grade >= 90 + puts "A grade" +elsif grade >= 80 + puts "B grade" +elsif grade >= 70 + puts "C grade" +elsif grade >= 60 + puts "D grade" +else + puts "F grade" +end +# => B grade +``` + +## Elsif vs Case Statements + +While `elsif` is useful for checking different conditions, when you're comparing the same value against multiple options, a `case` statement might be more readable: + +```ruby +# Using elsif +day = "Monday" + +if day == "Saturday" || day == "Sunday" + puts "Weekend!" +elsif day == "Friday" + puts "TGIF!" +else + puts "Weekday" +end + +# Equivalent case statement (often clearer for this type of comparison) +case day +when "Saturday", "Sunday" + puts "Weekend!" +when "Friday" + puts "TGIF!" +else + puts "Weekday" +end +``` + +## Complex Conditions + +`elsif` conditions can include complex boolean expressions: + +```ruby +user_age = 25 +has_id = true +is_member = false + +if user_age < 18 + puts "Too young" +elsif user_age >= 18 && has_id && is_member + puts "Welcome to the VIP section" +elsif user_age >= 18 && has_id + puts "Welcome to the regular section" +else + puts "Please provide ID" +end +# => Welcome to the regular section +``` + +## Best Practices + +1. Consider using a `case` statement when comparing a single value against multiple options +2. Keep conditions simple and readable +3. Consider extracting complex conditions into well-named methods +4. Don't overuse `elsif` - if you have too many conditions, consider refactoring into a more object-oriented approach + +```ruby +# Instead of many elsif statements +def process_status(status) + if status == "pending" + handle_pending + elsif status == "processing" + handle_processing + elsif status == "completed" + handle_completed + elsif status == "failed" + handle_failed + end +end + +# Consider using a hash or object-oriented approach +STATUS_HANDLERS = { + "pending" => :handle_pending, + "processing" => :handle_processing, + "completed" => :handle_completed, + "failed" => :handle_failed +} + +def process_status(status) + send(STATUS_HANDLERS[status]) +end +``` \ No newline at end of file diff --git a/static_docs/next.md b/static_docs/next.md new file mode 100644 index 000000000..5a646fcf3 --- /dev/null +++ b/static_docs/next.md @@ -0,0 +1,91 @@ +# Next + +In Ruby, the `next` keyword is used to skip to the next iteration of a loop. It's similar to `continue` in other programming languages and helps control the flow of iteration without breaking out of the loop entirely. + +```ruby +# Basic next usage in a loop +5.times do |i| + next if i == 2 # Skip when i is 2 + puts i +end +# Output: +# 0 +# 1 +# 3 +# 4 +``` + +The `next` statement can be used with any of Ruby's iteration methods or loops. + +```ruby +# Using next with different types of loops +array = [1, 2, 3, 4, 5] + +# With each +array.each do |num| + next if num.even? # Skip even numbers + puts "Odd number: #{num}" +end +# Output: +# Odd number: 1 +# Odd number: 3 +# Odd number: 5 + +# With while loop +i = 0 +while i < 5 + i += 1 + next if i == 3 # Skip when i is 3 + puts "Current number: #{i}" +end +``` + +## Next with a Value + +When used inside a block that's expected to return values (like `map`), `next` can take an argument that serves as the value for the current iteration. + +```ruby +# Using next with a value in map +result = [1, 2, 3, 4, 5].map do |num| + next 0 if num.even? # Replace even numbers with 0 + num * 2 # Double odd numbers +end +puts result.inspect +# Output: [2, 0, 6, 0, 10] + +# Another example with select_map (Ruby 2.7+) +numbers = [1, 2, 3, 4, 5].map do |num| + next nil if num < 3 # Replace numbers less than 3 with nil + num ** 2 # Square numbers >= 3 +end +puts numbers.compact.inspect # Remove nil values +# Output: [9, 16, 25] +``` + +## Next in Nested Loops + +When using `next` in nested loops, it only affects the innermost loop where it appears. + +```ruby +# Next in nested iteration +(1..3).each do |i| + puts "Outer loop: #{i}" + + (1..3).each do |j| + next if i == j # Skip when numbers match + puts " Inner loop: #{j}" + end +end +# Output: +# Outer loop: 1 +# Inner loop: 2 +# Inner loop: 3 +# Outer loop: 2 +# Inner loop: 1 +# Inner loop: 3 +# Outer loop: 3 +# Inner loop: 1 +# Inner loop: 2 +``` + +The `next` keyword is particularly useful when you want to skip certain iterations based on conditions without breaking the entire loop's execution. It helps make code more readable by avoiding deeply nested conditional blocks. \ No newline at end of file