rubocop-rails 2.4.2 → 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/LICENSE.txt +1 -1
- data/README.md +5 -1
- data/config/default.yml +179 -9
- data/lib/rubocop-rails.rb +3 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +84 -0
- data/lib/rubocop/cop/mixin/index_method.rb +161 -0
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +145 -0
- data/lib/rubocop/cop/rails/content_tag.rb +69 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
- data/lib/rubocop/cop/rails/default_scope.rb +54 -0
- data/lib/rubocop/cop/rails/delegate.rb +2 -4
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
- data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
- data/lib/rubocop/cop/rails/exit.rb +2 -2
- data/lib/rubocop/cop/rails/file_path.rb +2 -1
- data/lib/rubocop/cop/rails/find_by_id.rb +103 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
- data/lib/rubocop/cop/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rails/index_by.rb +56 -0
- data/lib/rubocop/cop/rails/index_with.rb +59 -0
- data/lib/rubocop/cop/rails/inquiry.rb +34 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
- data/lib/rubocop/cop/rails/link_to_blank.rb +3 -3
- 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 +55 -0
- 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/presence.rb +2 -6
- data/lib/rubocop/cop/rails/rake_environment.rb +17 -0
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
- data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
- 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/reversible_migration.rb +6 -1
- data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/rails/save_bang.rb +6 -7
- data/lib/rubocop/cop/rails/short_i18n.rb +76 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -8
- data/lib/rubocop/cop/rails/time_zone.rb +1 -3
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +12 -12
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
- data/lib/rubocop/cop/rails/where_exists.rb +68 -0
- data/lib/rubocop/cop/rails_cops.rb +22 -0
- data/lib/rubocop/rails/schema_loader.rb +61 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +46 -8
@@ -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
|
@@ -129,10 +129,6 @@ module RuboCop
|
|
129
129
|
# @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
|
130
130
|
# @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
|
131
131
|
class InverseOf < Cop
|
132
|
-
extend TargetRailsVersion
|
133
|
-
|
134
|
-
minimum_target_rails_version 4.1
|
135
|
-
|
136
132
|
SPECIFY_MSG = 'Specify an `:inverse_of` option.'
|
137
133
|
NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
|
138
134
|
'use `inverse_of: false`.'
|
@@ -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
|
|
@@ -42,11 +43,10 @@ module RuboCop
|
|
42
43
|
|
43
44
|
option_nodes.map(&:children).each do |options|
|
44
45
|
blank = options.find { |o| blank_target?(o) }
|
45
|
-
if blank && options.none? { |o| includes_noopener?(o) }
|
46
|
-
add_offense(blank)
|
47
|
-
end
|
46
|
+
add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
|
48
47
|
end
|
49
48
|
end
|
49
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
50
50
|
|
51
51
|
def autocorrect(node)
|
52
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
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
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.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# Model.pluck(:a).first
|
15
|
+
# [{ a: :b, c: :d }].pluck(:a, :b).first
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# Model.pick(:a)
|
19
|
+
# [{ a: :b, c: :d }].pick(:a, :b)
|
20
|
+
class Pick < Cop
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
MSG = 'Prefer `pick(%<args>s)` over `pluck(%<args>s).first`.'
|
24
|
+
|
25
|
+
minimum_target_rails_version 6.0
|
26
|
+
|
27
|
+
def_node_matcher :pick_candidate?, <<~PATTERN
|
28
|
+
(send (send _ :pluck ...) :first)
|
29
|
+
PATTERN
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
pick_candidate?(node) do
|
33
|
+
range = node.receiver.loc.selector.join(node.loc.selector)
|
34
|
+
add_offense(node, location: range)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def autocorrect(node)
|
39
|
+
first_range = node.receiver.source_range.end.join(node.loc.selector)
|
40
|
+
|
41
|
+
lambda do |corrector|
|
42
|
+
corrector.remove(first_range)
|
43
|
+
corrector.replace(node.receiver.loc.selector, 'pick')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def message(node)
|
50
|
+
format(MSG, args: node.receiver.arguments.map(&:source).join(', '))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -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
|
@@ -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
|