rubocop-rails 2.33.4 → 2.34.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/config/default.yml +23 -1
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +3 -1
- data/lib/rubocop/cop/rails/duplicate_scope.rb +2 -2
- data/lib/rubocop/cop/rails/env.rb +57 -0
- data/lib/rubocop/cop/rails/environment_comparison.rb +56 -48
- data/lib/rubocop/cop/rails/exit.rb +7 -4
- data/lib/rubocop/cop/rails/find_by_or_assignment_memoization.rb +26 -8
- 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/inverse_of.rb +7 -0
- data/lib/rubocop/cop/rails/output_safety.rb +3 -1
- data/lib/rubocop/cop/rails/presence.rb +52 -18
- data/lib/rubocop/cop/rails/redirect_back_or_to.rb +89 -0
- data/lib/rubocop/cop/rails_cops.rb +3 -0
- data/lib/rubocop/rails/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffddf417706a66433be596f397b807c824a2593bea0a5175907be9d9be318970
|
|
4
|
+
data.tar.gz: 40672bac8ab7a1961102c3f5306526fa98a360987c7095128313dd5bae7c5641
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16ba2fc1926397b8b5c413c207a9f45e80a30b53e1e73b215e18abf38218e3aea3ecd2090cc4858adfe4a9d32ebb527b83db57acb38cff48510d4eaf18dcdd9c
|
|
7
|
+
data.tar.gz: 6541216ccafd3cd5287517989ea8a49dcb4b547ca98d0c363f58ac5a11ebbe6452329ea0964357e7f9777a5224d05d271b6bd21ee31f0d0bffcb37dfaa26dbd0
|
data/config/default.yml
CHANGED
|
@@ -408,7 +408,7 @@ Rails/DuplicateAssociation:
|
|
|
408
408
|
VersionChanged: '2.18'
|
|
409
409
|
|
|
410
410
|
Rails/DuplicateScope:
|
|
411
|
-
Description: 'Multiple scopes share this same
|
|
411
|
+
Description: 'Multiple scopes share this same expression.'
|
|
412
412
|
Enabled: pending
|
|
413
413
|
Severity: warning
|
|
414
414
|
VersionAdded: '2.14'
|
|
@@ -468,6 +468,11 @@ Rails/EnumUniqueness:
|
|
|
468
468
|
Include:
|
|
469
469
|
- '**/app/models/**/*.rb'
|
|
470
470
|
|
|
471
|
+
Rails/Env:
|
|
472
|
+
Description: 'Use Feature Flags or config instead of `Rails.env`.'
|
|
473
|
+
Enabled: false
|
|
474
|
+
VersionAdded: '2.34'
|
|
475
|
+
|
|
471
476
|
Rails/EnvLocal:
|
|
472
477
|
Description: 'Use `Rails.env.local?` instead of `Rails.env.development? || Rails.env.test?`.'
|
|
473
478
|
Enabled: pending
|
|
@@ -609,6 +614,14 @@ Rails/HttpStatus:
|
|
|
609
614
|
- numeric
|
|
610
615
|
- symbolic
|
|
611
616
|
|
|
617
|
+
Rails/HttpStatusNameConsistency:
|
|
618
|
+
Description: 'Enforces consistency by using the current HTTP status names.'
|
|
619
|
+
Enabled: pending
|
|
620
|
+
Severity: warning
|
|
621
|
+
VersionAdded: '2.34'
|
|
622
|
+
Include:
|
|
623
|
+
- '**/app/controllers/**/*.rb'
|
|
624
|
+
|
|
612
625
|
Rails/I18nLazyLookup:
|
|
613
626
|
Description: 'Checks for places where I18n "lazy" lookup can be used.'
|
|
614
627
|
StyleGuide: 'https://rails.rubystyle.guide/#lazy-lookup'
|
|
@@ -831,6 +844,7 @@ Rails/Presence:
|
|
|
831
844
|
Description: 'Checks code that can be written more easily using `Object#presence` defined by Active Support.'
|
|
832
845
|
Enabled: true
|
|
833
846
|
VersionAdded: '0.52'
|
|
847
|
+
VersionChanged: '2.34'
|
|
834
848
|
|
|
835
849
|
Rails/Present:
|
|
836
850
|
Description: 'Enforces use of `present?`.'
|
|
@@ -867,6 +881,14 @@ Rails/ReadWriteAttribute:
|
|
|
867
881
|
Include:
|
|
868
882
|
- '**/app/models/**/*.rb'
|
|
869
883
|
|
|
884
|
+
Rails/RedirectBackOrTo:
|
|
885
|
+
Description: >-
|
|
886
|
+
Use `redirect_back_or_to` instead of `redirect_back` with
|
|
887
|
+
`fallback_location` option.
|
|
888
|
+
Enabled: pending
|
|
889
|
+
Severity: warning
|
|
890
|
+
VersionAdded: '2.34'
|
|
891
|
+
|
|
870
892
|
Rails/RedundantActiveRecordAllMethod:
|
|
871
893
|
Description: Detect redundant `all` used as a receiver for Active Record query methods.
|
|
872
894
|
StyleGuide: 'https://rails.rubystyle.guide/#redundant-all'
|
|
@@ -98,7 +98,9 @@ module RuboCop
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def use_redirect_to?(context)
|
|
101
|
-
context.right_siblings.
|
|
101
|
+
context.right_siblings.any? do |sibling|
|
|
102
|
+
next unless sibling.is_a?(AST::Node)
|
|
103
|
+
|
|
102
104
|
# Unwrap `return redirect_to :index`
|
|
103
105
|
sibling = sibling.children.first if sibling.return_type? && sibling.children.one?
|
|
104
106
|
sibling.send_type? && sibling.method?(:redirect_to)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RuboCop
|
|
4
4
|
module Cop
|
|
5
5
|
module Rails
|
|
6
|
-
# Checks for multiple scopes in a model that have the same
|
|
6
|
+
# Checks for multiple scopes in a model that have the same expression. This
|
|
7
7
|
# often means you copy/pasted a scope, updated the name, and forgot to change the condition.
|
|
8
8
|
#
|
|
9
9
|
# @example
|
|
@@ -19,7 +19,7 @@ module RuboCop
|
|
|
19
19
|
class DuplicateScope < Base
|
|
20
20
|
include ClassSendNodeHelper
|
|
21
21
|
|
|
22
|
-
MSG = 'Multiple scopes share this same
|
|
22
|
+
MSG = 'Multiple scopes share this same expression.'
|
|
23
23
|
|
|
24
24
|
def_node_matcher :scope, <<~PATTERN
|
|
25
25
|
(send nil? :scope _ $...)
|
|
@@ -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&.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
|
|
@@ -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
|
|
@@ -8,6 +8,9 @@ module RuboCop
|
|
|
8
8
|
# It is common to see code that attempts to memoize `find_by` result by `||=`,
|
|
9
9
|
# but `find_by` may return `nil`, in which case it is not memoized as intended.
|
|
10
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
|
+
#
|
|
11
14
|
# @safety
|
|
12
15
|
# This cop is unsafe because detected `find_by` may not be Active Record's method,
|
|
13
16
|
# or the code may have a different purpose than memoization.
|
|
@@ -56,14 +59,16 @@ module RuboCop
|
|
|
56
59
|
|
|
57
60
|
# When a method body contains only memoization, the correction can be more succinct.
|
|
58
61
|
def on_def(node)
|
|
59
|
-
find_by_or_assignment_memoization(node.body) do |
|
|
62
|
+
find_by_or_assignment_memoization(node.body) do |variable_name, find_by|
|
|
63
|
+
next if instance_variable_assigned?(variable_name)
|
|
64
|
+
|
|
60
65
|
add_offense(node.body) do |corrector|
|
|
61
66
|
corrector.replace(
|
|
62
67
|
node.body,
|
|
63
68
|
<<~RUBY.rstrip
|
|
64
|
-
return #{
|
|
69
|
+
return #{variable_name} if defined?(#{variable_name})
|
|
65
70
|
|
|
66
|
-
#{
|
|
71
|
+
#{variable_name} = #{find_by.source}
|
|
67
72
|
RUBY
|
|
68
73
|
)
|
|
69
74
|
|
|
@@ -74,17 +79,18 @@ module RuboCop
|
|
|
74
79
|
|
|
75
80
|
def on_send(node)
|
|
76
81
|
assignment_node = node.parent
|
|
77
|
-
|
|
78
|
-
|
|
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)
|
|
79
85
|
|
|
80
86
|
add_offense(assignment_node) do |corrector|
|
|
81
87
|
corrector.replace(
|
|
82
88
|
assignment_node,
|
|
83
89
|
<<~RUBY.rstrip
|
|
84
|
-
if defined?(#{
|
|
85
|
-
#{
|
|
90
|
+
if defined?(#{variable_name})
|
|
91
|
+
#{variable_name}
|
|
86
92
|
else
|
|
87
|
-
#{
|
|
93
|
+
#{variable_name} = #{find_by.source}
|
|
88
94
|
end
|
|
89
95
|
RUBY
|
|
90
96
|
)
|
|
@@ -94,6 +100,18 @@ module RuboCop
|
|
|
94
100
|
|
|
95
101
|
private
|
|
96
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
|
+
|
|
97
115
|
def correct_to_regular_method_definition(corrector, node)
|
|
98
116
|
range = node.loc.assignment.join(node.body.source_range.begin)
|
|
99
117
|
|
|
@@ -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
|
|
@@ -178,6 +178,7 @@ module RuboCop
|
|
|
178
178
|
(pair (sym :inverse_of) nil)
|
|
179
179
|
PATTERN
|
|
180
180
|
|
|
181
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
181
182
|
def on_send(node)
|
|
182
183
|
recv, arguments = association_recv_arguments(node)
|
|
183
184
|
return unless arguments
|
|
@@ -192,9 +193,11 @@ module RuboCop
|
|
|
192
193
|
return unless scope?(arguments) || options_requiring_inverse_of?(options)
|
|
193
194
|
|
|
194
195
|
return if options_contain_inverse_of?(options)
|
|
196
|
+
return if dynamic_options?(options) && options.none? { |opt| inverse_of_nil_option?(opt) }
|
|
195
197
|
|
|
196
198
|
add_offense(node.loc.selector, message: message(options))
|
|
197
199
|
end
|
|
200
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
198
201
|
|
|
199
202
|
def scope?(arguments)
|
|
200
203
|
!ignore_scopes? && arguments.any?(&:block_type?)
|
|
@@ -216,6 +219,10 @@ module RuboCop
|
|
|
216
219
|
end
|
|
217
220
|
end
|
|
218
221
|
|
|
222
|
+
def dynamic_options?(options)
|
|
223
|
+
options.any? { |option| option&.kwsplat_type? }
|
|
224
|
+
end
|
|
225
|
+
|
|
219
226
|
def options_contain_inverse_of?(options)
|
|
220
227
|
options.any? { |opt| inverse_of_option?(opt) }
|
|
221
228
|
end
|
|
@@ -84,7 +84,9 @@ module RuboCop
|
|
|
84
84
|
private
|
|
85
85
|
|
|
86
86
|
def non_interpolated_string?(node)
|
|
87
|
-
|
|
87
|
+
return false unless (receiver = node.receiver)
|
|
88
|
+
|
|
89
|
+
receiver.str_type? || (receiver.dstr_type? && receiver.children.all?(&:str_type?))
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def looks_like_rails_html_safe?(node)
|
|
@@ -37,6 +37,22 @@ module RuboCop
|
|
|
37
37
|
#
|
|
38
38
|
# # good
|
|
39
39
|
# a.presence || b
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# # bad
|
|
43
|
+
# a.present? ? a.foo : nil
|
|
44
|
+
#
|
|
45
|
+
# # bad
|
|
46
|
+
# !a.present? ? nil : a.foo
|
|
47
|
+
#
|
|
48
|
+
# # bad
|
|
49
|
+
# a.blank? ? nil : a.foo
|
|
50
|
+
#
|
|
51
|
+
# # bad
|
|
52
|
+
# !a.blank? ? a.foo : nil
|
|
53
|
+
#
|
|
54
|
+
# # good
|
|
55
|
+
# a.presence&.foo
|
|
40
56
|
class Presence < Base
|
|
41
57
|
include RangeHelp
|
|
42
58
|
extend AutoCorrector
|
|
@@ -46,29 +62,29 @@ module RuboCop
|
|
|
46
62
|
def_node_matcher :redundant_receiver_and_other, <<~PATTERN
|
|
47
63
|
{
|
|
48
64
|
(if
|
|
49
|
-
(send $_recv :present?)
|
|
50
|
-
_recv
|
|
65
|
+
{(send $_recv :blank?) (send (send $_recv :present?) :!)}
|
|
51
66
|
$!begin
|
|
67
|
+
_recv
|
|
52
68
|
)
|
|
53
69
|
(if
|
|
54
|
-
(send $_recv :blank?)
|
|
55
|
-
$!begin
|
|
70
|
+
{(send $_recv :present?) (send (send $_recv :blank?) :!)}
|
|
56
71
|
_recv
|
|
72
|
+
$!begin
|
|
57
73
|
)
|
|
58
74
|
}
|
|
59
75
|
PATTERN
|
|
60
76
|
|
|
61
|
-
def_node_matcher :
|
|
77
|
+
def_node_matcher :redundant_receiver_and_chain, <<~PATTERN
|
|
62
78
|
{
|
|
63
79
|
(if
|
|
64
|
-
(send (send $_recv :present?) :!)
|
|
65
|
-
|
|
66
|
-
_recv
|
|
80
|
+
{(send $_recv :blank?) (send (send $_recv :present?) :!)}
|
|
81
|
+
{nil? nil_type?}
|
|
82
|
+
$(send _recv ...)
|
|
67
83
|
)
|
|
68
84
|
(if
|
|
69
|
-
(send (send $_recv :blank?) :!)
|
|
70
|
-
_recv
|
|
71
|
-
|
|
85
|
+
{(send $_recv :present?) (send (send $_recv :blank?) :!)}
|
|
86
|
+
$(send _recv ...)
|
|
87
|
+
{nil? nil_type?}
|
|
72
88
|
)
|
|
73
89
|
}
|
|
74
90
|
PATTERN
|
|
@@ -82,18 +98,26 @@ module RuboCop
|
|
|
82
98
|
register_offense(node, receiver, other)
|
|
83
99
|
end
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
return if
|
|
101
|
+
redundant_receiver_and_chain(node) do |receiver, chain|
|
|
102
|
+
return if ignore_chain_node?(chain) || receiver.nil?
|
|
87
103
|
|
|
88
|
-
|
|
104
|
+
register_chain_offense(node, receiver, chain)
|
|
89
105
|
end
|
|
90
106
|
end
|
|
91
107
|
|
|
92
108
|
private
|
|
93
109
|
|
|
94
110
|
def register_offense(node, receiver, other)
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
replacement = replacement(receiver, other, node.left_sibling)
|
|
112
|
+
add_offense(node, message: message(node, replacement)) do |corrector|
|
|
113
|
+
corrector.replace(node, replacement)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def register_chain_offense(node, receiver, chain)
|
|
118
|
+
replacement = chain_replacement(receiver, chain, node.left_sibling)
|
|
119
|
+
add_offense(node, message: message(node, replacement)) do |corrector|
|
|
120
|
+
corrector.replace(node, replacement)
|
|
97
121
|
end
|
|
98
122
|
end
|
|
99
123
|
|
|
@@ -105,8 +129,12 @@ module RuboCop
|
|
|
105
129
|
node&.type?(:if, :rescue, :while)
|
|
106
130
|
end
|
|
107
131
|
|
|
108
|
-
def
|
|
109
|
-
|
|
132
|
+
def ignore_chain_node?(node)
|
|
133
|
+
node.method?('[]') || node.arithmetic_operation?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def message(node, replacement)
|
|
137
|
+
prefer = replacement.gsub(/^\s*|\n/, '')
|
|
110
138
|
current = current(node).gsub(/^\s*|\n/, '')
|
|
111
139
|
format(MSG, prefer: prefer, current: current)
|
|
112
140
|
end
|
|
@@ -146,6 +174,12 @@ module RuboCop
|
|
|
146
174
|
def method_range(node)
|
|
147
175
|
range_between(node.source_range.begin_pos, node.first_argument.source_range.begin_pos - 1)
|
|
148
176
|
end
|
|
177
|
+
|
|
178
|
+
def chain_replacement(receiver, chain, left_sibling)
|
|
179
|
+
replaced = "#{receiver.source}.presence&.#{chain.method_name}"
|
|
180
|
+
replaced += "(#{chain.arguments.map(&:source).join(', ')})" if chain.arguments?
|
|
181
|
+
left_sibling ? "(#{replaced})" : replaced
|
|
182
|
+
end
|
|
149
183
|
end
|
|
150
184
|
end
|
|
151
185
|
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Rails
|
|
6
|
+
# Checks for uses of `redirect_back(fallback_location: ...)` and
|
|
7
|
+
# suggests using `redirect_back_or_to(...)` instead.
|
|
8
|
+
#
|
|
9
|
+
# `redirect_back(fallback_location: ...)` was soft deprecated in Rails 7.0 and
|
|
10
|
+
# `redirect_back_or_to` was introduced as a replacement.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# redirect_back(fallback_location: root_path)
|
|
15
|
+
#
|
|
16
|
+
# # good
|
|
17
|
+
# redirect_back_or_to(root_path)
|
|
18
|
+
#
|
|
19
|
+
# # bad
|
|
20
|
+
# redirect_back(fallback_location: root_path, allow_other_host: false)
|
|
21
|
+
#
|
|
22
|
+
# # good
|
|
23
|
+
# redirect_back_or_to(root_path, allow_other_host: false)
|
|
24
|
+
#
|
|
25
|
+
class RedirectBackOrTo < Base
|
|
26
|
+
extend AutoCorrector
|
|
27
|
+
extend TargetRailsVersion
|
|
28
|
+
|
|
29
|
+
minimum_target_rails_version 7.0
|
|
30
|
+
|
|
31
|
+
MSG = 'Use `redirect_back_or_to` instead of `redirect_back` with `:fallback_location` keyword argument.'
|
|
32
|
+
RESTRICT_ON_SEND = %i[redirect_back].freeze
|
|
33
|
+
|
|
34
|
+
def_node_matcher :redirect_back_with_fallback_location, <<~PATTERN
|
|
35
|
+
(send nil? :redirect_back
|
|
36
|
+
(hash <$(pair (sym :fallback_location) $_) ...>)
|
|
37
|
+
)
|
|
38
|
+
PATTERN
|
|
39
|
+
|
|
40
|
+
def on_send(node)
|
|
41
|
+
redirect_back_with_fallback_location(node) do |fallback_pair, fallback_value|
|
|
42
|
+
add_offense(node.loc.selector) do |corrector|
|
|
43
|
+
correct_redirect_back(corrector, node, fallback_pair, fallback_value)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def correct_redirect_back(corrector, node, fallback_pair, fallback_value)
|
|
51
|
+
corrector.replace(node.loc.selector, 'redirect_back_or_to')
|
|
52
|
+
|
|
53
|
+
hash_arg = node.first_argument
|
|
54
|
+
|
|
55
|
+
if hash_arg.pairs.one?
|
|
56
|
+
corrector.replace(hash_arg, fallback_value.source)
|
|
57
|
+
else
|
|
58
|
+
remove_fallback_location_pair(corrector, hash_arg, fallback_pair)
|
|
59
|
+
first_pair = hash_arg.pairs.find { |pair| pair != fallback_pair }
|
|
60
|
+
corrector.insert_before(first_pair, "#{fallback_value.source}, ")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def remove_fallback_location_pair(corrector, hash_node, fallback_pair)
|
|
65
|
+
pairs = hash_node.pairs
|
|
66
|
+
index = pairs.index(fallback_pair)
|
|
67
|
+
|
|
68
|
+
if pairs.one?
|
|
69
|
+
corrector.remove(fallback_pair)
|
|
70
|
+
elsif index.zero?
|
|
71
|
+
remove_first_pair(corrector, fallback_pair, pairs[1])
|
|
72
|
+
else
|
|
73
|
+
remove_non_first_pair(corrector, fallback_pair, pairs[index - 1])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def remove_first_pair(corrector, fallback_pair, next_pair)
|
|
78
|
+
range = fallback_pair.source_range.join(next_pair.source_range.begin)
|
|
79
|
+
corrector.remove(range)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def remove_non_first_pair(corrector, fallback_pair, prev_pair)
|
|
83
|
+
range = prev_pair.source_range.end.join(fallback_pair.source_range.end)
|
|
84
|
+
corrector.remove(range)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -50,6 +50,7 @@ require_relative 'rails/eager_evaluation_log_message'
|
|
|
50
50
|
require_relative 'rails/enum_hash'
|
|
51
51
|
require_relative 'rails/enum_syntax'
|
|
52
52
|
require_relative 'rails/enum_uniqueness'
|
|
53
|
+
require_relative 'rails/env'
|
|
53
54
|
require_relative 'rails/env_local'
|
|
54
55
|
require_relative 'rails/environment_comparison'
|
|
55
56
|
require_relative 'rails/environment_variable_access'
|
|
@@ -66,6 +67,7 @@ require_relative 'rails/has_many_or_has_one_dependent'
|
|
|
66
67
|
require_relative 'rails/helper_instance_variable'
|
|
67
68
|
require_relative 'rails/http_positional_arguments'
|
|
68
69
|
require_relative 'rails/http_status'
|
|
70
|
+
require_relative 'rails/http_status_name_consistency'
|
|
69
71
|
require_relative 'rails/i18n_lazy_lookup'
|
|
70
72
|
require_relative 'rails/i18n_locale_assignment'
|
|
71
73
|
require_relative 'rails/i18n_locale_texts'
|
|
@@ -102,6 +104,7 @@ require_relative 'rails/redundant_foreign_key'
|
|
|
102
104
|
require_relative 'rails/redundant_presence_validation_on_belongs_to'
|
|
103
105
|
require_relative 'rails/redundant_receiver_in_with_options'
|
|
104
106
|
require_relative 'rails/redundant_travel_back'
|
|
107
|
+
require_relative 'rails/redirect_back_or_to'
|
|
105
108
|
require_relative 'rails/reflection_class_name'
|
|
106
109
|
require_relative 'rails/refute_methods'
|
|
107
110
|
require_relative 'rails/relative_date_constant'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.34.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bozhidar Batsov
|
|
@@ -156,6 +156,7 @@ files:
|
|
|
156
156
|
- lib/rubocop/cop/rails/enum_hash.rb
|
|
157
157
|
- lib/rubocop/cop/rails/enum_syntax.rb
|
|
158
158
|
- lib/rubocop/cop/rails/enum_uniqueness.rb
|
|
159
|
+
- lib/rubocop/cop/rails/env.rb
|
|
159
160
|
- lib/rubocop/cop/rails/env_local.rb
|
|
160
161
|
- lib/rubocop/cop/rails/environment_comparison.rb
|
|
161
162
|
- lib/rubocop/cop/rails/environment_variable_access.rb
|
|
@@ -172,6 +173,7 @@ files:
|
|
|
172
173
|
- lib/rubocop/cop/rails/helper_instance_variable.rb
|
|
173
174
|
- lib/rubocop/cop/rails/http_positional_arguments.rb
|
|
174
175
|
- lib/rubocop/cop/rails/http_status.rb
|
|
176
|
+
- lib/rubocop/cop/rails/http_status_name_consistency.rb
|
|
175
177
|
- lib/rubocop/cop/rails/i18n_lazy_lookup.rb
|
|
176
178
|
- lib/rubocop/cop/rails/i18n_locale_assignment.rb
|
|
177
179
|
- lib/rubocop/cop/rails/i18n_locale_texts.rb
|
|
@@ -202,6 +204,7 @@ files:
|
|
|
202
204
|
- lib/rubocop/cop/rails/present.rb
|
|
203
205
|
- lib/rubocop/cop/rails/rake_environment.rb
|
|
204
206
|
- lib/rubocop/cop/rails/read_write_attribute.rb
|
|
207
|
+
- lib/rubocop/cop/rails/redirect_back_or_to.rb
|
|
205
208
|
- lib/rubocop/cop/rails/redundant_active_record_all_method.rb
|
|
206
209
|
- lib/rubocop/cop/rails/redundant_allow_nil.rb
|
|
207
210
|
- lib/rubocop/cop/rails/redundant_foreign_key.rb
|
|
@@ -266,7 +269,7 @@ metadata:
|
|
|
266
269
|
homepage_uri: https://docs.rubocop.org/rubocop-rails/
|
|
267
270
|
changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
|
|
268
271
|
source_code_uri: https://github.com/rubocop/rubocop-rails/
|
|
269
|
-
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.
|
|
272
|
+
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.34/
|
|
270
273
|
bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
|
|
271
274
|
rubygems_mfa_required: 'true'
|
|
272
275
|
default_lint_roller_plugin: RuboCop::Rails::Plugin
|
|
@@ -284,7 +287,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
284
287
|
- !ruby/object:Gem::Version
|
|
285
288
|
version: '0'
|
|
286
289
|
requirements: []
|
|
287
|
-
rubygems_version:
|
|
290
|
+
rubygems_version: 4.0.0.dev
|
|
288
291
|
specification_version: 4
|
|
289
292
|
summary: Automatic Rails code style checking tool.
|
|
290
293
|
test_files: []
|