rubocop-rails 2.6.0 → 2.9.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/config/default.yml +189 -6
  4. data/lib/rubocop/cop/mixin/active_record_helper.rb +12 -3
  5. data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
  6. data/lib/rubocop/cop/mixin/index_method.rb +25 -11
  7. data/lib/rubocop/cop/rails/action_filter.rb +10 -14
  8. data/lib/rubocop/cop/rails/active_record_aliases.rb +13 -17
  9. data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +148 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +1 -1
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +12 -21
  12. data/lib/rubocop/cop/rails/after_commit_override.rb +91 -0
  13. data/lib/rubocop/cop/rails/application_controller.rb +3 -7
  14. data/lib/rubocop/cop/rails/application_job.rb +2 -1
  15. data/lib/rubocop/cop/rails/application_mailer.rb +2 -7
  16. data/lib/rubocop/cop/rails/application_record.rb +2 -7
  17. data/lib/rubocop/cop/rails/arel_star.rb +41 -0
  18. data/lib/rubocop/cop/rails/assert_not.rb +8 -10
  19. data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
  20. data/lib/rubocop/cop/rails/belongs_to.rb +9 -18
  21. data/lib/rubocop/cop/rails/blank.rb +27 -27
  22. data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -1
  23. data/lib/rubocop/cop/rails/content_tag.rb +20 -33
  24. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +2 -1
  25. data/lib/rubocop/cop/rails/date.rb +10 -11
  26. data/lib/rubocop/cop/rails/default_scope.rb +61 -0
  27. data/lib/rubocop/cop/rails/delegate.rb +10 -10
  28. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +7 -8
  29. data/lib/rubocop/cop/rails/dynamic_find_by.rb +13 -11
  30. data/lib/rubocop/cop/rails/enum_hash.rb +11 -10
  31. data/lib/rubocop/cop/rails/enum_uniqueness.rb +2 -1
  32. data/lib/rubocop/cop/rails/environment_comparison.rb +18 -14
  33. data/lib/rubocop/cop/rails/exit.rb +4 -10
  34. data/lib/rubocop/cop/rails/file_path.rb +5 -4
  35. data/lib/rubocop/cop/rails/find_by.rb +13 -13
  36. data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
  37. data/lib/rubocop/cop/rails/find_each.rb +16 -14
  38. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +3 -2
  39. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +4 -7
  40. data/lib/rubocop/cop/rails/helper_instance_variable.rb +4 -2
  41. data/lib/rubocop/cop/rails/http_positional_arguments.rb +25 -21
  42. data/lib/rubocop/cop/rails/http_status.rb +7 -9
  43. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +8 -6
  44. data/lib/rubocop/cop/rails/index_by.rb +11 -2
  45. data/lib/rubocop/cop/rails/index_with.rb +11 -2
  46. data/lib/rubocop/cop/rails/inquiry.rb +39 -0
  47. data/lib/rubocop/cop/rails/inverse_of.rb +3 -2
  48. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +17 -15
  49. data/lib/rubocop/cop/rails/link_to_blank.rb +20 -20
  50. data/lib/rubocop/cop/rails/mailer_name.rb +86 -0
  51. data/lib/rubocop/cop/rails/match_route.rb +120 -0
  52. data/lib/rubocop/cop/rails/negate_include.rb +41 -0
  53. data/lib/rubocop/cop/rails/not_null_column.rb +2 -1
  54. data/lib/rubocop/cop/rails/order_by_id.rb +52 -0
  55. data/lib/rubocop/cop/rails/output.rb +5 -2
  56. data/lib/rubocop/cop/rails/output_safety.rb +3 -2
  57. data/lib/rubocop/cop/rails/pick.rb +21 -15
  58. data/lib/rubocop/cop/rails/pluck.rb +56 -0
  59. data/lib/rubocop/cop/rails/pluck_id.rb +56 -0
  60. data/lib/rubocop/cop/rails/pluck_in_where.rb +70 -0
  61. data/lib/rubocop/cop/rails/pluralization_grammar.rb +10 -14
  62. data/lib/rubocop/cop/rails/presence.rb +12 -13
  63. data/lib/rubocop/cop/rails/present.rb +30 -24
  64. data/lib/rubocop/cop/rails/rake_environment.rb +9 -11
  65. data/lib/rubocop/cop/rails/read_write_attribute.rb +12 -11
  66. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +29 -31
  67. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +9 -12
  68. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +11 -10
  69. data/lib/rubocop/cop/rails/reflection_class_name.rb +4 -3
  70. data/lib/rubocop/cop/rails/refute_methods.rb +9 -10
  71. data/lib/rubocop/cop/rails/relative_date_constant.rb +20 -9
  72. data/lib/rubocop/cop/rails/render_inline.rb +41 -0
  73. data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
  74. data/lib/rubocop/cop/rails/request_referer.rb +7 -7
  75. data/lib/rubocop/cop/rails/reversible_migration.rb +82 -7
  76. data/lib/rubocop/cop/rails/safe_navigation.rb +12 -11
  77. data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +5 -10
  78. data/lib/rubocop/cop/rails/save_bang.rb +19 -22
  79. data/lib/rubocop/cop/rails/scope_args.rb +2 -1
  80. data/lib/rubocop/cop/rails/short_i18n.rb +74 -0
  81. data/lib/rubocop/cop/rails/skips_model_validations.rb +46 -11
  82. data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +82 -0
  83. data/lib/rubocop/cop/rails/time_zone.rb +22 -20
  84. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -10
  85. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +18 -8
  86. data/lib/rubocop/cop/rails/unknown_env.rb +15 -4
  87. data/lib/rubocop/cop/rails/validation.rb +15 -14
  88. data/lib/rubocop/cop/rails/where_equals.rb +94 -0
  89. data/lib/rubocop/cop/rails/where_exists.rb +126 -0
  90. data/lib/rubocop/cop/rails/where_not.rb +97 -0
  91. data/lib/rubocop/cop/rails_cops.rb +22 -0
  92. data/lib/rubocop/rails/schema_loader.rb +4 -4
  93. data/lib/rubocop/rails/schema_loader/schema.rb +5 -5
  94. data/lib/rubocop/rails/version.rb +5 -1
  95. metadata +37 -9
