forked from rubocop/rubocop
/
explicit_block_argument.rb
91 lines (77 loc) · 2.63 KB
/
explicit_block_argument.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop enforces the use of explicit block argument to avoid writing
# block literal that just passes its arguments to another block.
#
# @example
# # bad
# def with_tmp_dir
# Dir.mktmpdir do |tmp_dir|
# Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments
# end
# 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
#
class ExplicitBlockArgument < Base
include RangeHelp
extend AutoCorrector
MSG = 'Consider using explicit block argument in the '\
"surrounding method's signature over `yield`."
def_node_matcher :yielding_block?, <<~PATTERN
(block $_ (args $...) (yield $...))
PATTERN
def initialize(config = nil, options = nil)
super
@def_nodes = Set.new
end
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)
add_offense(block_node) do |corrector|
corrector.remove(block_body_range(block_node, send_node))
add_block_argument(send_node, corrector)
def_node = block_node.each_ancestor(:def, :defs).first
add_block_argument(def_node, corrector) if @def_nodes.add?(def_node)
end
end
end
private
def yielding_arguments?(block_args, yield_args)
return false if yield_args.empty?
yield_args.zip(block_args).all? do |yield_arg, block_arg|
block_arg && yield_arg.children.first == block_arg.children.first
end
end
def add_block_argument(node, corrector)
if node.arguments?
last_arg = node.arguments.last
corrector.insert_after(last_arg, ', &block') unless last_arg.blockarg_type?
elsif node.send_type?
corrector.insert_after(node, '(&block)')
else
corrector.insert_after(node.loc.name, '(&block)')
end
end
def block_body_range(block_node, send_node)
range_between(
send_node.loc.expression.end_pos,
block_node.loc.end.end_pos
)
end
end
end
end
end