Skip to content

Commit

Permalink
[Fix rubocop#486] Add new Rails/ExpandedDateRange cop
Browse files Browse the repository at this point in the history
Fixes rubocop#486.

This PR adds new `Rails/ExpandedDateRange` cop that checks for expanded date range.
It only compatible `..` range is targeted. Incompatible `...` range is ignored.

```ruby
# bad
date.beginning_of_day..date.end_of_day
date.beginning_of_week..date.end_of_week
date.beginning_of_month..date.end_of_month
date.beginning_of_quarter..date.end_of_quarter
date.beginning_of_year..date.end_of_year

# good
date.all_day
date.all_week
date.all_month
date.all_quarter
date.all_year
```
  • Loading branch information
koic committed May 17, 2021
1 parent ad4fb01 commit d5a2cdc
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#486](https://github.com/rubocop/rubocop-rails/issues/486): Add new `Rails/ExpandedDateRange` cop. ([@koic][])

### Bug fixes

* [#482](https://github.com/rubocop/rubocop-rails/pull/482): Fix a false positive for `Rails/RelativeDateConstant` when assigning (hashes/arrays/etc)-containing procs to a constant. ([@jdelStrother][])
Expand Down
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@ Rails/Exit:
Exclude:
- lib/**/*.rake

Rails/ExpandedDateRange:
Description: 'Checks for expanded date range.'
Enabled: pending
VersionAdded: '2.11'

Rails/FilePath:
Description: 'Use `Rails.root.join` for file path joining.'
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ based on the https://rails.rubystyle.guide/[Rails Style Guide].
* xref:cops_rails.adoc#railsenvironmentcomparison[Rails/EnvironmentComparison]
* xref:cops_rails.adoc#railsenvironmentvariableaccess[Rails/EnvironmentVariableAccess]
* xref:cops_rails.adoc#railsexit[Rails/Exit]
* xref:cops_rails.adoc#railsexpandeddaterange[Rails/ExpandedDateRange]
* xref:cops_rails.adoc#railsfilepath[Rails/FilePath]
* xref:cops_rails.adoc#railsfindby[Rails/FindBy]
* xref:cops_rails.adoc#railsfindbyid[Rails/FindById]
Expand Down
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/cops_rails.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,40 @@ raise 'a bad error has happened'
| Array
|===

== Rails/ExpandedDateRange

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| Yes
| 2.11
| -
|===

This cop checks for expanded date range. It only compatible `..` range is targeted.
Incompatible `...` range is ignored.

=== Examples

[source,ruby]
----
# bad
date.beginning_of_day..date.end_of_day
date.beginning_of_week..date.end_of_week
date.beginning_of_month..date.end_of_month
date.beginning_of_quarter..date.end_of_quarter
date.beginning_of_year..date.end_of_year
# good
date.all_day
date.all_week
date.all_month
date.all_quarter
date.all_year
----

== Rails/FilePath

|===
Expand Down
86 changes: 86 additions & 0 deletions lib/rubocop/cop/rails/expanded_date_range.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# This cop checks for expanded date range. It only compatible `..` range is targeted.
# Incompatible `...` range is ignored.
#
# @example
# # bad
# date.beginning_of_day..date.end_of_day
# date.beginning_of_week..date.end_of_week
# date.beginning_of_month..date.end_of_month
# date.beginning_of_quarter..date.end_of_quarter
# date.beginning_of_year..date.end_of_year
#
# # good
# date.all_day
# date.all_week
# date.all_month
# date.all_quarter
# date.all_year
#
class ExpandedDateRange < Base
extend TargetRailsVersion
extend AutoCorrector

MSG = 'Use `%<preferred_method>s` instead.'

minimum_target_rails_version 5.1

def_node_matcher :expanded_date_range, <<~PATTERN
(irange
(send
$_ {:beginning_of_day :beginning_of_week :beginning_of_month :beginning_of_quarter :beginning_of_year})
(send
$_ {:end_of_day :end_of_week :end_of_month :end_of_quarter :end_of_year}))
PATTERN

PREFERRED_METHODS = {
beginning_of_day: 'all_day',
beginning_of_week: 'all_week',
beginning_of_month: 'all_month',
beginning_of_quarter: 'all_quarter',
beginning_of_year: 'all_year'
}.freeze

MAPPED_DATE_RANGE_METHODS = {
beginning_of_day: :end_of_day,
beginning_of_week: :end_of_week,
beginning_of_month: :end_of_month,
beginning_of_quarter: :end_of_quarter,
beginning_of_year: :end_of_year
}.freeze

def on_irange(node)
return unless expanded_date_range(node)

begin_node = node.begin
end_node = node.end
return unless same_receiver?(begin_node, end_node)

beginning_method = begin_node.method_name
end_method = end_node.method_name
return unless use_mapped_methods?(beginning_method, end_method)

preferred_method = "#{begin_node.receiver.source}.#{PREFERRED_METHODS[beginning_method]}"

add_offense(node, message: format(MSG, preferred_method: preferred_method)) do |corrector|
corrector.replace(node, preferred_method)
end
end

private

def same_receiver?(begin_node, end_node)
begin_node.receiver.source == end_node.receiver.source
end

def use_mapped_methods?(beginning_method, end_method)
MAPPED_DATE_RANGE_METHODS[beginning_method] == end_method
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
require_relative 'rails/environment_comparison'
require_relative 'rails/environment_variable_access'
require_relative 'rails/exit'
require_relative 'rails/expanded_date_range'
require_relative 'rails/file_path'
require_relative 'rails/find_by'
require_relative 'rails/find_by_id'
Expand Down
146 changes: 146 additions & 0 deletions spec/rubocop/cop/rails/expanded_date_range_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::ExpandedDateRange, :config do
context 'Rails >= 5.1', :rails51 do
it 'registers and corrects an offense when using `date.beginning_of_day..date.end_of_day`' do
expect_offense(<<~RUBY)
date.beginning_of_day..date.end_of_day
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_day` instead.
RUBY

expect_correction(<<~RUBY)
date.all_day
RUBY
end

it 'registers and corrects an offense when using `date.beginning_of_week..date.end_of_week`' do
expect_offense(<<~RUBY)
date.beginning_of_week..date.end_of_week
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_week` instead.
RUBY

expect_correction(<<~RUBY)
date.all_week
RUBY
end

it 'registers and corrects an offense when using `date.beginning_of_month..date.end_of_month`' do
expect_offense(<<~RUBY)
date.beginning_of_month..date.end_of_month
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_month` instead.
RUBY

expect_correction(<<~RUBY)
date.all_month
RUBY
end

it 'registers and corrects an offense when using `date.beginning_of_quarter..date.end_of_quarter`' do
expect_offense(<<~RUBY)
date.beginning_of_quarter..date.end_of_quarter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_quarter` instead.
RUBY

expect_correction(<<~RUBY)
date.all_quarter
RUBY
end

it 'registers and corrects an offense when using `date.beginning_of_year..date.end_of_year`' do
expect_offense(<<~RUBY)
date.beginning_of_year..date.end_of_year
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_year` instead.
RUBY

expect_correction(<<~RUBY)
date.all_year
RUBY
end

it 'registers and corrects an offense when using an expanded date range as a method argument' do
expect_offense(<<~RUBY)
Model.do_something(foo_at: date.beginning_of_day..date.end_of_day)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_day` instead.
RUBY

expect_correction(<<~RUBY)
Model.do_something(foo_at: date.all_day)
RUBY
end

it 'registers and corrects an offense when assigning expanded date range' do
expect_offense(<<~RUBY)
foo_at = date.beginning_of_day..date.end_of_day
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date.all_day` instead.
Model.find_by(foo_at: foo_at)
RUBY

expect_correction(<<~RUBY)
foo_at = date.all_day
Model.find_by(foo_at: foo_at)
RUBY
end

it 'registers and corrects an offense when using multiple expanded date range conditions' do
expect_offense(<<~RUBY)
Model.where(foo_at: date1.beginning_of_week..date1.end_of_week, bar_at: date2.beginning_of_year..date2.end_of_year)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date1.all_week` instead.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `date2.all_year` instead.
RUBY

expect_correction(<<~RUBY)
Model.where(foo_at: date1.all_week, bar_at: date2.all_year)
RUBY
end

it 'does not register an offense when using `...` range syntax' do
expect_no_offenses(<<~RUBY)
date.beginning_of_day...date.end_of_day
RUBY
end

it 'does not register an offense when variables are different at the beginning and end of the range' do
expect_no_offenses(<<~RUBY)
date1.beginning_of_day..date2.end_of_day
RUBY
end

it 'does not register an offense when unmapped methods are at the beginning and end of the range' do
expect_no_offenses(<<~RUBY)
date.beginning_of_day..date.end_of_year
RUBY
end
end

context 'Rails <= 5.0', :rails50 do
it 'does not register an offense when using `date.beginning_of_day..date.end_of_day`' do
expect_no_offenses(<<~RUBY)
date.beginning_of_day..date.end_of_day
RUBY
end

it 'does not register an offense when using `date.beginning_of_week..date.end_of_week`' do
expect_no_offenses(<<~RUBY)
date.beginning_of_week..date.end_of_week
RUBY
end

it 'does not register an offense when using `date.beginning_of_month..date.end_of_month`' do
expect_no_offenses(<<~RUBY)
date.beginning_of_month..date.end_of_month
RUBY
end

it 'does not register an offense when using `date.beginning_of_quarter..date.end_of_quarter`' do
expect_no_offenses(<<~RUBY)
date.beginning_of_quarter..date.end_of_quarter
RUBY
end

it 'does not register an offense when using `date.beginning_of_year..date.end_of_year`' do
expect_no_offenses(<<~RUBY)
date.beginning_of_year..date.end_of_year
RUBY
end
end
end

0 comments on commit d5a2cdc

Please sign in to comment.