@@ -12,27 +12,30 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # User.all.find_each
15
- class FindEach < Cop
15
+ #
16
+ # @example IgnoredMethods: ['order']
17
+ # # good
18
+ # User.order(:foo).each
19
+ class FindEach < Base
20
+ extend AutoCorrector
21
+
16
22
  MSG = 'Use `find_each` instead of `each`.'
23
+ RESTRICT_ON_SEND = %i[each].freeze
17
24
 
18
25
  SCOPE_METHODS = %i[
19
26
  all eager_load includes joins left_joins left_outer_joins not preload
20
27
  references unscoped where
21
28
  ].freeze
22
- IGNORED_METHODS = %i[order limit select].freeze
23
29
 
24
30
  def on_send(node)
25
- return unless node.receiver&.send_type? &&
26
- node.method?(:each)
27
-
31
+ return unless node.receiver&.send_type?
28
32
  return unless SCOPE_METHODS.include?(node.receiver.method_name)
29
- return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
30
-
31
- add_offense(node, location: :selector)
32
- end
33
+ return if method_chain(node).any? { |m| ignored?(m) }
33
34
 
34
- def autocorrect(node)
35
- ->(corrector) { corrector.replace(node.loc.selector, 'find_each') }
35
+ range = node.loc.selector
36
+ add_offense(range) do |corrector|
37
+ corrector.replace(range, 'find_each')
38
+ end
36
39
  end
37
40
 
38
41
  private
@@ -41,9 +44,8 @@ module RuboCop
41
44
  node.each_node(:send).map(&:method_name)
42
45
  end
43
46
 
44
- def ignored_by_find_each?(relation_method)
45
- # Active Record's #find_each ignores various extra parameters
46
- IGNORED_METHODS.include?(relation_method)
47
+ def ignored?(relation_method)
48
+ cop_config['IgnoredMethods'].include?(relation_method)
47
49
  end
48
50
  end
49
51
  end
@@ -11,13 +11,14 @@ module RuboCop
11
11
  #
12
12
  # # good
13
13
  # # has_many :ingredients, through: :recipe_ingredients
14
- class HasAndBelongsToMany < Cop
14
+ class HasAndBelongsToMany < Base
15
15
  MSG = 'Prefer `has_many :through` to `has_and_belongs_to_many`.'
