rubocop-hq/rubocop

View on GitHub
lib/rubocop/cop/mixin/hash_alignment_styles.rb

Summary

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

module RuboCop
  module Cop
    # Common functionality for checking hash alignment.
    module HashAlignmentStyles
      # Handles calculation of deltas when the enforced style is 'key'.
      class KeyAlignment
        def checkable_layout?(_node)
          true
        end

        def deltas_for_first_pair(first_pair, _node)
          {
            separator: separator_delta(first_pair),
            value: value_delta(first_pair)
          }
        end

        def deltas(first_pair, current_pair)
          if Util.begins_its_line?(current_pair.source_range)
            key_delta = first_pair.key_delta(current_pair)
            separator_delta = separator_delta(current_pair)
            value_delta = value_delta(current_pair)

            { key: key_delta, separator: separator_delta, value: value_delta }
          else
            {}
          end
        end

        private

        def separator_delta(pair)
          if pair.hash_rocket?
            correct_separator_column = pair.key.source_range.end.column + 1
            actual_separator_column = pair.loc.operator.column

            correct_separator_column - actual_separator_column
          else
            0
          end
        end

        def value_delta(pair)
          return 0 if pair.value_on_new_line? || pair.value_omission?

          correct_value_column = pair.loc.operator.end.column + 1
          actual_value_column = pair.value.loc.column

          correct_value_column - actual_value_column
        end
      end

      # Common functionality for checking alignment of hash values.
      module ValueAlignment
        def checkable_layout?(node)
          !node.pairs_on_same_line? && !node.mixed_delimiters?
        end

        def deltas(first_pair, current_pair)
          key_delta = key_delta(first_pair, current_pair)
          separator_delta = separator_delta(first_pair, current_pair, key_delta)
          value_delta = value_delta(first_pair, current_pair) - key_delta - separator_delta

          { key: key_delta, separator: separator_delta, value: value_delta }
        end

        private

        def separator_delta(first_pair, current_pair, key_delta)
          if current_pair.hash_rocket?
            hash_rocket_delta(first_pair, current_pair) - key_delta
          else
            0
          end
        end
      end

      # Handles calculation of deltas when the enforced style is 'table'.
      class TableAlignment
        include ValueAlignment

        def initialize
          self.max_key_width = 0
        end

        def deltas_for_first_pair(first_pair, node)
          self.max_key_width = node.keys.map { |key| key.source.length }.max

          separator_delta = separator_delta(first_pair, first_pair, 0)
          {
            separator: separator_delta,
            value: value_delta(first_pair, first_pair) - separator_delta
          }
        end

        private

        attr_accessor :max_key_width

        def key_delta(first_pair, current_pair)
          first_pair.key_delta(current_pair)
        end

        def hash_rocket_delta(first_pair, current_pair)
          first_pair.loc.column + max_key_width + 1 - current_pair.loc.operator.column
        end

        def value_delta(first_pair, current_pair)
          correct_value_column = first_pair.key.loc.column +
                                 current_pair.delimiter(true).length +
                                 max_key_width

          current_pair.value_omission? ? 0 : correct_value_column - current_pair.value.loc.column
        end
      end

      # Handles calculation of deltas when the enforced style is 'separator'.
      class SeparatorAlignment
        include ValueAlignment

        def deltas_for_first_pair(*_nodes)
          {}
        end

        private

        def key_delta(first_pair, current_pair)
          first_pair.key_delta(current_pair, :right)
        end

        def hash_rocket_delta(first_pair, current_pair)
          first_pair.delimiter_delta(current_pair)
        end

        def value_delta(first_pair, current_pair)
          current_pair.value_omission? ? 0 : first_pair.value_delta(current_pair)
        end
      end

      # Handles calculation of deltas for `kwsplat` nodes.
      # This is a special case that just ensures the kwsplat is aligned with the rest of the hash
      # since a `kwsplat` does not have a key, separator or value.
      class KeywordSplatAlignment
        def deltas(first_pair, current_pair)
          if Util.begins_its_line?(current_pair.source_range)
            { key: first_pair.key_delta(current_pair) }
          else
            {}
          end
        end
      end
    end
  end
end