class RuboCop::Cop::Rails::TimeZone
This cop checks for the use of Time methods without zone.
Built on top of Ruby on Rails
style guide (github.com/rubocop-hq/rails-style-guide#time) and the article danilenko.org/2012/7/6/rails_timezones/
Two styles are supported for this cop. When EnforcedStyle is 'strict' then only use of Time.zone is allowed.
When EnforcedStyle is 'flexible' then it's also allowed to use Time.in_time_zone.
@example EnforcedStyle: strict
# `strict` means that `Time` should be used with `zone`. # bad Time.now Time.parse('2015-03-02 19:05:37') # bad Time.current Time.at(timestamp).in_time_zone # good Time.zone.now Time.zone.parse('2015-03-02 19:05:37')
@example EnforcedStyle: flexible (default)
# `flexible` allows usage of `in_time_zone` instead of `zone`. # bad Time.now Time.parse('2015-03-02 19:05:37') # good Time.zone.now Time.zone.parse('2015-03-02 19:05:37') # good Time.current Time.at(timestamp).in_time_zone
Constants
- ACCEPTED_METHODS
- DANGEROUS_METHODS
- GOOD_METHODS
- MSG
- MSG_ACCEPTABLE
- MSG_LOCALTIME
- TIMECLASSES
Public Instance Methods
# File lib/rubocop/cop/rails/time_zone.rb, line 76 def autocorrect(node) lambda do |corrector| # add `.zone`: `Time.at` => `Time.zone.at` corrector.insert_after(node.children[0].source_range, '.zone') # replace `Time.zone.current` => `Time.zone.now` if node.method_name == :current corrector.replace(node.loc.selector, 'now') end # prefer `Time` over `DateTime` class if strict? corrector.replace(node.children.first.source_range, 'Time') end remove_redundant_in_time_zone(corrector, node) end end
# File lib/rubocop/cop/rails/time_zone.rb, line 67 def on_const(node) mod, klass = *node # we should only check core classes # (`DateTime`, `Time`, `::DateTime` or `::Time`) return unless (mod.nil? || mod.cbase_type?) && method_send?(node) check_time_node(klass, node.parent) if TIMECLASSES.include?(klass) end
Private Instance Methods
# File lib/rubocop/cop/rails/time_zone.rb, line 215 def acceptable_methods(klass, method_name, node) acceptable = [ "`Time.zone.#{safe_method(method_name, node)}`", "`#{klass}.current`" ] ACCEPTED_METHODS.each do |am| acceptable << "`#{klass}.#{method_name}.#{am}`" end acceptable end
# File lib/rubocop/cop/rails/time_zone.rb, line 125 def build_message(klass, method_name, node) if flexible? format( MSG_ACCEPTABLE, current: "#{klass}.#{method_name}", prefer: acceptable_methods(klass, method_name, node).join(', ') ) else safe_method_name = safe_method(method_name, node) format(MSG, current: "#{klass}.#{method_name}", prefer: "Time.zone.#{safe_method_name}") end end
# File lib/rubocop/cop/rails/time_zone.rb, line 176 def check_localtime(node) selector_node = node while node&.send_type? break if node.method_name == :localtime node = node.parent end return if node.arguments? add_offense(selector_node, location: :selector, message: MSG_LOCALTIME) end
# File lib/rubocop/cop/rails/time_zone.rb, line 110 def check_time_node(klass, node) chain = extract_method_chain(node) return if not_danger_chain?(chain) return check_localtime(node) if need_check_localtime?(chain) method_name = (chain & DANGEROUS_METHODS).join('.') return if offset_provided?(node) message = build_message(klass, method_name, node) add_offense(node, location: :selector, message: message) end
# File lib/rubocop/cop/rails/time_zone.rb, line 140 def extract_method_chain(node) chain = [] while !node.nil? && node.send_type? chain << node.method_name if method_from_time_class?(node) node = node.parent end chain end
# File lib/rubocop/cop/rails/time_zone.rb, line 199 def flexible? style == :flexible end
# File lib/rubocop/cop/rails/time_zone.rb, line 207 def good_methods if strict? GOOD_METHODS else GOOD_METHODS + [:current] + ACCEPTED_METHODS end end
Only add the method to the chain if the method being called is part of the time class.
# File lib/rubocop/cop/rails/time_zone.rb, line 151 def method_from_time_class?(node) receiver, method_name, *_args = *node if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type? method_from_time_class?(receiver) else TIMECLASSES.include?(method_name) end end
checks that parent node of send_type and receiver is the given node
# File lib/rubocop/cop/rails/time_zone.rb, line 162 def method_send?(node) return false unless node.parent&.send_type? node.parent.receiver == node end
# File lib/rubocop/cop/rails/time_zone.rb, line 195 def need_check_localtime?(chain) flexible? && chain.include?(:localtime) end
# File lib/rubocop/cop/rails/time_zone.rb, line 191 def not_danger_chain?(chain) (chain & DANGEROUS_METHODS).empty? || !(chain & good_methods).empty? end
Time.new can be called with a time zone offset When it is, that should be considered safe Example: Time.new(1988, 3, 15, 3, 0, 0, “-05:00”)
# File lib/rubocop/cop/rails/time_zone.rb, line 232 def offset_provided?(node) node.arguments.size >= 7 end
remove redundant `.in_time_zone` from `Time.zone.now.in_time_zone`
# File lib/rubocop/cop/rails/time_zone.rb, line 95 def remove_redundant_in_time_zone(corrector, node) time_methods_called = extract_method_chain(node) return unless time_methods_called.include?(:in_time_zone) || time_methods_called.include?(:zone) while node&.send_type? if node.children.last == :in_time_zone in_time_zone_with_dot = node.loc.selector.adjust(begin_pos: -1) corrector.remove(in_time_zone_with_dot) end node = node.parent end end
# File lib/rubocop/cop/rails/time_zone.rb, line 168 def safe_method(method_name, node) if %w[new current].include?(method_name) node.arguments? ? 'local' : 'now' else method_name end end
# File lib/rubocop/cop/rails/time_zone.rb, line 203 def strict? style == :strict end