Skip to content

Commit 0610d73

Browse files
authored
Merge pull request #714 from ydah/add_new_rails_freeze_time
Add new `Rails/FreezeTime` cop
2 parents c1d5cd7 + 8bedc3f commit 0610d73

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* [#714](https://github.com/rubocop/rubocop-rails/pull/714): Add new `Rails/FreezeTime` cop. ([@ydah][])

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,13 @@ Rails/FindEach:
417417
- select
418418
- lock
419419

420+
Rails/FreezeTime:
421+
Description: 'Prefer `freeze_time` over `travel_to` with an argument of the current time.'
422+
StyleGuide: 'https://rails.rubystyle.guide/#freeze-time'
423+
Enabled: pending
424+
VersionAdded: '<<next>>'
425+
SafeAutoCorrect: false
426+
420427
Rails/HasAndBelongsToMany:
421428
Description: 'Prefer has_many :through to has_and_belongs_to_many.'
422429
StyleGuide: 'https://rails.rubystyle.guide#has-many-through'

lib/rubocop/cop/rails/freeze_time.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# Identifies usages of `travel_to` with an argument of the current time and
7+
# change them to use `freeze_time` instead.
8+
#
9+
# @safety
10+
# This cop’s autocorrection is unsafe because `freeze_time` just delegates to
11+
# `travel_to` with a default `Time.now`, it is not strictly equivalent to `Time.now`
12+
# if the argument of `travel_to` is the current time considering time zone.
13+
#
14+
# @example
15+
# # bad
16+
# travel_to(Time.now)
17+
# travel_to(Time.new)
18+
# travel_to(DateTime.now)
19+
# travel_to(Time.current)
20+
# travel_to(Time.zone.now)
21+
# travel_to(Time.now.in_time_zone)
22+
# travel_to(Time.current.to_time)
23+
#
24+
# # good
25+
# freeze_time
26+
#
27+
class FreezeTime < Base
28+
extend AutoCorrector
29+
30+
MSG = 'Use `freeze_time` instead of `travel_to`.'
31+
NOW_METHODS = %i[now new current].freeze
32+
CONV_METHODS = %i[to_time in_time_zone].freeze
33+
RESTRICT_ON_SEND = %i[travel_to].freeze
34+
35+
# @!method time_now?(node)
36+
def_node_matcher :time_now?, <<~PATTERN
37+
(const nil? {:Time :DateTime})
38+
PATTERN
39+
40+
# @!method zoned_time_now?(node)
41+
def_node_matcher :zoned_time_now?, <<~PATTERN
42+
(send (const nil? :Time) :zone)
43+
PATTERN
44+
45+
def on_send(node)
46+
child_node, method_name = *node.first_argument.children
47+
if current_time?(child_node, method_name) ||
48+
current_time_with_convert?(child_node, method_name)
49+
add_offense(node) { |corrector| corrector.replace(node, 'freeze_time') }
50+
end
51+
end
52+
53+
private
54+
55+
def current_time?(node, method_name)
56+
return false unless NOW_METHODS.include?(method_name)
57+
58+
node.send_type? ? zoned_time_now?(node) : time_now?(node)
59+
end
60+
61+
def current_time_with_convert?(node, method_name)
62+
return false unless CONV_METHODS.include?(method_name)
63+
64+
child_node, child_method_name = *node.children
65+
current_time?(child_node, child_method_name)
66+
end
67+
end
68+
end
69+
end
70+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
require_relative 'rails/find_by'
5151
require_relative 'rails/find_by_id'
5252
require_relative 'rails/find_each'
53+
require_relative 'rails/freeze_time'
5354
require_relative 'rails/has_and_belongs_to_many'
5455
require_relative 'rails/has_many_or_has_one_dependent'
5556
require_relative 'rails/helper_instance_variable'
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::FreezeTime, :config do
4+
it 'registers an offense when using `travel_to` with an argument of the current time' do
5+
expect_offense(<<~RUBY)
6+
travel_to(Time.now)
7+
^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
8+
travel_to(Time.new)
9+
^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
10+
travel_to(DateTime.now)
11+
^^^^^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
12+
travel_to(Time.current)
13+
^^^^^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
14+
travel_to(Time.zone.now)
15+
^^^^^^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
16+
travel_to(Time.now.in_time_zone)
17+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
18+
travel_to(Time.current.to_time)
19+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
20+
RUBY
21+
22+
expect_correction(<<~RUBY)
23+
freeze_time
24+
freeze_time
25+
freeze_time
26+
freeze_time
27+
freeze_time
28+
freeze_time
29+
freeze_time
30+
RUBY
31+
end
32+
33+
it 'registers an offense when using `travel_to` with an argument of the current time and `do-end` block' do
34+
expect_offense(<<~RUBY)
35+
travel_to(Time.now) do
36+
^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
37+
do_something
38+
end
39+
RUBY
40+
41+
expect_correction(<<~RUBY)
42+
freeze_time do
43+
do_something
44+
end
45+
RUBY
46+
end
47+
48+
it 'registers an offense when using `travel_to` with an argument of the current time and `{}` block' do
49+
expect_offense(<<~RUBY)
50+
travel_to(Time.now) { do_something }
51+
^^^^^^^^^^^^^^^^^^^ Use `freeze_time` instead of `travel_to`.
52+
RUBY
53+
54+
expect_correction(<<~RUBY)
55+
freeze_time { do_something }
56+
RUBY
57+
end
58+
59+
it 'does not register an offense when using `freeze_time`' do
60+
expect_no_offenses(<<~RUBY)
61+
freeze_time
62+
RUBY
63+
end
64+
65+
it 'does not register an offense when using `travel_to` with an argument of the not current time' do
66+
expect_no_offenses(<<~RUBY)
67+
travel_to(Time.current.yesterday)
68+
travel_to(Time.zone.tomorrow)
69+
travel_to(DateTime.next_day)
70+
travel_to(Time.zone.yesterday.in_time_zone)
71+
RUBY
72+
end
73+
end

0 commit comments

Comments
 (0)