16
+ RESTRICT_ON_SEND = %i[has_and_belongs_to_many].freeze
16
17
 
17
18
  def on_send(node)
18
19
  return unless node.command?(:has_and_belongs_to_many)
19
20
 
20
- add_offense(node, location: :selector)
21
+ add_offense(node.loc.selector)
21
22
  end
22
23
  end
23
24
  end
@@ -20,8 +20,9 @@ module RuboCop
20
20
  # has_one :avatar, dependent: :destroy
21
21
  # has_many :patients, through: :appointments
22
22
  # end
23
- class HasManyOrHasOneDependent < Cop
23
+ class HasManyOrHasOneDependent < Base
24
24
  MSG = 'Specify a `:dependent` option.'
25
+ RESTRICT_ON_SEND = %i[has_many has_one].freeze
25
26
 
26
27
  def_node_search :active_resource_class?, <<~PATTERN
27
28
  (const (const nil? :ActiveResource) :Base)
@@ -52,14 +53,10 @@ module RuboCop
52
53
 
53
54
  def on_send(node)
54
55
  return if active_resource?(node.parent)
55
-
56
- unless association_without_options?(node)
57
- return if valid_options?(association_with_options?(node))
58
- end
59
-
56
+ return if !association_without_options?(node) && valid_options?(association_with_options?(node))
60
57
  return if valid_options_in_with_options_block?(node)
61
58
 
62
- add_offense(node, location: :selector)
59
+ add_offense(node.loc.selector)
63
60
  end
64
61
 
65
62
  private
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # def welcome_message(user)
24
24
  # "Hello #{user.name}"
25
25
  # end
26
- class HelperInstanceVariable < Cop
26
+ class HelperInstanceVariable < Base
27
27
  MSG = 'Do not use instance variables in helpers.'
28
28
 
29
29
  def on_ivar(node)
@@ -31,7 +31,9 @@ module RuboCop
31
31
  end
32
32
 
33
33
  def on_ivasgn(node)
34
- add_offense(node, location: :name)
34
+ return if node.parent.or_asgn_type?
35
+
36
+ add_offense(node.loc.name)
35
37
  end
36
38
  end
37
39
  end
@@ -16,7 +16,9 @@ module RuboCop
16
16
  #
17
17
  # # good
18
18
  # get :new, params: { user_id: 1 }
19
- class HttpPositionalArguments < Cop
19
+ # get :new, **options
20
+ class HttpPositionalArguments < Base
21
+ extend AutoCorrector
20
22
  extend TargetRailsVersion
21
23
 
22
24
  MSG = 'Use keyword arguments instead of ' \
@@ -24,36 +26,37 @@ module RuboCop
24
26
  KEYWORD_ARGS = %i[
25
27
  method params session body flash xhr as headers env to
26
28
  ].freeze
27
- HTTP_METHODS = %i[get post put patch delete head].freeze
29
+ RESTRICT_ON_SEND = %i[get post put patch delete head].freeze
28
30
 
29
31
  minimum_target_rails_version 5.0
30
32
 
31
33
  def_node_matcher :http_request?, <<~PATTERN
