Skip to content

Commit 5d6f8bb

Browse files
committed
[Fix #67] Fix an incorrect auto-correct for Rails/TimeZone
Fixes #67. Closes #71. This PR fixes an incorrect auto-correct for `Rails/TimeZone` when using `DateTime`. ```diff - DateTime.new + DateTime.zone.new ``` ```ruby DateTime.zone.new # NoMethodError: undefined method `zone' for DateTime:Class ``` This PR changes to ignore `DateTime` with the following as background. `DateTime` is not mentioned in The Rails Style Guide and `Rails/TimeZone` cop's examples. - https://rails.rubystyle.guide/#time-now - https://docs.rubocop.org/projects/rails/en/stable/cops_rails/#railstimezone Recently, #81 and #82 have been feedback. I think breaking by auto-correction should be fixed with the highest priority in this case. Also, `DateTime` and `Time` are not replaced from `DateTime` to `Time` by auto-correct because they are different objects.
1 parent 2c62f07 commit 5d6f8bb

File tree

3 files changed

+129
-131
lines changed

3 files changed

+129
-131
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## master (unreleased)
44

5+
### Bug fixes
6+
7+
* [#67](https://github.com/rubocop-hq/rubocop-rails/issues/67): Fix an incorrect auto-correct for `Rails/TimeZone` when using `DateTime`. ([@koic][])
8+
59
## 2.1.0 (2019-06-26)
610

711
### Bug fixes

lib/rubocop/cop/rails/time_zone.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ class TimeZone < Cop
5555
MSG_LOCALTIME = 'Do not use `Time.localtime` without ' \
5656
'offset or zone.'
5757

58-
TIMECLASSES = %i[Time DateTime].freeze
59-
6058
GOOD_METHODS = %i[zone zone_default find_zone find_zone!].freeze
6159

6260
DANGEROUS_METHODS = %i[now local new parse at current].freeze
@@ -67,10 +65,10 @@ class TimeZone < Cop
6765
def on_const(node)
6866
mod, klass = *node
6967
# we should only check core classes
70-
# (`DateTime`, `Time`, `::DateTime` or `::Time`)
68+
# (`Time` or `::Time`)
7169
return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
7270

73-
check_time_node(klass, node.parent) if TIMECLASSES.include?(klass)
71+
check_time_node(klass, node.parent) if klass == :Time
7472
end
7573

7674
def autocorrect(node)
@@ -153,7 +151,7 @@ def method_from_time_class?(node)
153151
if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type?
154152
method_from_time_class?(receiver)
155153
else
156-
TIMECLASSES.include?(method_name)
154+
method_name == :Time
157155
end
158156
end
159157

spec/rubocop/cop/rails/time_zone_spec.rb

Lines changed: 122 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -6,92 +6,90 @@
66
context 'when EnforcedStyle is "strict"' do
77
let(:cop_config) { { 'EnforcedStyle' => 'strict' } }
88

9-
described_class::TIMECLASSES.each do |klass|
10-
it "registers an offense for #{klass}.now" do
11-
inspect_source("#{klass}.now")
12-
expect(cop.offenses.size).to eq(1)
13-
expect(cop.offenses.first.message).to include('`Time.zone.now`')
14-
end
9+
it 'registers an offense for Time.now' do
10+
inspect_source('Time.now')
11+
expect(cop.offenses.size).to eq(1)
12+
expect(cop.offenses.first.message).to include('`Time.zone.now`')
13+
end
1514

16-
it "registers an offense for #{klass}.current" do
17-
inspect_source("#{klass}.current")
18-
expect(cop.offenses.size).to eq(1)
19-
expect(cop.offenses.first.message).to include('`Time.zone.now`')
20-
end
15+
it 'registers an offense for Time.current' do
16+
inspect_source('Time.current')
17+
expect(cop.offenses.size).to eq(1)
18+
expect(cop.offenses.first.message).to include('`Time.zone.now`')
19+
end
2120

22-
it "registers an offense for #{klass}.new without argument" do
23-
inspect_source("#{klass}.new")
24-
expect(cop.offenses.size).to eq(1)
25-
expect(cop.offenses.first.message).to include('`Time.zone.now`')
26-
end
21+
it 'registers an offense for Time.new without argument' do
22+
inspect_source('Time.new')
23+
expect(cop.offenses.size).to eq(1)
24+
expect(cop.offenses.first.message).to include('`Time.zone.now`')
25+
end
2726

28-
it "registers an offense for #{klass}.new with argument" do
29-
inspect_source("#{klass}.new(2012, 6, 10, 12, 00)")
30-
expect(cop.offenses.size).to eq(1)
31-
expect(cop.offenses.first.message).to include('`Time.zone.local`')
32-
end
27+
it 'registers an offense for Time.new with argument' do
28+
inspect_source('Time.new(2012, 6, 10, 12, 00)')
29+
expect(cop.offenses.size).to eq(1)
30+
expect(cop.offenses.first.message).to include('`Time.zone.local`')
31+
end
3332

34-
it "does not register an offense when a .new method is called
35-
independently of the #{klass} class" do
36-
expect_no_offenses(<<~RUBY)
37-
Range.new(1, #{klass}.class.to_s)
38-
RUBY
39-
end
33+
it 'does not register an offense when a .new method is called
34+
independently of the Time class' do
35+
expect_no_offenses(<<~RUBY)
36+
Range.new(1, Time.class.to_s)
37+
RUBY
38+
end
4039

41-
it "does not register an offense for #{klass}.new with zone argument" do
42-
expect_no_offenses(<<~RUBY)
43-
#{klass}.new(1988, 3, 15, 3, 0, 0, '-05:00')
44-
RUBY
45-
end
40+
it 'does not register an offense for Time.new with zone argument' do
41+
expect_no_offenses(<<~RUBY)
42+
Time.new(1988, 3, 15, 3, 0, 0, '-05:00')
43+
RUBY
44+
end
4645

47-
it "registers an offense for ::#{klass}.now" do
48-
inspect_source("::#{klass}.now")
46+
it 'registers an offense for ::Time.now' do
47+
inspect_source('::Time.now')
48+
expect(cop.offenses.size).to eq(1)
49+
end
50+
51+
it 'accepts Some::Time.now' do
52+
expect_no_offenses(<<~RUBY)
53+
Some::Time.now(0).strftime('%H:%M')
54+
RUBY
55+
end
56+
57+
described_class::ACCEPTED_METHODS.each do |a_method|
58+
it "registers an offense Time.now.#{a_method}" do
59+
inspect_source("Time.now.#{a_method}")
4960
expect(cop.offenses.size).to eq(1)
61+
expect(cop.offenses.first.message).to include('`Time.zone.now`')
5062
end
63+
end
5164

52-
it "accepts Some::#{klass}.now" do
53-
expect_no_offenses(<<~RUBY)
54-
Some::#{klass}.now(0).strftime('%H:%M')
55-
RUBY
65+
context 'autocorrect' do
66+
let(:cop_config) do
67+
{ 'AutoCorrect' => 'true', 'EnforcedStyle' => 'strict' }
5668
end
5769

58-
described_class::ACCEPTED_METHODS.each do |a_method|
59-
it "registers an offense #{klass}.now.#{a_method}" do
60-
inspect_source("#{klass}.now.#{a_method}")
61-
expect(cop.offenses.size).to eq(1)
62-
expect(cop.offenses.first.message).to include('`Time.zone.now`')
63-
end
70+
it 'autocorrects correctly' do
71+
source = 'Time.now.in_time_zone'
72+
new_source = autocorrect_source(source)
73+
expect(new_source).to eq('Time.zone.now')
6474
end
6575

66-
context 'autocorrect' do
67-
let(:cop_config) do
68-
{ 'AutoCorrect' => 'true', 'EnforcedStyle' => 'strict' }
69-
end
70-
71-
it 'autocorrects correctly' do
72-
source = "#{klass}.now.in_time_zone"
76+
# :current is a special case and is treated separately below
77+
(described_class::DANGEROUS_METHODS - [:current]).each do |a_method|
78+
it 'corrects the error' do
79+
source = <<~RUBY
80+
Time.#{a_method}
81+
RUBY
7382
new_source = autocorrect_source(source)
74-
expect(new_source).to eq('Time.zone.now')
75-
end
76-
77-
# :current is a special case and is treated separately below
78-
(described_class::DANGEROUS_METHODS - [:current]).each do |a_method|
79-
it 'corrects the error' do
80-
source = <<~RUBY
81-
#{klass}.#{a_method}
82-
RUBY
83-
new_source = autocorrect_source(source)
84-
expect(new_source).to eq(<<~RUBY)
85-
Time.zone.#{a_method}
86-
RUBY
87-
end
83+
expect(new_source).to eq(<<~RUBY)
84+
Time.zone.#{a_method}
85+
RUBY
8886
end
87+
end
8988

90-
describe '.current' do
91-
it 'corrects the error' do
92-
new_source = autocorrect_source("#{klass}.current")
93-
expect(new_source).to eq('Time.zone.now')
94-
end
89+
describe '.current' do
90+
it 'corrects the error' do
91+
new_source = autocorrect_source('Time.current')
92+
expect(new_source).to eq('Time.zone.now')
9593
end
9694
end
9795
end
@@ -214,81 +212,79 @@
214212
context 'when EnforcedStyle is "flexible"' do
215213
let(:cop_config) { { 'EnforcedStyle' => 'flexible' } }
216214

217-
described_class::TIMECLASSES.each do |klass|
218-
it "registers an offense for #{klass}.now" do
219-
inspect_source("#{klass}.now")
220-
expect(cop.offenses.size).to eq(1)
215+
it 'registers an offense for Time.now' do
216+
inspect_source('Time.now')
217+
expect(cop.offenses.size).to eq(1)
221218

222-
expect(cop.offenses.first.message).to include('Use one of')
223-
expect(cop.offenses.first.message).to include('`Time.zone.now`')
224-
expect(cop.offenses.first.message).to include("`#{klass}.current`")
219+
expect(cop.offenses.first.message).to include('Use one of')
220+
expect(cop.offenses.first.message).to include('`Time.zone.now`')
221+
expect(cop.offenses.first.message).to include('`Time.current`')
225222

226-
described_class::ACCEPTED_METHODS.each do |a_method|
227-
expect(cop.offenses.first.message)
228-
.to include("#{klass}.now.#{a_method}")
229-
end
223+
described_class::ACCEPTED_METHODS.each do |a_method|
224+
expect(cop.offenses.first.message)
225+
.to include("Time.now.#{a_method}")
230226
end
227+
end
228+
229+
it 'accepts Time.current' do
230+
expect_no_offenses(<<~RUBY)
231+
Time.current
232+
RUBY
233+
end
231234

232-
it "accepts #{klass}.current" do
235+
described_class::ACCEPTED_METHODS.each do |a_method|
236+
it "accepts Time.now.#{a_method}" do
233237
expect_no_offenses(<<~RUBY)
234-
#{klass}.current
238+
Time.now.#{a_method}
235239
RUBY
236240
end
241+
end
237242

238-
described_class::ACCEPTED_METHODS.each do |a_method|
239-
it "accepts #{klass}.now.#{a_method}" do
240-
expect_no_offenses(<<~RUBY)
241-
#{klass}.now.#{a_method}
242-
RUBY
243-
end
244-
end
243+
it 'accepts Time.zone.now' do
244+
expect_no_offenses(<<~RUBY)
245+
Time.zone.now
246+
RUBY
247+
end
245248

246-
it "accepts #{klass}.zone.now" do
247-
expect_no_offenses(<<~RUBY)
248-
#{klass}.zone.now
249-
RUBY
250-
end
249+
it 'accepts Time.zone_default.now' do
250+
expect_no_offenses(<<~RUBY)
251+
Time.zone_default.now
252+
RUBY
253+
end
251254

252-
it "accepts #{klass}.zone_default.now" do
253-
expect_no_offenses(<<~RUBY)
254-
#{klass}.zone_default.now
255-
RUBY
256-
end
255+
it 'accepts Time.find_zone(time_zone).now' do
256+
expect_no_offenses(<<~RUBY)
257+
Time.find_zone('EST').now
258+
RUBY
259+
end
257260

258-
it "accepts #{klass}.find_zone(time_zone).now" do
259-
expect_no_offenses(<<~RUBY)
260-
#{klass}.find_zone('EST').now
261-
RUBY
262-
end
261+
it 'accepts Time.find_zone!(time_zone).now' do
262+
expect_no_offenses(<<~RUBY)
263+
Time.find_zone!('EST').now
264+
RUBY
265+
end
263266

264-
it "accepts #{klass}.find_zone!(time_zone).now" do
267+
described_class::DANGEROUS_METHODS.each do |a_method|
268+
it "accepts Time.current.#{a_method}" do
265269
expect_no_offenses(<<~RUBY)
266-
#{klass}.find_zone!('EST').now
270+
Time.current.#{a_method}
267271
RUBY
268272
end
269273

270-
described_class::DANGEROUS_METHODS.each do |a_method|
271-
it "accepts #{klass}.current.#{a_method}" do
272-
expect_no_offenses(<<~RUBY)
273-
#{klass}.current.#{a_method}
274-
RUBY
274+
context 'autocorrect' do
275+
let(:cop_config) do
276+
{ 'AutoCorrect' => 'true', 'EnforcedStyle' => 'flexible' }
275277
end
276278

277-
context 'autocorrect' do
278-
let(:cop_config) do
279-
{ 'AutoCorrect' => 'true', 'EnforcedStyle' => 'flexible' }
280-
end
281-
282-
it 'corrects the error' do
283-
source = <<~RUBY
284-
#{klass}.#{a_method}
279+
it 'corrects the error' do
280+
source = <<~RUBY
281+
Time.#{a_method}
282+
RUBY
283+
new_source = autocorrect_source(source)
284+
unless a_method == :current
285+
expect(new_source).to eq(<<~RUBY)
286+
Time.zone.#{a_method}
285287
RUBY
286-
new_source = autocorrect_source(source)
287-
unless a_method == :current
288-
expect(new_source).to eq(<<~RUBY)
289-
#{klass}.zone.#{a_method}
290-
RUBY
291-
end
292288
end
293289
end
294290
end

0 commit comments

Comments
 (0)