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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddb8c79abf97afc451393bd9a309ac575a99057e1143d4e4632fa84568173e8b
4
- data.tar.gz: b6cf48b796104788e4d2302f379328071dc5b5042f3cd6923eb625a3ec19128d
3
+ metadata.gz: ffddf417706a66433be596f397b807c824a2593bea0a5175907be9d9be318970
4
+ data.tar.gz: 40672bac8ab7a1961102c3f5306526fa98a360987c7095128313dd5bae7c5641
5
5
  SHA512:
6
- metadata.gz: 2618f426daf94c51e8fceb989eca50055dae465b334a5ef83d5dc940959a123e1f3a2337246db86defd3f189970a1848a207b5b6b8afcf9917149877bd3fee3e
7
- data.tar.gz: 164f84eeab842a88a39131e8c8949584487ac22893604de862ecc28cd984d6df2e5e244ab82e57295dbe9abba0c71830c018412325eccaa3a6ff376f3517ecfc
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 where clause.'
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.compact.any? do |sibling|
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 `where` clause. This
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 where clause.'
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 `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
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 :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
28
- (send
29
- (send (const {nil? cbase} :Rails) :env)
30
- {:== :!=}
31
- $str
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 :comparing_str_env_with_rails_env_on_rhs?, <<~PATTERN
36
- (send
37
- $str
38
- {:== :!=}
39
- (send (const {nil? cbase} :Rails) :env)
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
- if (env_node = comparing_str_env_with_rails_env_on_lhs?(node) ||
65
- comparing_str_env_with_rails_env_on_rhs?(node))
66
- env, = *env_node
67
- bang = node.method?(:!=) ? '!' : ''
68
- message = format(MSG, bang: bang, env: env, source: node.source)
69
-
70
- add_offense(node, message: message) do |corrector|
71
- autocorrect(corrector, node)
72
- end
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
- autocorrect(corrector, node)
96
+ replacement = build_predicate_method(node)
97
+ corrector.replace(node, replacement)
79
98
  end
80
99
  end
81
100
 
82
- private
101
+ def build_predicate_method(node)
102
+ bang = node.method?(:!=) ? '!' : ''
83
103
 
84
- def autocorrect(corrector, node)
85
- replacement = build_predicate_method(node)
104
+ receiver, argument = extract_receiver_and_argument(node)
105
+ receiver = receiver.receiver if receiver.method?(:to_sym)
86
106
 
87
- corrector.replace(node, replacement)
107
+ "#{bang}#{receiver.source}.#{argument.value}?"
88
108
  end
89
109
 
90
- def build_predicate_method(node)
110
+ def extract_receiver_and_argument(node)
91
111
  if rails_env_on_lhs?(node)
92
- build_predicate_method_for_rails_env_on_lhs(node)
112
+ [node.receiver, node.first_argument]
93
113
  else
94
- build_predicate_method_for_rails_env_on_rhs(node)
114
+ [node.first_argument, node.receiver]
95
115
  end
96
116
  end
97
117
 
98
118
  def rails_env_on_lhs?(node)
99
- comparing_str_env_with_rails_env_on_lhs?(node) || comparing_sym_env_with_rails_env_on_lhs?(node)
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 `exit` in Rails applications.'
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
- add_offense(node.loc.selector) if offending_node?(node)
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 |varible_name, find_by|
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 #{varible_name} if defined?(#{varible_name})
69
+ return #{variable_name} if defined?(#{variable_name})
65
70
 
66
- #{varible_name} = #{find_by.source}
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
- find_by_or_assignment_memoization(assignment_node) do |varible_name, find_by|
78
- next if assignment_node.each_ancestor(:if).any?
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?(#{varible_name})
85
- #{varible_name}
90
+ if defined?(#{variable_name})
91
+ #{variable_name}
86
92
  else
87
- #{varible_name} = #{find_by.source}
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 inherits `ActionView::Helpers::FormBuilder`,
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
- # class MyFormBuilder < ActionView::Helpers::FormBuilder
32
- # @template.do_something
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 inherit_form_builder?(node)
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? || inherit_form_builder?(node)
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 inherit_form_builder?(node)
58
- node.each_ancestor(:class) do |class_node|
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
- node.receiver&.str_type? && !node.receiver.dstr_type?
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 :redundant_negative_receiver_and_other, <<~PATTERN
77
+ def_node_matcher :redundant_receiver_and_chain, <<~PATTERN
62
78
  {
63
79
  (if
64
- (send (send $_recv :present?) :!)
65
- $!begin
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
- $!begin
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
- redundant_negative_receiver_and_other(node) do |receiver, other|
86
- return if ignore_other_node?(other) || receiver.nil?
101
+ redundant_receiver_and_chain(node) do |receiver, chain|
102
+ return if ignore_chain_node?(chain) || receiver.nil?
87
103
 
88
- register_offense(node, receiver, other)
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
- add_offense(node, message: message(node, receiver, other)) do |corrector|
96
- corrector.replace(node, replacement(receiver, other, node.left_sibling))
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 message(node, receiver, other)
109
- prefer = replacement(receiver, other, node.left_sibling).gsub(/^\s*|\n/, '')
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'
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Rails
5
5
  # This module holds the RuboCop Rails version information.
6
6
  module Version
7
- STRING = '2.33.4'
7
+ STRING = '2.34.0'
8
8
 
9
9
  def self.document_version
10
10
  STRING.match('\d+\.\d+').to_s
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.33.4
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.33/
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: 3.6.9
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: []