32
- (send nil? {#{HTTP_METHODS.map(&:inspect).join(' ')}} !nil? $_ ...)
34
+ (send nil? {#{RESTRICT_ON_SEND.map(&:inspect).join(' ')}} !nil? $_ ...)
35
+ PATTERN
36
+
37
+ def_node_matcher :kwsplat_hash?, <<~PATTERN
38
+ (hash (kwsplat _))
33
39
  PATTERN
34
40
 
35
41
  def on_send(node)
36
42
  http_request?(node) do |data|
37
43
  return unless needs_conversion?(data)
38
44
 
39
- add_offense(node, location: :selector,
40
- message: format(MSG, verb: node.method_name))
41
- end
42
- end
43
-
44
- # given a pre Rails 5 method: get :new, {user_id: @user.id}, {}
45
- #
46
- # @return lambda of auto correct procedure
47
- # the result should look like:
48
- # get :new, params: { user_id: @user.id }, session: {}
49
- # the http_method is the method used to call the controller
50
- # the controller node can be a symbol, method, object or string
51
- # that represents the path/action on the Rails controller
52
- # the data is the http parameters and environment sent in
53
- # the Rails 5 http call
54
- def autocorrect(node)
55
- lambda do |corrector|
56
- corrector.replace(node.loc.expression, correction(node))
45
+ message = format(MSG, verb: node.method_name)
46
+
47
+ add_offense(node.loc.selector, message: message) do |corrector|
48
+ # given a pre Rails 5 method: get :new, {user_id: @user.id}, {}
49
+ #
50
+ # @return lambda of auto correct procedure
51
+ # the result should look like:
52
+ # get :new, params: { user_id: @user.id }, session: {}
53
+ # the http_method is the method used to call the controller
54
+ # the controller node can be a symbol, method, object or string
55
+ # that represents the path/action on the Rails controller
56
+ # the data is the http parameters and environment sent in
57
+ # the Rails 5 http call
58
+ corrector.replace(node.loc.expression, correction(node))
59
+ end
57
60
  end
58
61
  end
59
62
 
@@ -61,6 +64,7 @@ module RuboCop
61
64
 
62
65
  def needs_conversion?(data)
63
66
  return true unless data.hash_type?
67
+ return false if kwsplat_hash?(data)
64
68
 
65
69
  data.each_pair.none? do |pair|
66
70
  special_keyword_arg?(pair.key) ||
@@ -31,8 +31,11 @@ module RuboCop
31
31
  # render plain: 'foo/bar', status: 304
32
32
  # redirect_to root_url, status: 301
33
33
  #
34
- class HttpStatus < Cop
34
+ class HttpStatus < Base
35
35
  include ConfigurableEnforcedStyle
36
+ extend AutoCorrector
37
+
38
+ RESTRICT_ON_SEND = %i[render redirect_to].freeze
36
39
 
37
40
  def_node_matcher :http_status, <<~PATTERN
38
41
  {
@@ -53,14 +56,9 @@ module RuboCop
53
56
  checker = checker_class.new(status)
54
57
  return unless checker.offensive?
55
58
 
56
- add_offense(checker.node, message: checker.message)
57
- end
58
- end
59
-
60
- def autocorrect(node)
61
- lambda do |corrector|
62
- checker = checker_class.new(node)
63
- corrector.replace(node.loc.expression, checker.preferred_style)
59
+ add_offense(checker.node, message: checker.message) do |corrector|
60
+ corrector.replace(checker.node.loc.expression, checker.preferred_style)
61
+ end
64
62
  end
65
63
  end
66
64
 
@@ -37,18 +37,20 @@ module RuboCop
37
37
  # end
38
38
  #
39
39
  # @see https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options
40
- class IgnoredSkipActionFilterOption < Cop
40
+ class IgnoredSkipActionFilterOption < Base
41
41
  MSG = <<~MSG.chomp.freeze
42
42
  `%<ignore>s` option will be ignored when `%<prefer>s` and `%<ignore>s` are used together.
43
43
  MSG
44
44
 
45
- FILTERS = %w[
46
- :skip_after_action
47
- :skip_around_action
48
- :skip_before_action
49
- :skip_action_callback
45
+ RESTRICT_ON_SEND = %i[
46
+ skip_after_action
47
+ skip_around_action
48
+ skip_before_action
49
+ skip_action_callback
50
50
  ].freeze
51
51
 
52
+ FILTERS = RESTRICT_ON_SEND.map { |method_name| ":#{method_name}" }
53
+
52
54
  def_node_matcher :filter_options, <<~PATTERN
53
55
  (send
54
56
  nil?
@@ -11,19 +11,28 @@ module RuboCop
11
11
  # @example
12
12
  # # bad
13
13
  # [1, 2, 3].each_with_object({}) { |el, h| h[foo(el)] = el }
14
+ # [1, 2, 3].to_h { |el| [foo(el), el] }
14
15
  # [1, 2, 3].map { |el| [foo(el), el] }.to_h
15
16
  # Hash[[1, 2, 3].collect { |el| [foo(el), el] }]
16
17
  #
17
18
  # # good
18
19
  # [1, 2, 3].index_by { |el| foo(el) }
19
- class IndexBy < Cop
20
+ class IndexBy < Base
20
21
  include IndexMethod
22
+ extend AutoCorrector
21
23
 
22
24
  def_node_matcher :on_bad_each_with_object, <<~PATTERN
23
25
  (block
24
26
  ({send csend} _ :each_with_object (hash))
25
27
  (args (arg $_el) (arg _memo))
26
- ({send csend} (lvar _memo) :[]= $_ (lvar _el)))
28
+ ({send csend} (lvar _memo) :[]= $!`_memo (lvar _el)))
29
+ PATTERN
30
+
31
+ def_node_matcher :on_bad_to_h, <<~PATTERN
32
+ (block
33
+ ({send csend} _ :to_h)
34
+ (args (arg $_el))
35
+ (array $_ (lvar _el)))
27
36
  PATTERN
28
37
 
29
38
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
@@ -11,12 +11,14 @@ module RuboCop
11
11
  # @example
12
12
  # # bad
13
13
  # [1, 2, 3].each_with_object({}) { |el, h| h[el] = foo(el) }
14
+ # [1, 2, 3].to_h { |el| [el, foo(el)] }
14
15
  # [1, 2, 3].map { |el| [el, foo(el)] }.to_h
15
16
  # Hash[[1, 2, 3].collect { |el| [el, foo(el)] }]
16
17
  #
17
18
  # # good
18
19
  # [1, 2, 3].index_with { |el| foo(el) }
19
- class IndexWith < Cop
20
+ class IndexWith < Base
21
+ extend AutoCorrector
20
22
  extend TargetRailsVersion
21
23
  include IndexMethod
22
24
 
@@ -26,7 +28,14 @@ module RuboCop
26
28
  (block
27
29
  ({send csend} _ :each_with_object (hash))
28
30
  (args (arg $_el) (arg _memo))
29
- ({send csend} (lvar _memo) :[]= (lvar _el) $_))
31
+ ({send csend} (lvar _memo) :[]= (lvar _el) $!`_memo))
32
+ PATTERN
33
+
34
+ def_node_matcher :on_bad_to_h, <<~PATTERN
35
+ (block
36
+ ({send csend} _ :to_h)
37
+ (args (arg $_el))
38
+ (array (lvar _el) $_))
30
39
  PATTERN
31
40
 
32
41
  def_node_matcher :on_bad_map_to_h, <<~PATTERN
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that Active Support's `inquiry` method is not used.
7
+ #
8
+ # @example
9
+ # # bad - String#inquiry
10
+ # ruby = 'two'.inquiry
11
+ # ruby.two?
12
+ #
13
+ # # good
14
+ # ruby = 'two'
15
+ # ruby == 'two'
16
+ #
17
+ # # bad - Array#inquiry
18
+ # pets = %w(cat dog).inquiry
19
+ # pets.gopher?
20
+ #
21
+ # # good
22
+ # pets = %w(cat dog)
23
+ # pets.include? 'cat'
24
+ #
25
+ class Inquiry < Base
26
+ MSG = "Prefer Ruby's comparison operators over Active Support's `inquiry`."
27
+ RESTRICT_ON_SEND = %i[inquiry].freeze
28
+
29
+ def on_send(node)
30
+ return unless node.arguments.empty?
31
+ return unless (receiver = node.receiver)
32
+ return if !receiver.str_type? && !receiver.array_type?
33
+
34
+ add_offense(node.loc.selector)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -128,10 +128,11 @@ module RuboCop
128
128
  #
129
129
  # @see https://guides.rubyonrails.org/association_basics.html#bi-directional-associations
130
130
  # @see https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
131
- class InverseOf < Cop
131
+ class InverseOf < Base
132
132
  SPECIFY_MSG = 'Specify an `:inverse_of` option.'
133
133
  NIL_MSG = 'You specified `inverse_of: nil`, you probably meant to ' \
134
134
  'use `inverse_of: false`.'
135
+ RESTRICT_ON_SEND = %i[has_many has_one belongs_to].freeze
135
136
 
136
137
  def_node_matcher :association_recv_arguments, <<~PATTERN
137
138
  (send $_ {:has_many :has_one :belongs_to} _ $...)
@@ -185,7 +186,7 @@ module RuboCop
185
186
 
186
187
  return if options_contain_inverse_of?(options)
187
188
 
188
- add_offense(node, message: message(options), location: :selector)
189
+ add_offense(node.loc.selector, message: message(options))
189
190
  end
190
191
 
191
192
  def scope?(arguments)
@@ -82,25 +82,27 @@ module RuboCop
82
82
  # @content = Article.find(params[:article_id])
83
83
  # end
84
84
  # end
85
- class LexicallyScopedActionFilter < Cop
85
+ class LexicallyScopedActionFilter < Base
86
86
  MSG = '%<action>s not explicitly defined on the %<type>s.'
87
87
 
88
- FILTERS = %w[
89
- :after_action
90
- :append_after_action
91
- :append_around_action
92
- :append_before_action
93
- :around_action
94
- :before_action
95
- :prepend_after_action
96
- :prepend_around_action
97
- :prepend_before_action
98
- :skip_after_action
99
- :skip_around_action
100
- :skip_before_action
101
- :skip_action_callback
88
+ RESTRICT_ON_SEND = %i[
89
+ after_action
90
+ append_after_action
91
+ append_around_action
92
+ append_before_action
93
+ around_action
94
+ before_action
95
+ prepend_after_action
96
+ prepend_around_action
97
+ prepend_before_action
98
+ skip_after_action
99
+ skip_around_action
100
+ skip_before_action
101
+ skip_action_callback
102
102
  ].freeze
103
103
 
104
+ FILTERS = RESTRICT_ON_SEND.map { |method_name| ":#{method_name}" }
105
+
104
106
  def_node_matcher :only_or_except_filter_methods, <<~PATTERN
105
107
  (send
106
108
  nil?
@@ -20,8 +20,11 @@ module RuboCop
20
20
  #
21
21
  # # good
22
22
  # link_to 'Click here', url, target: '_blank', rel: 'noreferrer'
23
- class LinkToBlank < Cop
23
+ class LinkToBlank < Base
24
+ extend AutoCorrector
25
+
24
26
  MSG = 'Specify a `:rel` option containing noopener.'
27
+ RESTRICT_ON_SEND = %i[link_to].freeze
25
28
 
26
29
  def_node_matcher :blank_target?, <<~PATTERN
27
30
  (pair {(sym :target) (str "target")} {(str "_blank") (sym :_blank)})
@@ -36,36 +39,33 @@ module RuboCop
36
39
  PATTERN
37
40
 
38
41
  def on_send(node)
39
- return unless node.method?(:link_to)
40
-
41
42
  option_nodes = node.each_child_node(:hash)
42
43
 
43
44
  option_nodes.map(&:children).each do |options|
44
45
  blank = options.find { |o| blank_target?(o) }
45
- add_offense(blank) if blank && options.none? { |o| includes_noopener?(o) }
46
+ next unless blank && options.none? { |o| includes_noopener?(o) }
47
+
48
+ add_offense(blank) do |corrector|
49
+ autocorrect(corrector, node, blank, option_nodes)
50
+ end
46
51
  end
47
52
  end
48
53
 
49
- def autocorrect(node)
50
- lambda do |corrector|
51
- send_node = node.parent.parent
54
+ private
52
55
 
53
- option_nodes = send_node.each_child_node(:hash)
54
- rel_node = nil
55
- option_nodes.map(&:children).each do |options|
56
- rel_node ||= options.find { |o| rel_node?(o) }
57
- end
56
+ def autocorrect(corrector, send_node, node, option_nodes)
57
+ rel_node = nil
58
+ option_nodes.map(&:children).each do |options|
59
+ rel_node ||= options.find { |o| rel_node?(o) }
60
+ end
58
61
 
59
- if rel_node
60
- append_to_rel(rel_node, corrector)
61
- else
62
- add_rel(send_node, node, corrector)
63
- end
62
+ if rel_node
63
+ append_to_rel(rel_node, corrector)
64
+ else
65
+ add_rel(send_node, node, corrector)
64
66
  end
65
67
  end
66
68
 
67
- private
68
-
69
69
  def append_to_rel(rel_node, corrector)
70
70
  existing_rel = rel_node.children.last.value
71
71
  str_range = rel_node.children.last.loc.expression.adjust(
@@ -87,7 +87,7 @@ module RuboCop
87
87
  def contains_noopener?(value)
88
88
  return false unless value
89
89
 
90
- rel_array = value.to_s.split(' ')
90
+ rel_array = value.to_s.split
91
91
  rel_array.include?('noopener') || rel_array.include?('noreferrer')
92
92
  end
93
93
  end