rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/layout/empty_comment.rb

Summary

Maintainability
A
35 mins
Test Coverage
A
100%
# frozen_string_literal: true

module RuboCop
  module Cop
    module Layout
      # Checks empty comment.
      #
      # @example
      #   # bad
      #
      #   #
      #   class Foo
      #   end
      #
      #   # good
      #
      #   #
      #   # Description of `Foo` class.
      #   #
      #   class Foo
      #   end
      #
      # @example AllowBorderComment: true (default)
      #   # good
      #
      #   def foo
      #   end
      #
      #   #################
      #
      #   def bar
      #   end
      #
      # @example AllowBorderComment: false
      #   # bad
      #
      #   def foo
      #   end
      #
      #   #################
      #
      #   def bar
      #   end
      #
      # @example AllowMarginComment: true (default)
      #   # good
      #
      #   #
      #   # Description of `Foo` class.
      #   #
      #   class Foo
      #   end
      #
      # @example AllowMarginComment: false
      #   # bad
      #
      #   #
      #   # Description of `Foo` class.
      #   #
      #   class Foo
      #   end
      #
      class EmptyComment < Base
        include RangeHelp
        extend AutoCorrector

        MSG = 'Source code comment is empty.'

        def on_new_investigation
          if allow_margin_comment?
            comments = concat_consecutive_comments(processed_source.comments)

            investigate(comments)
          else
            processed_source.comments.each do |comment|
              next unless empty_comment_only?(comment_text(comment))

              add_offense(comment) { |corrector| autocorrect(corrector, comment) }
            end
          end
        end

        private

        def investigate(comments)
          comments.each do |comment|
            next unless empty_comment_only?(comment[0])

            comment[1].each do |offense_comment|
              add_offense(offense_comment) do |corrector|
                autocorrect(corrector, offense_comment)
              end
            end
          end
        end

        def autocorrect(corrector, node)
          previous_token = previous_token(node)
          range = if previous_token && same_line?(node, previous_token)
                    range_with_surrounding_space(node.source_range, newlines: false)
                  else
                    range_by_whole_lines(node.source_range, include_final_newline: true)
                  end

          corrector.remove(range)
        end

        def concat_consecutive_comments(comments)
          consecutive_comments = comments.chunk_while { |i, j| i.loc.line.succ == j.loc.line }

          consecutive_comments.map do |chunk|
            joined_text = chunk.map { |c| comment_text(c) }.join
            [joined_text, chunk]
          end
        end

        def empty_comment_only?(comment_text)
          empty_comment_pattern = if allow_border_comment?
                                    /\A(#\n)+\z/
                                  else
                                    /\A(#+\n)+\z/
                                  end

          empty_comment_pattern.match?(comment_text)
        end

        def comment_text(comment)
          "#{comment.text.strip}\n"
        end

        def allow_border_comment?
          cop_config['AllowBorderComment']
        end

        def allow_margin_comment?
          cop_config['AllowMarginComment']
        end

        def current_token(comment)
          processed_source.tokens.find { |token| token.pos == comment.source_range }
        end

        def previous_token(node)
          current_token = current_token(node)
          index = processed_source.tokens.index(current_token)
          index.zero? ? nil : processed_source.tokens[index - 1]
        end
      end
    end
  end
end