rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/style/redundant_initialize.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Style
      # Checks for `initialize` methods that are redundant.
      #
      # An initializer is redundant if it does not do anything, or if it only
      # calls `super` with the same arguments given to it. If the initializer takes
      # an argument that accepts multiple values (`restarg`, `kwrestarg`, etc.) it
      # will not register an offense, because it allows the initializer to take a different
      # number of arguments as its superclass potentially does.
      #
      # NOTE: If an initializer argument has a default value, RuboCop assumes it
      # to *not* be redundant.
      #
      # NOTE: Empty initializers are registered as offenses, but it is possible
      # to purposely create an empty `initialize` method to override a superclass's
      # initializer.
      #
      # @safety
      #   This cop is unsafe because if subclass overrides `initialize` method with
      #   a different arity than superclass.
      #
      # @example
      #   # bad
      #   def initialize
      #   end
      #
      #   # bad
      #   def initialize
      #     super
      #   end
      #
      #   # bad
      #   def initialize(a, b)
      #     super
      #   end
      #
      #   # bad
      #   def initialize(a, b)
      #     super(a, b)
      #   end
      #
      #   # good
      #   def initialize
      #     do_something
      #   end
      #
      #   # good
      #   def initialize
      #     do_something
      #     super
      #   end
      #
      #   # good (different number of parameters)
      #   def initialize(a, b)
      #     super(a)
      #   end
      #
      #   # good (default value)
      #   def initialize(a, b = 5)
      #     super
      #   end
      #
      #   # good (default value)
      #   def initialize(a, b: 5)
      #     super
      #   end
      #
      #   # good (changes the parameter requirements)
      #   def initialize(*)
      #   end
      #
      #   # good (changes the parameter requirements)
      #   def initialize(**)
      #   end
      #
      #   # good (changes the parameter requirements)
      #   def initialize(...)
      #   end
      #
      # @example AllowComments: true (default)
      #
      #   # good
      #   def initialize
      #     # Overriding to negate superclass `initialize` method.
      #   end
      #
      # @example AllowComments: false
      #
      #   # bad
      #   def initialize
      #     # Overriding to negate superclass `initialize` method.
      #   end
      #
      class RedundantInitialize < Base
        include CommentsHelp
        include RangeHelp
        extend AutoCorrector

        MSG = 'Remove unnecessary `initialize` method.'
        MSG_EMPTY = 'Remove unnecessary empty `initialize` method.'

        # @!method initialize_forwards?(node)
        def_node_matcher :initialize_forwards?, <<~PATTERN
          (def _ (args $arg*) $({super zsuper} ...))
        PATTERN

        def on_def(node)
          return if acceptable?(node)

          if node.body.nil?
            register_offense(node, MSG_EMPTY)
          else
            return if node.body.begin_type?

            if (args, super_node = initialize_forwards?(node))
              return unless same_args?(super_node, args)

              register_offense(node, MSG)
            end
          end
        end

        private

        def register_offense(node, message)
          add_offense(node, message: message) do |corrector|
            corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
          end
        end

        def acceptable?(node)
          !node.method?(:initialize) || forwards?(node) || allow_comments?(node)
        end

        def forwards?(node)
          node.arguments.each_child_node(:restarg, :kwrestarg, :forward_args, :forward_arg).any?
        end

        def allow_comments?(node)
          return false unless cop_config['AllowComments']

          contains_comments?(node) && !comments_contain_disables?(node, name)
        end

        def same_args?(super_node, args)
          return true if super_node.zsuper_type?

          args.map(&:name) == super_node.arguments.map { |a| a.children[0] }
        end
      end
    end
  end
end