Skip to content

Commit 00318ce

Browse files
wata727koic
authored andcommitted
Add new Rails/IgnoredSkipActionFilterOption cop
Fixes #35 This cop checks that `if` and `only` (or `except`) are not used together as options of `skip_*` action filter. The `if` option will be ignored when `if` and `only` are used together. Similarly, the `except` option will be ignored when `if` and `except` are used together.
1 parent 67aeaa4 commit 00318ce

File tree

5 files changed

+181
-0
lines changed

5 files changed

+181
-0
lines changed

config/default.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ Rails/HttpStatus:
113113
- numeric
114114
- symbolic
115115

116+
Rails/IgnoredSkipActionFilterOption:
117+
Description: 'Checks that `if` and `only` (or `except`) are not used together as options of `skip_*` action filter.'
118+
Reference: 'https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options'
119+
Enabled: true
120+
Include:
121+
- app/controllers/**/*.rb
122+
116123
Rails/InverseOf:
117124
Include:
118125
- app/models/**/*.rb
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Rails
6+
# This cop checks that `if` and `only` (or `except`) are not used together
7+
# as options of `skip_*` action filter.
8+
#
9+
# The `if` option will be ignored when `if` and `only` are used together.
10+
# Similarly, the `except` option will be ignored when `if` and `except`
11+
# are used together.
12+
#
13+
# @example
14+
# # bad
15+
# class MyPageController < ApplicationController
16+
# skip_before_action :login_required,
17+
# only: :show, if: :trusted_origin?
18+
# end
19+
#
20+
# # good
21+
# class MyPageController < ApplicationController
22+
# skip_before_action :login_required,
23+
# if: -> { trusted_origin? && action_name == "show" }
24+
# end
25+
#
26+
# @example
27+
# # bad
28+
# class MyPageController < ApplicationController
29+
# skip_before_action :login_required,
30+
# except: :admin, if: :trusted_origin?
31+
# end
32+
#
33+
# # good
34+
# class MyPageController < ApplicationController
35+
# skip_before_action :login_required,
36+
# if: -> { trusted_origin? && action_name != "admin" }
37+
# end
38+
#
39+
# @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
40+
class IgnoredSkipActionFilterOption < Cop
41+
MSG = <<-MSG.strip_indent.chomp.freeze
42+
`%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
43+
MSG
44+
45+
FILTERS = %w[
46+
:skip_after_action
47+
:skip_around_action
48+
:skip_before_action
49+
:skip_action_callback
50+
].freeze
51+
52+
def_node_matcher :filter_options, <<-PATTERN
53+
(send
54+
nil?
55+
{#{FILTERS.join(' ')}}
56+
_
57+
$_)
58+
PATTERN
59+
60+
def on_send(node)
61+
options = filter_options(node)
62+
return unless options
63+
return unless options.hash_type?
64+
65+
options = options_hash(options)
66+
67+
if if_and_only?(options)
68+
add_offense(options[:if],
69+
message: format(MSG, prefer: :only, ignore: :if))
70+
elsif if_and_except?(options)
71+
add_offense(options[:except],
72+
message: format(MSG, prefer: :if, ignore: :except))
73+
end
74+
end
75+
76+
private
77+
78+
def options_hash(options)
79+
options.pairs
80+
.select { |pair| pair.key.sym_type? }
81+
.map { |pair| [pair.key.value, pair] }.to_h
82+
end
83+
84+
def if_and_only?(options)
85+
options.key?(:if) && options.key?(:only)
86+
end
87+
88+
def if_and_except?(options)
89+
options.key?(:if) && options.key?(:except)
90+
end
91+
end
92+
end
93+
end
94+
end

lib/rubocop/cop/rails_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module Cop
3232
require_relative 'rails/helper_instance_variable'
3333
require_relative 'rails/http_positional_arguments'
3434
require_relative 'rails/http_status'
35+
require_relative 'rails/ignored_skip_action_filter_option'
3536
require_relative 'rails/inverse_of'
3637
require_relative 'rails/lexically_scoped_action_filter'
3738
require_relative 'rails/link_to_blank'

manual/cops_rails.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,58 @@ Name | Default value | Configurable values
916916
--- | --- | ---
917917
EnforcedStyle | `symbolic` | `numeric`, `symbolic`
918918

919+
## Rails/IgnoredSkipActionFilterOption
920+
921+
Enabled by default | Supports autocorrection
922+
--- | ---
923+
Enabled | No
924+
925+
This cop checks that `if` and `only` (or `except`) are not used together
926+
as options of `skip_*` action filter.
927+
928+
The `if` option will be ignored when `if` and `only` are used together.
929+
Similarly, the `except` option will be ignored when `if` and `except`
930+
are used together.
931+
932+
### Examples
933+
934+
```ruby
935+
# bad
936+
class MyPageController < ApplicationController
937+
skip_before_action :login_required,
938+
only: :show, if: :trusted_origin?
939+
end
940+
941+
# good
942+
class MyPageController < ApplicationController
943+
skip_before_action :login_required,
944+
if: -> { trusted_origin? && action_name == "show" }
945+
end
946+
```
947+
```ruby
948+
# bad
949+
class MyPageController < ApplicationController
950+
skip_before_action :login_required,
951+
except: :admin, if: :trusted_origin?
952+
end
953+
954+
# good
955+
class MyPageController < ApplicationController
956+
skip_before_action :login_required,
957+
if: -> { trusted_origin? && action_name != "admin" }
958+
end
959+
```
960+
961+
### Configurable attributes
962+
963+
Name | Default value | Configurable values
964+
--- | --- | ---
965+
Include | `app/controllers/**/*.rb` | Array
966+
967+
### References
968+
969+
* [https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options](https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options)
970+
919971
## Rails/InverseOf
920972

921973
Enabled by default | Supports autocorrection
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::Rails::IgnoredSkipActionFilterOption do
4+
subject(:cop) { described_class.new(config) }
5+
6+
let(:config) { RuboCop::Config.new }
7+
8+
it 'registers an offense when `if` and `only` are used together' do
9+
expect_offense(<<-RUBY.strip_indent)
10+
skip_before_action :login_required, only: :show, if: :trusted_origin?
11+
^^^^^^^^^^^^^^^^^^^^ `if` option will be ignored when `only` and `if` are used together.
12+
RUBY
13+
end
14+
15+
it 'registers an offense when `if` and `except` are used together' do
16+
expect_offense(<<-RUBY.strip_indent)
17+
skip_before_action :login_required, except: :admin, if: :trusted_origin?
18+
^^^^^^^^^^^^^^ `except` option will be ignored when `if` and `except` are used together.
19+
RUBY
20+
end
21+
22+
it 'does not register an offense when `if` is used only' do
23+
expect_no_offenses(<<-RUBY.strip_indent)
24+
skip_before_action :login_required, if: -> { trusted_origin? && action_name == "show" }
25+
RUBY
26+
end
27+
end

0 commit comments

Comments
 (0)