rubocop-rails 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/default.yml +112 -3
- data/lib/rubocop/cop/mixin/active_record_helper.rb +7 -0
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
- data/lib/rubocop/cop/rails/content_tag.rb +8 -21
- data/lib/rubocop/cop/rails/default_scope.rb +54 -0
- data/lib/rubocop/cop/rails/delegate.rb +1 -1
- data/lib/rubocop/cop/rails/file_path.rb +1 -1
- data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
- data/lib/rubocop/cop/rails/inquiry.rb +34 -0
- data/lib/rubocop/cop/rails/link_to_blank.rb +2 -0
- data/lib/rubocop/cop/rails/mailer_name.rb +80 -0
- data/lib/rubocop/cop/rails/match_route.rb +117 -0
- data/lib/rubocop/cop/rails/negate_include.rb +39 -0
- data/lib/rubocop/cop/rails/pick.rb +7 -3
- data/lib/rubocop/cop/rails/pluck.rb +59 -0
- data/lib/rubocop/cop/rails/pluck_id.rb +58 -0
- data/lib/rubocop/cop/rails/pluck_in_where.rb +36 -0
- data/lib/rubocop/cop/rails/render_inline.rb +48 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +42 -7
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +4 -4
- data/lib/rubocop/cop/rails/where_exists.rb +68 -0
- data/lib/rubocop/cop/rails_cops.rb +14 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +19 -5
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `ids` over `pluck(:id)` and `pluck(primary_key)`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# User.pluck(:id)
|
11
|
+
# user.posts.pluck(:id)
|
12
|
+
#
|
13
|
+
# def self.user_ids
|
14
|
+
# pluck(primary_key)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# User.ids
|
19
|
+
# user.posts.ids
|
20
|
+
#
|
21
|
+
# def self.user_ids
|
22
|
+
# ids
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class PluckId < Cop
|
26
|
+
include RangeHelp
|
27
|
+
include ActiveRecordHelper
|
28
|
+
|
29
|
+
MSG = 'Use `ids` instead of `%<bad_method>s`.'
|
30
|
+
|
31
|
+
def_node_matcher :pluck_id_call?, <<~PATTERN
|
32
|
+
(send _ :pluck {(sym :id) (send nil? :primary_key)})
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
def on_send(node)
|
36
|
+
return if !pluck_id_call?(node) || in_where?(node)
|
37
|
+
|
38
|
+
range = offense_range(node)
|
39
|
+
message = format(MSG, bad_method: range.source)
|
40
|
+
|
41
|
+
add_offense(node, location: range, message: message)
|
42
|
+
end
|
43
|
+
|
44
|
+
def autocorrect(node)
|
45
|
+
lambda do |corrector|
|
46
|
+
corrector.replace(offense_range(node), 'ids')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def offense_range(node)
|
53
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where `pluck` is used in `where` query methods
|
7
|
+
# and can be replaced with `select`.
|
8
|
+
#
|
9
|
+
# Since `pluck` is an eager method and hits the database immediately,
|
10
|
+
# using `select` helps to avoid additional database queries.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# Post.where(user_id: User.active.pluck(:id))
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# Post.where(user_id: User.active.select(:id))
|
18
|
+
#
|
19
|
+
class PluckInWhere < Cop
|
20
|
+
include ActiveRecordHelper
|
21
|
+
|
22
|
+
MSG = 'Use `select` instead of `pluck` within `where` query method.'
|
23
|
+
|
24
|
+
def on_send(node)
|
25
|
+
add_offense(node, location: :selector) if node.method?(:pluck) && in_where?(node)
|
26
|
+
end
|
27
|
+
|
28
|
+
def autocorrect(node)
|
29
|
+
lambda do |corrector|
|
30
|
+
corrector.replace(node.loc.selector, 'select')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for inline rendering within controller actions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# class ProductsController < ApplicationController
|
11
|
+
# def index
|
12
|
+
# render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>", type: :erb
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# # app/views/products/index.html.erb
|
18
|
+
# # <% products.each do |p| %>
|
19
|
+
# # <p><%= p.name %></p>
|
20
|
+
# # <% end %>
|
21
|
+
#
|
22
|
+
# class ProductsController < ApplicationController
|
23
|
+
# def index
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class RenderInline < Cop
|
28
|
+
MSG = 'Prefer using a template over inline rendering.'
|
29
|
+
|
30
|
+
def_node_matcher :render_with_options?, <<~PATTERN
|
31
|
+
(send nil? :render $(hash ...))
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
render_with_options?(node) do |options|
|
36
|
+
add_offense(node) if includes_inline_key?(options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def includes_inline_key?(node)
|
43
|
+
node.keys.find { |key| key.value.to_sym == :inline }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where `render text:` can be
|
7
|
+
# replaced with `render plain:`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad - explicit MIME type to `text/plain`
|
11
|
+
# render text: 'Ruby!', content_type: 'text/plain'
|
12
|
+
#
|
13
|
+
# # good - short and precise
|
14
|
+
# render plain: 'Ruby!'
|
15
|
+
#
|
16
|
+
# # good - explicit MIME type not to `text/plain`
|
17
|
+
# render text: 'Ruby!', content_type: 'text/html'
|
18
|
+
#
|
19
|
+
# @example ContentTypeCompatibility: true (default)
|
20
|
+
# # good - sets MIME type to `text/html`
|
21
|
+
# render text: 'Ruby!'
|
22
|
+
#
|
23
|
+
# @example ContentTypeCompatibility: false
|
24
|
+
# # bad - sets MIME type to `text/html`
|
25
|
+
# render text: 'Ruby!'
|
26
|
+
#
|
27
|
+
class RenderPlainText < Cop
|
28
|
+
MSG = 'Prefer `render plain:` over `render text:`.'
|
29
|
+
|
30
|
+
def_node_matcher :render_plain_text?, <<~PATTERN
|
31
|
+
(send nil? :render $(hash <$(pair (sym :text) $_) ...>))
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
render_plain_text?(node) do |options_node, _option_node, _option_value|
|
36
|
+
content_type_node = find_content_type(options_node)
|
37
|
+
add_offense(node) if compatible_content_type?(content_type_node)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def autocorrect(node)
|
42
|
+
render_plain_text?(node) do |options_node, option_node, option_value|
|
43
|
+
content_type_node = find_content_type(options_node)
|
44
|
+
rest_options = options_node.pairs - [option_node, content_type_node].compact
|
45
|
+
|
46
|
+
lambda do |corrector|
|
47
|
+
corrector.replace(
|
48
|
+
node,
|
49
|
+
replacement(rest_options, option_value)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def find_content_type(node)
|
58
|
+
node.pairs.find { |p| p.key.value.to_sym == :content_type }
|
59
|
+
end
|
60
|
+
|
61
|
+
def compatible_content_type?(node)
|
62
|
+
(node && node.value.value == 'text/plain') ||
|
63
|
+
(!node && !cop_config['ContentTypeCompatibility'])
|
64
|
+
end
|
65
|
+
|
66
|
+
def replacement(rest_options, option_value)
|
67
|
+
if rest_options.any?
|
68
|
+
"render plain: #{option_value.source}, #{rest_options.map(&:source).join(', ')}"
|
69
|
+
else
|
70
|
+
"render plain: #{option_value.source}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -48,7 +48,7 @@ module RuboCop
|
|
48
48
|
def on_send(node)
|
49
49
|
try_call(node) do |try_method, dispatch|
|
50
50
|
return if try_method == :try && !cop_config['ConvertTry']
|
51
|
-
return unless dispatch.sym_type? && dispatch.value
|
51
|
+
return unless dispatch.sym_type? && dispatch.value.match?(/\w+[=!?]?/)
|
52
52
|
|
53
53
|
add_offense(node, message: format(MSG, try: try_method))
|
54
54
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces that short forms of `I18n` methods are used:
|
7
|
+
# `t` instead of `translate` and `l` instead of `localize`.
|
8
|
+
#
|
9
|
+
# This cop has two different enforcement modes. When the EnforcedStyle
|
10
|
+
# is conservative (the default) then only `I18n.translate` and `I18n.localize`
|
11
|
+
# calls are added as offenses.
|
12
|
+
#
|
13
|
+
# When the EnforcedStyle is aggressive then all `translate` and `localize` calls
|
14
|
+
# without a receiver are added as offenses.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# # bad
|
18
|
+
# I18n.translate :key
|
19
|
+
# I18n.localize Time.now
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# I18n.t :key
|
23
|
+
# I18n.l Time.now
|
24
|
+
#
|
25
|
+
# @example EnforcedStyle: conservative (default)
|
26
|
+
# # good
|
27
|
+
# translate :key
|
28
|
+
# localize Time.now
|
29
|
+
# t :key
|
30
|
+
# l Time.now
|
31
|
+
#
|
32
|
+
# @example EnforcedStyle: aggressive
|
33
|
+
# # bad
|
34
|
+
# translate :key
|
35
|
+
# localize Time.now
|
36
|
+
#
|
37
|
+
# # good
|
38
|
+
# t :key
|
39
|
+
# l Time.now
|
40
|
+
#
|
41
|
+
class ShortI18n < Cop
|
42
|
+
include ConfigurableEnforcedStyle
|
43
|
+
|
44
|
+
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
45
|
+
|
46
|
+
PREFERRED_METHODS = {
|
47
|
+
translate: :t,
|
48
|
+
localize: :l
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
def_node_matcher :long_i18n?, <<~PATTERN
|
52
|
+
(send {nil? (const nil? :I18n)} ${:translate :localize} ...)
|
53
|
+
PATTERN
|
54
|
+
|
55
|
+
def on_send(node)
|
56
|
+
return if style == :conservative && !node.receiver
|
57
|
+
|
58
|
+
long_i18n?(node) do |method_name|
|
59
|
+
good_method = PREFERRED_METHODS[method_name]
|
60
|
+
message = format(MSG, good_method: good_method, bad_method: method_name)
|
61
|
+
|
62
|
+
add_offense(node, location: :selector, message: message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def autocorrect(node)
|
67
|
+
long_i18n?(node) do |method_name|
|
68
|
+
lambda do |corrector|
|
69
|
+
corrector.replace(node.loc.selector, PREFERRED_METHODS[method_name])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -42,12 +42,18 @@ module RuboCop
|
|
42
42
|
decrement_counter
|
43
43
|
increment!
|
44
44
|
increment_counter
|
45
|
+
insert
|
46
|
+
insert!
|
47
|
+
insert_all
|
48
|
+
insert_all!
|
45
49
|
toggle!
|
46
50
|
update_all
|
47
51
|
update_attribute
|
48
52
|
update_column
|
49
53
|
update_columns
|
50
|
-
update_counters
|
54
|
+
update_counters
|
55
|
+
upsert
|
56
|
+
upsert_all].freeze
|
51
57
|
|
52
58
|
def_node_matcher :good_touch?, <<~PATTERN
|
53
59
|
{
|
@@ -56,16 +62,30 @@ module RuboCop
|
|
56
62
|
}
|
57
63
|
PATTERN
|
58
64
|
|
65
|
+
def_node_matcher :good_insert?, <<~PATTERN
|
66
|
+
(send _ {:insert :insert!} _ {
|
67
|
+
!(hash ...)
|
68
|
+
(hash <(pair (sym !{:returning :unique_by}) _) ...>)
|
69
|
+
} ...)
|
70
|
+
PATTERN
|
71
|
+
|
59
72
|
def on_send(node)
|
60
|
-
return if
|
61
|
-
return unless
|
73
|
+
return if allowed_methods.include?(node.method_name.to_s)
|
74
|
+
return unless forbidden_methods.include?(node.method_name.to_s)
|
62
75
|
return if allowed_method?(node)
|
63
76
|
return if good_touch?(node)
|
77
|
+
return if good_insert?(node)
|
64
78
|
|
65
79
|
add_offense(node, location: :selector)
|
66
80
|
end
|
67
81
|
alias on_csend on_send
|
68
82
|
|
83
|
+
def initialize(*)
|
84
|
+
super
|
85
|
+
@displayed_allowed_warning = false
|
86
|
+
@displayed_forbidden_warning = false
|
87
|
+
end
|
88
|
+
|
69
89
|
private
|
70
90
|
|
71
91
|
def message(node)
|
@@ -77,12 +97,27 @@ module RuboCop
|
|
77
97
|
!node.arguments?
|
78
98
|
end
|
79
99
|
|
80
|
-
def
|
81
|
-
cop_config['Blacklist']
|
100
|
+
def forbidden_methods
|
101
|
+
obsolete_result = cop_config['Blacklist']
|
102
|
+
if obsolete_result
|
103
|
+
warn '`Blacklist` has been renamed to `ForbiddenMethods`.' unless @displayed_forbidden_warning
|
104
|
+
@displayed_forbidden_warning = true
|
105
|
+
return obsolete_result
|
106
|
+
end
|
107
|
+
|
108
|
+
cop_config['ForbiddenMethods'] || []
|
82
109
|
end
|
83
110
|
|
84
|
-
def
|
85
|
-
cop_config['Whitelist']
|
111
|
+
def allowed_methods
|
112
|
+
obsolete_result = cop_config['Whitelist']
|
113
|
+
if obsolete_result
|
114
|
+
warn '`Whitelist` has been renamed to `AllowedMethods`.' unless @displayed_allowed_warning
|
115
|
+
@displayed_allowed_warning = true
|
116
|
+
|
117
|
+
return obsolete_result
|
118
|
+
end
|
119
|
+
|
120
|
+
cop_config['AllowedMethods'] || []
|
86
121
|
end
|
87
122
|
end
|
88
123
|
end
|
@@ -23,7 +23,7 @@ module RuboCop
|
|
23
23
|
#
|
24
24
|
# @example EnforcedStyle: conservative (default)
|
25
25
|
# # bad
|
26
|
-
# Model.pluck(:id).
|
26
|
+
# Model.pluck(:id).uniq
|
27
27
|
#
|
28
28
|
# # good
|
29
29
|
# Model.distinct.pluck(:id)
|
@@ -31,14 +31,14 @@ module RuboCop
|
|
31
31
|
# @example EnforcedStyle: aggressive
|
32
32
|
# # bad
|
33
33
|
# # this will return a Relation that pluck is called on
|
34
|
-
# Model.where(cond: true).pluck(:id).
|
34
|
+
# Model.where(cond: true).pluck(:id).uniq
|
35
35
|
#
|
36
36
|
# # bad
|
37
37
|
# # an association on an instance will return a CollectionProxy
|
38
|
-
# instance.assoc.pluck(:id).
|
38
|
+
# instance.assoc.pluck(:id).uniq
|
39
39
|
#
|
40
40
|
# # bad
|
41
|
-
# Model.pluck(:id).
|
41
|
+
# Model.pluck(:id).uniq
|
42
42
|
#
|
43
43
|
# # good
|
44
44
|
# Model.distinct.pluck(:id)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `exists?(...)` over `where(...).exists?`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# User.where(name: 'john').exists?
|
11
|
+
# User.where(['name = ?', 'john']).exists?
|
12
|
+
# User.where('name = ?', 'john').exists?
|
13
|
+
# user.posts.where(published: true).exists?
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# User.exists?(name: 'john')
|
17
|
+
# User.where('length(name) > 10').exists?
|
18
|
+
# user.posts.exists?(published: true)
|
19
|
+
#
|
20
|
+
class WhereExists < Cop
|
21
|
+
MSG = 'Prefer `%<good_method>s` over `%<bad_method>s`.'
|
22
|
+
|
23
|
+
def_node_matcher :where_exists_call?, <<~PATTERN
|
24
|
+
(send (send _ :where $...) :exists?)
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def on_send(node)
|
28
|
+
where_exists_call?(node) do |args|
|
29
|
+
return unless convertable_args?(args)
|
30
|
+
|
31
|
+
range = correction_range(node)
|
32
|
+
message = format(MSG, good_method: build_good_method(args), bad_method: range.source)
|
33
|
+
add_offense(node, location: range, message: message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
args = where_exists_call?(node)
|
39
|
+
|
40
|
+
lambda do |corrector|
|
41
|
+
corrector.replace(
|
42
|
+
correction_range(node),
|
43
|
+
build_good_method(args)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def convertable_args?(args)
|
51
|
+
args.size > 1 || args[0].hash_type? || args[0].array_type?
|
52
|
+
end
|
53
|
+
|
54
|
+
def correction_range(node)
|
55
|
+
node.receiver.loc.selector.join(node.loc.selector)
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_good_method(args)
|
59
|
+
if args.size > 1
|
60
|
+
"exists?([#{args.map(&:source).join(', ')}])"
|
61
|
+
else
|
62
|
+
"exists?(#{args[0].source})"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|