diff --git a/.rubocop.yml b/.rubocop.yml index 4fcabe3d29a..de805221ffc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -163,5 +163,9 @@ InternalAffairs/StyleDetectedApiUse: Exclude: - 'lib/rubocop/cop/mixin/percent_array.rb' +InternalAffairs/NumblockHandler: + Exclude: + - 'lib/rubocop/cop/internal_affairs/*.rb' + Gemspec/DependencyVersion: Enabled: true diff --git a/changelog/change_rubocop_ast_version.md b/changelog/change_rubocop_ast_version.md new file mode 100644 index 00000000000..ec7b770b054 --- /dev/null +++ b/changelog/change_rubocop_ast_version.md @@ -0,0 +1 @@ +* [#10915](https://github.com/rubocop/rubocop/pull/10915): Depend on rubocop-ast 1.20.1 for numblocks support in #macro?. ([@gsamokovarov][]) diff --git a/changelog/fix_block_cops_without_numblock_support.md b/changelog/fix_block_cops_without_numblock_support.md new file mode 100644 index 00000000000..d6b05c931e2 --- /dev/null +++ b/changelog/fix_block_cops_without_numblock_support.md @@ -0,0 +1 @@ +* [#10915](https://github.com/rubocop/rubocop/pull/10915): Fix numblock support to `Layout/BlockAlignment`, `Layout/BlockEndNewline`, `Layout/EmptyLinesAroundAccessModifier`, `Layout/EmptyLinesAroundBlockBody`, `Layout/IndentationWidth`, `Layout/LineLength`, `Layout/MultilineBlockLayout`, `Layout/SpaceBeforeBlockBraces`, `Lint/NextWithoutAccumulator`, `Lint/NonDeterministicRequireOrder`, `Lint/RedundantWithIndex`, `Lint/RedundantWithObject`, `Lint/UnreachableLoop`, `Lint/UselessAccessModifier`, `Lint/Void`, `Metrics/AbcSize`, `Metrics/CyclomaticComplexity`, `Style/CollectionMethods`, `Style/CombinableLoops`, `Style/EachWithObject`, `Style/For`, `Style/HashEachMethods`, `Style/InverseMethods`, `Style/MethodCalledOnDoEndBlock`, `Style/MultilineBlockChain`, `Style/Next`, `Style/ObjectThen`, `Style/Proc`, `Style/RedundantBegin`, `Style/RedundantSelf`, `Style/RedundantSortBy` and `Style/TopLevelMethodDefinition`. ([@gsamokovarov][]) diff --git a/lib/rubocop/cop/gemspec/require_mfa.rb b/lib/rubocop/cop/gemspec/require_mfa.rb index 06db67a14ed..b07fe7d2487 100644 --- a/lib/rubocop/cop/gemspec/require_mfa.rb +++ b/lib/rubocop/cop/gemspec/require_mfa.rb @@ -84,7 +84,7 @@ class RequireMFA < Base (str "true") PATTERN - def on_block(node) # rubocop:disable Metrics/MethodLength + def on_block(node) # rubocop:disable Metrics/MethodLength, InternalAffairs/NumblockHandler gem_specification(node) do |block_var| metadata_value = metadata(node) mfa_value = mfa_value(metadata_value) diff --git a/lib/rubocop/cop/internal_affairs.rb b/lib/rubocop/cop/internal_affairs.rb index eb006c6aa48..9a9831e87c4 100644 --- a/lib/rubocop/cop/internal_affairs.rb +++ b/lib/rubocop/cop/internal_affairs.rb @@ -10,6 +10,7 @@ require_relative 'internal_affairs/node_destructuring' require_relative 'internal_affairs/node_matcher_directive' require_relative 'internal_affairs/node_type_predicate' +require_relative 'internal_affairs/numblock_handler' require_relative 'internal_affairs/offense_location_keyword' require_relative 'internal_affairs/redundant_context_config_parameter' require_relative 'internal_affairs/redundant_described_class_as_subject' diff --git a/lib/rubocop/cop/internal_affairs/numblock_handler.rb b/lib/rubocop/cop/internal_affairs/numblock_handler.rb new file mode 100644 index 00000000000..4b884c81344 --- /dev/null +++ b/lib/rubocop/cop/internal_affairs/numblock_handler.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module InternalAffairs + # Checks for missing `numblock handlers. The blocks with numbered + # arguments introduced in Ruby 2.7 are parsed with a node type of + # `numblock` instead of block. Cops that define `block` handlers + # need to define `numblock` handlers or disable this cope for them. + # + # @example + # + # # bad + # class BlockRelatedCop < Base + # def on_block(node) + # end + # end + # + # # good + # class BlockRelatedCop < Base + # def on_block(node) + # end + # + # alias on_numblock on_block + # end + # + # class BlockRelatedCop < Base + # def on_block(node) + # end + # + # alias_method :on_numblock, :on_block + # end + # + # class BlockRelatedCop < Base + # def on_block(node) + # end + # + # def on_numblock(node) + # end + # end + class NumblockHandler < Base + MSG = 'Define on_numblock to handle blocks with numbered arguments.' + + def on_def(node) + return unless block_handler?(node) + return unless node.parent + + add_offense(node) unless numblock_handler?(node.parent) + end + + private + + # @!method block_handler?(node) + def_node_matcher :block_handler?, <<~PATTERN + (def :on_block (args (arg :node)) ...) + PATTERN + + # @!method numblock_handler?(node) + def_node_matcher :numblock_handler?, <<~PATTERN + { + `(def :on_numblock (args (arg :node)) ...) + `(alias (sym :on_numblock) (sym :on_block)) + `(send nil? :alias_method (sym :on_numblock) (sym :on_block)) + } + PATTERN + end + end + end +end diff --git a/lib/rubocop/cop/layout/block_alignment.rb b/lib/rubocop/cop/layout/block_alignment.rb index ebd13b39429..aef4e627fc5 100644 --- a/lib/rubocop/cop/layout/block_alignment.rb +++ b/lib/rubocop/cop/layout/block_alignment.rb @@ -82,6 +82,8 @@ def on_block(node) check_block_alignment(start_for_block_node(node), node) end + alias on_numblock on_block + def style_parameter_name 'EnforcedStyleAlignWith' end diff --git a/lib/rubocop/cop/layout/block_end_newline.rb b/lib/rubocop/cop/layout/block_end_newline.rb index 625ccfa4a2b..595eb684a7e 100644 --- a/lib/rubocop/cop/layout/block_end_newline.rb +++ b/lib/rubocop/cop/layout/block_end_newline.rb @@ -39,6 +39,8 @@ def on_block(node) register_offense(node) end + alias on_numblock on_block + private def register_offense(node) diff --git a/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb b/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb index 3d3b814a3bf..ecb17609b4e 100644 --- a/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb +++ b/lib/rubocop/cop/layout/empty_lines_around_access_modifier.rb @@ -80,8 +80,11 @@ def on_block(node) @block_line = node.source_range.first_line end - def on_send(node) - return unless node.bare_access_modifier? && !node.parent&.block_type? + alias on_numblock on_block + + def on_send(node) # rubocop:disable Metrics/CyclomaticComplexity + return unless node.bare_access_modifier? && + !(node.parent&.block_type? || node.parent&.numblock_type?) return if expected_empty_lines?(node) message = message(node) diff --git a/lib/rubocop/cop/layout/empty_lines_around_block_body.rb b/lib/rubocop/cop/layout/empty_lines_around_block_body.rb index 351f4cee4dc..f29901c1714 100644 --- a/lib/rubocop/cop/layout/empty_lines_around_block_body.rb +++ b/lib/rubocop/cop/layout/empty_lines_around_block_body.rb @@ -32,6 +32,8 @@ def on_block(node) check(node, node.body, adjusted_first_line: first_line) end + + alias on_numblock on_block end end end diff --git a/lib/rubocop/cop/layout/indentation_width.rb b/lib/rubocop/cop/layout/indentation_width.rb index d5cfcb9844b..7cfb3a6a523 100644 --- a/lib/rubocop/cop/layout/indentation_width.rb +++ b/lib/rubocop/cop/layout/indentation_width.rb @@ -90,6 +90,8 @@ def on_block(node) check_members(end_loc, [node.body]) end + alias on_numblock on_block + def on_class(node) base = node.loc.keyword return if same_line?(base, node.body) diff --git a/lib/rubocop/cop/layout/line_length.rb b/lib/rubocop/cop/layout/line_length.rb index 0b1d2866bb1..c9a146e4db9 100644 --- a/lib/rubocop/cop/layout/line_length.rb +++ b/lib/rubocop/cop/layout/line_length.rb @@ -74,6 +74,8 @@ def on_block(node) check_for_breakable_block(node) end + alias on_numblock on_block + def on_potential_breakable_node(node) check_for_breakable_node(node) end @@ -131,7 +133,7 @@ def breakable_block_range(block_node) if block_node.arguments? && !block_node.lambda? block_node.arguments.loc.end else - block_node.loc.begin + block_node.braces? ? block_node.loc.begin : block_node.loc.begin.adjust(begin_pos: 1) end end diff --git a/lib/rubocop/cop/layout/multiline_block_layout.rb b/lib/rubocop/cop/layout/multiline_block_layout.rb index 03482837fa1..080de95ab4f 100644 --- a/lib/rubocop/cop/layout/multiline_block_layout.rb +++ b/lib/rubocop/cop/layout/multiline_block_layout.rb @@ -68,6 +68,8 @@ def on_block(node) add_offense_for_expression(node, node.body, MSG) end + alias on_numblock on_block + private def args_on_beginning_line?(node) diff --git a/lib/rubocop/cop/layout/space_around_block_parameters.rb b/lib/rubocop/cop/layout/space_around_block_parameters.rb index 3202494bb01..a3de6c9e346 100644 --- a/lib/rubocop/cop/layout/space_around_block_parameters.rb +++ b/lib/rubocop/cop/layout/space_around_block_parameters.rb @@ -29,7 +29,7 @@ class SpaceAroundBlockParameters < Base include RangeHelp extend AutoCorrector - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler arguments = node.arguments return unless node.arguments? && pipes?(arguments) diff --git a/lib/rubocop/cop/layout/space_around_keyword.rb b/lib/rubocop/cop/layout/space_around_keyword.rb index 0a7fe3bdfc1..d3725cd5d0e 100644 --- a/lib/rubocop/cop/layout/space_around_keyword.rb +++ b/lib/rubocop/cop/layout/space_around_keyword.rb @@ -41,7 +41,7 @@ def on_and(node) check(node, [:operator].freeze) if node.keyword? end - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler check(node, %i[begin end].freeze) end diff --git a/lib/rubocop/cop/layout/space_before_block_braces.rb b/lib/rubocop/cop/layout/space_before_block_braces.rb index 710d1e40f07..b844c7e838a 100644 --- a/lib/rubocop/cop/layout/space_before_block_braces.rb +++ b/lib/rubocop/cop/layout/space_before_block_braces.rb @@ -76,6 +76,8 @@ def on_block(node) end end + alias on_numblock on_block + private def check_empty(left_brace, space_plus_brace, used_style) diff --git a/lib/rubocop/cop/lint/empty_block.rb b/lib/rubocop/cop/lint/empty_block.rb index a2ff31b6b32..286375b073b 100644 --- a/lib/rubocop/cop/lint/empty_block.rb +++ b/lib/rubocop/cop/lint/empty_block.rb @@ -63,7 +63,7 @@ module Lint class EmptyBlock < Base MSG = 'Empty block detected.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return if node.body return if allow_empty_lambdas? && lambda_or_proc?(node) return if cop_config['AllowComments'] && allow_comment?(node) diff --git a/lib/rubocop/cop/lint/next_without_accumulator.rb b/lib/rubocop/cop/lint/next_without_accumulator.rb index 0c239cb5324..da811e60ad0 100644 --- a/lib/rubocop/cop/lint/next_without_accumulator.rb +++ b/lib/rubocop/cop/lint/next_without_accumulator.rb @@ -25,13 +25,8 @@ module Lint class NextWithoutAccumulator < Base MSG = 'Use `next` with an accumulator argument in a `reduce`.' - # @!method on_body_of_reduce(node) - def_node_matcher :on_body_of_reduce, <<~PATTERN - (block (send _recv {:reduce :inject} !sym) _blockargs $(begin ...)) - PATTERN - def on_block(node) - on_body_of_reduce(node) do |body| + on_block_body_of_reduce(node) do |body| void_next = body.each_node(:next).find do |n| n.children.empty? && parent_block_node(n) == node end @@ -40,11 +35,35 @@ def on_block(node) end end + def on_numblock(node) + on_numblock_body_of_reduce(node) do |body| + void_next = body.each_node(:next).find do |n| + n.children.empty? && parent_numblock_node(n) == node + end + + add_offense(void_next) if void_next + end + end + private + # @!method on_block_body_of_reduce(node) + def_node_matcher :on_block_body_of_reduce, <<~PATTERN + (block (send _recv {:reduce :inject} !sym) _blockargs $(begin ...)) + PATTERN + + # @!method on_numblock_body_of_reduce(node) + def_node_matcher :on_numblock_body_of_reduce, <<~PATTERN + (numblock (send _recv {:reduce :inject} !sym) _argscount $(begin ...)) + PATTERN + def parent_block_node(node) node.each_ancestor(:block).first end + + def parent_numblock_node(node) + node.each_ancestor(:numblock).first + end end end end diff --git a/lib/rubocop/cop/lint/non_deterministic_require_order.rb b/lib/rubocop/cop/lint/non_deterministic_require_order.rb index df9bc2494e3..6a5aeac60bc 100644 --- a/lib/rubocop/cop/lint/non_deterministic_require_order.rb +++ b/lib/rubocop/cop/lint/non_deterministic_require_order.rb @@ -74,6 +74,18 @@ def on_block(node) end end + def on_numblock(node) + return if target_ruby_version >= 3.0 + return unless node.body + return unless unsorted_dir_loop?(node.send_node) + + node.argument_list + .filter { |argument| var_is_required?(node.body, argument.name) } + .each do + add_offense(node.send_node) { |corrector| correct_block(corrector, node.send_node) } + end + end + def on_block_pass(node) return if target_ruby_version >= 3.0 return unless method_require?(node) diff --git a/lib/rubocop/cop/lint/redundant_with_index.rb b/lib/rubocop/cop/lint/redundant_with_index.rb index 45e514dc7bc..8b22899f5cf 100644 --- a/lib/rubocop/cop/lint/redundant_with_index.rb +++ b/lib/rubocop/cop/lint/redundant_with_index.rb @@ -33,16 +33,6 @@ class RedundantWithIndex < Base MSG_EACH_WITH_INDEX = 'Use `each` instead of `each_with_index`.' MSG_WITH_INDEX = 'Remove redundant `with_index`.' - # @!method redundant_with_index?(node) - def_node_matcher :redundant_with_index?, <<~PATTERN - (block - $(send - _ {:each_with_index :with_index} ...) - (args - (arg _)) - ...) - PATTERN - def on_block(node) return unless (send = redundant_with_index?(node)) @@ -58,8 +48,21 @@ def on_block(node) end end + alias on_numblock on_block + private + # @!method redundant_with_index?(node) + def_node_matcher :redundant_with_index?, <<~PATTERN + { + (block + $(send _ {:each_with_index :with_index} ...) + (args (arg _)) ...) + (numblock + $(send _ {:each_with_index :with_index} ...) 1 ...) + } + PATTERN + def message(node) if node.method?(:each_with_index) MSG_EACH_WITH_INDEX diff --git a/lib/rubocop/cop/lint/redundant_with_object.rb b/lib/rubocop/cop/lint/redundant_with_object.rb index cfc54b1dba0..3e3c0b24ac6 100644 --- a/lib/rubocop/cop/lint/redundant_with_object.rb +++ b/lib/rubocop/cop/lint/redundant_with_object.rb @@ -31,19 +31,8 @@ class RedundantWithObject < Base extend AutoCorrector MSG_EACH_WITH_OBJECT = 'Use `each` instead of `each_with_object`.' - MSG_WITH_OBJECT = 'Remove redundant `with_object`.' - # @!method redundant_with_object?(node) - def_node_matcher :redundant_with_object?, <<~PATTERN - (block - $(send _ {:each_with_object :with_object} - _) - (args - (arg _)) - ...) - PATTERN - def on_block(node) return unless (send = redundant_with_object?(node)) @@ -59,8 +48,20 @@ def on_block(node) end end + alias on_numblock on_block + private + # @!method redundant_with_object?(node) + def_node_matcher :redundant_with_object?, <<~PATTERN + { + (block + $(send _ {:each_with_object :with_object} _) (args (arg _)) ...) + (numblock + $(send _ {:each_with_object :with_object} _) 1 ...) + } + PATTERN + def message(node) if node.method?(:each_with_object) MSG_EACH_WITH_OBJECT diff --git a/lib/rubocop/cop/lint/unreachable_loop.rb b/lib/rubocop/cop/lint/unreachable_loop.rb index a423b9b803a..4871fe6f6f4 100644 --- a/lib/rubocop/cop/lint/unreachable_loop.rb +++ b/lib/rubocop/cop/lint/unreachable_loop.rb @@ -101,10 +101,14 @@ def on_block(node) check(node) if loop_method?(node) end + def on_numblock(node) + check(node) if loop_method?(node) + end + private def loop_method?(node) - return false unless node.block_type? + return false unless node.block_type? || node.numblock_type? send_node = node.send_node return false if matches_allowed_pattern?(send_node.source) @@ -179,6 +183,8 @@ def check_case(node) def preceded_by_continue_statement?(break_statement) break_statement.left_siblings.any? do |sibling| + # Numblocks have the arguments count as a number in the AST. + next if sibling.is_a?(Integer) next if sibling.loop_keyword? || loop_method?(sibling) sibling.each_descendant(*CONTINUE_KEYWORDS).any? diff --git a/lib/rubocop/cop/lint/useless_access_modifier.rb b/lib/rubocop/cop/lint/useless_access_modifier.rb index 2b04eb7509d..ba74732f0ba 100644 --- a/lib/rubocop/cop/lint/useless_access_modifier.rb +++ b/lib/rubocop/cop/lint/useless_access_modifier.rb @@ -142,6 +142,8 @@ def on_block(node) check_node(node.body) end + alias on_numblock on_block + private def autocorrect(corrector, node) @@ -157,17 +159,17 @@ def autocorrect(corrector, node) # @!method dynamic_method_definition?(node) def_node_matcher :dynamic_method_definition?, <<~PATTERN - {(send nil? :define_method ...) (block (send nil? :define_method ...) ...)} + {(send nil? :define_method ...) ({block numblock} (send nil? :define_method ...) ...)} PATTERN # @!method class_or_instance_eval?(node) def_node_matcher :class_or_instance_eval?, <<~PATTERN - (block (send _ {:class_eval :instance_eval}) ...) + ({block numblock} (send _ {:class_eval :instance_eval}) ...) PATTERN # @!method class_or_module_or_struct_new_call?(node) def_node_matcher :class_or_module_or_struct_new_call?, <<~PATTERN - (block (send (const {nil? cbase} {:Class :Module :Struct}) :new ...) ...) + ({block numblock} (send (const {nil? cbase} {:Class :Module :Struct}) :new ...) ...) PATTERN def check_node(node) @@ -277,7 +279,7 @@ def any_context_creating_methods?(child) matcher_name = "#{m}_block?".to_sym unless respond_to?(matcher_name) self.class.def_node_matcher matcher_name, <<~PATTERN - (block (send {nil? const} {:#{m}} ...) ...) + ({block numblock} (send {nil? const} {:#{m}} ...) ...) PATTERN end diff --git a/lib/rubocop/cop/lint/void.rb b/lib/rubocop/cop/lint/void.rb index 3d0f8fa08e2..5ae162ddebb 100644 --- a/lib/rubocop/cop/lint/void.rb +++ b/lib/rubocop/cop/lint/void.rb @@ -67,6 +67,8 @@ def on_block(node) check_expression(node.body) end + alias on_numblock on_block + def on_begin(node) check_begin(node) end diff --git a/lib/rubocop/cop/mixin/hash_transform_method.rb b/lib/rubocop/cop/mixin/hash_transform_method.rb index c449ad2b07e..52e9fecf513 100644 --- a/lib/rubocop/cop/mixin/hash_transform_method.rb +++ b/lib/rubocop/cop/mixin/hash_transform_method.rb @@ -14,7 +14,7 @@ module HashTransformMethod {(array ...) (send _ :each_with_index) (send _ :with_index _ ?) (send _ :zip ...)} PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler on_bad_each_with_object(node) do |*match| handle_possible_offense(node, match, 'each_with_object') end diff --git a/lib/rubocop/cop/mixin/method_complexity.rb b/lib/rubocop/cop/mixin/method_complexity.rb index 92be750b239..7fb1f4b8687 100644 --- a/lib/rubocop/cop/mixin/method_complexity.rb +++ b/lib/rubocop/cop/mixin/method_complexity.rb @@ -29,14 +29,14 @@ def on_block(node) end end + alias on_numblock on_block + private # @!method define_method?(node) def_node_matcher :define_method?, <<~PATTERN - (block - (send nil? :define_method ({sym str} $_)) - args - _) + ({block numblock} + (send nil? :define_method ({sym str} $_)) _ _) PATTERN def check_complexity(node, method_name) diff --git a/lib/rubocop/cop/naming/block_parameter_name.rb b/lib/rubocop/cop/naming/block_parameter_name.rb index b4f44902092..b9c43a1f4a0 100644 --- a/lib/rubocop/cop/naming/block_parameter_name.rb +++ b/lib/rubocop/cop/naming/block_parameter_name.rb @@ -38,7 +38,7 @@ module Naming class BlockParameterName < Base include UncommunicativeName - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless node.arguments? check(node, node.arguments) diff --git a/lib/rubocop/cop/style/collection_methods.rb b/lib/rubocop/cop/style/collection_methods.rb index 2ab49a0804a..8d69a13d898 100644 --- a/lib/rubocop/cop/style/collection_methods.rb +++ b/lib/rubocop/cop/style/collection_methods.rb @@ -48,6 +48,8 @@ def on_block(node) check_method_node(node.send_node) end + alias on_numblock on_block + def on_send(node) return unless implicit_block?(node) diff --git a/lib/rubocop/cop/style/combinable_loops.rb b/lib/rubocop/cop/style/combinable_loops.rb index c29529a122e..4ed6d763666 100644 --- a/lib/rubocop/cop/style/combinable_loops.rb +++ b/lib/rubocop/cop/style/combinable_loops.rb @@ -66,6 +66,8 @@ def on_block(node) add_offense(node) if same_collection_looping?(node, node.left_sibling) end + alias on_numblock on_block + def on_for(node) return unless node.parent&.begin_type? @@ -82,7 +84,7 @@ def collection_looping_method?(node) end def same_collection_looping?(node, sibling) - sibling&.block_type? && + (sibling&.block_type? || sibling&.numblock_type?) && sibling.send_node.method?(node.method_name) && sibling.receiver == node.receiver && sibling.send_node.arguments == node.send_node.arguments diff --git a/lib/rubocop/cop/style/each_for_simple_loop.rb b/lib/rubocop/cop/style/each_for_simple_loop.rb index 1d3e13d9647..0262d4f36b3 100644 --- a/lib/rubocop/cop/style/each_for_simple_loop.rb +++ b/lib/rubocop/cop/style/each_for_simple_loop.rb @@ -27,7 +27,7 @@ class EachForSimpleLoop < Base MSG = 'Use `Integer#times` for a simple loop which iterates a fixed number of times.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless offending_each_range(node) send_node = node.send_node diff --git a/lib/rubocop/cop/style/each_with_object.rb b/lib/rubocop/cop/style/each_with_object.rb index 77e0abd79cd..3149231909c 100644 --- a/lib/rubocop/cop/style/each_with_object.rb +++ b/lib/rubocop/cop/style/each_with_object.rb @@ -23,13 +23,8 @@ class EachWithObject < Base MSG = 'Use `each_with_object` instead of `%s`.' METHODS = %i[inject reduce].freeze - # @!method each_with_object_candidate?(node) - def_node_matcher :each_with_object_candidate?, <<~PATTERN - (block $(send _ {:inject :reduce} _) $_ $_) - PATTERN - def on_block(node) - each_with_object_candidate?(node) do |method, args, body| + each_with_object_block_candidate?(node) do |method, args, body| _, method_name, method_arg = *method return if simple_method_arg?(method_arg) @@ -40,14 +35,38 @@ def on_block(node) message = format(MSG, method: method_name) add_offense(method.loc.selector, message: message) do |corrector| - autocorrect(corrector, node, return_value) + autocorrect_block(corrector, node, return_value) + end + end + end + + def on_numblock(node) + each_with_object_numblock_candidate?(node) do |method, body| + _, method_name, method_arg = *method + return if simple_method_arg?(method_arg) + + return unless return_value(body)&.source == '_1' + + message = format(MSG, method: method_name) + add_offense(method.loc.selector, message: message) do |corrector| + autocorrect_numblock(corrector, node) end end end private - def autocorrect(corrector, node, return_value) + # @!method each_with_object_block_candidate?(node) + def_node_matcher :each_with_object_block_candidate?, <<~PATTERN + (block $(send _ {:inject :reduce} _) $_ $_) + PATTERN + + # @!method each_with_object_numblock_candidate?(node) + def_node_matcher :each_with_object_numblock_candidate?, <<~PATTERN + (numblock $(send _ {:inject :reduce} _) 2 $_) + PATTERN + + def autocorrect_block(corrector, node, return_value) corrector.replace(node.send_node.loc.selector, 'each_with_object') first_arg, second_arg = *node.arguments @@ -62,6 +81,18 @@ def autocorrect(corrector, node, return_value) end end + def autocorrect_numblock(corrector, node) + corrector.replace(node.send_node.loc.selector, 'each_with_object') + + # We don't remove the return value to avoid a clobbering error. + node.body.each_descendant do |var| + next unless var.lvar_type? + + corrector.replace(var, '_2') if var.source == '_1' + corrector.replace(var, '_1') if var.source == '_2' + end + end + def simple_method_arg?(method_arg) method_arg&.basic_literal? end diff --git a/lib/rubocop/cop/style/empty_block_parameter.rb b/lib/rubocop/cop/style/empty_block_parameter.rb index 2222cdd1393..02426fa8ae3 100644 --- a/lib/rubocop/cop/style/empty_block_parameter.rb +++ b/lib/rubocop/cop/style/empty_block_parameter.rb @@ -28,7 +28,7 @@ class EmptyBlockParameter < Base MSG = 'Omit pipes for the empty block parameters.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler send_node = node.send_node check(node) unless send_node.send_type? && send_node.lambda_literal? end diff --git a/lib/rubocop/cop/style/empty_lambda_parameter.rb b/lib/rubocop/cop/style/empty_lambda_parameter.rb index cb2bc219935..6832f455a9d 100644 --- a/lib/rubocop/cop/style/empty_lambda_parameter.rb +++ b/lib/rubocop/cop/style/empty_lambda_parameter.rb @@ -23,7 +23,7 @@ class EmptyLambdaParameter < Base MSG = 'Omit parentheses for the empty lambda parameters.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler send_node = node.send_node return unless send_node.send_type? diff --git a/lib/rubocop/cop/style/for.rb b/lib/rubocop/cop/style/for.rb index 3bf47e9fc46..f3c336bca48 100644 --- a/lib/rubocop/cop/style/for.rb +++ b/lib/rubocop/cop/style/for.rb @@ -75,6 +75,8 @@ def on_block(node) end end + alias on_numblock on_block + private def suspect_enumerable?(node) diff --git a/lib/rubocop/cop/style/hash_each_methods.rb b/lib/rubocop/cop/style/hash_each_methods.rb index c37e4941d67..61d90657c89 100644 --- a/lib/rubocop/cop/style/hash_each_methods.rb +++ b/lib/rubocop/cop/style/hash_each_methods.rb @@ -35,13 +35,15 @@ class HashEachMethods < Base # @!method kv_each(node) def_node_matcher :kv_each, <<~PATTERN - (block $(send (send _ ${:keys :values}) :each) ...) + ({block numblock} $(send (send _ ${:keys :values}) :each) ...) PATTERN def on_block(node) register_kv_offense(node) end + alias on_numblock on_block + private def register_kv_offense(node) diff --git a/lib/rubocop/cop/style/inverse_methods.rb b/lib/rubocop/cop/style/inverse_methods.rb index 83c6d92085a..1ed7dfb9ecd 100644 --- a/lib/rubocop/cop/style/inverse_methods.rb +++ b/lib/rubocop/cop/style/inverse_methods.rb @@ -59,18 +59,18 @@ def self.autocorrect_incompatible_with def_node_matcher :inverse_candidate?, <<~PATTERN { (send $(send $(...) $_ $...) :!) - (send (block $(send $(...) $_) $...) :!) + (send ({block numblock} $(send $(...) $_) $...) :!) (send (begin $(send $(...) $_ $...)) :!) } PATTERN # @!method inverse_block?(node) def_node_matcher :inverse_block?, <<~PATTERN - (block $(send (...) $_) ... { $(send ... :!) - $(send (...) {:!= :!~} ...) - (begin ... $(send ... :!)) - (begin ... $(send (...) {:!= :!~} ...)) - }) + ({block numblock} $(send (...) $_) ... { $(send ... :!) + $(send (...) {:!= :!~} ...) + (begin ... $(send ... :!)) + (begin ... $(send (...) {:!= :!~} ...)) + }) PATTERN def on_send(node) @@ -102,6 +102,8 @@ def on_block(node) end end + alias on_numblock on_block + private def correct_inverse_method(corrector, node) diff --git a/lib/rubocop/cop/style/method_called_on_do_end_block.rb b/lib/rubocop/cop/style/method_called_on_do_end_block.rb index b58adc8f195..808558233f8 100644 --- a/lib/rubocop/cop/style/method_called_on_do_end_block.rb +++ b/lib/rubocop/cop/style/method_called_on_do_end_block.rb @@ -35,12 +35,15 @@ def on_block(node) ignore_node(node.send_node) end + alias on_numblock on_block + def on_send(node) return if ignored_node?(node) receiver = node.receiver - return unless receiver&.block_type? && receiver.loc.end.is?('end') + return unless (receiver&.block_type? || receiver&.numblock_type?) && + receiver.loc.end.is?('end') range = range_between(receiver.loc.end.begin_pos, node.source_range.end_pos) diff --git a/lib/rubocop/cop/style/multiline_block_chain.rb b/lib/rubocop/cop/style/multiline_block_chain.rb index 8634465366f..7f9de566ae3 100644 --- a/lib/rubocop/cop/style/multiline_block_chain.rb +++ b/lib/rubocop/cop/style/multiline_block_chain.rb @@ -31,7 +31,7 @@ def on_block(node) node.send_node.each_node(:send) do |send_node| receiver = send_node.receiver - next unless receiver&.block_type? && receiver&.multiline? + next unless (receiver&.block_type? || receiver&.numblock_type?) && receiver&.multiline? range = range_between(receiver.loc.end.begin_pos, node.send_node.source_range.end_pos) @@ -42,6 +42,8 @@ def on_block(node) break end end + + alias on_numblock on_block end end end diff --git a/lib/rubocop/cop/style/next.rb b/lib/rubocop/cop/style/next.rb index b3b47b231a5..e17db2cb62b 100644 --- a/lib/rubocop/cop/style/next.rb +++ b/lib/rubocop/cop/style/next.rb @@ -71,6 +71,8 @@ def on_block(node) check(node) end + alias on_numblock on_block + def on_while(node) check(node) end diff --git a/lib/rubocop/cop/style/nil_lambda.rb b/lib/rubocop/cop/style/nil_lambda.rb index b84972255e0..fdedf855103 100644 --- a/lib/rubocop/cop/style/nil_lambda.rb +++ b/lib/rubocop/cop/style/nil_lambda.rb @@ -43,7 +43,7 @@ class NilLambda < Base { ({return next break} nil) (nil) } PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless node.lambda? || node.proc? return unless nil_return?(node.body) diff --git a/lib/rubocop/cop/style/object_then.rb b/lib/rubocop/cop/style/object_then.rb index fc09314a750..f36ae076fdd 100644 --- a/lib/rubocop/cop/style/object_then.rb +++ b/lib/rubocop/cop/style/object_then.rb @@ -32,6 +32,8 @@ def on_block(node) check_method_node(node.send_node) end + alias on_numblock on_block + def on_send(node) return unless node.arguments.one? && node.first_argument.block_pass_type? diff --git a/lib/rubocop/cop/style/proc.rb b/lib/rubocop/cop/style/proc.rb index 02bdc65b504..c9091183069 100644 --- a/lib/rubocop/cop/style/proc.rb +++ b/lib/rubocop/cop/style/proc.rb @@ -19,7 +19,8 @@ class Proc < Base MSG = 'Use `proc` instead of `Proc.new`.' # @!method proc_new?(node) - def_node_matcher :proc_new?, '(block $(send (const {nil? cbase} :Proc) :new) ...)' + def_node_matcher :proc_new?, + '({block numblock} $(send (const {nil? cbase} :Proc) :new) ...)' def on_block(node) proc_new?(node) do |block_method| @@ -28,6 +29,8 @@ def on_block(node) end end end + + alias on_numblock on_block end end end diff --git a/lib/rubocop/cop/style/redundant_begin.rb b/lib/rubocop/cop/style/redundant_begin.rb index 54f0a3a8df4..fe5b0bfd44a 100644 --- a/lib/rubocop/cop/style/redundant_begin.rb +++ b/lib/rubocop/cop/style/redundant_begin.rb @@ -89,6 +89,8 @@ def on_block(node) register_offense(node.body) end + alias on_numblock on_block + def on_kwbegin(node) return unless (target_node = offensive_kwbegins(node).to_a.last) diff --git a/lib/rubocop/cop/style/redundant_fetch_block.rb b/lib/rubocop/cop/style/redundant_fetch_block.rb index 17503445188..26ac4b14835 100644 --- a/lib/rubocop/cop/style/redundant_fetch_block.rb +++ b/lib/rubocop/cop/style/redundant_fetch_block.rb @@ -50,7 +50,7 @@ class RedundantFetchBlock < Base ${nil? #basic_literal? #const_type?}) PATTERN - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler redundant_fetch_block_candidate?(node) do |send, body| return if should_not_check?(send, body) diff --git a/lib/rubocop/cop/style/redundant_self.rb b/lib/rubocop/cop/style/redundant_self.rb index 3edfa90cf7d..af9279c6b45 100644 --- a/lib/rubocop/cop/style/redundant_self.rb +++ b/lib/rubocop/cop/style/redundant_self.rb @@ -120,6 +120,8 @@ def on_block(node) add_scope(node, @local_variables_scopes[node]) end + alias on_numblock on_block + def on_if(node) # Allow conditional nodes to use `self` in the condition if that variable # name is used in an `lvasgn` or `masgn` within the `if`. diff --git a/lib/rubocop/cop/style/redundant_sort_by.rb b/lib/rubocop/cop/style/redundant_sort_by.rb index a746cdf3263..2cc1be8b78d 100644 --- a/lib/rubocop/cop/style/redundant_sort_by.rb +++ b/lib/rubocop/cop/style/redundant_sort_by.rb @@ -19,18 +19,24 @@ class RedundantSortBy < Base include RangeHelp extend AutoCorrector - MSG = 'Use `sort` instead of `sort_by { |%s| %s }`.' - - # @!method redundant_sort_by(node) - def_node_matcher :redundant_sort_by, <<~PATTERN - (block $(send _ :sort_by) (args (arg $_x)) (lvar _x)) - PATTERN + MSG_BLOCK = 'Use `sort` instead of `sort_by { |%s| %s }`.' + MSG_NUMBLOCK = 'Use `sort` instead of `sort_by { _1 }`.' def on_block(node) - redundant_sort_by(node) do |send, var_name| + redundant_sort_by_block(node) do |send, var_name| range = sort_by_range(send, node) - add_offense(range, message: format(MSG, var: var_name)) do |corrector| + add_offense(range, message: format(MSG_BLOCK, var: var_name)) do |corrector| + corrector.replace(range, 'sort') + end + end + end + + def on_numblock(node) + redundant_sort_by_numblock(node) do |send| + range = sort_by_range(send, node) + + add_offense(range, message: format(MSG_NUMBLOCK)) do |corrector| corrector.replace(range, 'sort') end end @@ -38,6 +44,16 @@ def on_block(node) private + # @!method redundant_sort_by_block(node) + def_node_matcher :redundant_sort_by_block, <<~PATTERN + (block $(send _ :sort_by) (args (arg $_x)) (lvar _x)) + PATTERN + + # @!method redundant_sort_by_numblock(node) + def_node_matcher :redundant_sort_by_numblock, <<~PATTERN + (numblock $(send _ :sort_by) 1 (lvar :_1)) + PATTERN + def sort_by_range(send, node) range_between(send.loc.selector.begin_pos, node.loc.end.end_pos) end diff --git a/lib/rubocop/cop/style/single_line_block_params.rb b/lib/rubocop/cop/style/single_line_block_params.rb index 6df4fd4ebe5..89e9b431df3 100644 --- a/lib/rubocop/cop/style/single_line_block_params.rb +++ b/lib/rubocop/cop/style/single_line_block_params.rb @@ -33,7 +33,7 @@ class SingleLineBlockParams < Base MSG = 'Name `%s` block params `|%s|`.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler return unless node.single_line? return unless eligible_method?(node) diff --git a/lib/rubocop/cop/style/top_level_method_definition.rb b/lib/rubocop/cop/style/top_level_method_definition.rb index 4b94428b2ae..c896db41785 100644 --- a/lib/rubocop/cop/style/top_level_method_definition.rb +++ b/lib/rubocop/cop/style/top_level_method_definition.rb @@ -63,6 +63,8 @@ def on_block(node) add_offense(node) end + alias on_numblock on_block + private def top_level_method_definition?(node) @@ -75,7 +77,7 @@ def top_level_method_definition?(node) # @!method define_method_block?(node) def_node_matcher :define_method_block?, <<~PATTERN - (block (send _ {:define_method} _) ...) + ({block numblock} (send _ {:define_method} _) ...) PATTERN end end diff --git a/lib/rubocop/cop/style/trailing_comma_in_block_args.rb b/lib/rubocop/cop/style/trailing_comma_in_block_args.rb index 1ed5e6edce3..9f4cf315d3b 100644 --- a/lib/rubocop/cop/style/trailing_comma_in_block_args.rb +++ b/lib/rubocop/cop/style/trailing_comma_in_block_args.rb @@ -64,7 +64,7 @@ class TrailingCommaInBlockArgs < Base MSG = 'Useless trailing comma present in block arguments.' - def on_block(node) + def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler # lambda literal (`->`) never has block arguments. return if node.send_node.lambda_literal? return unless useless_trailing_comma?(node) diff --git a/rubocop.gemspec b/rubocop.gemspec index d0977aa4ff9..dda7f76decf 100644 --- a/rubocop.gemspec +++ b/rubocop.gemspec @@ -37,7 +37,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 4.0') s.add_runtime_dependency('regexp_parser', '>= 1.8', '< 3.0') s.add_runtime_dependency('rexml', '>= 3.2.5', '< 4.0') - s.add_runtime_dependency('rubocop-ast', '>= 1.20.0', '< 2.0') + s.add_runtime_dependency('rubocop-ast', '>= 1.20.1', '< 2.0') s.add_runtime_dependency('ruby-progressbar', '~> 1.7') s.add_runtime_dependency('unicode-display_width', '>= 1.4.0', '< 3.0') diff --git a/spec/rubocop/cop/internal_affairs/numblock_handler_spec.rb b/spec/rubocop/cop/internal_affairs/numblock_handler_spec.rb new file mode 100644 index 00000000000..7bdd71690e4 --- /dev/null +++ b/spec/rubocop/cop/internal_affairs/numblock_handler_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::InternalAffairs::NumblockHandler, :config do + it 'registers an offense for cops with forgotten numblock handlers' do + expect_offense <<~RUBY + class Cop < Base + def on_block(node) + ^^^^^^^^^^^^^^^^^^ Define on_numblock to handle blocks with numbered arguments. + end + end + RUBY + end + + it 'does not register an offense for cops with on_numblock alias' do + expect_no_offenses <<~RUBY + class Cop < Base + def on_block(node) + end + + alias on_numblock on_block + end + RUBY + end + + it 'does not register an offense for cops with on_numblock alias_method' do + expect_no_offenses <<~RUBY + class Cop < Base + def on_block(node) + end + + alias_method :on_numblock, :on_block + end + RUBY + end + + it 'does not register an offense for cops with on_numblock method' do + expect_no_offenses <<~RUBY + class Cop < Base + def on_block(node) + end + + def on_numblock(node) + end + end + RUBY + end +end diff --git a/spec/rubocop/cop/layout/block_alignment_spec.rb b/spec/rubocop/cop/layout/block_alignment_spec.rb index 65e7471803d..30c2e1cf354 100644 --- a/spec/rubocop/cop/layout/block_alignment_spec.rb +++ b/spec/rubocop/cop/layout/block_alignment_spec.rb @@ -675,4 +675,23 @@ def abc RUBY end end + + context 'Ruby 2.7', :ruby27 do + it 'accepts end aligned with a call chain left hand side' do + expect_no_offenses(<<~RUBY) + parser.diagnostics.consumer = lambda do + _1 << diagnostic + end + RUBY + end + + it 'registers an offense for mismatched block end with a mass assignment' do + expect_offense(<<~RUBY) + var1, var2 = lambda do + [_1, _2] + end + ^^^ `end` at 3, 2 is not aligned with `var1, var2` at 1, 0. + RUBY + end + end end diff --git a/spec/rubocop/cop/layout/block_end_newline_spec.rb b/spec/rubocop/cop/layout/block_end_newline_spec.rb index 51bdd3784ac..f687d9fee2c 100644 --- a/spec/rubocop/cop/layout/block_end_newline_spec.rb +++ b/spec/rubocop/cop/layout/block_end_newline_spec.rb @@ -182,4 +182,40 @@ } RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense and corrects when multiline block `}` is not on its own line ' \ + 'and using method chain' do + expect_offense(<<~RUBY) + test { + _1 }.bar.baz + ^ Expression at 2, 6 should be on its own line. + RUBY + + expect_correction(<<~RUBY) + test { + _1 + }.bar.baz + RUBY + end + + it 'registers an offense and corrects when multiline block `}` is not on its own line ' \ + 'and using heredoc argument' do + expect_offense(<<~RUBY) + test { + _1.push(<<~EOS) } + ^ Expression at 2, 19 should be on its own line. + Heredoc text. + EOS + RUBY + + expect_correction(<<~RUBY) + test { + _1.push(<<~EOS) + Heredoc text. + EOS + } + RUBY + end + end end diff --git a/spec/rubocop/cop/layout/empty_lines_around_access_modifier_spec.rb b/spec/rubocop/cop/layout/empty_lines_around_access_modifier_spec.rb index 51bf8b88fac..87baf160511 100644 --- a/spec/rubocop/cop/layout/empty_lines_around_access_modifier_spec.rb +++ b/spec/rubocop/cop/layout/empty_lines_around_access_modifier_spec.rb @@ -418,4 +418,37 @@ def test; end end end end + + context 'Ruby 2.7', :ruby27 do + %w[private protected public module_function].each do |access_modifier| + it "registers an offense for missing around line before #{access_modifier}" do + expect_offense(<<~RUBY) + included do + _1 + #{access_modifier} + #{'^' * access_modifier.size} Keep a blank line before and after `#{access_modifier}`. + def test; end + end + RUBY + + expect_correction(<<~RUBY) + included do + _1 + + #{access_modifier} + + def test; end + end + RUBY + end + + it "ignores #{access_modifier} with numblock argument" do + expect_no_offenses(<<~RUBY) + def foo + #{access_modifier} { _1 } + end + RUBY + end + end + end end diff --git a/spec/rubocop/cop/layout/empty_lines_around_block_body_spec.rb b/spec/rubocop/cop/layout/empty_lines_around_block_body_spec.rb index cb85fd7fd9f..2910b2f8350 100644 --- a/spec/rubocop/cop/layout/empty_lines_around_block_body_spec.rb +++ b/spec/rubocop/cop/layout/empty_lines_around_block_body_spec.rb @@ -40,6 +40,24 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for block body ending with a blank' do + expect_offense(<<~RUBY) + some_method #{open} + _1 + + ^{} Extra empty line detected at block body end. + #{close} + RUBY + + expect_correction(<<~RUBY) + some_method #{open} + _1 + #{close} + RUBY + end + end + it 'accepts block body starting with a line with spaces' do expect_no_offenses(<<~RUBY) some_method #{open} diff --git a/spec/rubocop/cop/layout/indentation_width_spec.rb b/spec/rubocop/cop/layout/indentation_width_spec.rb index a65e2337f8e..993937e94a4 100644 --- a/spec/rubocop/cop/layout/indentation_width_spec.rb +++ b/spec/rubocop/cop/layout/indentation_width_spec.rb @@ -1599,6 +1599,26 @@ def bar end end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for bad indentation of a {} body' do + expect_offense(<<~RUBY) + func { + _1&.foo + ^^^ Use 2 (not 3) spaces for indentation. + } + RUBY + end + + it 'registers an offense for bad indentation of a do-end body' do + expect_offense(<<~RUBY) + func do + _1&.foo + ^^^ Use 2 (not 3) spaces for indentation. + end + RUBY + end + end + # The cop uses the block end/} as the base for indentation, so if it's not # on its own line, all bets are off. it 'accepts badly indented code if block end is not on separate line' do diff --git a/spec/rubocop/cop/layout/line_length_spec.rb b/spec/rubocop/cop/layout/line_length_spec.rb index ecf519c7345..fd6913556f2 100644 --- a/spec/rubocop/cop/layout/line_length_spec.rb +++ b/spec/rubocop/cop/layout/line_length_spec.rb @@ -966,7 +966,7 @@ def baz(bar) end context 'do/end' do - it 'adds an offense and does correct it' do + it 'adds an offense for block with arguments and does correct it' do expect_offense(<<~RUBY) foo.select do |bar| 4444000039123123129993912312312999199291203123 end ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [70/40] @@ -977,6 +977,18 @@ def baz(bar) 4444000039123123129993912312312999199291203123 end RUBY end + + it 'adds an offense for block without arguments and does correct it' do + expect_offense(<<~RUBY) + foo.select do 4444000039123123129993912312312999199291203123 end + ^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [64/40] + RUBY + + expect_correction(<<~RUBY) + foo.select do + 4444000039123123129993912312312999199291203123 end + RUBY + end end context 'let block' do @@ -1036,6 +1048,32 @@ def baz(bar) end end end + + context 'Ruby 2.7', :ruby27 do + it 'adds an offense for {} block does correct it' do + expect_offense(<<~RUBY) + foo.select { _1 + 4444000039123123129993912312312999199291203123123 } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [69/40] + RUBY + + expect_correction(<<~RUBY) + foo.select { + _1 + 4444000039123123129993912312312999199291203123123 } + RUBY + end + + it 'adds an offense for do-end block and does correct it' do + expect_offense(<<~RUBY) + foo.select do _1 + 4444000039123123129993912312312999199291203123 end + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Line is too long. [69/40] + RUBY + + expect_correction(<<~RUBY) + foo.select do + _1 + 4444000039123123129993912312312999199291203123 end + RUBY + end + end end context 'semicolon' do diff --git a/spec/rubocop/cop/layout/multiline_block_layout_spec.rb b/spec/rubocop/cop/layout/multiline_block_layout_spec.rb index d1f390e1a59..cf00c16ca03 100644 --- a/spec/rubocop/cop/layout/multiline_block_layout_spec.rb +++ b/spec/rubocop/cop/layout/multiline_block_layout_spec.rb @@ -348,4 +348,34 @@ def f end RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense and corrects for missing newline in {} block w/o params' do + expect_offense(<<~RUBY) + test { _1 + ^^ Block body expression is on the same line as the block start. + } + RUBY + + expect_correction(<<~RUBY) + test {#{trailing_whitespace} + _1 + } + RUBY + end + + it 'registers an offense and corrects for missing newline in do/end block with params' do + expect_offense(<<~RUBY) + test do _1 + ^^ Block body expression is on the same line as the block start. + end + RUBY + + expect_correction(<<~RUBY) + test do#{trailing_whitespace} + _1 + end + RUBY + end + end end diff --git a/spec/rubocop/cop/layout/space_before_block_braces_spec.rb b/spec/rubocop/cop/layout/space_before_block_braces_spec.rb index ec4bc161569..ce7de02b510 100644 --- a/spec/rubocop/cop/layout/space_before_block_braces_spec.rb +++ b/spec/rubocop/cop/layout/space_before_block_braces_spec.rb @@ -47,6 +47,37 @@ } RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense and corrects opposite + correct style' do + expect_offense(<<~RUBY) + each{ _1 } + ^ Space missing to the left of {. + each { _1 } + RUBY + + expect_correction(<<~RUBY) + each { _1 } + each { _1 } + RUBY + end + + it 'registers an offense and corrects multiline block where the left ' \ + 'brace has no outer space' do + expect_offense(<<~RUBY) + foo.map{ + ^ Space missing to the left of {. + _1.bar.to_s + } + RUBY + + expect_correction(<<~RUBY) + foo.map { + _1.bar.to_s + } + RUBY + end + end end context 'when EnforcedStyle is no_space' do @@ -80,6 +111,21 @@ expect_no_offenses('each{ puts }') end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense and corrects correct + opposite style' do + expect_offense(<<~RUBY) + each{ _1 } + each { _1 } + ^ Space detected to the left of {. + RUBY + + expect_correction(<<~RUBY) + each{ _1 } + each{ _1 } + RUBY + end + end + context 'with `EnforcedStyle` of `Style/BlockDelimiters`' do let(:config) do merged_config = RuboCop::ConfigLoader.default_configuration[ diff --git a/spec/rubocop/cop/lint/next_without_accumulator_spec.rb b/spec/rubocop/cop/lint/next_without_accumulator_spec.rb index 179095af295..832ec4a4463 100644 --- a/spec/rubocop/cop/lint/next_without_accumulator_spec.rb +++ b/spec/rubocop/cop/lint/next_without_accumulator_spec.rb @@ -33,6 +33,18 @@ end RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for a bare next' do + expect_offense(<<~RUBY) + (1..4).#{reduce_alias}(0) do + next if _2.odd? + ^^^^ Use `next` with an accumulator argument in a `reduce`. + _1 + i + end + RUBY + end + end end end diff --git a/spec/rubocop/cop/lint/non_deterministic_require_order_spec.rb b/spec/rubocop/cop/lint/non_deterministic_require_order_spec.rb index 2328b8cf13a..086ea99ed55 100644 --- a/spec/rubocop/cop/lint/non_deterministic_require_order_spec.rb +++ b/spec/rubocop/cop/lint/non_deterministic_require_order_spec.rb @@ -131,6 +131,21 @@ RUBY end + it 'registers an offsense and autocorrects to add .sort when the numblock has `require`' do + expect_offense(<<~RUBY) + Dir["./lib/**/*.rb"].each do + ^^^^^^^^^^^^^^^^^^^^^^^^^ Sort files before requiring them. + require _1 + end + RUBY + + expect_correction(<<~RUBY) + Dir["./lib/**/*.rb"].sort.each do + require _1 + end + RUBY + end + it 'registers an offsense and autocorrects to add .sort when the block has `require_relative`' do expect_offense(<<~RUBY) Dir["./lib/**/*.rb"].each do |file| diff --git a/spec/rubocop/cop/lint/redundant_with_index_spec.rb b/spec/rubocop/cop/lint/redundant_with_index_spec.rb index 068f52dfb03..fc9744351fd 100644 --- a/spec/rubocop/cop/lint/redundant_with_index_spec.rb +++ b/spec/rubocop/cop/lint/redundant_with_index_spec.rb @@ -51,4 +51,32 @@ it 'accepts an index is used as a block argument' do expect_no_offenses('ary.each_with_index { |v, i| v; i }') end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for `ary.each_with_index { _1 }` and corrects to `ary.each`' do + expect_offense(<<~RUBY) + ary.each_with_index { _1 } + ^^^^^^^^^^^^^^^ Use `each` instead of `each_with_index`. + RUBY + + expect_correction(<<~RUBY) + ary.each { _1 } + RUBY + end + + it 'registers an offense when using `ary.each.with_index { _1 }` and corrects to `ary.each`' do + expect_offense(<<~RUBY) + ary.each.with_index { _1 } + ^^^^^^^^^^ Remove redundant `with_index`. + RUBY + + expect_correction(<<~RUBY) + ary.each { _1 } + RUBY + end + + it 'accepts an index is used as a numblock argument' do + expect_no_offenses('ary.each_with_index { _1; _2 }') + end + end end diff --git a/spec/rubocop/cop/lint/redundant_with_object_spec.rb b/spec/rubocop/cop/lint/redundant_with_object_spec.rb index 7dd5f0073e2..9fcb70e26b2 100644 --- a/spec/rubocop/cop/lint/redundant_with_object_spec.rb +++ b/spec/rubocop/cop/lint/redundant_with_object_spec.rb @@ -67,4 +67,28 @@ expect_no_offenses('ary.each_with_object { |v| v }') end end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense and corrects when using `ary.each_with_object { _1 }`' do + expect_offense(<<~RUBY) + ary.each_with_object([]) { _1 } + ^^^^^^^^^^^^^^^^^^^^ Use `each` instead of `each_with_object`. + RUBY + + expect_correction(<<~RUBY) + ary.each { _1 } + RUBY + end + + it 'registers an offense and corrects when using `ary.each.with_object([]) { _1 }`' do + expect_offense(<<~RUBY) + ary.each.with_object([]) { _1 } + ^^^^^^^^^^^^^^^ Remove redundant `with_object`. + RUBY + + expect_correction(<<~RUBY) + ary.each { _1 } + RUBY + end + end end diff --git a/spec/rubocop/cop/lint/unreachable_loop_spec.rb b/spec/rubocop/cop/lint/unreachable_loop_spec.rb index baf2e841758..8ebd1a001ec 100644 --- a/spec/rubocop/cop/lint/unreachable_loop_spec.rb +++ b/spec/rubocop/cop/lint/unreachable_loop_spec.rb @@ -178,6 +178,15 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This loop will have at most one iteration. RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense' do + expect_offense(<<~RUBY) + 2.times { raise _1 } + ^^^^^^^^^^^^^^^^^^^^ This loop will have at most one iteration. + RUBY + end + end end end @@ -250,4 +259,15 @@ end RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense when using `return do_something(value) || break` in a loop' do + expect_offense(<<~RUBY) + [1, 2, 3].each do + ^^^^^^^^^^^^^^^^^ This loop will have at most one iteration. + return _1.odd? || break + end + RUBY + end + end end diff --git a/spec/rubocop/cop/lint/useless_access_modifier_spec.rb b/spec/rubocop/cop/lint/useless_access_modifier_spec.rb index 6ab9b2a80cc..a851b3a08dd 100644 --- a/spec/rubocop/cop/lint/useless_access_modifier_spec.rb +++ b/spec/rubocop/cop/lint/useless_access_modifier_spec.rb @@ -329,6 +329,42 @@ def another_method end RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'still points out redundant uses within the block' do + expect_offense(<<~RUBY) + class SomeClass + concerning :SecondThing do + p _1 + def omg + end + private + def method + end + private + ^^^^^^^ Useless `private` access modifier. + def another_method + end + end + end + RUBY + + expect_correction(<<~RUBY) + class SomeClass + concerning :SecondThing do + p _1 + def omg + end + private + def method + end + def another_method + end + end + end + RUBY + end + end end context 'when using ActiveSupport behavior when Rails is not eabled' do diff --git a/spec/rubocop/cop/lint/void_spec.rb b/spec/rubocop/cop/lint/void_spec.rb index 5226d616c2e..5844b48e22f 100644 --- a/spec/rubocop/cop/lint/void_spec.rb +++ b/spec/rubocop/cop/lint/void_spec.rb @@ -188,6 +188,18 @@ def foo=(rhs) RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers two offenses for void literals in `#tap` method' do + expect_offense(<<~RUBY) + foo.tap do + _1 + ^^ Variable `_1` used in void context. + 42 + end + RUBY + end + end + it 'accepts empty block' do expect_no_offenses(<<~RUBY) array.each { |_item| } diff --git a/spec/rubocop/cop/metrics/abc_size_spec.rb b/spec/rubocop/cop/metrics/abc_size_spec.rb index 24de31eb20d..b79817342e0 100644 --- a/spec/rubocop/cop/metrics/abc_size_spec.rb +++ b/spec/rubocop/cop/metrics/abc_size_spec.rb @@ -67,6 +67,17 @@ def method_name RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for a `define_method` with numblock' do + expect_offense(<<~RUBY) + define_method :method_name do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Assignment Branch Condition size for method_name is too high. [<1, 0, 0> 1/0] + x = _1 + end + RUBY + end + end + it 'treats safe navigation method calls like regular method calls + a condition' do expect_offense(<<~RUBY) def method_name diff --git a/spec/rubocop/cop/metrics/cyclomatic_complexity_spec.rb b/spec/rubocop/cop/metrics/cyclomatic_complexity_spec.rb index 1b147f213e2..dffbb3d5214 100644 --- a/spec/rubocop/cop/metrics/cyclomatic_complexity_spec.rb +++ b/spec/rubocop/cop/metrics/cyclomatic_complexity_spec.rb @@ -268,6 +268,20 @@ def method_name_2 RUBY end + context 'Ruby 2.7', :ruby27 do + it 'counts enumerating methods with numblocks as +1' do + expect_offense(<<~RUBY) + define_method :method_name do + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Cyclomatic complexity for method_name is too high. [3/1] + (_1.._2).map do |i| # map: +1 + i * 2 + end.each.with_index { |val, i| puts val, i } # each: +0, with_index: +1 + return treasure.map + end + RUBY + end + end + it 'counts enumerating methods with block-pass as +1' do expect_offense(<<~RUBY) define_method :method_name do diff --git a/spec/rubocop/cop/style/collection_methods_spec.rb b/spec/rubocop/cop/style/collection_methods_spec.rb index e6e97a6e202..87d158a2864 100644 --- a/spec/rubocop/cop/style/collection_methods_spec.rb +++ b/spec/rubocop/cop/style/collection_methods_spec.rb @@ -25,6 +25,19 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it "registers an offense for #{method} with numblock" do + expect_offense(<<~RUBY, method: method) + [1, 2, 3].%{method} { _1 + 1 } + ^{method} Prefer `#{preferred_method}` over `#{method}`. + RUBY + + expect_correction(<<~RUBY) + [1, 2, 3].#{preferred_method} { _1 + 1 } + RUBY + end + end + it "registers an offense for #{method} with proc param" do expect_offense(<<~RUBY, method: method) [1, 2, 3].%{method}(&:test) diff --git a/spec/rubocop/cop/style/combinable_loops_spec.rb b/spec/rubocop/cop/style/combinable_loops_spec.rb index 7ac3e3e3118..465deb208a3 100644 --- a/spec/rubocop/cop/style/combinable_loops_spec.rb +++ b/spec/rubocop/cop/style/combinable_loops_spec.rb @@ -18,6 +18,24 @@ RUBY end + context 'Ruby 2.7' do + it 'registers an offense when looping over the same data as previous loop in numblocks' do + expect_offense(<<~RUBY) + items.each { do_something(_1) } + items.each { do_something_else(_1, arg) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Combine this loop with the previous loop. + + items.each_with_index { do_something(_1) } + items.each_with_index { do_something_else(_1, arg) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Combine this loop with the previous loop. + + items.reverse_each { do_something(_1) } + items.reverse_each { do_something_else(_1, arg) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Combine this loop with the previous loop. + RUBY + end + end + it 'does not register an offense when the same loops are interleaved with some code' do expect_no_offenses(<<~RUBY) items.each { |item| do_something(item) } diff --git a/spec/rubocop/cop/style/each_with_object_spec.rb b/spec/rubocop/cop/style/each_with_object_spec.rb index c312b74c846..07ef3ed8e8f 100644 --- a/spec/rubocop/cop/style/each_with_object_spec.rb +++ b/spec/rubocop/cop/style/each_with_object_spec.rb @@ -24,6 +24,25 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'finds inject and reduce with passed in and returned hash and numblock' do + expect_offense(<<~RUBY) + [].reduce({}) do + ^^^^^^ Use `each_with_object` instead of `reduce`. + _1[_2] = 1 + _1 + end + RUBY + + expect_correction(<<~RUBY) + [].each_with_object({}) do + _2[_1] = 1 + _2 + end + RUBY + end + end + it 'correctly autocorrects' do expect_offense(<<~RUBY) [1, 2, 3].inject({}) do |h, i| diff --git a/spec/rubocop/cop/style/for_spec.rb b/spec/rubocop/cop/style/for_spec.rb index cda622103e4..739ee7f06b3 100644 --- a/spec/rubocop/cop/style/for_spec.rb +++ b/spec/rubocop/cop/style/for_spec.rb @@ -442,6 +442,27 @@ def func RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for each without an item and uses _ as the item' do + expect_offense(<<~RUBY) + def func + [1, 2, 3].each do + ^^^^^^^^^^^^^^^^^ Prefer `for` over `each`. + puts _1 + end + end + RUBY + + expect_correction(<<~RUBY) + def func + for _ in [1, 2, 3] do + puts _1 + end + end + RUBY + end + end + it 'registers an offense for correct + opposite style' do expect_offense(<<~RUBY) def func diff --git a/spec/rubocop/cop/style/hash_each_methods_spec.rb b/spec/rubocop/cop/style/hash_each_methods_spec.rb index d34423c7ff1..5dd05782671 100644 --- a/spec/rubocop/cop/style/hash_each_methods_spec.rb +++ b/spec/rubocop/cop/style/hash_each_methods_spec.rb @@ -32,6 +32,19 @@ it 'does not register an offense for Hash#each_value' do expect_no_offenses('foo.each_value { |v| p v }') end + + context 'Ruby 2.7' do + it 'registers offense, autocorrects foo#keys.each to foo#each_key with numblock' do + expect_offense(<<~RUBY) + foo.keys.each { p _1 } + ^^^^^^^^^ Use `each_key` instead of `keys.each`. + RUBY + + expect_correction(<<~RUBY) + foo.each_key { p _1 } + RUBY + end + end end context 'when receiver is a hash literal' do diff --git a/spec/rubocop/cop/style/inverse_methods_spec.rb b/spec/rubocop/cop/style/inverse_methods_spec.rb index 25d8f8f8215..699bae6bc2f 100644 --- a/spec/rubocop/cop/style/inverse_methods_spec.rb +++ b/spec/rubocop/cop/style/inverse_methods_spec.rb @@ -44,6 +44,19 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for calling !.none? with a numblock' do + expect_offense(<<~RUBY) + !foo.none? { _1.even? } + ^^^^^^^^^^^^^^^^^^^^^^^ Use `any?` instead of inverting `none?`. + RUBY + + expect_correction(<<~RUBY) + foo.any? { _1.even? } + RUBY + end + end + it 'registers an offense for calling !.any? inside parens' do expect_offense(<<~RUBY) !(foo.any? &:working?) diff --git a/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb b/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb index 51eedd011e6..82943c99b35 100644 --- a/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb +++ b/spec/rubocop/cop/style/method_called_on_do_end_block_spec.rb @@ -67,4 +67,15 @@ expect_no_offenses('a { b }.c') end end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for a chained call' do + expect_offense(<<~RUBY) + a do + _1 + end.c + ^^^^^ Avoid chaining a method call on a do...end block. + RUBY + end + end end diff --git a/spec/rubocop/cop/style/multiline_block_chain_spec.rb b/spec/rubocop/cop/style/multiline_block_chain_spec.rb index 0ee57f02224..2e8330fa993 100644 --- a/spec/rubocop/cop/style/multiline_block_chain_spec.rb +++ b/spec/rubocop/cop/style/multiline_block_chain_spec.rb @@ -24,6 +24,19 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for a slightly more complicated case' do + expect_offense(<<~RUBY) + a do + _1 + end.c1.c2 do + ^^^^^^^^^ Avoid multi-line chains of blocks. + _1 + end + RUBY + end + end + it 'registers two offenses for a chain of three blocks' do expect_offense(<<~RUBY) a do diff --git a/spec/rubocop/cop/style/next_spec.rb b/spec/rubocop/cop/style/next_spec.rb index 461e6859a80..e1f214ea82a 100644 --- a/spec/rubocop/cop/style/next_spec.rb +++ b/spec/rubocop/cop/style/next_spec.rb @@ -24,6 +24,26 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it "registers an offense for #{condition} inside of downto numblock" do + expect_offense(<<~RUBY, condition: condition) + 3.downto(1) do + %{condition} _1 == 1 + ^{condition}^^^^^^^^ Use `next` to skip iteration. + puts _1 + end + end + RUBY + + expect_correction(<<~RUBY) + 3.downto(1) do + next #{opposite} _1 == 1 + puts _1 + end + RUBY + end + end + it "registers an offense for #{condition} inside of each" do expect_offense(<<~RUBY, condition: condition) [].each do |o| diff --git a/spec/rubocop/cop/style/object_then_spec.rb b/spec/rubocop/cop/style/object_then_spec.rb index 80b34dcdcaf..27780e00b5f 100644 --- a/spec/rubocop/cop/style/object_then_spec.rb +++ b/spec/rubocop/cop/style/object_then_spec.rb @@ -15,6 +15,19 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for yield_self with block' do + expect_offense(<<~RUBY) + obj.yield_self { _1.test } + ^^^^^^^^^^ Prefer `then` over `yield_self`. + RUBY + + expect_correction(<<~RUBY) + obj.then { _1.test } + RUBY + end + end + it 'registers an offense for yield_self with proc param' do expect_offense(<<~RUBY) obj.yield_self(&:test) diff --git a/spec/rubocop/cop/style/proc_spec.rb b/spec/rubocop/cop/style/proc_spec.rb index bbf64250789..0536448a224 100644 --- a/spec/rubocop/cop/style/proc_spec.rb +++ b/spec/rubocop/cop/style/proc_spec.rb @@ -30,4 +30,17 @@ it 'accepts the ::Proc.new call without block' do expect_no_offenses('p = ::Proc.new') end + + context 'Ruby 2.7', :ruby27 do + it 'registers an offense for a Proc.new call' do + expect_offense(<<~RUBY) + f = Proc.new { puts _1 } + ^^^^^^^^ Use `proc` instead of `Proc.new`. + RUBY + + expect_correction(<<~RUBY) + f = proc { puts _1 } + RUBY + end + end end diff --git a/spec/rubocop/cop/style/redundant_begin_spec.rb b/spec/rubocop/cop/style/redundant_begin_spec.rb index 8f2f0011df6..ff4781d8b49 100644 --- a/spec/rubocop/cop/style/redundant_begin_spec.rb +++ b/spec/rubocop/cop/style/redundant_begin_spec.rb @@ -531,4 +531,42 @@ def a_method end RUBY end + + context 'Ruby 2.7', :ruby27 do + it 'reports an offense when assigning nested blocks which contain `begin` blocks' do + expect_offense(<<~RUBY) + var = do_something do + begin + ^^^^^ Redundant `begin` block detected. + do_something do + begin + ^^^^^ Redundant `begin` block detected. + _1 + ensure + bar + end + end + ensure + baz + end + end + RUBY + + expect_correction(<<~RUBY) + var = do_something do + #{trailing_whitespace} + do_something do + #{trailing_whitespace} + _1 + ensure + bar + #{trailing_whitespace} + end + ensure + baz + #{trailing_whitespace} + end + RUBY + end + end end diff --git a/spec/rubocop/cop/style/redundant_self_spec.rb b/spec/rubocop/cop/style/redundant_self_spec.rb index 3aaee581dcc..71c7890a2fd 100644 --- a/spec/rubocop/cop/style/redundant_self_spec.rb +++ b/spec/rubocop/cop/style/redundant_self_spec.rb @@ -167,6 +167,23 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'registers offense for self usage in numblocks' do + expect_offense(<<~RUBY) + %w[x y z].select do + self.axis == _1 + ^^^^ Redundant `self` detected. + end + RUBY + + expect_correction(<<~RUBY) + %w[x y z].select do + axis == _1 + end + RUBY + end + end + describe 'instance methods' do it 'accepts a self receiver used to distinguish from blockarg' do expect_no_offenses(<<~RUBY) diff --git a/spec/rubocop/cop/style/redundant_sort_by_spec.rb b/spec/rubocop/cop/style/redundant_sort_by_spec.rb index 91d9a5bbd46..f3fcbe6ee1e 100644 --- a/spec/rubocop/cop/style/redundant_sort_by_spec.rb +++ b/spec/rubocop/cop/style/redundant_sort_by_spec.rb @@ -12,6 +12,19 @@ RUBY end + context 'Ruby 2.7', :ruby27 do + it 'autocorrects array.sort_by { |x| x }' do + expect_offense(<<~RUBY) + array.sort_by { _1 } + ^^^^^^^^^^^^^^ Use `sort` instead of `sort_by { _1 }`. + RUBY + + expect_correction(<<~RUBY) + array.sort + RUBY + end + end + it 'autocorrects array.sort_by { |y| y }' do expect_offense(<<~RUBY) array.sort_by { |y| y } diff --git a/spec/rubocop/cop/style/top_level_method_definition_spec.rb b/spec/rubocop/cop/style/top_level_method_definition_spec.rb index d1d5ccb9090..05456ad13c4 100644 --- a/spec/rubocop/cop/style/top_level_method_definition_spec.rb +++ b/spec/rubocop/cop/style/top_level_method_definition_spec.rb @@ -23,6 +23,15 @@ def self.foo; end RUBY end + context 'Ruby >= 2.7', :ruby27 do + it 'registers offense with inline numblock' do + expect_offense(<<~RUBY) + define_method(:foo) { puts _1 } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not define methods at the top-level. + RUBY + end + end + it 'registers offense for multi-line block' do expect_offense(<<~RUBY) define_method(:foo) do |x|