class RuboCop::Cop::Style::ExplicitBlockArgument

Enforces the use of explicit block argument to avoid writing block literal that just passes its arguments to another block.

NOTE: This cop only registers an offense if the block args match the yield args exactly.

@example

# bad
def with_tmp_dir
  Dir.mktmpdir do |tmp_dir|
    Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments
  end
end

# bad
def nine_times
  9.times { yield }
end

# good
def with_tmp_dir(&block)
  Dir.mktmpdir do |tmp_dir|
    Dir.chdir(tmp_dir, &block)
  end
end

with_tmp_dir do |dir|
  puts "dir is accessible as a parameter and pwd is set: #{dir}"
end

# good
def nine_times(&block)
  9.times(&block)
end

Constants

MSG

Public Class Methods

autocorrect_incompatible_with() click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 53
def self.autocorrect_incompatible_with
  [Lint::UnusedMethodArgument]
end
new(config = nil, options = nil) click to toggle source
Calls superclass method RuboCop::Cop::Base::new
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 57
def initialize(config = nil, options = nil)
  super
  @def_nodes = Set.new
end

Public Instance Methods

on_yield(node) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 62
def on_yield(node)
  block_node = node.parent

  yielding_block?(block_node) do |send_node, block_args, yield_args|
    return unless yielding_arguments?(block_args, yield_args)

    def_node = block_node.each_ancestor(:def, :defs).first
    # if `yield` is being called outside of a method context, ignore
    # this is not a valid ruby pattern, but can happen in haml or erb,
    # so this can cause crashes in haml_lint
    return unless def_node

    block_name = extract_block_name(def_node)

    add_offense(block_node) do |corrector|
      corrector.remove(block_body_range(block_node, send_node))

      add_block_argument(send_node, corrector, block_name)
      add_block_argument(def_node, corrector, block_name) if @def_nodes.add?(def_node)
    end
  end
end

Private Instance Methods

add_block_argument(node, corrector, block_name) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 108
def add_block_argument(node, corrector, block_name)
  if node.arguments?
    insert_argument(node, corrector, block_name)
  elsif empty_arguments?(node)
    corrector.replace(node.arguments, "(&#{block_name})")
  elsif call_like?(node)
    correct_call_node(node, corrector, block_name)
  else
    corrector.insert_after(node.loc.name, "(&#{block_name})")
  end
end
block_body_range(block_node, send_node) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 147
def block_body_range(block_node, send_node)
  range_between(send_node.loc.expression.end_pos, block_node.loc.end.end_pos)
end
call_like?(node) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 125
def call_like?(node)
  node.call_type? || node.zsuper_type? || node.super_type?
end
correct_call_node(node, corrector, block_name) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 137
def correct_call_node(node, corrector, block_name)
  corrector.insert_after(node, "(&#{block_name})")
  return unless node.parenthesized?

  args_begin = Util.args_begin(node)
  args_end = Util.args_end(node)
  range = range_between(args_begin.begin_pos, args_end.end_pos)
  corrector.remove(range)
end
empty_arguments?(node) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 120
def empty_arguments?(node)
  # Is there an arguments node with only parentheses?
  node.arguments.is_a?(RuboCop::AST::Node) && node.arguments.loc.begin
end
extract_block_name(def_node) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 87
def extract_block_name(def_node)
  if def_node.block_argument?
    def_node.arguments.last.name
  else
    'block'
  end
end
insert_argument(node, corrector, block_name) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 129
def insert_argument(node, corrector, block_name)
  last_arg = node.arguments.last
  arg_range = range_with_surrounding_comma(last_arg.source_range, :right)
  replacement = " &#{block_name}"
  replacement = ",#{replacement}" unless arg_range.source.end_with?(',')
  corrector.insert_after(arg_range, replacement) unless last_arg.blockarg_type?
end
yielding_arguments?(block_args, yield_args) click to toggle source
# File lib/rubocop/cop/style/explicit_block_argument.rb, line 95
def yielding_arguments?(block_args, yield_args)
  yield_args = yield_args.dup.fill(
    nil,
    yield_args.length, block_args.length - yield_args.length
  )

  yield_args.zip(block_args).all? do |yield_arg, block_arg|
    next false unless yield_arg && block_arg

    block_arg && yield_arg.children.first == block_arg.children.first
  end
end