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,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces that `ActiveRecord#find` is used instead of
|
7
|
+
# `where.take!`, `find_by!`, and `find_by_id!` to retrieve a single record
|
8
|
+
# by primary key when you expect it to be found.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# User.where(id: id).take!
|
13
|
+
# User.find_by_id!(id)
|
14
|
+
# User.find_by!(id: id)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# User.find(id)
|
18
|
+
#
|
19
|
+
class FindById < Cop
|
20
|
+
include RangeHelp
|
21
|
+
|
22
|
+
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
23
|
+
|
24
|
+
def_node_matcher :where_take?, <<~PATTERN
|
25
|
+
(send
|
26
|
+
$(send _ :where
|
27
|
+
(hash
|
28
|
+
(pair (sym :id) $_))) :take!)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def_node_matcher :find_by?, <<~PATTERN
|
32
|
+
{
|
33
|
+
(send _ :find_by_id! $_)
|
34
|
+
(send _ :find_by! (hash (pair (sym :id) $_)))
|
35
|
+
}
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
where_take?(node) do |where, id_value|
|
40
|
+
range = where_take_offense_range(node, where)
|
41
|
+
|
42
|
+
good_method = build_good_method(id_value)
|
43
|
+
bad_method = build_where_take_bad_method(id_value)
|
44
|
+
message = format(MSG, good_method: good_method, bad_method: bad_method)
|
45
|
+
|
46
|
+
add_offense(node, location: range, message: message)
|
47
|
+
end
|
48
|
+
|
49
|
+
find_by?(node) do |id_value|
|
50
|
+
range = find_by_offense_range(node)
|
51
|
+
|
52
|
+
good_method = build_good_method(id_value)
|
53
|
+
bad_method = build_find_by_bad_method(node, id_value)
|
54
|
+
message = format(MSG, good_method: good_method, bad_method: bad_method)
|
55
|
+
|
56
|
+
add_offense(node, location: range, message: message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def autocorrect(node)
|
61
|
+
if (matches = where_take?(node))
|
62
|
+
where, id_value = *matches
|
63
|
+
range = where_take_offense_range(node, where)
|
64
|
+
elsif (id_value = find_by?(node))
|
65
|
+
range = find_by_offense_range(node)
|
66
|
+
end
|
67
|
+
|
68
|
+
lambda do |corrector|
|
69
|
+
replacement = build_good_method(id_value)
|
70
|
+
corrector.replace(range, replacement)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def where_take_offense_range(node, where)
|
77
|
+
range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_by_offense_range(node)
|
81
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_good_method(id_value)
|
85
|
+
"find(#{id_value.source})"
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_where_take_bad_method(id_value)
|
89
|
+
"where(id: #{id_value.source}).take!"
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_find_by_bad_method(node, id_value)
|
93
|
+
case node.method_name
|
94
|
+
when :find_by_id!
|
95
|
+
"find_by_id!(#{id_value.source})"
|
96
|
+
when :find_by!
|
97
|
+
"find_by!(id: #{id_value.source})"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that Active Support's `inquiry` method is not used.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad - String#inquiry
|
10
|
+
# ruby = 'two'.inquiry
|
11
|
+
# ruby.two?
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# ruby = 'two'
|
15
|
+
# ruby == 'two'
|
16
|
+
#
|
17
|
+
# # bad - Array#inquiry
|
18
|
+
# pets = %w(cat dog).inquiry
|
19
|
+
# pets.gopher?
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# pets = %w(cat dog)
|
23
|
+
# pets.include? 'cat'
|
24
|
+
#
|
25
|
+
class Inquiry < Cop
|
26
|
+
MSG = "Prefer Ruby's comparison operators over Active Support's `inquiry`."
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
add_offense(node, location: :selector) if node.method?(:inquiry) && node.arguments.empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -35,6 +35,7 @@ module RuboCop
|
|
35
35
|
(pair {(sym :rel) (str "rel")} (str _))
|
36
36
|
PATTERN
|
37
37
|
|
38
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
38
39
|
def on_send(node)
|
39
40
|
return unless node.method?(:link_to)
|
40
41
|
|
@@ -45,6 +46,7 @@ module RuboCop
|
|
45
46
|
add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
|
46
47
|
end
|
47
48
|
end
|
49
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
48
50
|
|
49
51
|
def autocorrect(node)
|
50
52
|
lambda do |corrector|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces that mailer names end with `Mailer` suffix.
|
7
|
+
#
|
8
|
+
# Without the `Mailer` suffix it isn't immediately apparent what's a mailer
|
9
|
+
# and which views are related to the mailer.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# class User < ActionMailer::Base
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# class User < ApplicationMailer
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# class UserMailer < ActionMailer::Base
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class UserMailer < ApplicationMailer
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class MailerName < Cop
|
27
|
+
MSG = 'Mailer should end with `Mailer` suffix.'
|
28
|
+
|
29
|
+
def_node_matcher :mailer_base_class?, <<~PATTERN
|
30
|
+
{
|
31
|
+
(const (const nil? :ActionMailer) :Base)
|
32
|
+
(const nil? :ApplicationMailer)
|
33
|
+
}
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def_node_matcher :class_definition?, <<~PATTERN
|
37
|
+
(class $(const _ !#mailer_suffix?) #mailer_base_class? ...)
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def_node_matcher :class_new_definition?, <<~PATTERN
|
41
|
+
(send (const nil? :Class) :new #mailer_base_class?)
|
42
|
+
PATTERN
|
43
|
+
|
44
|
+
def on_class(node)
|
45
|
+
class_definition?(node) do |name_node|
|
46
|
+
add_offense(name_node)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_send(node)
|
51
|
+
return unless class_new_definition?(node)
|
52
|
+
|
53
|
+
casgn_parent = node.each_ancestor(:casgn).first
|
54
|
+
return unless casgn_parent
|
55
|
+
|
56
|
+
name = casgn_parent.children[1]
|
57
|
+
add_offense(casgn_parent, location: :name) unless mailer_suffix?(name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def autocorrect(node)
|
61
|
+
lambda do |corrector|
|
62
|
+
if node.casgn_type?
|
63
|
+
name = node.children[1]
|
64
|
+
corrector.replace(node.loc.name, "#{name}Mailer")
|
65
|
+
else
|
66
|
+
name = node.children.last
|
67
|
+
corrector.replace(node.source_range, "#{name}Mailer")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def mailer_suffix?(mailer_name)
|
75
|
+
mailer_name.to_s.end_with?('Mailer')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop identifies places where defining routes with `match`
|
7
|
+
# can be replaced with a specific HTTP method.
|
8
|
+
#
|
9
|
+
# Don't use `match` to define any routes unless there is a need to map multiple request types
|
10
|
+
# among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# match ':controller/:action/:id'
|
15
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# get ':controller/:action/:id'
|
19
|
+
# get 'photos/:id', to: 'photos#show'
|
20
|
+
# match 'photos/:id', to: 'photos#show', via: [:get, :post]
|
21
|
+
# match 'photos/:id', to: 'photos#show', via: :all
|
22
|
+
#
|
23
|
+
class MatchRoute < Cop
|
24
|
+
MSG = 'Use `%<http_method>s` instead of `match` to define a route.'
|
25
|
+
HTTP_METHODS = %i[get post put patch delete].freeze
|
26
|
+
|
27
|
+
def_node_matcher :match_method_call?, <<~PATTERN
|
28
|
+
(send nil? :match $_ $(hash ...) ?)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
match_method_call?(node) do |path_node, options_node|
|
33
|
+
return unless within_routes?(node)
|
34
|
+
|
35
|
+
options_node = path_node.hash_type? ? path_node : options_node.first
|
36
|
+
|
37
|
+
if options_node.nil?
|
38
|
+
message = format(MSG, http_method: 'get')
|
39
|
+
add_offense(node, message: message)
|
40
|
+
else
|
41
|
+
via = extract_via(options_node)
|
42
|
+
if via.size == 1 && http_method?(via.first)
|
43
|
+
message = format(MSG, http_method: via.first)
|
44
|
+
add_offense(node, message: message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocorrect(node)
|
51
|
+
match_method_call?(node) do |path_node, options_node|
|
52
|
+
options_node = options_node.first
|
53
|
+
|
54
|
+
lambda do |corrector|
|
55
|
+
corrector.replace(node, replacement(path_node, options_node))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def_node_matcher :routes_draw?, <<~PATTERN
|
63
|
+
(send (send _ :routes) :draw)
|
64
|
+
PATTERN
|
65
|
+
|
66
|
+
def within_routes?(node)
|
67
|
+
node.each_ancestor(:block).any? { |a| routes_draw?(a.send_node) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_via(node)
|
71
|
+
via_pair = via_pair(node)
|
72
|
+
return %i[get] unless via_pair
|
73
|
+
|
74
|
+
_, via = *via_pair
|
75
|
+
|
76
|
+
if via.basic_literal?
|
77
|
+
[via.value]
|
78
|
+
elsif via.array_type?
|
79
|
+
via.values.map(&:value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def via_pair(node)
|
84
|
+
node.pairs.find { |p| p.key.value == :via }
|
85
|
+
end
|
86
|
+
|
87
|
+
def http_method?(method)
|
88
|
+
HTTP_METHODS.include?(method.to_sym)
|
89
|
+
end
|
90
|
+
|
91
|
+
def replacement(path_node, options_node)
|
92
|
+
if path_node.hash_type?
|
93
|
+
http_method, options = *http_method_and_options(path_node)
|
94
|
+
"#{http_method} #{options.map(&:source).join(', ')}"
|
95
|
+
elsif options_node.nil?
|
96
|
+
"get #{path_node.source}"
|
97
|
+
else
|
98
|
+
http_method, options = *http_method_and_options(options_node)
|
99
|
+
|
100
|
+
if options.any?
|
101
|
+
"#{http_method} #{path_node.source}, #{options.map(&:source).join(', ')}"
|
102
|
+
else
|
103
|
+
"#{http_method} #{path_node.source}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def http_method_and_options(node)
|
109
|
+
via_pair = via_pair(node)
|
110
|
+
http_method = extract_via(node).first
|
111
|
+
rest_pairs = node.pairs - [via_pair]
|
112
|
+
[http_method, rest_pairs]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `collection.exclude?(obj)`
|
7
|
+
# over `!collection.include?(obj)`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# !array.include?(2)
|
12
|
+
# !hash.include?(:key)
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# array.exclude?(2)
|
16
|
+
# hash.exclude?(:key)
|
17
|
+
#
|
18
|
+
class NegateInclude < Cop
|
19
|
+
MSG = 'Use `.exclude?` and remove the negation part.'
|
20
|
+
|
21
|
+
def_node_matcher :negate_include_call?, <<~PATTERN
|
22
|
+
(send (send $_ :include? $_) :!)
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
add_offense(node) if negate_include_call?(node)
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocorrect(node)
|
30
|
+
negate_include_call?(node) do |receiver, obj|
|
31
|
+
lambda do |corrector|
|
32
|
+
corrector.replace(node, "#{receiver.source}.exclude?(#{obj.source})")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -3,16 +3,20 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# This cop enforces the use `pick` over `pluck(...).first`.
|
6
|
+
# This cop enforces the use of `pick` over `pluck(...).first`.
|
7
|
+
#
|
8
|
+
# Using `pluck` followed by `first` creates an intermediate array, which
|
9
|
+
# `pick` avoids. When called on an Active Record relation, `pick` adds a
|
10
|
+
# limit to the query so that only one value is fetched from the database.
|
7
11
|
#
|
8
12
|
# @example
|
9
13
|
# # bad
|
10
14
|
# Model.pluck(:a).first
|
11
|
-
#
|
15
|
+
# [{ a: :b, c: :d }].pluck(:a, :b).first
|
12
16
|
#
|
13
17
|
# # good
|
14
18
|
# Model.pick(:a)
|
15
|
-
#
|
19
|
+
# [{ a: :b, c: :d }].pick(:a, :b)
|
16
20
|
class Pick < Cop
|
17
21
|
extend TargetRailsVersion
|
18
22
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces the use of `pluck` over `map`.
|
7
|
+
#
|
8
|
+
# `pluck` can be used instead of `map` to extract a single key from each
|
9
|
+
# element in an enumerable. When called on an Active Record relation, it
|
10
|
+
# results in a more efficient query that only selects the necessary key.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# Post.published.map { |post| post[:title] }
|
15
|
+
# [{ a: :b, c: :d }].collect { |el| el[:a] }
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Post.published.pluck(:title)
|
19
|
+
# [{ a: :b, c: :d }].pluck(:a)
|
20
|
+
class Pluck < Cop
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
MSG = 'Prefer `pluck(:%<value>s)` over `%<method>s { |%<argument>s| %<element>s[:%<value>s] }`.'
|
24
|
+
|
25
|
+
minimum_target_rails_version 5.0
|
26
|
+
|
27
|
+
def_node_matcher :pluck_candidate?, <<~PATTERN
|
28
|
+
(block (send _ ${:map :collect}) (args (arg $_argument)) (send (lvar $_element) :[] (sym $_value)))
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_block(node)
|
32
|
+
pluck_candidate?(node) do |method, argument, element, value|
|
33
|
+
next unless argument == element
|
34
|
+
|
35
|
+
add_offense(node, location: offense_range(node), message: message(method, argument, element, value))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def autocorrect(node)
|
40
|
+
_method, _argument, _element, value = pluck_candidate?(node)
|
41
|
+
|
42
|
+
lambda do |corrector|
|
43
|
+
corrector.replace(offense_range(node), "pluck(:#{value})")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def offense_range(node)
|
50
|
+
node.send_node.loc.selector.join(node.loc.end)
|
51
|
+
end
|
52
|
+
|
53
|
+
def message(method, argument, element, value)
|
54
|
+
format(MSG, method: method, argument: argument, element: element, value: value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|