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
@@ -9,14 +9,14 @@ module RuboCop
|
|
9
9
|
#
|
10
10
|
# This will allow:
|
11
11
|
#
|
12
|
-
#
|
12
|
+
# * update or save calls, assigned to a variable,
|
13
13
|
# or used as a condition in an if/unless/case statement.
|
14
|
-
#
|
14
|
+
# * create calls, assigned to a variable that then has a
|
15
15
|
# call to `persisted?`, or whose return value is checked by
|
16
16
|
# `persisted?` immediately
|
17
|
-
#
|
17
|
+
# * calls if the result is explicitly returned from methods and blocks,
|
18
18
|
# or provided as arguments.
|
19
|
-
#
|
19
|
+
# * calls whose signature doesn't look like an ActiveRecord
|
20
20
|
# persistence method.
|
21
21
|
#
|
22
22
|
# By default it will also allow implicit returns from methods and blocks.
|
@@ -218,9 +218,7 @@ module RuboCop
|
|
218
218
|
def check_used_in_condition_or_compound_boolean(node)
|
219
219
|
return false unless in_condition_or_compound_boolean?(node)
|
220
220
|
|
221
|
-
unless MODIFY_PERSIST_METHODS.include?(node.method_name)
|
222
|
-
add_offense_for_node(node, CREATE_CONDITIONAL_MSG)
|
223
|
-
end
|
221
|
+
add_offense_for_node(node, CREATE_CONDITIONAL_MSG) unless MODIFY_PERSIST_METHODS.include?(node.method_name)
|
224
222
|
|
225
223
|
true
|
226
224
|
end
|
@@ -248,6 +246,7 @@ module RuboCop
|
|
248
246
|
|
249
247
|
def allowed_receiver?(node)
|
250
248
|
return false unless node.receiver
|
249
|
+
return true if node.receiver.const_name == 'ENV'
|
251
250
|
return false unless cop_config['AllowedReceivers']
|
252
251
|
|
253
252
|
cop_config['AllowedReceivers'].any? do |allowed_receiver|
|
@@ -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,27 +42,50 @@ 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
|
+
{
|
60
|
+
(send (const nil? :FileUtils) :touch ...)
|
61
|
+
(send _ :touch {true false})
|
62
|
+
}
|
63
|
+
PATTERN
|
64
|
+
|
65
|
+
def_node_matcher :good_insert?, <<~PATTERN
|
66
|
+
(send _ {:insert :insert!} _ {
|
67
|
+
!(hash ...)
|
68
|
+
(hash <(pair (sym !{:returning :unique_by}) _) ...>)
|
69
|
+
} ...)
|
54
70
|
PATTERN
|
55
71
|
|
56
72
|
def on_send(node)
|
57
|
-
return if
|
58
|
-
return unless
|
73
|
+
return if allowed_methods.include?(node.method_name.to_s)
|
74
|
+
return unless forbidden_methods.include?(node.method_name.to_s)
|
59
75
|
return if allowed_method?(node)
|
60
76
|
return if good_touch?(node)
|
77
|
+
return if good_insert?(node)
|
61
78
|
|
62
79
|
add_offense(node, location: :selector)
|
63
80
|
end
|
64
81
|
alias on_csend on_send
|
65
82
|
|
83
|
+
def initialize(*)
|
84
|
+
super
|
85
|
+
@displayed_allowed_warning = false
|
86
|
+
@displayed_forbidden_warning = false
|
87
|
+
end
|
88
|
+
|
66
89
|
private
|
67
90
|
|
68
91
|
def message(node)
|
@@ -74,12 +97,27 @@ module RuboCop
|
|
74
97
|
!node.arguments?
|
75
98
|
end
|
76
99
|
|
77
|
-
def
|
78
|
-
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'] || []
|
79
109
|
end
|
80
110
|
|
81
|
-
def
|
82
|
-
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'] || []
|
83
121
|
end
|
84
122
|
end
|
85
123
|
end
|
@@ -85,9 +85,7 @@ module RuboCop
|
|
85
85
|
end
|
86
86
|
|
87
87
|
# prefer `Time` over `DateTime` class
|
88
|
-
if strict?
|
89
|
-
corrector.replace(node.children.first.source_range, 'Time')
|
90
|
-
end
|
88
|
+
corrector.replace(node.children.first.source_range, 'Time') if strict?
|
91
89
|
remove_redundant_in_time_zone(corrector, node)
|
92
90
|
end
|
93
91
|
end
|
@@ -3,19 +3,20 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
# Prefer the use of
|
6
|
+
# Prefer the use of distinct, before pluck instead of after.
|
7
7
|
#
|
8
|
-
# The use of
|
8
|
+
# The use of distinct before pluck is preferred because it executes within
|
9
9
|
# the database.
|
10
10
|
#
|
11
11
|
# This cop has two different enforcement modes. When the EnforcedStyle
|
12
12
|
# is conservative (the default) then only calls to pluck on a constant
|
13
|
-
# (i.e. a model class) before
|
13
|
+
# (i.e. a model class) before distinct are added as offenses.
|
14
14
|
#
|
15
15
|
# When the EnforcedStyle is aggressive then all calls to pluck before
|
16
|
-
#
|
17
|
-
# cannot distinguish between calls to pluck on an
|
18
|
-
# vs a call to pluck on an
|
16
|
+
# distinct are added as offenses. This may lead to false positives
|
17
|
+
# as the cop cannot distinguish between calls to pluck on an
|
18
|
+
# ActiveRecord::Relation vs a call to pluck on an
|
19
|
+
# ActiveRecord::Associations::CollectionProxy.
|
19
20
|
#
|
20
21
|
# Autocorrect is disabled by default for this cop since it may generate
|
21
22
|
# false positives.
|
@@ -25,7 +26,7 @@ module RuboCop
|
|
25
26
|
# Model.pluck(:id).uniq
|
26
27
|
#
|
27
28
|
# # good
|
28
|
-
# Model.
|
29
|
+
# Model.distinct.pluck(:id)
|
29
30
|
#
|
30
31
|
# @example EnforcedStyle: aggressive
|
31
32
|
# # bad
|
@@ -40,13 +41,13 @@ module RuboCop
|
|
40
41
|
# Model.pluck(:id).uniq
|
41
42
|
#
|
42
43
|
# # good
|
43
|
-
# Model.
|
44
|
+
# Model.distinct.pluck(:id)
|
44
45
|
#
|
45
46
|
class UniqBeforePluck < RuboCop::Cop::Cop
|
46
47
|
include ConfigurableEnforcedStyle
|
47
48
|
include RangeHelp
|
48
49
|
|
49
|
-
MSG = 'Use
|
50
|
+
MSG = 'Use `distinct` before `pluck`.'
|
50
51
|
NEWLINE = "\n"
|
51
52
|
PATTERN = '[!^block (send (send %<type>s :pluck ...) ' \
|
52
53
|
'${:uniq :distinct} ...)]'
|
@@ -66,8 +67,7 @@ module RuboCop
|
|
66
67
|
|
67
68
|
return unless method
|
68
69
|
|
69
|
-
add_offense(node, location: :selector
|
70
|
-
message: format(MSG, method: method))
|
70
|
+
add_offense(node, location: :selector)
|
71
71
|
end
|
72
72
|
|
73
73
|
def autocorrect(node)
|
@@ -75,7 +75,7 @@ module RuboCop
|
|
75
75
|
method = node.method_name
|
76
76
|
|
77
77
|
corrector.remove(dot_method_with_whitespace(method, node))
|
78
|
-
corrector.insert_before(node.receiver.loc.dot.begin,
|
78
|
+
corrector.insert_before(node.receiver.loc.dot.begin, '.distinct')
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# When you define a uniqueness validation in Active Record model,
|
7
|
+
# you also should add a unique index for the column. There are two reasons
|
8
|
+
# First, duplicated records may occur even if Active Record's validation
|
9
|
+
# is defined.
|
10
|
+
# Second, it will cause slow queries. The validation executes a `SELECT`
|
11
|
+
# statement with the target column when inserting/updating a record.
|
12
|
+
# If the column does not have an index and the table is large,
|
13
|
+
# the query will be heavy.
|
14
|
+
#
|
15
|
+
# Note that the cop does nothing if db/schema.rb does not exist.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# # bad - if the schema does not have a unique index
|
19
|
+
# validates :account, uniqueness: true
|
20
|
+
#
|
21
|
+
# # good - if the schema has a unique index
|
22
|
+
# validates :account, uniqueness: true
|
23
|
+
#
|
24
|
+
# # good - even if the schema does not have a unique index
|
25
|
+
# validates :account, length: { minimum: MIN_LENGTH }
|
26
|
+
#
|
27
|
+
class UniqueValidationWithoutIndex < Cop
|
28
|
+
include ActiveRecordHelper
|
29
|
+
|
30
|
+
MSG = 'Uniqueness validation should be with a unique index.'
|
31
|
+
|
32
|
+
def on_send(node)
|
33
|
+
return unless node.method?(:validates)
|
34
|
+
return unless uniqueness_part(node)
|
35
|
+
return if condition_part?(node)
|
36
|
+
return unless schema
|
37
|
+
return if with_index?(node)
|
38
|
+
|
39
|
+
add_offense(node)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def with_index?(node)
|
45
|
+
klass = class_node(node)
|
46
|
+
return true unless klass # Skip analysis
|
47
|
+
|
48
|
+
table = schema.table_by(name: table_name(klass))
|
49
|
+
return true unless table # Skip analysis if it can't find the table
|
50
|
+
|
51
|
+
names = column_names(node)
|
52
|
+
return true unless names
|
53
|
+
|
54
|
+
# Compatibility for Rails 4.2.
|
55
|
+
add_indicies = schema.add_indicies_by(table_name: table_name(klass))
|
56
|
+
|
57
|
+
(table.indices + add_indicies).any? do |index|
|
58
|
+
index.unique &&
|
59
|
+
(index.columns.to_set == names ||
|
60
|
+
include_column_names_in_expression_index?(index, names))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def include_column_names_in_expression_index?(index, column_names)
|
65
|
+
return false unless (expression_index = index.expression)
|
66
|
+
|
67
|
+
column_names.all? do |column_name|
|
68
|
+
expression_index.include?(column_name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def column_names(node)
|
73
|
+
arg = node.first_argument
|
74
|
+
return unless arg.str_type? || arg.sym_type?
|
75
|
+
|
76
|
+
ret = [arg.value]
|
77
|
+
names_from_scope = column_names_from_scope(node)
|
78
|
+
ret.concat(names_from_scope) if names_from_scope
|
79
|
+
|
80
|
+
ret.map! do |name|
|
81
|
+
klass = class_node(node)
|
82
|
+
resolve_relation_into_column(
|
83
|
+
name: name.to_s,
|
84
|
+
class_node: klass,
|
85
|
+
table: schema.table_by(name: table_name(klass))
|
86
|
+
)
|
87
|
+
end
|
88
|
+
ret.include?(nil) ? nil : ret.to_set
|
89
|
+
end
|
90
|
+
|
91
|
+
def column_names_from_scope(node)
|
92
|
+
uniq = uniqueness_part(node)
|
93
|
+
return unless uniq.hash_type?
|
94
|
+
|
95
|
+
scope = find_scope(uniq)
|
96
|
+
return unless scope
|
97
|
+
|
98
|
+
case scope.type
|
99
|
+
when :sym, :str
|
100
|
+
[scope.value]
|
101
|
+
when :array
|
102
|
+
array_node_to_array(scope)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_scope(pairs)
|
107
|
+
pairs.each_pair.find do |pair|
|
108
|
+
key = pair.key
|
109
|
+
next unless key.sym_type? && key.value == :scope
|
110
|
+
|
111
|
+
break pair.value
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def class_node(node)
|
116
|
+
node.each_ancestor.find(&:class_type?)
|
117
|
+
end
|
118
|
+
|
119
|
+
def uniqueness_part(node)
|
120
|
+
pairs = node.arguments.last
|
121
|
+
return unless pairs.hash_type?
|
122
|
+
|
123
|
+
pairs.each_pair.find do |pair|
|
124
|
+
next unless pair.key.sym_type? && pair.key.value == :uniqueness
|
125
|
+
|
126
|
+
break pair.value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def condition_part?(node)
|
131
|
+
pairs = node.arguments.last
|
132
|
+
return unless pairs.hash_type?
|
133
|
+
|
134
|
+
pairs.each_pair.any? do |pair|
|
135
|
+
key = pair.key
|
136
|
+
next unless key.sym_type?
|
137
|
+
|
138
|
+
key.value == :if || key.value == :unless
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def array_node_to_array(node)
|
143
|
+
node.values.map do |elm|
|
144
|
+
case elm.type
|
145
|
+
when :str, :sym
|
146
|
+
elm.value
|
147
|
+
else
|
148
|
+
return nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -15,8 +15,6 @@ module RuboCop
|
|
15
15
|
# Rails.env.production?
|
16
16
|
# Rails.env == 'production'
|
17
17
|
class UnknownEnv < Cop
|
18
|
-
include NameSimilarity
|
19
|
-
|
20
18
|
MSG = 'Unknown environment `%<name>s`.'
|
21
19
|
MSG_SIMILAR = 'Unknown environment `%<name>s`. ' \
|
22
20
|
'Did you mean `%<similar>s`?'
|
@@ -57,11 +55,14 @@ module RuboCop
|
|
57
55
|
|
58
56
|
def message(name)
|
59
57
|
name = name.to_s.chomp('?')
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
|
59
|
+
spell_checker = DidYouMean::SpellChecker.new(dictionary: environments)
|
60
|
+
similar_names = spell_checker.correct(name)
|
61
|
+
|
62
|
+
if similar_names.empty?
|
64
63
|
format(MSG, name: name)
|
64
|
+
else
|
65
|
+
format(MSG_SIMILAR, name: name, similar: similar_names.join(', '))
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|