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,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for Rails/IndexBy and Rails/IndexWith
|
6
|
+
module IndexMethod # rubocop:disable Metrics/ModuleLength
|
7
|
+
def on_block(node)
|
8
|
+
on_bad_each_with_object(node) do |*match|
|
9
|
+
handle_possible_offense(node, match, 'each_with_object')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_send(node)
|
14
|
+
on_bad_map_to_h(node) do |*match|
|
15
|
+
handle_possible_offense(node, match, 'map { ... }.to_h')
|
16
|
+
end
|
17
|
+
|
18
|
+
on_bad_hash_brackets_map(node) do |*match|
|
19
|
+
handle_possible_offense(node, match, 'Hash[map { ... }]')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_csend(node)
|
24
|
+
on_bad_map_to_h(node) do |*match|
|
25
|
+
handle_possible_offense(node, match, 'map { ... }.to_h')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def autocorrect(node)
|
30
|
+
lambda do |corrector|
|
31
|
+
correction = prepare_correction(node)
|
32
|
+
execute_correction(corrector, node, correction)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @abstract Implemented with `def_node_matcher`
|
39
|
+
def on_bad_each_with_object(_node)
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
42
|
+
|
43
|
+
# @abstract Implemented with `def_node_matcher`
|
44
|
+
def on_bad_map_to_h(_node)
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
# @abstract Implemented with `def_node_matcher`
|
49
|
+
def on_bad_hash_brackets_map(_node)
|
50
|
+
raise NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_possible_offense(node, match, match_desc)
|
54
|
+
captures = extract_captures(match)
|
55
|
+
|
56
|
+
return if captures.noop_transformation?
|
57
|
+
|
58
|
+
add_offense(
|
59
|
+
node,
|
60
|
+
message: "Prefer `#{new_method_name}` over `#{match_desc}`."
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_captures(match)
|
65
|
+
argname, body_expr = *match
|
66
|
+
Captures.new(argname, body_expr)
|
67
|
+
end
|
68
|
+
|
69
|
+
def new_method_name
|
70
|
+
raise NotImplementedError
|
71
|
+
end
|
72
|
+
|
73
|
+
def prepare_correction(node)
|
74
|
+
if (match = on_bad_each_with_object(node))
|
75
|
+
Autocorrection.from_each_with_object(node, match)
|
76
|
+
elsif (match = on_bad_map_to_h(node))
|
77
|
+
Autocorrection.from_map_to_h(node, match)
|
78
|
+
elsif (match = on_bad_hash_brackets_map(node))
|
79
|
+
Autocorrection.from_hash_brackets_map(node, match)
|
80
|
+
else
|
81
|
+
raise 'unreachable'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def execute_correction(corrector, node, correction)
|
86
|
+
correction.strip_prefix_and_suffix(node, corrector)
|
87
|
+
correction.set_new_method_name(new_method_name, corrector)
|
88
|
+
|
89
|
+
captures = extract_captures(correction.match)
|
90
|
+
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
91
|
+
correction.set_new_body_expression(
|
92
|
+
captures.transforming_body_expr,
|
93
|
+
corrector
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal helper class to hold match data
|
98
|
+
Captures = Struct.new(
|
99
|
+
:transformed_argname,
|
100
|
+
:transforming_body_expr
|
101
|
+
) do
|
102
|
+
def noop_transformation?
|
103
|
+
transforming_body_expr.lvar_type? &&
|
104
|
+
transforming_body_expr.children == [transformed_argname]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal helper class to hold autocorrect data
|
109
|
+
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
|
110
|
+
def self.from_each_with_object(node, match)
|
111
|
+
new(match, node, 0, 0)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.from_map_to_h(node, match)
|
115
|
+
strip_trailing_chars = 0
|
116
|
+
|
117
|
+
unless node.parent&.block_type?
|
118
|
+
map_range = node.children.first.source_range
|
119
|
+
node_range = node.source_range
|
120
|
+
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
121
|
+
end
|
122
|
+
|
123
|
+
new(match, node.children.first, 0, strip_trailing_chars)
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.from_hash_brackets_map(node, match)
|
127
|
+
new(match, node.children.last, 'Hash['.length, ']'.length)
|
128
|
+
end
|
129
|
+
|
130
|
+
def strip_prefix_and_suffix(node, corrector)
|
131
|
+
expression = node.loc.expression
|
132
|
+
corrector.remove_leading(expression, leading)
|
133
|
+
corrector.remove_trailing(expression, trailing)
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_new_method_name(new_method_name, corrector)
|
137
|
+
range = block_node.send_node.loc.selector
|
138
|
+
if (send_end = block_node.send_node.loc.end)
|
139
|
+
# If there are arguments (only true in the `each_with_object` case)
|
140
|
+
range = range.begin.join(send_end)
|
141
|
+
end
|
142
|
+
corrector.replace(range, new_method_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
def set_new_arg_name(transformed_argname, corrector)
|
146
|
+
corrector.replace(
|
147
|
+
block_node.arguments.loc.expression,
|
148
|
+
"|#{transformed_argname}|"
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def set_new_body_expression(transforming_body_expr, corrector)
|
153
|
+
corrector.replace(
|
154
|
+
block_node.body.loc.expression,
|
155
|
+
transforming_body_expr.loc.expression.source
|
156
|
+
)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,145 @@
|
|
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 = Hash[
|
48
|
+
CALLBACKS_IN_ORDER.map.with_index { |name, index| [name, index] }
|
49
|
+
].freeze
|
50
|
+
|
51
|
+
def on_class(class_node)
|
52
|
+
previous_index = -1
|
53
|
+
previous_callback = nil
|
54
|
+
|
55
|
+
defined_callbacks(class_node).each do |node|
|
56
|
+
callback = node.method_name
|
57
|
+
index = CALLBACKS_ORDER_MAP[callback]
|
58
|
+
|
59
|
+
if index < previous_index
|
60
|
+
message = format(MSG, current: callback,
|
61
|
+
previous: previous_callback)
|
62
|
+
add_offense(node, message: message)
|
63
|
+
end
|
64
|
+
previous_index = index
|
65
|
+
previous_callback = callback
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Autocorrect by swapping between two nodes autocorrecting them
|
70
|
+
def autocorrect(node)
|
71
|
+
previous = left_siblings_of(node).find do |sibling|
|
72
|
+
callback?(sibling)
|
73
|
+
end
|
74
|
+
|
75
|
+
current_range = source_range_with_comment(node)
|
76
|
+
previous_range = source_range_with_comment(previous)
|
77
|
+
|
78
|
+
lambda do |corrector|
|
79
|
+
corrector.insert_before(previous_range, current_range.source)
|
80
|
+
corrector.remove(current_range)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def defined_callbacks(class_node)
|
87
|
+
class_def = class_node.body
|
88
|
+
|
89
|
+
if class_def
|
90
|
+
class_def.each_child_node.select { |c| callback?(c) }
|
91
|
+
else
|
92
|
+
[]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def callback?(node)
|
97
|
+
node.send_type? && CALLBACKS_ORDER_MAP.key?(node.method_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
def left_siblings_of(node)
|
101
|
+
siblings_of(node)[0, node.sibling_index]
|
102
|
+
end
|
103
|
+
|
104
|
+
def siblings_of(node)
|
105
|
+
node.parent.children
|
106
|
+
end
|
107
|
+
|
108
|
+
def source_range_with_comment(node)
|
109
|
+
begin_pos = begin_pos_with_comment(node)
|
110
|
+
end_pos = end_position_for(node)
|
111
|
+
|
112
|
+
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
113
|
+
end
|
114
|
+
|
115
|
+
def end_position_for(node)
|
116
|
+
end_line = buffer.line_for_position(node.loc.expression.end_pos)
|
117
|
+
buffer.line_range(end_line).end_pos
|
118
|
+
end
|
119
|
+
|
120
|
+
def begin_pos_with_comment(node)
|
121
|
+
annotation_line = node.first_line - 1
|
122
|
+
first_comment = nil
|
123
|
+
|
124
|
+
processed_source.comments_before_line(annotation_line)
|
125
|
+
.reverse_each do |comment|
|
126
|
+
if comment.location.line == annotation_line
|
127
|
+
first_comment = comment
|
128
|
+
annotation_line -= 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
start_line_position(first_comment || node)
|
133
|
+
end
|
134
|
+
|
135
|
+
def start_line_position(node)
|
136
|
+
buffer.line_range(node.loc.line).begin_pos - 1
|
137
|
+
end
|
138
|
+
|
139
|
+
def buffer
|
140
|
+
processed_source.buffer
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
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}(#{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
|