rubocop-rails 2.19.1 → 2.30.3
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 +70 -16
- data/config/default.yml +173 -28
- data/lib/rubocop/cop/mixin/active_record_helper.rb +16 -4
- data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/database_type_resolvable.rb +66 -0
- data/lib/rubocop/cop/mixin/index_method.rb +68 -61
- data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +27 -2
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
- data/lib/rubocop/cop/rails/action_controller_test_case.rb +2 -2
- data/lib/rubocop/cop/rails/action_filter.rb +3 -0
- data/lib/rubocop/cop/rails/action_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_record_aliases.rb +2 -2
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +1 -5
- data/lib/rubocop/cop/rails/active_support_aliases.rb +6 -5
- data/lib/rubocop/cop/rails/active_support_on_load.rb +21 -1
- data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +1 -1
- data/lib/rubocop/cop/rails/application_record.rb +4 -0
- data/lib/rubocop/cop/rails/assert_not.rb +0 -1
- data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
- data/lib/rubocop/cop/rails/blank.rb +1 -1
- data/lib/rubocop/cop/rails/bulk_change_table.rb +19 -45
- data/lib/rubocop/cop/rails/compact_blank.rb +29 -8
- data/lib/rubocop/cop/rails/content_tag.rb +2 -2
- data/lib/rubocop/cop/rails/dangerous_column_names.rb +448 -0
- data/lib/rubocop/cop/rails/date.rb +14 -5
- data/lib/rubocop/cop/rails/delegate.rb +53 -7
- data/lib/rubocop/cop/rails/duplicate_association.rb +71 -10
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +3 -3
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +2 -2
- data/lib/rubocop/cop/rails/enum_hash.rb +31 -8
- data/lib/rubocop/cop/rails/enum_syntax.rb +130 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +29 -7
- data/lib/rubocop/cop/rails/env_local.rb +69 -0
- data/lib/rubocop/cop/rails/expanded_date_range.rb +1 -1
- data/lib/rubocop/cop/rails/file_path.rb +186 -18
- data/lib/rubocop/cop/rails/find_by.rb +3 -3
- data/lib/rubocop/cop/rails/find_by_id.rb +9 -23
- data/lib/rubocop/cop/rails/find_each.rb +1 -1
- data/lib/rubocop/cop/rails/freeze_time.rb +1 -1
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +1 -1
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +1 -1
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
- data/lib/rubocop/cop/rails/http_status.rb +16 -5
- data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +63 -13
- data/lib/rubocop/cop/rails/i18n_locale_texts.rb +5 -1
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +23 -3
- data/lib/rubocop/cop/rails/index_by.rb +28 -12
- data/lib/rubocop/cop/rails/index_with.rb +28 -12
- data/lib/rubocop/cop/rails/inquiry.rb +2 -1
- data/lib/rubocop/cop/rails/inverse_of.rb +1 -1
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +19 -10
- data/lib/rubocop/cop/rails/link_to_blank.rb +2 -2
- data/lib/rubocop/cop/rails/match_route.rb +1 -9
- data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +100 -6
- data/lib/rubocop/cop/rails/output.rb +3 -2
- data/lib/rubocop/cop/rails/pick.rb +10 -5
- data/lib/rubocop/cop/rails/pluck.rb +21 -1
- data/lib/rubocop/cop/rails/pluck_id.rb +2 -1
- data/lib/rubocop/cop/rails/pluck_in_where.rb +35 -13
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +30 -16
- data/lib/rubocop/cop/rails/presence.rb +1 -1
- data/lib/rubocop/cop/rails/present.rb +1 -3
- data/lib/rubocop/cop/rails/rake_environment.rb +22 -6
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +190 -0
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +16 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +2 -2
- data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
- data/lib/rubocop/cop/rails/refute_methods.rb +0 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
- data/lib/rubocop/cop/rails/render_plain_text.rb +6 -3
- data/lib/rubocop/cop/rails/request_referer.rb +1 -1
- data/lib/rubocop/cop/rails/response_parsed_body.rb +52 -10
- data/lib/rubocop/cop/rails/reversible_migration.rb +7 -5
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +58 -15
- data/lib/rubocop/cop/rails/save_bang.rb +22 -14
- data/lib/rubocop/cop/rails/schema_comment.rb +17 -10
- data/lib/rubocop/cop/rails/select_map.rb +79 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +9 -4
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -2
- data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
- data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +4 -5
- data/lib/rubocop/cop/rails/time_zone.rb +26 -11
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +40 -9
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +11 -26
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +17 -21
- data/lib/rubocop/cop/rails/unknown_env.rb +5 -1
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +6 -0
- data/lib/rubocop/cop/rails/unused_render_content.rb +67 -0
- data/lib/rubocop/cop/rails/validation.rb +9 -4
- data/lib/rubocop/cop/rails/where_equals.rb +29 -12
- data/lib/rubocop/cop/rails/where_exists.rb +9 -9
- data/lib/rubocop/cop/rails/where_missing.rb +6 -2
- data/lib/rubocop/cop/rails/where_not.rb +18 -11
- data/lib/rubocop/cop/rails/where_range.rb +203 -0
- data/lib/rubocop/cop/rails_cops.rb +11 -0
- data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
- data/lib/rubocop/rails/plugin.rb +48 -0
- data/lib/rubocop/rails/schema_loader/schema.rb +8 -7
- data/lib/rubocop/rails/schema_loader.rb +5 -15
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop/rails.rb +1 -8
- data/lib/rubocop-rails.rb +12 -4
- metadata +55 -11
- data/lib/rubocop/rails/inject.rb +0 -18
@@ -15,6 +15,9 @@ module RuboCop
|
|
15
15
|
# without using the `delegate` method will be a violation.
|
16
16
|
# When set to `false`, this case is legal.
|
17
17
|
#
|
18
|
+
# It is disabled for controllers in order to keep controller actions
|
19
|
+
# explicitly defined.
|
20
|
+
#
|
18
21
|
# @example
|
19
22
|
# # bad
|
20
23
|
# def bar
|
@@ -68,12 +71,13 @@ module RuboCop
|
|
68
71
|
|
69
72
|
def_node_matcher :delegate?, <<~PATTERN
|
70
73
|
(def _method_name _args
|
71
|
-
(send {(send nil? _) (self)} _ ...))
|
74
|
+
(send {(send nil? _) (self) (send (self) :class) ({cvar gvar ivar} _) (const _ _)} _ ...))
|
72
75
|
PATTERN
|
73
76
|
|
74
77
|
def on_def(node)
|
75
78
|
return unless trivial_delegate?(node)
|
76
79
|
return if private_or_protected_delegation(node)
|
80
|
+
return if module_function_declared?(node)
|
77
81
|
|
78
82
|
register_offense(node)
|
79
83
|
end
|
@@ -82,15 +86,40 @@ module RuboCop
|
|
82
86
|
|
83
87
|
def register_offense(node)
|
84
88
|
add_offense(node.loc.keyword) do |corrector|
|
85
|
-
|
89
|
+
receiver = determine_register_offense_receiver(node.body.receiver)
|
90
|
+
delegation = build_delegation(node, receiver)
|
86
91
|
|
87
|
-
|
92
|
+
corrector.replace(node, delegation)
|
93
|
+
end
|
94
|
+
end
|
88
95
|
|
89
|
-
|
90
|
-
|
96
|
+
def determine_register_offense_receiver(receiver)
|
97
|
+
case receiver.type
|
98
|
+
when :self
|
99
|
+
'self'
|
100
|
+
when :const
|
101
|
+
full_name = full_const_name(receiver)
|
102
|
+
full_name.include?('::') ? ":'#{full_name}'" : ":#{full_name}"
|
103
|
+
when :cvar, :gvar, :ivar
|
104
|
+
":#{receiver.source}"
|
105
|
+
else
|
106
|
+
":#{receiver.method_name}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_delegation(node, receiver)
|
111
|
+
delegation = ["delegate :#{node.body.method_name}", "to: #{receiver}"]
|
112
|
+
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
|
113
|
+
delegation.join(', ')
|
114
|
+
end
|
91
115
|
|
92
|
-
|
116
|
+
def full_const_name(node)
|
117
|
+
return unless node.const_type?
|
118
|
+
unless node.namespace
|
119
|
+
return node.absolute? ? "::#{node.source}" : node.source
|
93
120
|
end
|
121
|
+
|
122
|
+
"#{full_const_name(node.namespace)}::#{node.short_name}"
|
94
123
|
end
|
95
124
|
|
96
125
|
def trivial_delegate?(def_node)
|
@@ -120,13 +149,30 @@ module RuboCop
|
|
120
149
|
def prefixed_method_name(body)
|
121
150
|
return '' if body.receiver.self_type?
|
122
151
|
|
123
|
-
[body.receiver
|
152
|
+
[determine_prefixed_method_receiver_name(body.receiver), body.method_name].join('_').to_sym
|
153
|
+
end
|
154
|
+
|
155
|
+
def determine_prefixed_method_receiver_name(receiver)
|
156
|
+
case receiver.type
|
157
|
+
when :cvar, :gvar, :ivar
|
158
|
+
receiver.source
|
159
|
+
when :const
|
160
|
+
full_const_name(receiver)
|
161
|
+
else
|
162
|
+
receiver.method_name.to_s
|
163
|
+
end
|
124
164
|
end
|
125
165
|
|
126
166
|
def private_or_protected_delegation(node)
|
127
167
|
private_or_protected_inline(node) || node_visibility(node) != :public
|
128
168
|
end
|
129
169
|
|
170
|
+
def module_function_declared?(node)
|
171
|
+
node.each_ancestor(:module, :begin).any? do |ancestor|
|
172
|
+
ancestor.children.any? { |child| child.send_type? && child.method?(:module_function) }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
130
176
|
def private_or_protected_inline(node)
|
131
177
|
processed_source[node.first_line - 1].strip.match?(/\A(private )|(protected )/)
|
132
178
|
end
|
@@ -20,35 +20,96 @@ module RuboCop
|
|
20
20
|
# belongs_to :bar
|
21
21
|
# has_one :foo
|
22
22
|
#
|
23
|
+
# # bad
|
24
|
+
# has_many :foo, class_name: 'Foo'
|
25
|
+
# has_many :bar, class_name: 'Foo'
|
26
|
+
# has_one :baz
|
27
|
+
#
|
28
|
+
# # good
|
29
|
+
# has_many :bar, class_name: 'Foo'
|
30
|
+
# has_one :foo
|
31
|
+
#
|
23
32
|
class DuplicateAssociation < Base
|
24
33
|
include RangeHelp
|
25
34
|
extend AutoCorrector
|
26
35
|
include ClassSendNodeHelper
|
36
|
+
include ActiveRecordHelper
|
27
37
|
|
28
38
|
MSG = "Association `%<name>s` is defined multiple times. Don't repeat associations."
|
39
|
+
MSG_CLASS_NAME = "Association `class_name: %<name>s` is defined multiple times. Don't repeat associations."
|
29
40
|
|
30
41
|
def_node_matcher :association, <<~PATTERN
|
31
|
-
(send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_)
|
42
|
+
(send nil? {:belongs_to :has_one :has_many :has_and_belongs_to_many} ({sym str} $_) $...)
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def_node_matcher :class_name, <<~PATTERN
|
46
|
+
(hash (pair (sym :class_name) $_))
|
32
47
|
PATTERN
|
33
48
|
|
34
49
|
def on_class(class_node)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
50
|
+
return unless active_record?(class_node.parent_class)
|
51
|
+
|
52
|
+
association_nodes = association_nodes(class_node)
|
53
|
+
|
54
|
+
duplicated_association_name_nodes(association_nodes).each do |name, nodes|
|
55
|
+
register_offense(name, nodes, MSG)
|
56
|
+
end
|
57
|
+
|
58
|
+
duplicated_class_name_nodes(association_nodes).each do |class_name, nodes|
|
59
|
+
register_offense(class_name, nodes, MSG_CLASS_NAME)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def register_offense(name, nodes, message_template)
|
66
|
+
last_node = nodes.last
|
39
67
|
|
68
|
+
nodes.each_with_index do |node, index|
|
69
|
+
add_offense(node, message: format(message_template, name: name)) do |corrector|
|
70
|
+
if index.zero?
|
71
|
+
corrector.replace(node, last_node.source)
|
72
|
+
else
|
40
73
|
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
|
41
74
|
end
|
42
75
|
end
|
43
76
|
end
|
44
77
|
end
|
45
78
|
|
46
|
-
|
79
|
+
def association_nodes(class_node)
|
80
|
+
class_send_nodes(class_node).select do |node|
|
81
|
+
association(node)&.first
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def duplicated_association_name_nodes(association_nodes)
|
86
|
+
grouped_associations = association_nodes.group_by do |node|
|
87
|
+
association(node).first.to_sym
|
88
|
+
end
|
89
|
+
|
90
|
+
leave_duplicated_association(grouped_associations)
|
91
|
+
end
|
92
|
+
|
93
|
+
def duplicated_class_name_nodes(association_nodes)
|
94
|
+
filtered_nodes = association_nodes.reject { |node| node.method?(:belongs_to) }
|
95
|
+
grouped_associations = filtered_nodes.group_by do |node|
|
96
|
+
arguments = association(node).last
|
97
|
+
next unless arguments.count == 1
|
47
98
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
99
|
+
if (class_name = class_name(arguments.first))
|
100
|
+
class_name.source
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
grouped_associations.delete(nil)
|
105
|
+
|
106
|
+
leave_duplicated_association(grouped_associations)
|
107
|
+
end
|
108
|
+
|
109
|
+
def leave_duplicated_association(grouped_associations)
|
110
|
+
grouped_associations.select do |_, nodes|
|
111
|
+
nodes.length > 1
|
112
|
+
end
|
52
113
|
end
|
53
114
|
end
|
54
115
|
end
|
@@ -74,13 +74,13 @@ module RuboCop
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def allowed_method?(node)
|
77
|
-
return unless cop_config['AllowedMethods']
|
77
|
+
return false unless cop_config['AllowedMethods']
|
78
78
|
|
79
79
|
cop_config['AllowedMethods'].include?(node.method_name.to_s)
|
80
80
|
end
|
81
81
|
|
82
82
|
def allowed_receiver?(node)
|
83
|
-
return unless cop_config['AllowedReceivers'] && node.receiver
|
83
|
+
return false unless cop_config['AllowedReceivers'] && node.receiver
|
84
84
|
|
85
85
|
cop_config['AllowedReceivers'].include?(node.receiver.source)
|
86
86
|
end
|
@@ -88,7 +88,7 @@ module RuboCop
|
|
88
88
|
# config option `WhiteList` will be deprecated soon
|
89
89
|
def whitelisted?(node)
|
90
90
|
whitelist_config = cop_config['Whitelist']
|
91
|
-
return unless whitelist_config
|
91
|
+
return false unless whitelist_config
|
92
92
|
|
93
93
|
whitelist_config.include?(node.method_name.to_s)
|
94
94
|
end
|
@@ -14,10 +14,10 @@ module RuboCop
|
|
14
14
|
# when no output would be produced anyway.
|
15
15
|
#
|
16
16
|
# @example
|
17
|
-
# #bad
|
17
|
+
# # bad
|
18
18
|
# Rails.logger.debug "The time is #{Time.zone.now}."
|
19
19
|
#
|
20
|
-
# #good
|
20
|
+
# # good
|
21
21
|
# Rails.logger.debug { "The time is #{Time.zone.now}." }
|
22
22
|
#
|
23
23
|
class EagerEvaluationLogMessage < Base
|
@@ -12,6 +12,12 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# @example
|
14
14
|
# # bad
|
15
|
+
# enum :status, [:active, :archived]
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# enum :status, { active: 0, archived: 1 }
|
19
|
+
#
|
20
|
+
# # bad
|
15
21
|
# enum status: [:active, :archived]
|
16
22
|
#
|
17
23
|
# # good
|
@@ -23,7 +29,11 @@ module RuboCop
|
|
23
29
|
MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. Use hash syntax instead.'
|
24
30
|
RESTRICT_ON_SEND = %i[enum].freeze
|
25
31
|
|
26
|
-
def_node_matcher :
|
32
|
+
def_node_matcher :enum_with_array?, <<~PATTERN
|
33
|
+
(send nil? :enum $_ ${array} ...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def_node_matcher :enum_with_old_syntax?, <<~PATTERN
|
27
37
|
(send nil? :enum (hash $...))
|
28
38
|
PATTERN
|
29
39
|
|
@@ -32,17 +42,19 @@ module RuboCop
|
|
32
42
|
PATTERN
|
33
43
|
|
34
44
|
def on_send(node)
|
35
|
-
|
45
|
+
target_rails_version >= 7.0 && enum_with_array?(node) do |key, array|
|
46
|
+
add_offense(array, message: message(key)) do |corrector|
|
47
|
+
corrector.replace(array, build_hash(array))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
enum_with_old_syntax?(node) do |pairs|
|
36
52
|
pairs.each do |pair|
|
37
53
|
key, array = array_pair?(pair)
|
38
54
|
next unless key
|
39
55
|
|
40
|
-
add_offense(array, message:
|
41
|
-
|
42
|
-
"#{source(elem)} => #{index}"
|
43
|
-
end.join(', ')
|
44
|
-
|
45
|
-
corrector.replace(array, "{#{hash}}")
|
56
|
+
add_offense(array, message: message(key)) do |corrector|
|
57
|
+
corrector.replace(array, build_hash(array))
|
46
58
|
end
|
47
59
|
end
|
48
60
|
end
|
@@ -50,6 +62,10 @@ module RuboCop
|
|
50
62
|
|
51
63
|
private
|
52
64
|
|
65
|
+
def message(key)
|
66
|
+
format(MSG, enum: enum_name(key))
|
67
|
+
end
|
68
|
+
|
53
69
|
def enum_name(key)
|
54
70
|
case key.type
|
55
71
|
when :sym, :str
|
@@ -69,6 +85,13 @@ module RuboCop
|
|
69
85
|
elem.source
|
70
86
|
end
|
71
87
|
end
|
88
|
+
|
89
|
+
def build_hash(array)
|
90
|
+
hash = array.children.each_with_index.map do |elem, index|
|
91
|
+
"#{source(elem)} => #{index}"
|
92
|
+
end.join(', ')
|
93
|
+
"{#{hash}}"
|
94
|
+
end
|
72
95
|
end
|
73
96
|
end
|
74
97
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Looks for enums written with keyword arguments syntax.
|
7
|
+
#
|
8
|
+
# Defining enums with keyword arguments syntax is deprecated and will be removed in Rails 8.0.
|
9
|
+
# Positional arguments should be used instead:
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# enum status: { active: 0, archived: 1 }, _prefix: true
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# enum :status, { active: 0, archived: 1 }, prefix: true
|
17
|
+
#
|
18
|
+
class EnumSyntax < Base
|
19
|
+
extend AutoCorrector
|
20
|
+
extend TargetRubyVersion
|
21
|
+
extend TargetRailsVersion
|
22
|
+
|
23
|
+
minimum_target_ruby_version 3.0
|
24
|
+
minimum_target_rails_version 7.0
|
25
|
+
|
26
|
+
MSG = 'Enum defined with keyword arguments in `%<enum>s` enum declaration. Use positional arguments instead.'
|
27
|
+
MSG_OPTIONS = 'Enum defined with deprecated options in `%<enum>s` enum declaration. Remove the `_` prefix.'
|
28
|
+
RESTRICT_ON_SEND = %i[enum].freeze
|
29
|
+
|
30
|
+
# From https://github.com/rails/rails/blob/v7.2.1/activerecord/lib/active_record/enum.rb#L231
|
31
|
+
OPTION_NAMES = %w[prefix suffix scopes default instance_methods].freeze
|
32
|
+
UNDERSCORED_OPTION_NAMES = OPTION_NAMES.map { |option| "_#{option}" }.freeze
|
33
|
+
|
34
|
+
def_node_matcher :enum?, <<~PATTERN
|
35
|
+
(send nil? :enum (hash $...))
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def_node_matcher :enum_with_options?, <<~PATTERN
|
39
|
+
(send nil? :enum $_ ${array hash} $_)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def on_send(node)
|
43
|
+
check_and_correct_keyword_args(node)
|
44
|
+
check_enum_options(node)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def check_and_correct_keyword_args(node)
|
50
|
+
enum?(node) do |pairs|
|
51
|
+
pairs.each do |pair|
|
52
|
+
next if option_key?(pair)
|
53
|
+
|
54
|
+
correct_keyword_args(node, pair.key, pair.value, pairs[1..])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_enum_options(node)
|
60
|
+
enum_with_options?(node) do |key, _, options|
|
61
|
+
options.children.each do |option|
|
62
|
+
next unless option_key?(option)
|
63
|
+
|
64
|
+
add_offense(option.key, message: format(MSG_OPTIONS, enum: enum_name_value(key))) do |corrector|
|
65
|
+
corrector.replace(option.key, option.key.source.delete_prefix('_'))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def correct_keyword_args(node, key, values, options)
|
72
|
+
add_offense(values, message: format(MSG, enum: enum_name_value(key))) do |corrector|
|
73
|
+
# TODO: Multi-line autocorrect could be implemented in the future.
|
74
|
+
next if multiple_enum_definitions?(node)
|
75
|
+
|
76
|
+
preferred_syntax = "enum #{enum_name(key)}, #{values.source}#{correct_options(options)}"
|
77
|
+
|
78
|
+
corrector.replace(node, preferred_syntax)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def multiple_enum_definitions?(node)
|
83
|
+
keys = node.first_argument.keys.map { |key| key.source.delete_prefix('_') }
|
84
|
+
filterred_keys = keys.filter { |key| !OPTION_NAMES.include?(key) }
|
85
|
+
filterred_keys.size >= 2
|
86
|
+
end
|
87
|
+
|
88
|
+
def enum_name_value(key)
|
89
|
+
case key.type
|
90
|
+
when :sym, :str
|
91
|
+
key.value
|
92
|
+
else
|
93
|
+
key.source
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def enum_name(elem)
|
98
|
+
case elem.type
|
99
|
+
when :str
|
100
|
+
elem.value.dump
|
101
|
+
when :sym
|
102
|
+
elem.value.inspect
|
103
|
+
else
|
104
|
+
elem.source
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def option_key?(pair)
|
109
|
+
return false unless pair.respond_to?(:key)
|
110
|
+
|
111
|
+
UNDERSCORED_OPTION_NAMES.include?(pair.key.source)
|
112
|
+
end
|
113
|
+
|
114
|
+
def correct_options(options)
|
115
|
+
corrected_options = options.map do |pair|
|
116
|
+
name = if pair.key.source[0] == '_'
|
117
|
+
pair.key.source[1..]
|
118
|
+
else
|
119
|
+
pair.key.source
|
120
|
+
end
|
121
|
+
|
122
|
+
"#{name}: #{pair.value.source}"
|
123
|
+
end.join(', ')
|
124
|
+
|
125
|
+
", #{corrected_options}" unless corrected_options.empty?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -7,6 +7,18 @@ module RuboCop
|
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# # bad
|
10
|
+
# enum :status, { active: 0, archived: 0 }
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# enum :status, { active: 0, archived: 1 }
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# enum :status, [:active, :archived, :active]
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# enum :status, [:active, :archived]
|
20
|
+
#
|
21
|
+
# # bad
|
10
22
|
# enum status: { active: 0, archived: 0 }
|
11
23
|
#
|
12
24
|
# # good
|
@@ -24,6 +36,10 @@ module RuboCop
|
|
24
36
|
RESTRICT_ON_SEND = %i[enum].freeze
|
25
37
|
|
26
38
|
def_node_matcher :enum?, <<~PATTERN
|
39
|
+
(send nil? :enum $_ ${array hash} ...)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def_node_matcher :enum_with_old_syntax?, <<~PATTERN
|
27
43
|
(send nil? :enum (hash $...))
|
28
44
|
PATTERN
|
29
45
|
|
@@ -32,15 +48,17 @@ module RuboCop
|
|
32
48
|
PATTERN
|
33
49
|
|
34
50
|
def on_send(node)
|
35
|
-
enum?(node) do |
|
51
|
+
enum?(node) do |key, args|
|
52
|
+
consecutive_duplicates(args.values).each do |item|
|
53
|
+
add_offense(item, message: message(key, item))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
enum_with_old_syntax?(node) do |pairs|
|
36
58
|
pairs.each do |pair|
|
37
59
|
enum_values(pair) do |key, args|
|
38
|
-
|
39
|
-
|
40
|
-
next unless duplicates?(items)
|
41
|
-
|
42
|
-
consecutive_duplicates(items).each do |item|
|
43
|
-
add_offense(item, message: format(MSG, value: item.source, enum: enum_name(key)))
|
60
|
+
consecutive_duplicates(args.values).each do |item|
|
61
|
+
add_offense(item, message: message(key, item))
|
44
62
|
end
|
45
63
|
end
|
46
64
|
end
|
@@ -57,6 +75,10 @@ module RuboCop
|
|
57
75
|
key.source
|
58
76
|
end
|
59
77
|
end
|
78
|
+
|
79
|
+
def message(key, item)
|
80
|
+
format(MSG, value: item.source, enum: enum_name(key))
|
81
|
+
end
|
60
82
|
end
|
61
83
|
end
|
62
84
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks for usage of `Rails.env.development? || Rails.env.test?` which
|
7
|
+
# can be replaced with `Rails.env.local?`, introduced in Rails 7.1.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# Rails.env.development? || Rails.env.test?
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# Rails.env.local?
|
16
|
+
#
|
17
|
+
class EnvLocal < Base
|
18
|
+
extend AutoCorrector
|
19
|
+
extend TargetRailsVersion
|
20
|
+
|
21
|
+
MSG = 'Use `Rails.env.local?` instead.'
|
22
|
+
MSG_NEGATED = 'Use `!Rails.env.local?` instead.'
|
23
|
+
LOCAL_ENVIRONMENTS = %i[development? test?].to_set.freeze
|
24
|
+
|
25
|
+
minimum_target_rails_version 7.1
|
26
|
+
|
27
|
+
# @!method rails_env_local_or?(node)
|
28
|
+
def_node_matcher :rails_env_local_or?, <<~PATTERN
|
29
|
+
(or
|
30
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
31
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
32
|
+
)
|
33
|
+
PATTERN
|
34
|
+
|
35
|
+
# @!method rails_env_local_and?(node)
|
36
|
+
def_node_matcher :rails_env_local_and?, <<~PATTERN
|
37
|
+
(and
|
38
|
+
(send
|
39
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
40
|
+
:!)
|
41
|
+
(send
|
42
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
43
|
+
:!)
|
44
|
+
)
|
45
|
+
PATTERN
|
46
|
+
|
47
|
+
def on_or(node)
|
48
|
+
rails_env_local_or?(node) do |*environments|
|
49
|
+
next unless environments.to_set == LOCAL_ENVIRONMENTS
|
50
|
+
|
51
|
+
add_offense(node) do |corrector|
|
52
|
+
corrector.replace(node, 'Rails.env.local?')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_and(node)
|
58
|
+
rails_env_local_and?(node) do |*environments|
|
59
|
+
next unless environments.to_set == LOCAL_ENVIRONMENTS
|
60
|
+
|
61
|
+
add_offense(node, message: MSG_NEGATED) do |corrector|
|
62
|
+
corrector.replace(node, '!Rails.env.local?')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -51,7 +51,7 @@ module RuboCop
|
|
51
51
|
return if allow?(begin_node, end_node)
|
52
52
|
|
53
53
|
preferred_method = preferred_method(begin_node)
|
54
|
-
if begin_node.method?(:beginning_of_week) && begin_node.arguments.one?
|
54
|
+
if begin_node.method?(:beginning_of_week) && begin_node.arguments.one? && end_node.arguments.one?
|
55
55
|
return unless same_argument?(begin_node, end_node)
|
56
56
|
|
57
57
|
preferred_method << "(#{begin_node.first_argument.source})"
|