rubocop-rails 2.30.2 → 2.34.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 +2 -1
- data/config/default.yml +110 -50
- data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/database_type_resolvable.rb +2 -2
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +6 -1
- data/lib/rubocop/cop/mixin/index_method.rb +6 -1
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +4 -2
- data/lib/rubocop/cop/rails/arel_star.rb +5 -5
- data/lib/rubocop/cop/rails/delegate.rb +7 -4
- data/lib/rubocop/cop/rails/duplicate_association.rb +1 -1
- data/lib/rubocop/cop/rails/duplicate_scope.rb +2 -2
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +1 -3
- data/lib/rubocop/cop/rails/env.rb +57 -0
- data/lib/rubocop/cop/rails/env_local.rb +50 -26
- data/lib/rubocop/cop/rails/environment_comparison.rb +56 -48
- data/lib/rubocop/cop/rails/exit.rb +7 -4
- data/lib/rubocop/cop/rails/file_path.rb +2 -2
- data/lib/rubocop/cop/rails/find_by.rb +1 -1
- data/lib/rubocop/cop/rails/find_by_or_assignment_memoization.rb +124 -0
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +16 -17
- data/lib/rubocop/cop/rails/http_status_name_consistency.rb +80 -0
- data/lib/rubocop/cop/rails/index_by.rb +9 -0
- data/lib/rubocop/cop/rails/index_with.rb +14 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +7 -0
- data/lib/rubocop/cop/rails/order_arguments.rb +84 -0
- data/lib/rubocop/cop/rails/output.rb +4 -2
- data/lib/rubocop/cop/rails/output_safety.rb +3 -1
- data/lib/rubocop/cop/rails/pluck.rb +13 -4
- data/lib/rubocop/cop/rails/presence.rb +67 -18
- data/lib/rubocop/cop/rails/read_write_attribute.rb +1 -1
- data/lib/rubocop/cop/rails/redirect_back_or_to.rb +99 -0
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +3 -3
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +7 -2
- data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
- data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
- data/lib/rubocop/cop/rails/reversible_migration.rb +2 -1
- data/lib/rubocop/cop/rails/save_bang.rb +4 -4
- data/lib/rubocop/cop/rails/schema_comment.rb +1 -1
- data/lib/rubocop/cop/rails/select_map.rb +12 -4
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +1 -1
- data/lib/rubocop/cop/rails/time_zone.rb +3 -1
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +5 -2
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +1 -1
- data/lib/rubocop/cop/rails/where_exists.rb +5 -5
- data/lib/rubocop/cop/rails_cops.rb +5 -0
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop-rails.rb +0 -1
- metadata +13 -8
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Checks for usage of `Rails.env` which can be replaced with Feature Flags
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
#
|
|
10
|
+
# # bad
|
|
11
|
+
# Rails.env.production? || Rails.env.local?
|
|
12
|
+
#
|
|
13
|
+
# # good
|
|
14
|
+
# if FeatureFlag.enabled?(:new_feature)
|
|
15
|
+
# # new feature code
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
class Env < Base
|
|
19
|
+
MSG = 'Use Feature Flags or config instead of `Rails.env`.'
|
|
20
|
+
RESTRICT_ON_SEND = %i[env].freeze
|
|
21
|
+
# This allow list is derived from:
|
|
22
|
+
# (Rails.env.methods - Object.instance_methods).select { |m| m.to_s.end_with?('?') }
|
|
23
|
+
# and then removing the environment specific methods like development?, test?, production?, local?
|
|
24
|
+
ALLOWED_LIST = Set.new(
|
|
25
|
+
%i[
|
|
26
|
+
unicode_normalized?
|
|
27
|
+
exclude?
|
|
28
|
+
empty?
|
|
29
|
+
acts_like_string?
|
|
30
|
+
include?
|
|
31
|
+
is_utf8?
|
|
32
|
+
casecmp?
|
|
33
|
+
match?
|
|
34
|
+
starts_with?
|
|
35
|
+
ends_with?
|
|
36
|
+
start_with?
|
|
37
|
+
end_with?
|
|
38
|
+
valid_encoding?
|
|
39
|
+
ascii_only?
|
|
40
|
+
between?
|
|
41
|
+
]
|
|
42
|
+
).freeze
|
|
43
|
+
|
|
44
|
+
def on_send(node)
|
|
45
|
+
return unless node.receiver&.const_name == 'Rails'
|
|
46
|
+
|
|
47
|
+
parent = node.parent
|
|
48
|
+
return unless parent.respond_to?(:predicate_method?) && parent.predicate_method?
|
|
49
|
+
|
|
50
|
+
return if ALLOWED_LIST.include?(parent.method_name)
|
|
51
|
+
|
|
52
|
+
add_offense(parent)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -24,45 +24,69 @@ module RuboCop
|
|
|
24
24
|
|
|
25
25
|
minimum_target_rails_version 7.1
|
|
26
26
|
|
|
27
|
-
# @!method
|
|
28
|
-
def_node_matcher :
|
|
29
|
-
(
|
|
30
|
-
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
|
31
|
-
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
|
32
|
-
)
|
|
27
|
+
# @!method rails_env_local?(node)
|
|
28
|
+
def_node_matcher :rails_env_local?, <<~PATTERN
|
|
29
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
|
33
30
|
PATTERN
|
|
34
31
|
|
|
35
|
-
# @!method
|
|
36
|
-
def_node_matcher :
|
|
37
|
-
(
|
|
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
|
-
)
|
|
32
|
+
# @!method not_rails_env_local?(node)
|
|
33
|
+
def_node_matcher :not_rails_env_local?, <<~PATTERN
|
|
34
|
+
(send #rails_env_local? :!)
|
|
45
35
|
PATTERN
|
|
46
36
|
|
|
47
37
|
def on_or(node)
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
lhs, rhs = *node.children
|
|
39
|
+
return unless rails_env_local?(rhs)
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
nodes = [rhs]
|
|
42
|
+
|
|
43
|
+
if rails_env_local?(lhs)
|
|
44
|
+
nodes << lhs
|
|
45
|
+
elsif lhs.or_type? && rails_env_local?(lhs.rhs)
|
|
46
|
+
nodes << lhs.rhs
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return unless environments(nodes).to_set == LOCAL_ENVIRONMENTS
|
|
50
|
+
|
|
51
|
+
range = offense_range(nodes)
|
|
52
|
+
add_offense(range) do |corrector|
|
|
53
|
+
corrector.replace(range, 'Rails.env.local?')
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def on_and(node)
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
lhs, rhs = *node.children
|
|
59
|
+
return unless not_rails_env_local?(rhs)
|
|
60
|
+
|
|
61
|
+
nodes = [rhs]
|
|
62
|
+
|
|
63
|
+
if not_rails_env_local?(lhs)
|
|
64
|
+
nodes << lhs
|
|
65
|
+
elsif lhs.operator_keyword? && not_rails_env_local?(lhs.rhs)
|
|
66
|
+
nodes << lhs.rhs
|
|
67
|
+
end
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
return unless environments(nodes).to_set == LOCAL_ENVIRONMENTS
|
|
70
|
+
|
|
71
|
+
range = offense_range(nodes)
|
|
72
|
+
add_offense(range, message: MSG_NEGATED) do |corrector|
|
|
73
|
+
corrector.replace(range, '!Rails.env.local?')
|
|
64
74
|
end
|
|
65
75
|
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def environments(nodes)
|
|
80
|
+
if nodes[0].method?(:!)
|
|
81
|
+
nodes.map { |node| node.receiver.method_name }
|
|
82
|
+
else
|
|
83
|
+
nodes.map(&:method_name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def offense_range(nodes)
|
|
88
|
+
nodes[1].source_range.begin.join(nodes[0].source_range.end)
|
|
89
|
+
end
|
|
66
90
|
end
|
|
67
91
|
end
|
|
68
92
|
end
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Rails
|
|
6
|
-
# Checks that Rails.env is compared using `.production?`-like
|
|
6
|
+
# Checks that `Rails.env` is compared using `.production?`-like
|
|
7
7
|
# methods instead of equality against a string or symbol.
|
|
8
8
|
#
|
|
9
9
|
# @example
|
|
10
10
|
# # bad
|
|
11
11
|
# Rails.env == 'production'
|
|
12
|
+
# Rails.env.to_sym == :production
|
|
12
13
|
#
|
|
13
14
|
# # bad, always returns false
|
|
14
15
|
# Rails.env == :test
|
|
@@ -18,26 +19,40 @@ module RuboCop
|
|
|
18
19
|
class EnvironmentComparison < Base
|
|
19
20
|
extend AutoCorrector
|
|
20
21
|
|
|
21
|
-
MSG = 'Favor `%<
|
|
22
|
+
MSG = 'Favor `%<prefer>s` over `%<source>s`.'
|
|
22
23
|
|
|
23
24
|
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always evaluate to `false`.'
|
|
24
25
|
|
|
25
26
|
RESTRICT_ON_SEND = %i[== !=].freeze
|
|
26
27
|
|
|
27
|
-
def_node_matcher :
|
|
28
|
-
|
|
29
|
-
(send
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
def_node_matcher :comparing_env_with_rails_env_on_lhs?, <<~PATTERN
|
|
29
|
+
{
|
|
30
|
+
(send
|
|
31
|
+
(send (const {nil? cbase} :Rails) :env)
|
|
32
|
+
{:== :!=}
|
|
33
|
+
$str
|
|
34
|
+
)
|
|
35
|
+
(send
|
|
36
|
+
(send (send (const {nil? cbase} :Rails) :env) :to_sym)
|
|
37
|
+
{:== :!=}
|
|
38
|
+
$sym
|
|
39
|
+
)
|
|
40
|
+
}
|
|
33
41
|
PATTERN
|
|
34
42
|
|
|
35
|
-
def_node_matcher :
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
def_node_matcher :comparing_env_with_rails_env_on_rhs?, <<~PATTERN
|
|
44
|
+
{
|
|
45
|
+
(send
|
|
46
|
+
$str
|
|
47
|
+
{:== :!=}
|
|
48
|
+
(send (const {nil? cbase} :Rails) :env)
|
|
49
|
+
)
|
|
50
|
+
(send
|
|
51
|
+
$sym
|
|
52
|
+
{:== :!=}
|
|
53
|
+
(send (send (const {nil? cbase} :Rails) :env) :to_sym)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
41
56
|
PATTERN
|
|
42
57
|
|
|
43
58
|
def_node_matcher :comparing_sym_env_with_rails_env_on_lhs?, <<~PATTERN
|
|
@@ -56,59 +71,52 @@ module RuboCop
|
|
|
56
71
|
)
|
|
57
72
|
PATTERN
|
|
58
73
|
|
|
59
|
-
def_node_matcher :content, <<~PATTERN
|
|
60
|
-
({str sym} $_)
|
|
61
|
-
PATTERN
|
|
62
|
-
|
|
63
74
|
def on_send(node)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
check_env_comparison_with_rails_env(node)
|
|
76
|
+
check_sym_env_comparison_with_rails_env(node)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def check_env_comparison_with_rails_env(node)
|
|
82
|
+
return unless comparing_env_with_rails_env_on_lhs?(node) || comparing_env_with_rails_env_on_rhs?(node)
|
|
83
|
+
|
|
84
|
+
replacement = build_predicate_method(node)
|
|
85
|
+
message = format(MSG, prefer: replacement, source: node.source)
|
|
86
|
+
|
|
87
|
+
add_offense(node, message: message) do |corrector|
|
|
88
|
+
corrector.replace(node, replacement)
|
|
73
89
|
end
|
|
90
|
+
end
|
|
74
91
|
|
|
92
|
+
def check_sym_env_comparison_with_rails_env(node)
|
|
75
93
|
return unless comparing_sym_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_rhs?(node)
|
|
76
94
|
|
|
77
95
|
add_offense(node, message: SYM_MSG) do |corrector|
|
|
78
|
-
|
|
96
|
+
replacement = build_predicate_method(node)
|
|
97
|
+
corrector.replace(node, replacement)
|
|
79
98
|
end
|
|
80
99
|
end
|
|
81
100
|
|
|
82
|
-
|
|
101
|
+
def build_predicate_method(node)
|
|
102
|
+
bang = node.method?(:!=) ? '!' : ''
|
|
83
103
|
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
receiver, argument = extract_receiver_and_argument(node)
|
|
105
|
+
receiver = receiver.receiver if receiver.method?(:to_sym)
|
|
86
106
|
|
|
87
|
-
|
|
107
|
+
"#{bang}#{receiver.source}.#{argument.value}?"
|
|
88
108
|
end
|
|
89
109
|
|
|
90
|
-
def
|
|
110
|
+
def extract_receiver_and_argument(node)
|
|
91
111
|
if rails_env_on_lhs?(node)
|
|
92
|
-
|
|
112
|
+
[node.receiver, node.first_argument]
|
|
93
113
|
else
|
|
94
|
-
|
|
114
|
+
[node.first_argument, node.receiver]
|
|
95
115
|
end
|
|
96
116
|
end
|
|
97
117
|
|
|
98
118
|
def rails_env_on_lhs?(node)
|
|
99
|
-
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def build_predicate_method_for_rails_env_on_lhs(node)
|
|
103
|
-
bang = node.method?(:!=) ? '!' : ''
|
|
104
|
-
|
|
105
|
-
"#{bang}#{node.receiver.source}.#{content(node.first_argument)}?"
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def build_predicate_method_for_rails_env_on_rhs(node)
|
|
109
|
-
bang = node.method?(:!=) ? '!' : ''
|
|
110
|
-
|
|
111
|
-
"#{bang}#{node.first_argument.source}.#{content(node.receiver)}?"
|
|
119
|
+
comparing_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_lhs?(node)
|
|
112
120
|
end
|
|
113
121
|
end
|
|
114
122
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Rails
|
|
6
|
-
# Enforces that `exit` calls are not used within a rails app.
|
|
6
|
+
# Enforces that `exit` and `abort` calls are not used within a rails app.
|
|
7
7
|
# Valid options are instead to raise an error, break, return, or some
|
|
8
8
|
# other form of stopping execution of current request.
|
|
9
9
|
#
|
|
@@ -26,12 +26,15 @@ module RuboCop
|
|
|
26
26
|
class Exit < Base
|
|
27
27
|
include ConfigurableEnforcedStyle
|
|
28
28
|
|
|
29
|
-
MSG = 'Do not use `
|
|
30
|
-
RESTRICT_ON_SEND = %i[exit exit!].freeze
|
|
29
|
+
MSG = 'Do not use `%<current>s` in Rails applications.'
|
|
30
|
+
RESTRICT_ON_SEND = %i[exit exit! abort].freeze
|
|
31
31
|
EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
|
|
32
32
|
|
|
33
33
|
def on_send(node)
|
|
34
|
-
|
|
34
|
+
return unless offending_node?(node)
|
|
35
|
+
|
|
36
|
+
message = format(MSG, current: node.method_name)
|
|
37
|
+
add_offense(node.loc.selector, message: message)
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
private
|
|
@@ -190,7 +190,7 @@ module RuboCop
|
|
|
190
190
|
else
|
|
191
191
|
replace_with_rails_root_join(corrector, rails_root_node, argument_source)
|
|
192
192
|
end
|
|
193
|
-
node.children[rails_root_index + 1..].each { |child| corrector.remove(child) }
|
|
193
|
+
node.children[(rails_root_index + 1)..].each { |child| corrector.remove(child) }
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
def autocorrect_extension_after_rails_root_join_in_dstr(corrector, node, rails_root_index, extension_node)
|
|
@@ -281,7 +281,7 @@ module RuboCop
|
|
|
281
281
|
end
|
|
282
282
|
|
|
283
283
|
def extract_rails_root_join_argument_source(node, rails_root_index)
|
|
284
|
-
node.children[rails_root_index + 1..].map(&:source).join.delete_prefix(File::SEPARATOR)
|
|
284
|
+
node.children[(rails_root_index + 1)..].map(&:source).join.delete_prefix(File::SEPARATOR)
|
|
285
285
|
end
|
|
286
286
|
|
|
287
287
|
def extension_node?(node)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Avoid memoizing `find_by` results with `||=`.
|
|
7
|
+
#
|
|
8
|
+
# It is common to see code that attempts to memoize `find_by` result by `||=`,
|
|
9
|
+
# but `find_by` may return `nil`, in which case it is not memoized as intended.
|
|
10
|
+
#
|
|
11
|
+
# NOTE: Respecting the object shapes introduced in Ruby 3.2, instance variables used
|
|
12
|
+
# for memoization that are initialized at object creation are ignored.
|
|
13
|
+
#
|
|
14
|
+
# @safety
|
|
15
|
+
# This cop is unsafe because detected `find_by` may not be Active Record's method,
|
|
16
|
+
# or the code may have a different purpose than memoization.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# # bad - exclusively doing memoization
|
|
20
|
+
# def current_user
|
|
21
|
+
# @current_user ||= User.find_by(id: session[:user_id])
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# def current_user
|
|
26
|
+
# return @current_user if defined?(@current_user)
|
|
27
|
+
#
|
|
28
|
+
# @current_user = User.find_by(id: session[:user_id])
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # bad - method contains other code
|
|
32
|
+
# def current_user
|
|
33
|
+
# @current_user ||= User.find_by(id: session[:user_id])
|
|
34
|
+
# @current_user.do_something
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# # good
|
|
38
|
+
# def current_user
|
|
39
|
+
# if defined?(@current_user)
|
|
40
|
+
# @current_user
|
|
41
|
+
# else
|
|
42
|
+
# @current_user = User.find_by(id: session[:user_id])
|
|
43
|
+
# end
|
|
44
|
+
# @current_user.do_something
|
|
45
|
+
# end
|
|
46
|
+
class FindByOrAssignmentMemoization < Base
|
|
47
|
+
extend AutoCorrector
|
|
48
|
+
|
|
49
|
+
MSG = 'Avoid memoizing `find_by` results with `||=`.'
|
|
50
|
+
|
|
51
|
+
RESTRICT_ON_SEND = %i[find_by].freeze
|
|
52
|
+
|
|
53
|
+
def_node_matcher :find_by_or_assignment_memoization, <<~PATTERN
|
|
54
|
+
(or_asgn
|
|
55
|
+
(ivasgn $_)
|
|
56
|
+
$(send _ :find_by ...)
|
|
57
|
+
)
|
|
58
|
+
PATTERN
|
|
59
|
+
|
|
60
|
+
# When a method body contains only memoization, the correction can be more succinct.
|
|
61
|
+
def on_def(node)
|
|
62
|
+
find_by_or_assignment_memoization(node.body) do |variable_name, find_by|
|
|
63
|
+
next if instance_variable_assigned?(variable_name)
|
|
64
|
+
|
|
65
|
+
add_offense(node.body) do |corrector|
|
|
66
|
+
corrector.replace(
|
|
67
|
+
node.body,
|
|
68
|
+
<<~RUBY.rstrip
|
|
69
|
+
return #{variable_name} if defined?(#{variable_name})
|
|
70
|
+
|
|
71
|
+
#{variable_name} = #{find_by.source}
|
|
72
|
+
RUBY
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
correct_to_regular_method_definition(corrector, node) if node.endless?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def on_send(node)
|
|
81
|
+
assignment_node = node.parent
|
|
82
|
+
|
|
83
|
+
find_by_or_assignment_memoization(assignment_node) do |variable_name, find_by|
|
|
84
|
+
next if assignment_node.each_ancestor(:if).any? || instance_variable_assigned?(variable_name)
|
|
85
|
+
|
|
86
|
+
add_offense(assignment_node) do |corrector|
|
|
87
|
+
corrector.replace(
|
|
88
|
+
assignment_node,
|
|
89
|
+
<<~RUBY.rstrip
|
|
90
|
+
if defined?(#{variable_name})
|
|
91
|
+
#{variable_name}
|
|
92
|
+
else
|
|
93
|
+
#{variable_name} = #{find_by.source}
|
|
94
|
+
end
|
|
95
|
+
RUBY
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def instance_variable_assigned?(instance_variable_name)
|
|
104
|
+
initialize_methods.any? do |def_node|
|
|
105
|
+
def_node.each_descendant(:ivasgn).any? do |asgn_node|
|
|
106
|
+
asgn_node.name == instance_variable_name
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def initialize_methods
|
|
112
|
+
@initialize_methods ||= processed_source.ast.each_descendant(:def).select { |node| node.method?(:initialize) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def correct_to_regular_method_definition(corrector, node)
|
|
116
|
+
range = node.loc.assignment.join(node.body.source_range.begin)
|
|
117
|
+
|
|
118
|
+
corrector.replace(range, "\n")
|
|
119
|
+
corrector.insert_after(node, "\nend")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -13,7 +13,7 @@ module RuboCop
|
|
|
13
13
|
# variable, consider moving the behavior elsewhere, for
|
|
14
14
|
# example to a model, decorator or presenter.
|
|
15
15
|
#
|
|
16
|
-
# Provided that a class
|
|
16
|
+
# Provided that an instance variable belongs to a class,
|
|
17
17
|
# an offense will not be registered.
|
|
18
18
|
#
|
|
19
19
|
# @example
|
|
@@ -28,38 +28,37 @@ module RuboCop
|
|
|
28
28
|
# end
|
|
29
29
|
#
|
|
30
30
|
# # good
|
|
31
|
-
#
|
|
32
|
-
#
|
|
31
|
+
# module ButtonHelper
|
|
32
|
+
# class Welcome
|
|
33
|
+
# def initialize(text:)
|
|
34
|
+
# @text = text
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# def welcome(**)
|
|
39
|
+
# render Welcome.new(**)
|
|
40
|
+
# end
|
|
33
41
|
# end
|
|
42
|
+
#
|
|
34
43
|
class HelperInstanceVariable < Base
|
|
35
44
|
MSG = 'Do not use instance variables in helpers.'
|
|
36
45
|
|
|
37
|
-
def_node_matcher :form_builder_class?, <<~PATTERN
|
|
38
|
-
(const
|
|
39
|
-
(const
|
|
40
|
-
(const {nil? cbase} :ActionView) :Helpers) :FormBuilder)
|
|
41
|
-
PATTERN
|
|
42
|
-
|
|
43
46
|
def on_ivar(node)
|
|
44
|
-
return if
|
|
47
|
+
return if instance_variable_belongs_to_class?(node)
|
|
45
48
|
|
|
46
49
|
add_offense(node)
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def on_ivasgn(node)
|
|
50
|
-
return if node.parent.or_asgn_type? ||
|
|
53
|
+
return if node.parent.or_asgn_type? || instance_variable_belongs_to_class?(node)
|
|
51
54
|
|
|
52
55
|
add_offense(node.loc.name)
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
private
|
|
56
59
|
|
|
57
|
-
def
|
|
58
|
-
node.each_ancestor(:class)
|
|
59
|
-
return true if form_builder_class?(class_node.parent_class)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
false
|
|
60
|
+
def instance_variable_belongs_to_class?(node)
|
|
61
|
+
node.each_ancestor(:class).any?
|
|
63
62
|
end
|
|
64
63
|
end
|
|
65
64
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Enforces consistency by using the current HTTP status names.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad
|
|
10
|
+
# render json: { error: "Invalid data" }, status: :unprocessable_entity
|
|
11
|
+
# head :payload_too_large
|
|
12
|
+
#
|
|
13
|
+
# # good
|
|
14
|
+
# render json: { error: "Invalid data" }, status: :unprocessable_content
|
|
15
|
+
# head :content_too_large
|
|
16
|
+
#
|
|
17
|
+
class HttpStatusNameConsistency < Base
|
|
18
|
+
extend AutoCorrector
|
|
19
|
+
|
|
20
|
+
requires_gem 'rack', '>= 3.1.0'
|
|
21
|
+
|
|
22
|
+
RESTRICT_ON_SEND = %i[render redirect_to head assert_response assert_redirected_to].freeze
|
|
23
|
+
|
|
24
|
+
PREFERRED_STATUSES = {
|
|
25
|
+
unprocessable_entity: :unprocessable_content,
|
|
26
|
+
payload_too_large: :content_too_large
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
MSG = 'Prefer `:%<preferred>s` over `:%<current>s`.'
|
|
30
|
+
|
|
31
|
+
def_node_matcher :status_method_call, <<~PATTERN
|
|
32
|
+
{
|
|
33
|
+
(send nil? {:render :redirect_to} _ $hash)
|
|
34
|
+
(send nil? {:render :redirect_to} $hash)
|
|
35
|
+
(send nil? {:head :assert_response} $_ ...)
|
|
36
|
+
(send nil? :assert_redirected_to _ $hash ...)
|
|
37
|
+
(send nil? :assert_redirected_to $hash ...)
|
|
38
|
+
}
|
|
39
|
+
PATTERN
|
|
40
|
+
|
|
41
|
+
def_node_matcher :status_hash_value, <<~PATTERN
|
|
42
|
+
(hash <(pair (sym :status) $_) ...>)
|
|
43
|
+
PATTERN
|
|
44
|
+
|
|
45
|
+
def on_send(node)
|
|
46
|
+
status_method_call(node) do |status_node|
|
|
47
|
+
if status_node.hash_type?
|
|
48
|
+
# Handle hash arguments like { status: :unprocessable_entity }
|
|
49
|
+
status_hash_value(status_node) do |status_value|
|
|
50
|
+
check_status_name_consistency(status_value)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
# Handle positional arguments like head :unprocessable_entity
|
|
54
|
+
check_status_name_consistency(status_node)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def check_status_name_consistency(node)
|
|
62
|
+
if node.sym_type? && PREFERRED_STATUSES.key?(node.value)
|
|
63
|
+
current_status = node.value
|
|
64
|
+
preferred_status = PREFERRED_STATUSES[current_status]
|
|
65
|
+
|
|
66
|
+
message = format(MSG, current: current_status, preferred: preferred_status)
|
|
67
|
+
|
|
68
|
+
add_offense(node, message: message) do |corrector|
|
|
69
|
+
corrector.replace(node, ":#{preferred_status}")
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
node.children.each do |child|
|
|
73
|
+
check_status_name_consistency(child) if child.is_a?(Parser::AST::Node)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -37,6 +37,9 @@ module RuboCop
|
|
|
37
37
|
(numblock
|
|
38
38
|
(call _ :to_h) $1
|
|
39
39
|
(array $_ (lvar :_1)))
|
|
40
|
+
(itblock
|
|
41
|
+
(call _ :to_h) $:it
|
|
42
|
+
(array $_ (lvar :it)))
|
|
40
43
|
}
|
|
41
44
|
PATTERN
|
|
42
45
|
|
|
@@ -50,6 +53,9 @@ module RuboCop
|
|
|
50
53
|
(numblock
|
|
51
54
|
(call _ {:map :collect}) $1
|
|
52
55
|
(array $_ (lvar :_1)))
|
|
56
|
+
(itblock
|
|
57
|
+
(call _ {:map :collect}) $:it
|
|
58
|
+
(array $_ (lvar :it)))
|
|
53
59
|
}
|
|
54
60
|
:to_h)
|
|
55
61
|
PATTERN
|
|
@@ -66,6 +72,9 @@ module RuboCop
|
|
|
66
72
|
(numblock
|
|
67
73
|
(call _ {:map :collect}) $1
|
|
68
74
|
(array $_ (lvar :_1)))
|
|
75
|
+
(itblock
|
|
76
|
+
(call _ {:map :collect}) $:it
|
|
77
|
+
(array $_ (lvar :it)))
|
|
69
78
|
}
|
|
70
79
|
)
|
|
71
80
|
PATTERN
|