rubocop-rails 2.5.1 → 2.8.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 +2 -2
- data/config/default.yml +192 -11
- data/lib/rubocop/cop/mixin/active_record_helper.rb +22 -0
- data/lib/rubocop/cop/mixin/index_method.rb +25 -1
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +84 -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/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/has_many_or_has_one_dependent.rb +1 -5
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +2 -0
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +1 -1
- data/lib/rubocop/cop/rails/http_status.rb +2 -0
- data/lib/rubocop/cop/rails/index_by.rb +8 -0
- data/lib/rubocop/cop/rails/index_with.rb +8 -0
- data/lib/rubocop/cop/rails/inquiry.rb +38 -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 +119 -0
- data/lib/rubocop/cop/rails/negate_include.rb +39 -0
- data/lib/rubocop/cop/rails/order_by_id.rb +53 -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 +70 -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/reflection_class_name.rb +1 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +5 -2
- data/lib/rubocop/cop/rails/render_inline.rb +40 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +76 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +79 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +1 -1
- data/lib/rubocop/cop/rails/save_bang.rb +8 -9
- 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/squished_sql_heredocs.rb +83 -0
- data/lib/rubocop/cop/rails/time_zone.rb +1 -3
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +14 -12
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +15 -11
- data/lib/rubocop/cop/rails/unknown_env.rb +18 -6
- data/lib/rubocop/cop/rails/where_exists.rb +131 -0
- data/lib/rubocop/cop/rails/where_not.rb +106 -0
- data/lib/rubocop/cop/rails_cops.rb +21 -0
- data/lib/rubocop/rails/schema_loader.rb +10 -10
- data/lib/rubocop/rails/schema_loader/schema.rb +4 -4
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +31 -10
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that Active Record callbacks are declared
|
7
|
+
# in the order in which they will be executed.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class Person < ApplicationRecord
|
12
|
+
# after_commit :after_commit_callback
|
13
|
+
# before_validation :before_validation_callback
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# class Person < ApplicationRecord
|
18
|
+
# before_validation :before_validation_callback
|
19
|
+
# after_commit :after_commit_callback
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class ActiveRecordCallbacksOrder < Cop
|
23
|
+
MSG = '`%<current>s` is supposed to appear before `%<previous>s`.'
|
24
|
+
|
25
|
+
CALLBACKS_IN_ORDER = %i[
|
26
|
+
after_initialize
|
27
|
+
before_validation
|
28
|
+
after_validation
|
29
|
+
before_save
|
30
|
+
around_save
|
31
|
+
before_create
|
32
|
+
around_create
|
33
|
+
after_create
|
34
|
+
before_update
|
35
|
+
around_update
|
36
|
+
after_update
|
37
|
+
before_destroy
|
38
|
+
around_destroy
|
39
|
+
after_destroy
|
40
|
+
after_save
|
41
|
+
after_commit
|
42
|
+
after_rollback
|
43
|
+
after_find
|
44
|
+
after_touch
|
45
|
+
].freeze
|
46
|
+
|
47
|
+
CALLBACKS_ORDER_MAP = CALLBACKS_IN_ORDER.each_with_index.to_h.freeze
|
48
|
+
|
49
|
+
def on_class(class_node)
|
50
|
+
previous_index = -1
|
51
|
+
previous_callback = nil
|
52
|
+
|
53
|
+
defined_callbacks(class_node).each do |node|
|
54
|
+
callback = node.method_name
|
55
|
+
index = CALLBACKS_ORDER_MAP[callback]
|
56
|
+
|
57
|
+
if index < previous_index
|
58
|
+
message = format(MSG, current: callback,
|
59
|
+
previous: previous_callback)
|
60
|
+
add_offense(node, message: message)
|
61
|
+
end
|
62
|
+
previous_index = index
|
63
|
+
previous_callback = callback
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Autocorrect by swapping between two nodes autocorrecting them
|
68
|
+
def autocorrect(node)
|
69
|
+
previous = left_siblings_of(node).reverse_each.find do |sibling|
|
70
|
+
callback?(sibling)
|
71
|
+
end
|
72
|
+
|
73
|
+
current_range = source_range_with_comment(node)
|
74
|
+
previous_range = source_range_with_comment(previous)
|
75
|
+
|
76
|
+
lambda do |corrector|
|
77
|
+
corrector.insert_before(previous_range, current_range.source)
|
78
|
+
corrector.remove(current_range)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def defined_callbacks(class_node)
|
85
|
+
class_def = class_node.body
|
86
|
+
|
87
|
+
if class_def
|
88
|
+
class_def.each_child_node.select { |c| callback?(c) }
|
89
|
+
else
|
90
|
+
[]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def callback?(node)
|
95
|
+
node.send_type? && CALLBACKS_ORDER_MAP.key?(node.method_name)
|
96
|
+
end
|
97
|
+
|
98
|
+
def left_siblings_of(node)
|
99
|
+
siblings_of(node)[0, node.sibling_index]
|
100
|
+
end
|
101
|
+
|
102
|
+
def siblings_of(node)
|
103
|
+
node.parent.children
|
104
|
+
end
|
105
|
+
|
106
|
+
def source_range_with_comment(node)
|
107
|
+
begin_pos = begin_pos_with_comment(node)
|
108
|
+
end_pos = end_position_for(node)
|
109
|
+
|
110
|
+
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
111
|
+
end
|
112
|
+
|
113
|
+
def end_position_for(node)
|
114
|
+
end_line = buffer.line_for_position(node.loc.expression.end_pos)
|
115
|
+
buffer.line_range(end_line).end_pos
|
116
|
+
end
|
117
|
+
|
118
|
+
def begin_pos_with_comment(node)
|
119
|
+
annotation_line = node.first_line - 1
|
120
|
+
first_comment = nil
|
121
|
+
|
122
|
+
processed_source.comments_before_line(annotation_line)
|
123
|
+
.reverse_each do |comment|
|
124
|
+
if comment.location.line == annotation_line
|
125
|
+
first_comment = comment
|
126
|
+
annotation_line -= 1
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
start_line_position(first_comment || node)
|
131
|
+
end
|
132
|
+
|
133
|
+
def start_line_position(node)
|
134
|
+
buffer.line_range(node.loc.line).begin_pos - 1
|
135
|
+
end
|
136
|
+
|
137
|
+
def buffer
|
138
|
+
processed_source.buffer
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop enforces that there is only one call to `after_commit`
|
7
|
+
# (and its aliases - `after_create_commit`, `after_update_commit`,
|
8
|
+
# and `after_destroy_commit`) with the same callback name per model.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# # This won't be triggered.
|
13
|
+
# after_create_commit :log_action
|
14
|
+
#
|
15
|
+
# # This will override the callback added by
|
16
|
+
# # after_create_commit.
|
17
|
+
# after_update_commit :log_action
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# # This won't be triggered.
|
21
|
+
# after_commit :log_action, on: :create
|
22
|
+
# # This won't be triggered.
|
23
|
+
# after_update_commit :log_action
|
24
|
+
# # This will override both previous callbacks.
|
25
|
+
# after_commit :log_action, on: :destroy
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# after_save_commit :log_action
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# after_create_commit :log_create_action
|
32
|
+
# after_update_commit :log_update_action
|
33
|
+
#
|
34
|
+
class AfterCommitOverride < Cop
|
35
|
+
MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
|
36
|
+
|
37
|
+
AFTER_COMMIT_CALLBACKS = %i[
|
38
|
+
after_commit
|
39
|
+
after_create_commit
|
40
|
+
after_update_commit
|
41
|
+
after_save_commit
|
42
|
+
after_destroy_commit
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
def on_class(class_node)
|
46
|
+
seen_callback_names = {}
|
47
|
+
|
48
|
+
each_after_commit_callback(class_node) do |node|
|
49
|
+
callback_name = node.arguments[0].value
|
50
|
+
if seen_callback_names.key?(callback_name)
|
51
|
+
add_offense(node, message: format(MSG, name: callback_name))
|
52
|
+
else
|
53
|
+
seen_callback_names[callback_name] = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def each_after_commit_callback(class_node)
|
61
|
+
class_send_nodes(class_node).each do |node|
|
62
|
+
yield node if after_commit_callback?(node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def class_send_nodes(class_node)
|
67
|
+
class_def = class_node.body
|
68
|
+
|
69
|
+
return [] unless class_def
|
70
|
+
|
71
|
+
if class_def.send_type?
|
72
|
+
[class_def]
|
73
|
+
else
|
74
|
+
class_def.each_child_node(:send).to_a
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def after_commit_callback?(node)
|
79
|
+
AFTER_COMMIT_CALLBACKS.include?(node.method_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop checks that `tag` is used instead of `content_tag`
|
7
|
+
# because `content_tag` is legacy syntax.
|
8
|
+
#
|
9
|
+
# NOTE: Allow `content_tag` when the first argument is a variable because
|
10
|
+
# `content_tag(name)` is simpler rather than `tag.public_send(name)`.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# content_tag(:p, 'Hello world!')
|
15
|
+
# content_tag(:br)
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# tag.p('Hello world!')
|
19
|
+
# tag.br
|
20
|
+
# content_tag(name, 'Hello world!')
|
21
|
+
class ContentTag < Cop
|
22
|
+
include RangeHelp
|
23
|
+
extend TargetRailsVersion
|
24
|
+
|
25
|
+
minimum_target_rails_version 5.1
|
26
|
+
|
27
|
+
MSG = 'Use `tag` instead of `content_tag`.'
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
return unless node.method?(:content_tag)
|
31
|
+
|
32
|
+
first_argument = node.first_argument
|
33
|
+
return unless first_argument
|
34
|
+
|
35
|
+
return if first_argument.variable? || first_argument.send_type? || first_argument.const_type?
|
36
|
+
|
37
|
+
add_offense(node)
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocorrect(node)
|
41
|
+
lambda do |corrector|
|
42
|
+
if method_name?(node.first_argument)
|
43
|
+
range = correction_range(node)
|
44
|
+
|
45
|
+
rest_args = node.arguments.drop(1)
|
46
|
+
replacement = "tag.#{node.first_argument.value.to_s.underscore}(#{rest_args.map(&:source).join(', ')})"
|
47
|
+
|
48
|
+
corrector.replace(range, replacement)
|
49
|
+
else
|
50
|
+
corrector.replace(node.loc.selector, 'tag')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def method_name?(node)
|
58
|
+
return false unless node.str_type? || node.sym_type?
|
59
|
+
|
60
|
+
/^[a-zA-Z_][a-zA-Z_\-0-9]*$/.match?(node.value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def correction_range(node)
|
64
|
+
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -70,9 +70,7 @@ module RuboCop
|
|
70
70
|
parent = node.parent
|
71
71
|
|
72
72
|
if create_table_with_block?(parent)
|
73
|
-
if parent.body.nil? || !time_columns_included?(parent.body)
|
74
|
-
add_offense(parent)
|
75
|
-
end
|
73
|
+
add_offense(parent) if parent.body.nil? || !time_columns_included?(parent.body)
|
76
74
|
elsif create_table_with_timestamps_proc?(node)
|
77
75
|
# nothing to do
|
78
76
|
else
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# This cop looks for uses of `default_scope`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# default_scope -> { where(hidden: false) }
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# scope :published, -> { where(hidden: false) }
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# def self.default_scope
|
17
|
+
# where(hidden: false)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# def self.published
|
22
|
+
# where(hidden: false)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
class DefaultScope < Cop
|
26
|
+
MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
|
27
|
+
|
28
|
+
def_node_matcher :method_call?, <<~PATTERN
|
29
|
+
(send nil? :default_scope ...)
|
30
|
+
PATTERN
|
31
|
+
|
32
|
+
def_node_matcher :class_method_definition?, <<~PATTERN
|
33
|
+
(defs _ :default_scope args ...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def_node_matcher :eigenclass_method_definition?, <<~PATTERN
|
37
|
+
(sclass (self) $(def :default_scope args ...))
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
add_offense(node, location: :selector) if method_call?(node)
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_defs(node)
|
45
|
+
add_offense(node, location: :name) if class_method_definition?(node)
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_sclass(node)
|
49
|
+
eigenclass_method_definition?(node) { |default_scope| add_offense(default_scope, location: :name) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -71,9 +71,7 @@ module RuboCop
|
|
71
71
|
delegation = ["delegate :#{node.body.method_name}",
|
72
72
|
"to: :#{node.body.receiver.method_name}"]
|
73
73
|
|
74
|
-
if node.method?(prefixed_method_name(node.body))
|
75
|
-
delegation << ['prefix: true']
|
76
|
-
end
|
74
|
+
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
|
77
75
|
|
78
76
|
lambda do |corrector|
|
79
77
|
corrector.replace(node.source_range, delegation.join(', '))
|
@@ -124,7 +122,7 @@ module RuboCop
|
|
124
122
|
end
|
125
123
|
|
126
124
|
def private_or_protected_inline(line)
|
127
|
-
processed_source[line - 1].strip
|
125
|
+
processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
|
128
126
|
end
|
129
127
|
end
|
130
128
|
end
|
@@ -10,37 +10,41 @@ module RuboCop
|
|
10
10
|
# @example
|
11
11
|
# # bad
|
12
12
|
# User.find_by_name(name)
|
13
|
-
#
|
14
|
-
# # bad
|
15
13
|
# User.find_by_name_and_email(name)
|
16
|
-
#
|
17
|
-
# # bad
|
18
14
|
# User.find_by_email!(name)
|
19
15
|
#
|
20
16
|
# # good
|
21
17
|
# User.find_by(name: name)
|
18
|
+
# User.find_by(name: name, email: email)
|
19
|
+
# User.find_by!(email: email)
|
20
|
+
#
|
21
|
+
# @example AllowedMethods: find_by_sql
|
22
|
+
# # bad
|
23
|
+
# User.find_by_query(users_query)
|
22
24
|
#
|
23
25
|
# # good
|
24
|
-
# User.
|
26
|
+
# User.find_by_sql(users_sql)
|
27
|
+
#
|
28
|
+
# @example AllowedReceivers: Gem::Specification
|
29
|
+
# # bad
|
30
|
+
# Specification.find_by_name('backend').gem_dir
|
25
31
|
#
|
26
32
|
# # good
|
27
|
-
#
|
33
|
+
# Gem::Specification.find_by_name('backend').gem_dir
|
28
34
|
class DynamicFindBy < Cop
|
29
35
|
MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
|
30
36
|
METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
|
31
37
|
|
32
38
|
def on_send(node)
|
33
|
-
|
34
|
-
|
35
|
-
return if whitelist.include?(method_name)
|
39
|
+
return if allowed_invocation?(node)
|
36
40
|
|
41
|
+
method_name = node.method_name
|
37
42
|
static_name = static_method_name(method_name)
|
38
|
-
|
39
43
|
return unless static_name
|
40
44
|
|
41
45
|
add_offense(node,
|
42
46
|
message: format(MSG, static_name: static_name,
|
43
|
-
method:
|
47
|
+
method: method_name))
|
44
48
|
end
|
45
49
|
alias on_csend on_send
|
46
50
|
|
@@ -57,6 +61,31 @@ module RuboCop
|
|
57
61
|
|
58
62
|
private
|
59
63
|
|
64
|
+
def allowed_invocation?(node)
|
65
|
+
allowed_method?(node) || allowed_receiver?(node) ||
|
66
|
+
whitelisted?(node)
|
67
|
+
end
|
68
|
+
|
69
|
+
def allowed_method?(node)
|
70
|
+
return unless cop_config['AllowedMethods']
|
71
|
+
|
72
|
+
cop_config['AllowedMethods'].include?(node.method_name.to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
def allowed_receiver?(node)
|
76
|
+
return unless cop_config['AllowedReceivers'] && node.receiver
|
77
|
+
|
78
|
+
cop_config['AllowedReceivers'].include?(node.receiver.source)
|
79
|
+
end
|
80
|
+
|
81
|
+
# config option `WhiteList` will be deprecated soon
|
82
|
+
def whitelisted?(node)
|
83
|
+
whitelist_config = cop_config['Whitelist']
|
84
|
+
return unless whitelist_config
|
85
|
+
|
86
|
+
whitelist_config.include?(node.method_name.to_s)
|
87
|
+
end
|
88
|
+
|
60
89
|
def autocorrect_method_name(corrector, node)
|
61
90
|
corrector.replace(node.loc.selector,
|
62
91
|
static_method_name(node.method_name.to_s))
|
@@ -68,10 +97,6 @@ module RuboCop
|
|
68
97
|
end
|
69
98
|
end
|
70
99
|
|
71
|
-
def whitelist
|
72
|
-
cop_config['Whitelist']
|
73
|
-
end
|
74
|
-
|
75
100
|
def column_keywords(method)
|
76
101
|
keyword_string = method.to_s[METHOD_PATTERN, 1]
|
77
102
|
keyword_string.split('_and_').map { |keyword| "#{keyword}: " }
|