Skip to content

Commit

Permalink
Merge pull request #186 from r7kamura/map-method-chain
Browse files Browse the repository at this point in the history
Add `Sevencop/MapMethodChain` cop
  • Loading branch information
r7kamura authored Jan 27, 2024
2 parents 44c97aa + 785c7ec commit 8f79b47
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Note that all cops are `Enabled: false` by default.
- [Sevencop/FactoryBotAssociationOption](lib/rubocop/cop/sevencop/factory_bot_association_option.rb)
- [Sevencop/FactoryBotAssociationStyle](lib/rubocop/cop/sevencop/factory_bot_association_style.rb)
- [Sevencop/HashElementOrdered](lib/rubocop/cop/sevencop/hash_element_ordered.rb)
- [Sevencop/MapMethodChain](lib/rubocop/cop/sevencop/map_method_chain.rb)
- [Sevencop/MethodDefinitionArgumentsMultiline](lib/rubocop/cop/sevencop/method_definition_arguments_multiline.rb)
- [Sevencop/MethodDefinitionInIncluded](lib/rubocop/cop/sevencop/method_definition_in_included.rb)
- [Sevencop/MethodDefinitionKeywordArgumentOrdered](lib/rubocop/cop/sevencop/method_definition_keyword_argument_ordered.rb)
Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ Sevencop/HashElementOrdered:
Enabled: false
Safe: false

Sevencop/MapMethodChain:
Description: |
Checks if the map method is used in a chain.
Enabled: false
Safe: false

Sevencop/MethodDefinitionArgumentsMultiline:
Description: |
Inserts new lines between method definition arguments.
Expand Down
74 changes: 74 additions & 0 deletions lib/rubocop/cop/sevencop/map_method_chain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Sevencop
# Checks if the map method is used in a chain.
#
# This cop is another version of `Performance/MapMethodChain` cop which has the autocorrection support.
# They have decided not to add autocorrection, so we have this cop in case you want to use it.
# https://github.com/rubocop/rubocop-performance/issues/436
#
# @example
# # bad
# array.map(&:foo).map(&:bar)
#
# # good
# array.map { |element| element.foo.bar }
class MapMethodChain < Base
extend AutoCorrector

include IgnoredNode

RESTRICT_ON_SEND = %i[map collect].freeze

# @!method block_pass_with_symbol_arg?(node)
def_node_matcher :block_pass_with_symbol_arg?, <<~PATTERN
(:block_pass (:sym $_))
PATTERN

def on_send(node)
return if part_of_ignored_node?(node)
return unless (map_arg = block_pass_with_symbol_arg?(node.first_argument))

map_args = [map_arg]
return unless (begin_of_chained_map_method = find_begin_of_chained_map_method(node, map_args))

range = begin_of_chained_map_method.loc.selector.begin.join(node.source_range.end)
replacement = "#{begin_of_chained_map_method.method_name} { |element| element.#{map_args.join('.')} }"
add_offense(
range,
message: format(
'Use `%<replacement>s` instead of `%<method_name>s` method chain.',
method_name: begin_of_chained_map_method.method_name,
replacement: replacement
)
) do |corrector|
corrector.replace(range, replacement)
end

ignore_node(node)
end

private

def find_begin_of_chained_map_method(
node,
map_args
)
return unless (chained_map_method = node.receiver)
return if !chained_map_method.call_type? || !RESTRICT_ON_SEND.include?(chained_map_method.method_name)
return unless (map_arg = block_pass_with_symbol_arg?(chained_map_method.first_argument))

map_args.unshift(map_arg)

receiver = chained_map_method.receiver

return chained_map_method unless receiver&.call_type? && block_pass_with_symbol_arg?(receiver.first_argument)

find_begin_of_chained_map_method(chained_map_method, map_args)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/sevencop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative 'rubocop/cop/sevencop/factory_bot_association_option'
require_relative 'rubocop/cop/sevencop/factory_bot_association_style'
require_relative 'rubocop/cop/sevencop/hash_element_ordered'
require_relative 'rubocop/cop/sevencop/map_method_chain'
require_relative 'rubocop/cop/sevencop/method_definition_arguments_multiline'
require_relative 'rubocop/cop/sevencop/method_definition_in_included'
require_relative 'rubocop/cop/sevencop/method_definition_keyword_argument_ordered'
Expand Down
29 changes: 29 additions & 0 deletions spec/rubocop/cop/sevencop/map_method_chain_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Sevencop::MapMethodChain, :config do
context 'with 2 map method chain' do
it 'registers offense' do
expect_offense(<<~RUBY)
array.map(&:foo).map(&:bar)
^^^^^^^^^^^^^^^^^^^^^ Use `map { |element| element.foo.bar }` instead of `map` method chain.
RUBY

expect_correction(<<~RUBY)
array.map { |element| element.foo.bar }
RUBY
end
end

context 'with 3 map method chain' do
it 'registers offense' do
expect_offense(<<~RUBY)
array&.map(&:foo).map(&:bar).map(&:baz)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `map { |element| element.foo.bar.baz }` instead of `map` method chain.
RUBY

expect_correction(<<~RUBY)
array&.map { |element| element.foo.bar.baz }
RUBY
end
end
end

0 comments on commit 8f79b47

Please sign in to comment.