Class: RuboCop::Cop::Style::PreferItParameter
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::Style::PreferItParameter
- Extended by:
- AutoCorrector, TargetRubyVersion
- Includes:
- RangeHelp
- Defined in:
- lib/rubocop/cop/style/prefer_it_parameter.rb,
sig/rubocop/cop/style/prefer_it_parameter.rbs
Overview
Prefer the it block parameter over a named block argument in single-line blocks.
Only blocks consisting of a single statement are converted. This cop complements
Style/ItBlockParameter in RuboCop core, which converts _1 to it but in its
default style does not check named block arguments.
Constant Summary collapse
- MSG =
"Use the `it` block parameter instead of the named block argument `%<name>s`."- INNER_BLOCK_TYPES =
%i[block numblock itblock].freeze
- CALLABLE_METHODS =
%i[define_method define_singleton_method lambda proc].freeze
Instance Method Summary collapse
- #arguments_removal_range(node) ⇒ Parser::Source::Range
-
#contains_block?(body) ⇒ Boolean
itis a syntax error inside a block that has an ordinary parameter, and silently shadows the outer one inside a parameterless block, so only the innermost block is converted. - #convertible_argument_name(node, body) ⇒ Symbol?
- #convertible_body?(body, name) ⇒ Boolean
-
#defines_callable?(node) ⇒ Boolean
itdrops the parameter name fromProc#parameters, which changes the meaning of a block that defines a callable object or a method: there the parameter list is part of the API, unlike a block passed toeachormap. - #lvar_references(body, name) ⇒ Array[RuboCop::AST::Node]
-
#omitted_value_pair(reference) ⇒ RuboCop::AST::PairNode?
Returns the enclosing pair when the reference is a value omission (
{x:},foo(x:)). - #on_block(node) ⇒ void
- #proc_new?(node) ⇒ Boolean
-
#reassigned?(body, name) ⇒ Boolean
A block that rebinds the name cannot be converted: the value
itrefers to would no longer be the one the block was yielded. - #register_offense(node, name, references) ⇒ void
- #replace_reference(corrector, reference) ⇒ void
-
#shadows_it?(body) ⇒ Boolean
itwould not mean what the block expects when a local variable nameditis already in scope, or when the body assigns toit— assigning turnsitinto a plain local variable and disables the implicit block parameter. -
#single_statement?(body) ⇒ Boolean
begin(parentheses) andkwbegin(begin ... end) both wrap a sequence of statements as well as a single expression, so the number of children is what tells the two apart. - #sole_argument_name(node) ⇒ Symbol?
Instance Method Details
#arguments_removal_range(node) ⇒ Parser::Source::Range
227 228 229 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 227 def arguments_removal_range(node) #: Parser::Source::Range range_with_surrounding_space(node.arguments.source_range, side: :right, newlines: false) end |
#contains_block?(body) ⇒ Boolean
it is a syntax error inside a block that has an ordinary parameter, and
silently shadows the outer one inside a parameterless block, so only the
innermost block is converted.
165 166 167 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 165 def contains_block?(body) #: bool body.each_node(*INNER_BLOCK_TYPES).any? end |
#convertible_argument_name(node, body) ⇒ Symbol?
87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 87 def convertible_argument_name(node, body) #: Symbol? return unless node.single_line? return if defines_callable?(node) name = sole_argument_name(node) return unless name return unless convertible_body?(body, name) return if shadows_it?(body) name end |
#convertible_body?(body, name) ⇒ Boolean
145 146 147 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 145 def convertible_body?(body, name) #: bool single_statement?(body) && !contains_block?(body) && !reassigned?(body, name) end |
#defines_callable?(node) ⇒ Boolean
it drops the parameter name from Proc#parameters, which changes the
meaning of a block that defines a callable object or a method: there the
parameter list is part of the API, unlike a block passed to each or map.
113 114 115 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 113 def defines_callable?(node) #: bool CALLABLE_METHODS.include?(node.method_name) || proc_new?(node) end |
#lvar_references(body, name) ⇒ Array[RuboCop::AST::Node]
196 197 198 199 200 201 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 196 def lvar_references(body, name) #: Array[RuboCop::AST::Node] body.each_node(:lvar).select do |node| variable = node #: RuboCop::AST::VarNode variable.name == name end end |
#omitted_value_pair(reference) ⇒ RuboCop::AST::PairNode?
Returns the enclosing pair when the reference is a value omission ({x:},
foo(x:)). Such a reference shares its source range with the label, so the
value has to be written after the pair rather than replaced.
186 187 188 189 190 191 192 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 186 def omitted_value_pair(reference) #: RuboCop::AST::PairNode? parent = reference.parent return unless parent&.pair_type? pair = parent #: RuboCop::AST::PairNode pair if pair.value_omission? end |
#on_block(node) ⇒ void
This method returns an undefined value.
70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 70 def on_block(node) #: void body = node.body return unless body name = convertible_argument_name(node, body) return unless name references = lvar_references(body, name) return if references.empty? register_offense(node, name, references) end |
#proc_new?(node) ⇒ Boolean
118 119 120 121 122 123 124 125 126 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 118 def proc_new?(node) #: bool return false unless node.method?(:new) receiver = node.send_node.receiver return false unless receiver&.const_type? const = receiver #: RuboCop::AST::ConstNode const.short_name == :Proc end |
#reassigned?(body, name) ⇒ Boolean
A block that rebinds the name cannot be converted: the value it refers to
would no longer be the one the block was yielded. match_var covers pattern
matching (1 in x), which rebinds just like an assignment.
175 176 177 178 179 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 175 def reassigned?(body, name) #: bool body.each_node(:lvasgn, :match_var).any? do |node| node.to_a.first == name end end |
#register_offense(node, name, references) ⇒ void
This method returns an undefined value.
206 207 208 209 210 211 212 213 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 206 def register_offense(node, name, references) #: void add_offense(node.arguments, message: format(MSG, name:)) do |corrector| references.each do |reference| replace_reference(corrector, reference) end corrector.remove(arguments_removal_range(node)) end end |
#replace_reference(corrector, reference) ⇒ void
This method returns an undefined value.
217 218 219 220 221 222 223 224 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 217 def replace_reference(corrector, reference) #: void pair = omitted_value_pair(reference) if pair corrector.insert_after(pair.source_range, " it") else corrector.replace(reference.source_range, "it") end end |
#shadows_it?(body) ⇒ Boolean
it would not mean what the block expects when a local variable named it
is already in scope, or when the body assigns to it — assigning turns it
into a plain local variable and disables the implicit block parameter.
104 105 106 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 104 def shadows_it?(body) #: bool lvar_references(body, :it).any? || reassigned?(body, :it) end |
#single_statement?(body) ⇒ Boolean
begin (parentheses) and kwbegin (begin ... end) both wrap a sequence of
statements as well as a single expression, so the number of children is what
tells the two apart.
154 155 156 157 158 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 154 def single_statement?(body) #: bool return body.each_child_node.one? if body.type?(:begin, :kwbegin) true end |
#sole_argument_name(node) ⇒ Symbol?
129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/rubocop/cop/style/prefer_it_parameter.rb', line 129 def sole_argument_name(node) #: Symbol? return unless node.argument_list.one? argument = node.first_argument # Rules out optarg, restarg, kwarg, blockarg, shadowarg and mlhs at once. return unless argument&.arg_type? # `|x,|` is indistinguishable from `|x|` in the AST even though it # destructures the yielded value, so the source has to be checked. return if node.arguments.source&.include?(",") plain_argument = argument #: RuboCop::AST::ArgNode plain_argument.name end |