rubocop-hq/rubocop

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

Summary

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

module RuboCop
  module Cop
    module Style
      # Checks for consistent usage of the `DateTime` class over the
      # `Time` class. This cop is disabled by default since these classes,
      # although highly overlapping, have particularities that make them not
      # replaceable in certain situations when dealing with multiple timezones
      # and/or DST.
      #
      # @safety
      #   Autocorrection is not safe, because `DateTime` and `Time` do not have
      #   exactly the same behavior, although in most cases the autocorrection
      #   will be fine.
      #
      # @example
      #
      #   # bad - uses `DateTime` for current time
      #   DateTime.now
      #
      #   # good - uses `Time` for current time
      #   Time.now
      #
      #   # bad - uses `DateTime` for modern date
      #   DateTime.iso8601('2016-06-29')
      #
      #   # good - uses `Time` for modern date
      #   Time.iso8601('2016-06-29')
      #
      #   # good - uses `DateTime` with start argument for historical date
      #   DateTime.iso8601('1751-04-23', Date::ENGLAND)
      #
      # @example AllowCoercion: false (default)
      #
      #   # bad - coerces to `DateTime`
      #   something.to_datetime
      #
      #   # good - coerces to `Time`
      #   something.to_time
      #
      # @example AllowCoercion: true
      #
      #   # good
      #   something.to_datetime
      #
      #   # good
      #   something.to_time
      class DateTime < Base
        extend AutoCorrector

        CLASS_MSG = 'Prefer `Time` over `DateTime`.'
        COERCION_MSG = 'Do not use `#to_datetime`.'

        # @!method date_time?(node)
        def_node_matcher :date_time?, <<~PATTERN
          (call (const {nil? (cbase)} :DateTime) ...)
        PATTERN

        # @!method historic_date?(node)
        def_node_matcher :historic_date?, <<~PATTERN
          (send _ _ _ (const (const {nil? (cbase)} :Date) _))
        PATTERN

        # @!method to_datetime?(node)
        def_node_matcher :to_datetime?, <<~PATTERN
          (call _ :to_datetime)
        PATTERN

        def on_send(node)
          return unless date_time?(node) || (to_datetime?(node) && disallow_coercion?)
          return if historic_date?(node)

          message = to_datetime?(node) ? COERCION_MSG : CLASS_MSG

          add_offense(node, message: message) { |corrector| autocorrect(corrector, node) }
        end
        alias on_csend on_send

        private

        def disallow_coercion?
          !cop_config['AllowCoercion']
        end

        def autocorrect(corrector, node)
          return if to_datetime?(node)

          corrector.replace(node.receiver.loc.name, 'Time')
        end
      end
    end
  end
end