123456789_123456789_123456789_123456789_123456789_

Class: RuboCop::Cop::Style::BlockDelimiters

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
self, ::RuboCop::Cop::AutoCorrector, ::RuboCop::Cop::Base, ::RuboCop::ExcludeLimit, NodePattern::Macros, RuboCop::AST::Sexp
Instance Chain:
Inherits: RuboCop::Cop::Base
Defined in: lib/rubocop/cop/style/block_delimiters.rb

Overview

Check for uses of braces or do/end around single line or multi-line blocks.

Methods that can be either procedural or functional and cannot be categorised from their usage alone is ignored. lambda, proc, and it are their defaults. Additional methods can be added to the ::RuboCop::Cop::AllowedMethods.

Examples:

EnforcedStyle: line_count_based (default)

# bad - single line block
items.each do |item| item / 5 end

# good - single line block
items.each { |item| item / 5 }

# bad - multi-line block
things.map { |thing|
  something = thing.some_method
  process(something)
}

# good - multi-line block
things.map do |thing|
  something = thing.some_method
  process(something)
end

EnforcedStyle: semantic

# Prefer {do...end} over `{...}` for procedural blocks.

# return value is used/assigned
# bad
foo = map do |x|
  x
end
puts (map do |x|
  x
end)

# return value is not used out of scope
# good
map do |x|
  x
end

# Prefer `{...}` over {do...end} for functional blocks.

# return value is not used out of scope
# bad
each { |x|
  x
}

# return value is used/assigned
# good
foo = map { |x|
  x
}
map { |x|
  x
}.inspect

# The AllowBracesOnProceduralOneLiners option is allowed unless the
# EnforcedStyle is set to {semantic}. If so:

# If the AllowBracesOnProceduralOneLiners option is unspecified, or
# set to {false} or any other falsey value, then semantic purity is
# maintained, so one-line procedural blocks must use do-end, not
# braces.

# bad
collection.each { |element| puts element }

# good
collection.each do |element| puts element end

# If the AllowBracesOnProceduralOneLiners option is set to {true}, or
# any other truthy value, then one-line procedural blocks may use
# either style. (There is no setting for requiring braces on them.)

# good
collection.each { |element| puts element }

# also good
collection.each do |element| puts element end

EnforcedStyle: braces_for_chaining

# bad
words.each do |word|
  word.flip.flop
end.join("-")

# good
words.each { |word|
  word.flip.flop
}.join("-")

EnforcedStyle: always_braces

# bad
words.each do |word|
  word.flip.flop
end

# good
words.each { |word|
  word.flip.flop
}

BracesRequiredMethods: ['sig']

# Methods listed in the BracesRequiredMethods list, such as 'sig'
# in this example, will require `{...}` braces. This option takes
# precedence over all other configurations except AllowedMethods.

# bad
sig do
  params(
    foo: string,
  ).void
end
def bar(foo)
  puts foo
end

# good
sig {
  params(
    foo: string,
  ).void
}
def bar(foo)
  puts foo
end

AllowedMethods: ['lambda', 'proc', 'it' ] (default)

# good
foo = lambda do |x|
  puts "Hello, #{x}"
end

foo = lambda do |x|
  x * 100
end

AllowedPatterns: [] (default)

# bad
things.map { |thing|
  something = thing.some_method
  process(something)
}

AllowedPatterns: ['map']

# good
things.map { |thing|
  something = thing.some_method
  process(something)
}

Constant Summary

::RuboCop::Cop::Base - Inherited

EMPTY_OFFENSES, RESTRICT_ON_SEND

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

SYMBOL_TO_STRING_CACHE

::RuboCop::Cop::RangeHelp - Included

BYTE_ORDER_MARK, NOT_GIVEN

Class Attribute Summary

::RuboCop::Cop::AutoCorrector - Extended

::RuboCop::Cop::Base - Inherited

.gem_requirements, .lint?,
.support_autocorrect?

Returns if class supports autocorrect.

.support_multiple_source?

Override if your cop should be called repeatedly for multiple investigations Between calls to on_new_investigation and on_investigation_end, the result of processed_source will remain constant.

Class Method Summary

::RuboCop::Cop::Base - Inherited

.autocorrect_incompatible_with

List of cops that should not try to autocorrect at the same time as this cop.

.badge

Naming.

.callbacks_needed, .cop_name, .department,
.documentation_url

Returns a url to view this cops documentation online.

.exclude_from_registry

Call for abstract Cop classes.

.inherited,
.joining_forces

Override and return the Force class(es) you need to join.

.match?

Returns true if the cop name or the cop namespace matches any of the given names.

.new,
.requires_gem

Register a version requirement for the given gem name.

.restrict_on_send

::RuboCop::ExcludeLimit - Extended

exclude_limit

Sets up a configuration option to have an exclude limit tracked.

transform

Instance Attribute Summary

Instance Method Summary

::RuboCop::Cop::RangeHelp - Included

#add_range, #column_offset_between,
#contents_range

A range containing only the contents of a literal with delimiters (e.g.

#directions,
#effective_column

Returns the column attribute of the range, except if the range is on the first line and there’s a byte order mark at the beginning of that line, in which case 1 is subtracted from the column value.

#final_pos, #move_pos, #move_pos_str, #range_between, #range_by_whole_lines, #range_with_comments, #range_with_comments_and_lines, #range_with_surrounding_comma, #range_with_surrounding_space, #source_range

::RuboCop::Cop::AllowedPattern - Included

::RuboCop::Cop::AllowedMethods - Included

::RuboCop::Cop::ConfigurableEnforcedStyle - Included

::RuboCop::Cop::Base - Inherited

#add_global_offense

Adds an offense that has no particular location.

#add_offense

Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense.

#begin_investigation

Called before any investigation.

#callbacks_needed,
#cop_config

Configuration Helpers.

#cop_name, #excluded_file?,
#external_dependency_checksum

This method should be overridden when a cop’s behavior depends on state that lives outside of these locations:

#inspect,
#message

Gets called if no message is specified when calling add_offense or add_global_offense Cops are discouraged to override this; instead pass your message directly.

#name

Alias for Base#cop_name.

#offenses,
#on_investigation_end

Called after all on_…​

#on_new_investigation

Called before all on_…​

#on_other_file

Called instead of all on_…​

#parse

There should be very limited reasons for a Cop to do it’s own parsing.

#parser_engine,
#ready

Called between investigations.

#relevant_file?,
#target_gem_version

Returns a gems locked versions (i.e.

#target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction,
#callback_argument

Reserved for Cop::Cop.

#complete_investigation

Called to complete an investigation.

#correct, #current_corrector,
#current_offense_locations

Reserved for Commissioner:

#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range,
#reset_investigation

Actually private methods.

#use_corrector

::RuboCop::Cop::AutocorrectLogic - Included

::RuboCop::Cop::IgnoredNode - Included

Constructor Details

This class inherits a constructor from RuboCop::Cop::Base

Class Method Details

.autocorrect_incompatible_with

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 179

def self.autocorrect_incompatible_with
  [Style::RedundantBegin]
end

Instance Attribute Details

#procedural_oneliners_may_have_braces?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 465

def procedural_oneliners_may_have_braces?
  cop_config['AllowBracesOnProceduralOneLiners']
end

Instance Method Details

#array_or_range?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 496

def array_or_range?(node)
  node.array_type? || node.range_type?
end

#autocorrect(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 213

def autocorrect(corrector, node)
  return if correction_would_break_code?(node)

  if node.braces?
    replace_braces_with_do_end(corrector, node.loc)
  else
    replace_do_end_with_braces(corrector, node)
  end
end

#begin_required?(block_node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 500

def begin_required?(block_node)
  # If the block contains `rescue` or `ensure`, it needs to be wrapped in
  # `begin`...`end` when changing `do-end` to `{}`.
  block_node.each_child_node(:rescue, :ensure).any? && !block_node.single_line?
end

#braces_for_chaining_message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 241

def braces_for_chaining_message(node)
  if node.multiline?
    if node.chained?
      'Prefer `{...}` over `do...end` for multi-line chained blocks.'
    else
      'Prefer `do...end` for multi-line blocks without chaining.'
    end
  else
    'Prefer `{...}` over `do...end` for single-line blocks.'
  end
end

#braces_for_chaining_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 437

def braces_for_chaining_style?(node)
  block_begin = node.loc.begin.source

  block_begin == if node.multiline?
                   (node.chained? ? '{' : 'do')
                 else
                   '{'
                 end
end

#braces_required_message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 253

def braces_required_message(node)
  format(BRACES_REQUIRED_MESSAGE, method_name: node.method_name.to_s)
end

#braces_required_method?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 414

def braces_required_method?(method_name)
  braces_required_methods.include?(method_name.to_s)
end

#braces_required_methods (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 418

def braces_required_methods
  cop_config.fetch('BracesRequiredMethods', [])
end

#braces_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 447

def braces_style?(node)
  node.loc.begin.source == '{'
end

#conditional?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 492

def conditional?(node)
  node.if_type? || node.operator_keyword?
end

#correction_would_break_code?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 451

def correction_would_break_code?(node)
  return false unless node.keywords?

  node.send_node.arguments? && !node.send_node.parenthesized?
end

#end_of_chain(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 329

def end_of_chain(node)
  return end_of_chain(node.block_node) if with_block?(node)
  return node unless node.chained?

  end_of_chain(node.parent)
end

#functional_block?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 461

def functional_block?(node)
  return_value_used?(node) || return_value_of_scope?(node)
end

#functional_method?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 457

def functional_method?(method_name)
  cop_config['FunctionalMethods'].map(&:to_sym).include?(method_name)
end

#get_blocks(node, &block) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 347

def get_blocks(node, &block)
  case node.type
  when :block, :numblock
    yield node
  when :send
    # When a method has an argument which is another method with a block,
    # that block needs braces, otherwise a syntax error will be introduced
    # for subsequent arguments.
    # Additionally, even without additional arguments, changing `{...}` to
    # `do...end` will change the binding of the block to the outer method.
    get_blocks(node.receiver, &block) if node.receiver
    node.arguments.each { |argument| get_blocks(argument, &block) }
  when :hash
    # A hash which is passed as method argument may have no braces
    # In that case, one of the K/V pairs could contain a block node
    # which could change in meaning if `do...end` is replaced with `{...}`
    return if node.braces?

    node.each_child_node { |child| get_blocks(child, &block) }
  when :pair
    node.each_child_node { |child| get_blocks(child, &block) }
  end
end

#line_count_based_block_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 422

def line_count_based_block_style?(node)
  node.multiline? ^ node.braces?
end

#line_count_based_message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 223

def line_count_based_message(node)
  if node.multiline?
    'Avoid using `{...}` for multi-line blocks.'
  else
    'Prefer `{...}` over `do...end` for single-line blocks.'
  end
end

#message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 257

def message(node)
  return braces_required_message(node) if braces_required_method?(node.method_name)

  case style
  when :line_count_based    then line_count_based_message(node)
  when :semantic            then semantic_message(node)
  when :braces_for_chaining then braces_for_chaining_message(node)
  when :always_braces       then ALWAYS_BRACES_MESSAGE
  end
end

#move_comment_before_block(corrector, comment, block_node, closing_brace) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 305

def move_comment_before_block(corrector, comment, block_node, closing_brace)
  range = block_node.chained? ? end_of_chain(block_node.parent).source_range : closing_brace

  # It is possible that there is code between the block and the comment
  # which needs to be preserved and trimmed.
  pre_comment_range = source_range_before_comment(range, comment)

  corrector.remove(range_with_surrounding_space(comment.source_range, side: :right))
  remove_trailing_whitespace(corrector, pre_comment_range, comment)
  corrector.insert_after(pre_comment_range, "\n")

  corrector.insert_before(block_node, "#{comment.text}\n")
end

#on_block(node) Also known as: #on_numblock

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 199

def on_block(node)
  return if ignored_node?(node)
  return if proper_block_style?(node)

  message = message(node)
  add_offense(node.loc.begin, message: message) do |corrector|
    autocorrect(corrector, node)
  end
end

#on_numblock(node)

Alias for #on_block.

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 209

alias on_numblock on_block

#on_send(node)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 183

def on_send(node)
  return unless node.arguments?
  return if node.parenthesized?
  return if node.assignment_method?
  return if single_argument_operator_method?(node)

  node.arguments.each do |arg|
    get_blocks(arg) do |block|
      # If there are no parentheses around the arguments, then braces
      # and do-end have different meaning due to how they bind, so we
      # allow either.
      ignore_node(block)
    end
  end
end

#procedural_method?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 469

def procedural_method?(method_name)
  cop_config['ProceduralMethods'].map(&:to_sym).include?(method_name)
end

#proper_block_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 373

def proper_block_style?(node)
  return true if require_braces?(node) || require_do_end?(node)
  return special_method_proper_block_style?(node) if special_method?(node.method_name)

  case style
  when :line_count_based    then line_count_based_block_style?(node)
  when :semantic            then semantic_block_style?(node)
  when :braces_for_chaining then braces_for_chaining_style?(node)
  when :always_braces       then braces_style?(node)
  end
end

#remove_trailing_whitespace(corrector, range, comment) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 336

def remove_trailing_whitespace(corrector, range, comment)
  range_of_trailing = range.end.join(comment.source_range.begin)

  corrector.remove(range_of_trailing) if range_of_trailing.source.match?(/\A\s+\z/)
end

#replace_braces_with_do_end(corrector, loc) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 268

def replace_braces_with_do_end(corrector, loc)
  b = loc.begin
  e = loc.end

  corrector.insert_before(b, ' ') unless whitespace_before?(b)
  corrector.insert_before(e, ' ') unless whitespace_before?(e)
  corrector.insert_after(b, ' ') unless whitespace_after?(b)
  corrector.replace(b, 'do')

  if (comment = processed_source.comment_at_line(e.line))
    move_comment_before_block(corrector, comment, loc.node, e)
  end

  corrector.replace(e, 'end')
end

#replace_do_end_with_braces(corrector, node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 284

def replace_do_end_with_braces(corrector, node)
  loc = node.loc
  b = loc.begin
  e = loc.end

  corrector.insert_after(b, ' ') unless whitespace_after?(b, 2)

  corrector.replace(b, '{')
  corrector.replace(e, '}')

  corrector.wrap(node.body, "begin\n", "\nend") if begin_required?(node)
end

#require_braces?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 386

def require_braces?(node)
  return false unless node.braces?

  node.each_ancestor(:send).any? do |send|
    send.arithmetic_operation? && node.source_range.end_pos < send.loc.selector.begin_pos
  end
end

#require_do_end?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 394

def require_do_end?(node)
  return false if node.braces? || node.multiline?
  return false unless (resbody = node.each_descendant(:resbody).first)

  resbody.children.first&.array_type?
end

#return_value_of_scope?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 485

def return_value_of_scope?(node)
  return false unless node.parent

  conditional?(node.parent) || array_or_range?(node.parent) ||
    node.parent.children.last == node
end

#return_value_used?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 473

def return_value_used?(node)
  return false unless node.parent

  # If there are parentheses around the block, check if that
  # is being used.
  if node.parent.begin_type?
    return_value_used?(node.parent)
  else
    node.parent.assignment? || node.parent.call_type?
  end
end

#semantic_block_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 426

def semantic_block_style?(node)
  method_name = node.method_name

  if node.braces?
    functional_method?(method_name) || functional_block?(node) ||
      (procedural_oneliners_may_have_braces? && !node.multiline?)
  else
    procedural_method?(method_name) || !return_value_used?(node)
  end
end

#semantic_message(node) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 231

def semantic_message(node)
  block_begin = node.loc.begin.source

  if block_begin == '{'
    'Prefer `do...end` over `{...}` for procedural blocks.'
  else
    'Prefer `{...}` over `do...end` for functional blocks.'
  end
end

#single_argument_operator_method?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 506

def single_argument_operator_method?(node)
  return false unless node.operator_method?

  node.arguments.one? && node.first_argument.block_type?
end

#source_range_before_comment(range, comment) (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 319

def source_range_before_comment(range, comment)
  range = range.end.join(comment.source_range.begin)

  # End the range before any whitespace that precedes the comment
  trailing_whitespace_count = range.source[/\s+\z/]&.length
  range = range.adjust(end_pos: -trailing_whitespace_count) if trailing_whitespace_count

  range
end

#special_method?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 401

def special_method?(method_name)
  allowed_method?(method_name) ||
    matches_allowed_pattern?(method_name) ||
    braces_required_method?(method_name)
end

#special_method_proper_block_style?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 407

def special_method_proper_block_style?(node)
  method_name = node.method_name
  return true if allowed_method?(method_name) || matches_allowed_pattern?(method_name)

  node.braces? if braces_required_method?(method_name)
end

#whitespace_after?(range, length = 1) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 301

def whitespace_after?(range, length = 1)
  /\s/.match?(range.source_buffer.source[range.begin_pos + length, 1])
end

#whitespace_before?(range) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 297

def whitespace_before?(range)
  /\s/.match?(range.source_buffer.source[range.begin_pos - 1, 1])
end

#with_block?(node) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/rubocop/cop/style/block_delimiters.rb', line 342

def with_block?(node)
  node.respond_to?(:block_node) && node.block_node
end